From 85f72a71e3d857190cac73e37ac44fc186718a48 Mon Sep 17 00:00:00 2001 From: hyperpolymath <6759885+hyperpolymath@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:14:58 +0000 Subject: [PATCH] refactor!: rename dictask to dicta-task and re-base on rsr-template-repo - Mint from rsr-template-repo@05bf1b9 via just repo-init; port project code. - Rename modules/crates/packages/C prefix to dicta-task / DictaTask / DICTA_TASK. - Move schemas, config, deploy and root docs to RSR canonical locations. - Fill descriptiles (CLADE uuid re-derived, STATE, ECOSYSTEM, ANCHOR, rsr-profile). - Wire just build/test to cargo, cabal and zig; modernise FFI build.zig and replace the placeholder integration test with real ABI tests (8/8 pass). - ADR-0004: keep SQLite canonical; TaskStore trait; LMDB only as side store. Co-authored-by: arena-agent <297053741+arena-agent@users.noreply.github.com> --- .cicd-hygiene-allow | 12 + .claude/CLAUDE.md | 47 - .../ai/.clinerules => .clinerules | 24 +- .conflow.yaml | 26 - .cursorrules | 54 ++ .devcontainer/Containerfile | 10 +- .devcontainer/README.adoc | 3 +- .devcontainer/devcontainer.json | 4 +- .editorconfig | 95 +- .envrc | 14 +- .gitattributes | 99 +- .github/.mailmap | 1 - .github/.nojekyll | 0 .github/0.1-AI-MANIFEST.a2ml | 1 - .github/CODE_OF_CONDUCT.md | 53 +- .github/CONTRIBUTING.md | 68 +- .github/DIRECTORY.adoc | 1 - .github/FUNDING.yml | 16 +- .github/GOVERNANCE.md | 160 ++++ .github/ISSUE_TEMPLATE/bug_report.yml | 58 ++ .github/ISSUE_TEMPLATE/config.yml | 13 + .github/ISSUE_TEMPLATE/feature_request.yml | 37 + .github/MAINTAINERS | 10 - .github/SECURITY.md | 41 +- .github/SUPPORT | 7 - .github/SUPPORT.md | 7 + .github/copilot-instructions.md | 39 +- .github/copilot/coding-agent.yml | 6 + .github/dependabot.yml | 50 +- .github/hooks/install.sh | 10 + .github/hooks/pre-push | 81 ++ .github/hooks/scan-secrets.sh | 204 +++++ .github/hooks/validate-deed.sh | 393 ++++++++ .github/hooks/validate-k9.sh | 389 ++++++++ .github/pull_request_template.md | 13 +- .github/rulesets/Immutable-Tags.json | 28 + .github/rulesets/README.adoc | 52 ++ .github/rulesets/base.json | 102 +++ .github/settings.yml | 160 ++++ .github/workflows/README.adoc | 63 ++ .github/workflows/actions.lock | 287 ++++++ .github/workflows/boj-build.yml | 20 - .github/workflows/build-notification.yml | 50 + .github/workflows/casket-pages.yml | 117 --- .github/workflows/codeql.yml | 32 +- .github/workflows/deed-validate.yml | 59 ++ .github/workflows/dependabot-automerge.yml | 137 +++ .github/workflows/docker-build.yml | 58 ++ .github/workflows/dogfood-gate.yml | 406 +++++++-- .github/workflows/dogfood-summary.yml | 99 ++ .../workflows/dot-wellknown-enforcement.yml | 157 ++++ .github/workflows/e2e.yml.template | 224 +++++ .github/workflows/eclexiaiser-validate.yml | 64 ++ .github/workflows/empty-linter.yml | 89 ++ .github/workflows/estate-rules.yml | 85 ++ .github/workflows/governance.yml | 22 +- .github/workflows/groove-check.yml | 91 ++ .github/workflows/guix-policy.yml | 49 + .github/workflows/hypatia-scan.yml | 18 +- .github/workflows/instant-sync.yml | 39 - .github/workflows/k9-validate.yml | 64 ++ .github/workflows/label-triage.yml | 2 + .github/workflows/labels.yml | 2 + .github/workflows/lock-sync-gate.yml | 64 ++ .github/workflows/main-estate-audit.yml | 20 + .github/workflows/mirror.yml | 24 +- .github/workflows/ossf-best-practices.yml | 97 ++ .github/workflows/pages.yml | 91 ++ .github/workflows/push-email-notify.yml | 15 +- .github/workflows/quality.yml | 79 ++ .github/workflows/release.yml | 133 +-- .github/workflows/rsr-compliance-canary.yml | 95 ++ .github/workflows/runtime-policy.yml | 72 ++ .github/workflows/rust-ci.yml | 19 + .github/workflows/scorecard.yml | 22 +- .github/workflows/secret-scanner.yml | 19 +- .github/workflows/security-policy.yml | 54 ++ .github/workflows/sonarqube.yml | 69 ++ .github/workflows/static-analysis-gate.yml | 454 ++++++++++ .github/workflows/workflow-linter.yml | 178 ++++ .gitignore | 85 +- .gitleaksignore | 4 + .gitmessage | 18 + .guix-channel | 22 - .hypatia-ignore | 55 ++ .machine_readable/0.1-AI-MANIFEST.a2ml | 29 - .machine_readable/6a2/META.a2ml | 114 --- .machine_readable/6a2/PLAYBOOK.a2ml | 35 - .machine_readable/ADJUST.contractile | 126 --- .machine_readable/CLADE.a2ml | 47 - .machine_readable/ENSAID_CONFIG.a2ml | 96 ++ .machine_readable/INTENT.contractile | 72 -- .machine_readable/MUST.contractile | 91 -- .machine_readable/PROVENANCE.a2ml | 28 + .machine_readable/README.adoc | 41 +- .machine_readable/TRUST.contractile | 80 -- .machine_readable/ai/.cursorrules | 47 - .machine_readable/ai/.windsurfrules | 43 - .machine_readable/ai/0.2-AI-MANIFEST.a2ml | 11 - .machine_readable/ai/AI.a2ml | 28 +- .machine_readable/ai/PLACEHOLDERS.adoc | 91 +- .machine_readable/ai/README.adoc | 8 +- .../anchors/0.2-AI-MANIFEST.a2ml | 11 - .machine_readable/anchors/README.adoc | 1 - .machine_readable/arrival-pack/README.adoc | 55 ++ .../arrival-pack/arrival-pack.ncl | 90 ++ .../arrival-pack/claude-md.k9.ncl | 59 ++ .machine_readable/arrival-pack/extract.sh | 89 ++ .machine_readable/arrival-pack/generate.sh | 45 + .machine_readable/arrival-pack/verify.sh | 27 + .../README.adoc | 4 +- .../coverage.a2ml | 2 +- .../debt.a2ml | 2 +- .../methodology.a2ml | 14 +- .machine_readable/bot_directives/rra.a2ml | 55 ++ .machine_readable/coaptation/README.adoc | 147 +++ .machine_readable/coaptation/coapt.k9.ncl | 70 ++ .machine_readable/coaptation/coapt.ncl | 196 ++++ .machine_readable/coaptation/coapt.sh | 87 ++ .../coaptation/core/Coaptation.idr | 112 +++ .../coaptation/extract-clauses.sh | 104 +++ .machine_readable/coaptation/extract-facts.sh | 122 +++ .machine_readable/coaptation/grades.ncl | 45 + .../coaptation/receipts/latest.a2ml | 100 ++ .machine_readable/coaptation/verify.sh | 34 + .machine_readable/coaptation/witness-map.ncl | 88 ++ .machine_readable/compliance/reuse/dep5 | 34 +- .machine_readable/compliance/rust/deny.toml | 1 - .../configs/0.2-AI-MANIFEST.a2ml | 11 - .machine_readable/configs/README.adoc | 2 + .machine_readable/configs/eclexiaiser.toml | 26 + .../configs/git-cliff/cliff.toml | 6 +- .../configs/stapeln.toml | 35 +- .machine_readable/contractiles/INDEX.a2ml | 133 +++ .machine_readable/contractiles/Justfile | 720 +++++++++++++++ .machine_readable/contractiles/README.adoc | 170 +++- .machine_readable/contractiles/_base.ncl | 140 +++ .../contractiles/adjust/Adjustfile.a2ml | 72 ++ .../contractiles/adjust/adjust.k9.ncl | 167 ++++ .../contractiles/adjust/adjust.manifest.a2ml | 47 + .../contractiles/adjust/adjust.ncl | 65 ++ .../contractiles/bust/Bustfile.a2ml | 52 ++ .../contractiles/bust/bust.k9.ncl | 162 ++++ .../contractiles/bust/bust.manifest.a2ml | 48 + .machine_readable/contractiles/bust/bust.ncl | 69 ++ .machine_readable/contractiles/dust/Dustfile | 29 - .../contractiles/dust/Dustfile.a2ml | 95 +- .../contractiles/dust/dust.k9.ncl | 172 ++++ .../contractiles/dust/dust.manifest.a2ml | 51 ++ .machine_readable/contractiles/dust/dust.ncl | 69 ++ .../contractiles/intend/Intentfile.a2ml | 99 ++ .../contractiles/intend/intend.k9.ncl | 252 +++++ .../contractiles/intend/intend.manifest.a2ml | 73 ++ .../contractiles/intend/intend.ncl | 84 ++ .../contractiles/lust/Intentfile | 21 - .machine_readable/contractiles/must/Mustfile | 35 - .../contractiles/must/Mustfile.a2ml | 103 +++ .../contractiles/must/must.k9.ncl | 238 +++++ .../contractiles/must/must.manifest.a2ml | 59 ++ .machine_readable/contractiles/must/must.ncl | 67 ++ .../contractiles/trust/Trustfile.a2ml | 349 +++---- .../contractiles/trust/trust.k9.ncl | 278 ++++++ .../contractiles/trust/trust.manifest.a2ml | 72 ++ .../contractiles/trust/trust.ncl | 94 ++ .../{6a2 => descriptiles}/AGENTIC.a2ml | 23 +- .machine_readable/descriptiles/CLADE.a2ml | 132 +++ .../{6a2 => descriptiles}/ECOSYSTEM.a2ml | 23 +- .machine_readable/descriptiles/META.a2ml | 53 ++ .../{6a2 => descriptiles}/NEUROSYM.a2ml | 2 +- .machine_readable/descriptiles/PLAYBOOK.a2ml | 137 +++ .machine_readable/descriptiles/README.adoc | 66 ++ .../{6a2 => descriptiles}/STATE.a2ml | 49 +- .machine_readable/descriptiles/VARIANT.a2ml | 40 + .../{ => descriptiles}/anchors/ANCHOR.a2ml | 12 +- .../descriptiles/anchors/README.adoc | 25 + .../integrations/feedback-o-tron.a2ml | 3 +- .machine_readable/integrations/groove.a2ml | 38 + .machine_readable/integrations/proven.a2ml | 4 +- .machine_readable/integrations/verisimdb.a2ml | 4 +- .machine_readable/integrations/vexometer.a2ml | 3 +- .../policies/0.2-AI-MANIFEST.a2ml | 11 - .../policies/MAINTENANCE-AXES.a2ml | 4 +- .../policies/MAINTENANCE-CHECKLIST.a2ml | 10 +- .machine_readable/policies/README.adoc | 2 + .../SOFTWARE-DEVELOPMENT-APPROACH.a2ml | 2 +- .machine_readable/root-allow.txt | 82 ++ .machine_readable/rsr-profile.a2ml | 24 + .../scripts/0.2-AI-MANIFEST.a2ml | 18 - .../scripts/forge/0.3-AI-MANIFEST.a2ml | 11 - .machine_readable/scripts/forge/README.adoc | 2 + .../scripts/forge/git-cleanup.sh | 2 + .../scripts/lifecycle/0.3-AI-MANIFEST.a2ml | 11 - .../scripts/lifecycle/README.adoc | 2 + .../scripts/lifecycle/install-tools.sh | 10 +- .../scripts/maintenance/maint-assault.sh | 2 +- .../scripts/verification/0.3-AI-MANIFEST.a2ml | 11 - .../scripts/verification/README.adoc | 2 + .../k9 => self-validating}/README.adoc | 14 +- .../examples/ci-config.k9.ncl | 2 +- .../examples/project-metadata.k9.ncl | 8 +- .../examples/setup-repo.k9.ncl | 12 +- .../self-validating/methodology-guard.k9.ncl | 79 ++ .../template-hunt.k9.ncl | 2 +- .../template-kennel.k9.ncl | 2 +- .../template-yard.k9.ncl | 2 +- .mailmap | 1 + .mise.toml | 10 - .tool-versions | 9 + .windsurfrules | 51 ++ 0-AI-MANIFEST.a2ml | 146 --- CHANGELOG.adoc | 17 + CITATION.cff | 17 + CLAUDE.md | 71 ++ CODE_OF_CONDUCT.adoc | 24 - CONTRIBUTING.adoc | 122 +++ Containerfile | 41 - GEMINI.md | 8 + GOVERNANCE.adoc | 60 -- Justfile | 842 ++++++++++++----- LICENSES/AGPL-3.0-or-later.txt | 661 -------------- MAINTAINERS | 43 - README.adoc | 162 ++-- SECURITY.adoc | 16 - benches/template_bench.sh | 227 +++++ build/container/.containerignore | 59 ++ {container => build/container}/Containerfile | 68 +- build/container/README.adoc | 245 +++++ build/container/compose.example.yaml | 108 +++ build/container/compose.yaml | 99 ++ {container => build/container}/entrypoint.sh | 14 +- .../container/stapeln}/.gatekeeper.yaml | 6 +- .../container/stapeln}/compose.example.toml | 2 +- .../container/stapeln}/compose.toml | 46 +- .../container/stapeln}/ct-build.sh | 40 +- .../container/stapeln}/deploy.k9.ncl | 48 +- .../container/stapeln}/manifest.toml | 16 +- build/container/stapeln/rokur.toml | 81 ++ .../container/stapeln}/vordr.toml | 33 +- {deploy => build/deploy}/ansible/setup.yml | 64 +- {deploy => build/deploy}/terraform/main.tf | 20 +- build/guix.scm | 20 + build/just/assess.just | 270 ++++++ build/just/container.just | 284 ++++++ build/just/groove.just | 98 ++ build/just/proofs.just | 61 ++ build/just/repo-init.just | 857 ++++++++++++++++++ build/just/validate.just | 135 +++ .gitlab-ci.yml => ci/.gitlab-ci.yml | 33 +- ci/.pre-commit-config.yaml | 73 ++ ci/README.adoc | 43 + container/0.1-AI-MANIFEST.a2ml | 143 --- container/README.adoc | 179 ---- contractile.just | 75 -- contractiles/intend/Intentfile.a2ml | 22 - contractiles/must/Mustfile.a2ml | 42 - contractiles/trust/Trustfile.a2ml | 25 - coordination.k9.ncl | 49 + dicta-task_chora.deed | 152 ++++ docs/0.1-AI-MANIFEST.a2ml | 33 - docs/AFFIRMATION.adoc | 235 +++++ docs/AI-INSTALL-README-SECTION.adoc | 20 + docs/AI_INSTALLATION_GUIDE.adoc | 133 +++ ARCHITECTURE.adoc => docs/ARCHITECTURE.adoc | 24 +- docs/AUDIT.adoc | 48 + EXPLAINME.adoc => docs/EXPLAINME.adoc | 2 +- docs/GOVERNANCE.adoc | 65 ++ docs/MAINTAINERS.adoc | 63 ++ docs/QUICKSTART.adoc | 6 +- docs/README.adoc | 59 +- docs/RSR-PHILOSOPHY.adoc | 118 +++ docs/RSR_OUTLINE.adoc | 258 ++++++ docs/STATE-VISUALIZER.adoc | 15 +- docs/accessibility/README.adoc | 257 ------ docs/architecture.adoc | 79 ++ docs/architecture/0.2-AI-MANIFEST.a2ml | 17 - docs/architecture/REPOSITORY-MAP.adoc | 264 ++++++ docs/architecture/THREAT-MODEL.adoc | 6 +- .../architecture/TOPOLOGY.adoc | 2 +- docs/attribution/0.2-AI-MANIFEST.a2ml | 11 - docs/attribution/CITATION.cff | 17 - docs/attribution/CITATIONS.adoc | 20 +- docs/attribution/CODEOWNERS.adoc | 4 +- docs/attribution/MAINTAINERS.adoc | 47 - docs/attribution/README.adoc | 2 + docs/compliance/ACCESSIBILITY.adoc | 3 - docs/contributing.adoc | 91 ++ docs/decisions/0.2-AI-MANIFEST.a2ml | 11 - docs/decisions/0000-template.adoc | 24 +- docs/decisions/0001-adopt-rsr-standard.adoc | 29 +- docs/decisions/0001-template.adoc | 54 ++ docs/decisions/0002-variant-contract.adoc | 75 ++ .../0003-forge-and-sustain-lifecycle.adoc | 118 +++ docs/decisions/0004-storage-backend.adoc | 78 ++ docs/decisions/README.adoc | 2 + docs/developer/0.2-AI-MANIFEST.a2ml | 11 - docs/developer/ABI-FFI-README.adoc | 233 ++--- docs/developer/README.adoc | 2 + docs/developer/invariant-path.adoc | 20 + docs/governance/0.1-AI-MANIFEST.a2ml | 21 - docs/governance/CRG-AUDIT-TEMPLATE.adoc | 288 ++++++ docs/governance/CRG-CRITERIA.adoc | 2 + docs/governance/MAINTENANCE-CHECKLIST.a2ml | 160 ---- docs/governance/MAINTENANCE-CHECKLIST.adoc | 24 +- docs/governance/README.adoc | 2 + .../SOFTWARE-DEVELOPMENT-APPROACH.a2ml | 53 -- .../SOFTWARE-DEVELOPMENT-APPROACH.adoc | 2 + docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc | 230 +++++ docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc | 178 ++++ docs/governance/TSDM.adoc | 2 + docs/governance/audit/0.2-AI-MANIFEST.a2ml | 11 - docs/governance/audit/README.adoc | 2 + .../audit/compliance/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/audit/compliance/README.adoc | 2 + .../audit/effects/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/audit/effects/README.adoc | 2 + .../audit/systems/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/audit/systems/README.adoc | 2 + .../maintenance/0.2-AI-MANIFEST.a2ml | 11 - docs/governance/maintenance/README.adoc | 2 + .../maintenance/adaptive/0.3-AI-MANIFEST.a2ml | 11 - .../maintenance/adaptive/README.adoc | 2 + .../corrective/0.3-AI-MANIFEST.a2ml | 11 - .../maintenance/corrective/README.adoc | 2 + .../perfective/0.3-AI-MANIFEST.a2ml | 11 - .../maintenance/perfective/README.adoc | 2 + docs/governance/planning/0.2-AI-MANIFEST.a2ml | 11 - docs/governance/planning/README.adoc | 2 + .../planning/could/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/planning/could/README.adoc | 2 + .../planning/must/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/planning/must/README.adoc | 2 + .../planning/should/0.3-AI-MANIFEST.a2ml | 11 - docs/governance/planning/should/README.adoc | 2 + docs/legal/0.2-AI-MANIFEST.a2ml | 16 - docs/legal/EXHIBIT-A-ETHICAL-USE.txt | 20 + docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt | 104 +++ .../onboarding/QUICKSTART-DEV.adoc | 6 +- .../onboarding/QUICKSTART-MAINTAINER.adoc | 4 +- .../onboarding/QUICKSTART-USER.adoc | 8 +- .../onboarding/llm-warmup-dev.adoc | 4 +- .../onboarding/llm-warmup-user.adoc | 4 +- docs/practice/0.2-AI-MANIFEST.a2ml | 11 - docs/practice/AI-CONVENTIONS.adoc | 101 ++- docs/practice/README.adoc | 2 + docs/practice/STATE-VISUALIZER-GUIDE.adoc | 7 +- docs/practice/ci-cost-reduction.adoc | 278 ++++++ docs/proposals/root-cleanup.adoc | 231 +++++ docs/reports/0.2-AI-MANIFEST.a2ml | 19 - docs/reports/README.adoc | 2 + .../audit/pillar-audit-2026-04-15.adoc | 23 + docs/reports/compliance/0.3-AI-MANIFEST.a2ml | 11 - docs/reports/compliance/README.adoc | 2 + docs/reports/maintenance/0.3-AI-MANIFEST.a2ml | 11 - docs/reports/maintenance/README.adoc | 2 + docs/reports/performance/0.3-AI-MANIFEST.a2ml | 11 - docs/reports/performance/README.adoc | 2 + docs/reports/quality/0.3-AI-MANIFEST.a2ml | 11 - docs/reports/quality/README.adoc | 2 + docs/reports/security/0.3-AI-MANIFEST.a2ml | 11 - docs/reports/security/README.adoc | 2 + docs/standards/0.2-AI-MANIFEST.a2ml | 11 - docs/standards/README.adoc | 2 + .../status/PROOF-NEEDS.adoc | 0 docs/status/PROOF-STATUS.adoc | 101 +++ docs/status/READINESS.adoc | 186 ++++ ROADMAP.adoc => docs/status/ROADMAP.adoc | 2 +- .../status/TEST-NEEDS.adoc | 2 +- docs/theory/0.2-AI-MANIFEST.a2ml | 23 - docs/theory/README.adoc | 2 + docs/theory/computing/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/computing/README.adoc | 2 + docs/theory/formalisms/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/formalisms/README.adoc | 2 + docs/theory/mathematics/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/mathematics/README.adoc | 2 + docs/theory/ontologies/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/ontologies/README.adoc | 2 + docs/theory/other/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/other/README.adoc | 2 + .../socio-technical/0.3-AI-MANIFEST.a2ml | 11 - docs/theory/socio-technical/README.adoc | 2 + docs/troubleshooting.adoc | 58 ++ docs/usage.adoc | 82 ++ docs/whitepapers/0.2-AI-MANIFEST.a2ml | 20 - docs/whitepapers/README.adoc | 2 + docs/whitepapers/academic/.gitkeep | 0 .../whitepapers/academic/0.3-AI-MANIFEST.a2ml | 11 - docs/whitepapers/academic/README.adoc | 2 + docs/whitepapers/industry/.gitkeep | 0 .../whitepapers/industry/0.3-AI-MANIFEST.a2ml | 11 - docs/whitepapers/industry/README.adoc | 2 + .../whitepapers/outreach/0.3-AI-MANIFEST.a2ml | 16 - docs/whitepapers/outreach/README.adoc | 2 + docs/wikis/0.2-AI-MANIFEST.a2ml | 15 - docs/wikis/README.adoc | 15 - docs/wikis/README.md | 17 + eclexiaiser.toml | 19 - examples/0.1-AI-MANIFEST.a2ml | 1 - examples/README.adoc | 2 + examples/web-project-deno.json | 20 - features/0.1-AI-MANIFEST.a2ml | 17 - features/README.adoc | 2 + features/boj-server/0.2-AI-MANIFEST.a2ml | 11 - features/boj-server/README.adoc | 2 + features/panic-attacker/0.2-AI-MANIFEST.a2ml | 11 - features/panic-attacker/README.adoc | 2 + features/ssg/0.2-AI-MANIFEST.a2ml | 11 - features/ssg/README.adoc | 2 + features/ssg/ssg-bootstrap.sh | 70 +- k9iser.toml | 51 -- mise.toml | 83 +- scripts/campaigns/www-bundle.campaign | 58 ++ scripts/check-action-pinning.js | 105 +++ scripts/check-adoc-renders.sh | 89 ++ scripts/check-invisible-characters.sh | 72 ++ scripts/check-lock-sync.sh | 297 ++++++ scripts/check-no-md-in-docs.sh | 65 ++ scripts/check-no-placeholders.sh | 178 ++++ scripts/check-no-vlang.sh | 87 ++ scripts/check-proofs.sh | 183 ++++ scripts/check-root-shape.sh | 156 ++++ scripts/check-variant-drift.sh | 122 +++ scripts/gen-repo-map.sh | 130 +++ scripts/invariant-path.sh | 33 + scripts/migrate-wellknown-to-www.sh | 246 +++++ scripts/prune-dependabot-ecosystems.rs | 133 +++ scripts/rsr-campaign.sh | 415 +++++++++ scripts/rust-tool.sh | 51 ++ scripts/scan-dangerous.sh | 74 ++ scripts/strip-instruction-blocks.rs | 171 ++++ scripts/sweep-wellknown.sh | 421 +++++++++ scripts/validate-session-contracts.sh | 34 + scripts/validate-template.sh | 491 ++++++++++ selur-compose.toml | 17 - session/README.adoc | 50 + session/custom-checks.k9.ncl | 51 ++ session/dispatch.sh | 139 +++ session/local-hooks.sh | 23 + setup.sh | 64 -- sonar-project.properties | 26 + src/0.1-AI-MANIFEST.a2ml | 27 - src/README.adoc | 2 + src/aspects/0.2-AI-MANIFEST.a2ml | 17 - src/aspects/README.adoc | 55 ++ src/aspects/integrity/0.3-AI-MANIFEST.a2ml | 11 - src/aspects/integrity/README.adoc | 2 + .../observability/0.3-AI-MANIFEST.a2ml | 11 - src/aspects/observability/README.adoc | 2 + src/aspects/security/0.3-AI-MANIFEST.a2ml | 11 - src/aspects/security/README.adoc | 2 + {docs/practice => src/bridges}/.gitkeep | 0 src/bridges/0.2-AI-MANIFEST.a2ml | 11 - src/contracts/0.2-AI-MANIFEST.a2ml | 11 - src/contracts/README.adoc | 2 + {docs/theory => src/core}/.gitkeep | 0 src/core/0.2-AI-MANIFEST.a2ml | 11 - src/definitions/0.2-AI-MANIFEST.a2ml | 11 - src/definitions/README.adoc | 2 + {configs => src/definitions}/config.ncl | 0 .../schemas}/candidate-intent.json | 4 +- .../definitions/schemas}/config.cue | 0 .../definitions/schemas}/tasks.sql | 4 +- src/errors/0.2-AI-MANIFEST.a2ml | 11 - src/errors/README.adoc | 2 + src/ingest/Cargo.toml | 4 +- src/ingest/src/main.rs | 8 +- src/interface/0.2-AI-MANIFEST.a2ml | 24 - src/interface/Abi/Foreign.idr | 83 ++ src/interface/Abi/Layout.idr | 128 +++ src/interface/Abi/README.adoc | 3 + src/interface/{abi => Abi}/Task.idr | 4 +- src/interface/Abi/Types.idr | 112 +++ src/interface/README.adoc | 2 + src/interface/abi.ipkg | 37 + src/interface/abi/0.3-AI-MANIFEST.a2ml | 11 - src/interface/abi/README.adoc | 1 - src/interface/ffi/0.3-AI-MANIFEST.a2ml | 11 - src/interface/ffi/README.adoc | 2 + src/interface/ffi/build.zig | 38 +- src/interface/ffi/src/0.4-AI-MANIFEST.a2ml | 11 - src/interface/ffi/src/README.adoc | 2 + src/interface/ffi/src/main.zig | 12 +- src/interface/ffi/src/verisimdb.zig | 12 +- src/interface/ffi/test/0.4-AI-MANIFEST.a2ml | 11 - src/interface/ffi/test/abi_header_smoke.c | 26 +- src/interface/ffi/test/integration_test.zig | 206 +---- src/interface/generated/0.3-AI-MANIFEST.a2ml | 11 - src/interface/generated/README.adoc | 2 + src/interface/generated/abi/.gitkeep | 0 .../generated/abi/0.4-AI-MANIFEST.a2ml | 11 - src/interface/generated/abi/README.adoc | 2 + .../{dictask_ffi.h => dicta_task_ffi.h} | 64 +- ...ask-parse.cabal => dicta-task-parse.cabal} | 32 +- .../Parse/Confidence.hs | 4 +- .../Parse/DateResolver.hs | 4 +- .../src/{Dictask => DictaTask}/Parse/Dedup.hs | 4 +- .../{Dictask => DictaTask}/Parse/Intent.hs | 6 +- .../Parse/Internal/Patterns.hs | 4 +- .../Parse/Internal/Tokeniser.hs | 4 +- .../{Dictask => DictaTask}/Parse/Priority.hs | 4 +- .../src/{Dictask => DictaTask}/Parse/Types.hs | 6 +- src/parse/test/Main.hs | 8 +- .../Parse/ConfidenceSpec.hs | 6 +- .../Parse/DateResolverSpec.hs | 6 +- .../{Dictask => DictaTask}/Parse/DedupSpec.hs | 8 +- .../Parse/IntentSpec.hs | 6 +- src/store/Cargo.toml | 4 +- src/store/src/lib.rs | 4 +- src/transcribe/Cargo.toml | 4 +- src/transcribe/src/lib.rs | 2 +- tests/aspect_tests.sh | 134 +++ tests/e2e.sh | 167 ++++ tests/e2e/julia_mint_test.sh | 125 +++ tests/e2e/template_instantiation_test.sh | 422 +++++++++ tests/fuzz/README.adoc | 112 +++ tests/fuzz/placeholder.txt | 1 - tests/invisible-characters-test.sh | 101 +++ tests/shape/check_root_shape_test.sh | 121 +++ tests/shape/repo_map_determinism_test.sh | 46 + tests/workflows/check_adoc_renders_test.sh | 126 +++ tests/workflows/check_no_vlang_test.sh | 61 ++ tests/workflows/k9_typecheck_test.sh | 17 + tests/workflows/mint_cleanup_test.sh | 39 + tests/workflows/session_contracts_test.sh | 25 + tests/workflows/validate_workflows_test.sh | 144 +++ verification/0.1-AI-MANIFEST.a2ml | 27 - verification/README.adoc | 2 + verification/benchmarks/0.2-AI-MANIFEST.a2ml | 11 - verification/benchmarks/README.adoc | 2 + verification/coverage/0.2-AI-MANIFEST.a2ml | 12 - verification/coverage/README.adoc | 2 + verification/fuzzing/0.2-AI-MANIFEST.a2ml | 11 - verification/fuzzing/README.adoc | 2 + verification/proofs/0.2-AI-MANIFEST.a2ml | 11 - verification/proofs/README.adoc | 61 +- verification/proofs/agda/MANIFEST | 4 + verification/proofs/agda/Properties.agda | 37 + verification/proofs/coq/MANIFEST | 4 + verification/proofs/coq/TypeSafety.v | 73 ++ verification/proofs/idris2/ABI/Compliance.idr | 41 + verification/proofs/idris2/ABI/Foreign.idr | 53 ++ verification/proofs/idris2/ABI/Layout.idr | 63 ++ verification/proofs/idris2/ABI/Platform.idr | 63 ++ verification/proofs/idris2/ABI/Pointers.idr | 52 ++ verification/proofs/idris2/MANIFEST | 10 + verification/proofs/idris2/Types.idr | 40 + verification/proofs/lean4/ApiTypes.lean | 45 + verification/proofs/lean4/MANIFEST | 4 + verification/proofs/lean4/lean-toolchain | 1 + verification/proofs/tlaplus/StateMachine.tla | 91 ++ verification/safety_case/0.2-AI-MANIFEST.a2ml | 12 - verification/safety_case/README.adoc | 2 + verification/simulations/0.2-AI-MANIFEST.a2ml | 11 - verification/simulations/README.adoc | 2 + verification/tests/0.2-AI-MANIFEST.a2ml | 1 - verification/tests/README.adoc | 2 + .../traceability/0.2-AI-MANIFEST.a2ml | 12 - verification/traceability/README.adoc | 2 + www/.well-known/ai.txt | 3 +- www/.well-known/aibdp.json | 79 ++ www/.well-known/aibdp.json.license | 2 + www/.well-known/humans.txt | 8 +- www/.well-known/security.txt | 15 +- www/README.adoc | 117 +++ www/dns/bind9/README.adoc | 56 ++ www/dns/bind9/named.conf.example | 72 ++ www/dns/privacy/ENCRYPTED-DNS.adoc | 97 ++ www/dns/records/2.0.192.in-addr.arpa.zone | 14 + www/dns/records/README.adoc | 43 + www/dns/records/example.invalid.zone | 38 + www/errors/403.html | 15 + www/errors/404.html | 15 + www/errors/429.html | 15 + www/errors/500.html | 15 + www/errors/502.html | 15 + www/errors/503.html | 15 + www/policies/acceptable-use.adoc | 30 + www/policies/ai-use.adoc | 36 + www/policies/privacy.adoc | 33 + www/profiles/README.adoc | 69 ++ www/profiles/authoritative-dns.toml | 14 + www/profiles/baseline-site.toml | 10 + www/profiles/consent-aware-web.toml | 18 + www/profiles/full-expert.toml | 15 + www/profiles/privacy-enhanced.toml | 14 + www/public/index.html | 37 + www/public/styles/site.css | 10 + www/runbooks/cert-rotation.adoc | 45 + www/runbooks/deploy.adoc | 60 ++ www/runbooks/dns-change.adoc | 65 ++ www/runbooks/rollback.adoc | 43 + www/runbooks/stage5-wellknown-sweep.adoc | 157 ++++ www/schemas/aibdp-schema-v0.2.json | 377 ++++++++ www/schemas/aibdp-schema-v0.2.json.license | 2 + www/schemas/publishable-paths.txt | 20 + www/security_headers/README.adoc | 32 + www/security_headers/csp.conf | 23 + www/tests/check-aibdp.sh | 106 +++ www/tests/check-bind-safety.sh | 128 +++ www/tests/check-migration.sh | 214 +++++ www/tests/check-profiles.sh | 94 ++ www/tests/check-publication-boundary.sh | 150 +++ www/tests/check-wellknown.sh | 57 ++ .../controls/aibdp-bad-status.control.json | 7 + .../aibdp-bad-status.control.json.license | 2 + .../controls/aibdp-enforce-430.control.json | 8 + .../aibdp-enforce-430.control.json.license | 2 + www/tests/controls/bad-deploy/dns/named.conf | 1 + www/tests/controls/bad-deploy/index.html | 1 + .../controls/bad-deploy/tests/run-all.sh | 2 + .../controls/caddy-serve-everything.control | 7 + .../good-deploy/.well-known/security.txt | 2 + .../controls/good-deploy/errors/404.html | 1 + www/tests/controls/good-deploy/index.html | 1 + .../good-deploy/policies/privacy.html | 1 + .../named.conf.open-recursion.control | 20 + .../controls/profile-enforce-430.control.toml | 8 + .../profile-hidden-start.control.toml | 8 + www/tests/run-all.sh | 52 ++ www/tls/POLICY.adoc | 60 ++ www/webservers/README.adoc | 42 + www/webservers/apache/vhost.conf.example | 61 ++ www/webservers/caddy/Caddyfile.example | 40 + www/webservers/nginx/site.conf.example | 74 ++ 624 files changed, 29823 insertions(+), 6200 deletions(-) create mode 100644 .cicd-hygiene-allow delete mode 100644 .claude/CLAUDE.md rename .machine_readable/ai/.clinerules => .clinerules (50%) delete mode 100644 .conflow.yaml create mode 100644 .cursorrules delete mode 100644 .github/.mailmap delete mode 100644 .github/.nojekyll delete mode 100644 .github/0.1-AI-MANIFEST.a2ml delete mode 100644 .github/DIRECTORY.adoc create mode 100644 .github/GOVERNANCE.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 .github/MAINTAINERS delete mode 100644 .github/SUPPORT create mode 100644 .github/SUPPORT.md create mode 100644 .github/copilot/coding-agent.yml create mode 100755 .github/hooks/install.sh create mode 100755 .github/hooks/pre-push create mode 100755 .github/hooks/scan-secrets.sh create mode 100755 .github/hooks/validate-deed.sh create mode 100755 .github/hooks/validate-k9.sh create mode 100644 .github/rulesets/Immutable-Tags.json create mode 100644 .github/rulesets/README.adoc create mode 100644 .github/rulesets/base.json create mode 100644 .github/settings.yml create mode 100644 .github/workflows/README.adoc create mode 100644 .github/workflows/actions.lock delete mode 100644 .github/workflows/boj-build.yml create mode 100644 .github/workflows/build-notification.yml delete mode 100644 .github/workflows/casket-pages.yml create mode 100644 .github/workflows/deed-validate.yml create mode 100644 .github/workflows/dependabot-automerge.yml create mode 100644 .github/workflows/docker-build.yml create mode 100644 .github/workflows/dogfood-summary.yml create mode 100644 .github/workflows/dot-wellknown-enforcement.yml create mode 100644 .github/workflows/e2e.yml.template create mode 100644 .github/workflows/eclexiaiser-validate.yml create mode 100644 .github/workflows/empty-linter.yml create mode 100644 .github/workflows/estate-rules.yml create mode 100644 .github/workflows/groove-check.yml create mode 100644 .github/workflows/guix-policy.yml delete mode 100644 .github/workflows/instant-sync.yml create mode 100644 .github/workflows/k9-validate.yml create mode 100644 .github/workflows/lock-sync-gate.yml create mode 100644 .github/workflows/main-estate-audit.yml create mode 100644 .github/workflows/ossf-best-practices.yml create mode 100644 .github/workflows/pages.yml create mode 100644 .github/workflows/quality.yml create mode 100644 .github/workflows/rsr-compliance-canary.yml create mode 100644 .github/workflows/runtime-policy.yml create mode 100644 .github/workflows/rust-ci.yml create mode 100644 .github/workflows/security-policy.yml create mode 100644 .github/workflows/sonarqube.yml create mode 100644 .github/workflows/static-analysis-gate.yml create mode 100644 .github/workflows/workflow-linter.yml create mode 100644 .gitleaksignore create mode 100644 .gitmessage delete mode 100644 .guix-channel create mode 100644 .hypatia-ignore delete mode 100644 .machine_readable/0.1-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/6a2/META.a2ml delete mode 100644 .machine_readable/6a2/PLAYBOOK.a2ml delete mode 100644 .machine_readable/ADJUST.contractile delete mode 100644 .machine_readable/CLADE.a2ml create mode 100644 .machine_readable/ENSAID_CONFIG.a2ml delete mode 100644 .machine_readable/INTENT.contractile delete mode 100644 .machine_readable/MUST.contractile create mode 100644 .machine_readable/PROVENANCE.a2ml delete mode 100644 .machine_readable/TRUST.contractile delete mode 100644 .machine_readable/ai/.cursorrules delete mode 100644 .machine_readable/ai/.windsurfrules delete mode 100644 .machine_readable/ai/0.2-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/anchors/0.2-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/anchors/README.adoc create mode 100644 .machine_readable/arrival-pack/README.adoc create mode 100644 .machine_readable/arrival-pack/arrival-pack.ncl create mode 100644 .machine_readable/arrival-pack/claude-md.k9.ncl create mode 100755 .machine_readable/arrival-pack/extract.sh create mode 100755 .machine_readable/arrival-pack/generate.sh create mode 100755 .machine_readable/arrival-pack/verify.sh rename .machine_readable/{agent_instructions => bot_directives}/README.adoc (88%) rename .machine_readable/{agent_instructions => bot_directives}/coverage.a2ml (98%) rename .machine_readable/{agent_instructions => bot_directives}/debt.a2ml (98%) rename .machine_readable/{agent_instructions => bot_directives}/methodology.a2ml (87%) create mode 100644 .machine_readable/bot_directives/rra.a2ml create mode 100644 .machine_readable/coaptation/README.adoc create mode 100644 .machine_readable/coaptation/coapt.k9.ncl create mode 100644 .machine_readable/coaptation/coapt.ncl create mode 100755 .machine_readable/coaptation/coapt.sh create mode 100644 .machine_readable/coaptation/core/Coaptation.idr create mode 100755 .machine_readable/coaptation/extract-clauses.sh create mode 100755 .machine_readable/coaptation/extract-facts.sh create mode 100644 .machine_readable/coaptation/grades.ncl create mode 100644 .machine_readable/coaptation/receipts/latest.a2ml create mode 100755 .machine_readable/coaptation/verify.sh create mode 100644 .machine_readable/coaptation/witness-map.ncl delete mode 100644 .machine_readable/configs/0.2-AI-MANIFEST.a2ml create mode 100644 .machine_readable/configs/eclexiaiser.toml rename stapeln.toml => .machine_readable/configs/stapeln.toml (71%) create mode 100644 .machine_readable/contractiles/INDEX.a2ml create mode 100644 .machine_readable/contractiles/Justfile create mode 100644 .machine_readable/contractiles/_base.ncl create mode 100644 .machine_readable/contractiles/adjust/Adjustfile.a2ml create mode 100644 .machine_readable/contractiles/adjust/adjust.k9.ncl create mode 100644 .machine_readable/contractiles/adjust/adjust.manifest.a2ml create mode 100644 .machine_readable/contractiles/adjust/adjust.ncl create mode 100644 .machine_readable/contractiles/bust/Bustfile.a2ml create mode 100644 .machine_readable/contractiles/bust/bust.k9.ncl create mode 100644 .machine_readable/contractiles/bust/bust.manifest.a2ml create mode 100644 .machine_readable/contractiles/bust/bust.ncl delete mode 100644 .machine_readable/contractiles/dust/Dustfile create mode 100644 .machine_readable/contractiles/dust/dust.k9.ncl create mode 100644 .machine_readable/contractiles/dust/dust.manifest.a2ml create mode 100644 .machine_readable/contractiles/dust/dust.ncl create mode 100644 .machine_readable/contractiles/intend/Intentfile.a2ml create mode 100644 .machine_readable/contractiles/intend/intend.k9.ncl create mode 100644 .machine_readable/contractiles/intend/intend.manifest.a2ml create mode 100644 .machine_readable/contractiles/intend/intend.ncl delete mode 100644 .machine_readable/contractiles/lust/Intentfile delete mode 100644 .machine_readable/contractiles/must/Mustfile create mode 100644 .machine_readable/contractiles/must/Mustfile.a2ml create mode 100644 .machine_readable/contractiles/must/must.k9.ncl create mode 100644 .machine_readable/contractiles/must/must.manifest.a2ml create mode 100644 .machine_readable/contractiles/must/must.ncl create mode 100644 .machine_readable/contractiles/trust/trust.k9.ncl create mode 100644 .machine_readable/contractiles/trust/trust.manifest.a2ml create mode 100644 .machine_readable/contractiles/trust/trust.ncl rename .machine_readable/{6a2 => descriptiles}/AGENTIC.a2ml (59%) create mode 100644 .machine_readable/descriptiles/CLADE.a2ml rename .machine_readable/{6a2 => descriptiles}/ECOSYSTEM.a2ml (73%) create mode 100644 .machine_readable/descriptiles/META.a2ml rename .machine_readable/{6a2 => descriptiles}/NEUROSYM.a2ml (95%) create mode 100644 .machine_readable/descriptiles/PLAYBOOK.a2ml create mode 100644 .machine_readable/descriptiles/README.adoc rename .machine_readable/{6a2 => descriptiles}/STATE.a2ml (56%) create mode 100644 .machine_readable/descriptiles/VARIANT.a2ml rename .machine_readable/{ => descriptiles}/anchors/ANCHOR.a2ml (80%) create mode 100644 .machine_readable/descriptiles/anchors/README.adoc create mode 100644 .machine_readable/integrations/groove.a2ml delete mode 100644 .machine_readable/policies/0.2-AI-MANIFEST.a2ml create mode 100644 .machine_readable/root-allow.txt create mode 100644 .machine_readable/rsr-profile.a2ml delete mode 100644 .machine_readable/scripts/0.2-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/scripts/forge/0.3-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/scripts/lifecycle/0.3-AI-MANIFEST.a2ml delete mode 100644 .machine_readable/scripts/verification/0.3-AI-MANIFEST.a2ml rename .machine_readable/{contractiles/k9 => self-validating}/README.adoc (89%) rename .machine_readable/{contractiles/k9 => self-validating}/examples/ci-config.k9.ncl (97%) rename .machine_readable/{contractiles/k9 => self-validating}/examples/project-metadata.k9.ncl (86%) rename .machine_readable/{contractiles/k9 => self-validating}/examples/setup-repo.k9.ncl (92%) create mode 100644 .machine_readable/self-validating/methodology-guard.k9.ncl rename .machine_readable/{contractiles/k9 => self-validating}/template-hunt.k9.ncl (98%) rename .machine_readable/{contractiles/k9 => self-validating}/template-kennel.k9.ncl (95%) rename .machine_readable/{contractiles/k9 => self-validating}/template-yard.k9.ncl (97%) create mode 100644 .mailmap delete mode 100644 .mise.toml create mode 100644 .tool-versions create mode 100644 .windsurfrules delete mode 100644 0-AI-MANIFEST.a2ml create mode 100644 CITATION.cff create mode 100644 CLAUDE.md delete mode 100644 CODE_OF_CONDUCT.adoc create mode 100644 CONTRIBUTING.adoc delete mode 100644 Containerfile create mode 100644 GEMINI.md delete mode 100644 GOVERNANCE.adoc delete mode 100644 LICENSES/AGPL-3.0-or-later.txt delete mode 100644 MAINTAINERS delete mode 100644 SECURITY.adoc create mode 100755 benches/template_bench.sh create mode 100644 build/container/.containerignore rename {container => build/container}/Containerfile (58%) create mode 100644 build/container/README.adoc create mode 100644 build/container/compose.example.yaml create mode 100644 build/container/compose.yaml rename {container => build/container}/entrypoint.sh (85%) rename {container => build/container/stapeln}/.gatekeeper.yaml (96%) rename {container => build/container/stapeln}/compose.example.toml (99%) rename {container => build/container/stapeln}/compose.toml (58%) rename {container => build/container/stapeln}/ct-build.sh (77%) rename {container => build/container/stapeln}/deploy.k9.ncl (79%) rename {container => build/container/stapeln}/manifest.toml (71%) create mode 100644 build/container/stapeln/rokur.toml rename {container => build/container/stapeln}/vordr.toml (78%) rename {deploy => build/deploy}/ansible/setup.yml (59%) rename {deploy => build/deploy}/terraform/main.tf (85%) create mode 100755 build/guix.scm create mode 100644 build/just/assess.just create mode 100644 build/just/container.just create mode 100644 build/just/groove.just create mode 100644 build/just/proofs.just create mode 100644 build/just/repo-init.just create mode 100644 build/just/validate.just rename .gitlab-ci.yml => ci/.gitlab-ci.yml (92%) create mode 100644 ci/.pre-commit-config.yaml create mode 100644 ci/README.adoc delete mode 100644 container/0.1-AI-MANIFEST.a2ml delete mode 100644 container/README.adoc delete mode 100644 contractile.just delete mode 100644 contractiles/intend/Intentfile.a2ml delete mode 100644 contractiles/must/Mustfile.a2ml delete mode 100644 contractiles/trust/Trustfile.a2ml create mode 100644 coordination.k9.ncl create mode 100644 dicta-task_chora.deed delete mode 100644 docs/0.1-AI-MANIFEST.a2ml create mode 100644 docs/AFFIRMATION.adoc create mode 100644 docs/AI-INSTALL-README-SECTION.adoc create mode 100644 docs/AI_INSTALLATION_GUIDE.adoc rename ARCHITECTURE.adoc => docs/ARCHITECTURE.adoc (83%) create mode 100644 docs/AUDIT.adoc rename EXPLAINME.adoc => docs/EXPLAINME.adoc (85%) create mode 100644 docs/GOVERNANCE.adoc create mode 100644 docs/MAINTAINERS.adoc create mode 100644 docs/RSR-PHILOSOPHY.adoc create mode 100644 docs/RSR_OUTLINE.adoc delete mode 100644 docs/accessibility/README.adoc create mode 100644 docs/architecture.adoc delete mode 100644 docs/architecture/0.2-AI-MANIFEST.a2ml create mode 100644 docs/architecture/REPOSITORY-MAP.adoc rename TOPOLOGY.adoc => docs/architecture/TOPOLOGY.adoc (99%) delete mode 100644 docs/attribution/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/attribution/CITATION.cff delete mode 100644 docs/attribution/MAINTAINERS.adoc delete mode 100644 docs/compliance/ACCESSIBILITY.adoc create mode 100644 docs/contributing.adoc delete mode 100644 docs/decisions/0.2-AI-MANIFEST.a2ml create mode 100644 docs/decisions/0001-template.adoc create mode 100644 docs/decisions/0002-variant-contract.adoc create mode 100644 docs/decisions/0003-forge-and-sustain-lifecycle.adoc create mode 100644 docs/decisions/0004-storage-backend.adoc delete mode 100644 docs/developer/0.2-AI-MANIFEST.a2ml create mode 100644 docs/developer/invariant-path.adoc delete mode 100644 docs/governance/0.1-AI-MANIFEST.a2ml create mode 100644 docs/governance/CRG-AUDIT-TEMPLATE.adoc delete mode 100644 docs/governance/MAINTENANCE-CHECKLIST.a2ml delete mode 100644 docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.a2ml create mode 100644 docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc create mode 100644 docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc delete mode 100644 docs/governance/audit/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/governance/audit/compliance/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/audit/effects/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/audit/systems/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/maintenance/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/governance/maintenance/adaptive/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/maintenance/corrective/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/maintenance/perfective/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/planning/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/governance/planning/could/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/planning/must/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/governance/planning/should/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/legal/0.2-AI-MANIFEST.a2ml create mode 100644 docs/legal/EXHIBIT-A-ETHICAL-USE.txt create mode 100644 docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt rename QUICKSTART-DEV.adoc => docs/onboarding/QUICKSTART-DEV.adoc (84%) rename QUICKSTART-MAINTAINER.adoc => docs/onboarding/QUICKSTART-MAINTAINER.adoc (89%) rename QUICKSTART-USER.adoc => docs/onboarding/QUICKSTART-USER.adoc (72%) rename llm-warmup-dev.adoc => docs/onboarding/llm-warmup-dev.adoc (84%) rename llm-warmup-user.adoc => docs/onboarding/llm-warmup-user.adoc (85%) delete mode 100644 docs/practice/0.2-AI-MANIFEST.a2ml create mode 100644 docs/practice/ci-cost-reduction.adoc create mode 100644 docs/proposals/root-cleanup.adoc delete mode 100644 docs/reports/0.2-AI-MANIFEST.a2ml create mode 100644 docs/reports/audit/pillar-audit-2026-04-15.adoc delete mode 100644 docs/reports/compliance/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/reports/maintenance/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/reports/performance/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/reports/quality/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/reports/security/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/standards/0.2-AI-MANIFEST.a2ml rename PROOF-NEEDS.adoc => docs/status/PROOF-NEEDS.adoc (100%) create mode 100644 docs/status/PROOF-STATUS.adoc create mode 100644 docs/status/READINESS.adoc rename ROADMAP.adoc => docs/status/ROADMAP.adoc (98%) rename TEST-NEEDS.adoc => docs/status/TEST-NEEDS.adoc (95%) delete mode 100644 docs/theory/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/theory/computing/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/theory/formalisms/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/theory/mathematics/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/theory/ontologies/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/theory/other/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/theory/socio-technical/0.3-AI-MANIFEST.a2ml create mode 100644 docs/troubleshooting.adoc create mode 100644 docs/usage.adoc delete mode 100644 docs/whitepapers/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/whitepapers/academic/.gitkeep delete mode 100644 docs/whitepapers/academic/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/whitepapers/industry/.gitkeep delete mode 100644 docs/whitepapers/industry/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/whitepapers/outreach/0.3-AI-MANIFEST.a2ml delete mode 100644 docs/wikis/0.2-AI-MANIFEST.a2ml delete mode 100644 docs/wikis/README.adoc create mode 100644 docs/wikis/README.md delete mode 100644 eclexiaiser.toml delete mode 100644 examples/0.1-AI-MANIFEST.a2ml delete mode 100644 examples/web-project-deno.json delete mode 100644 features/0.1-AI-MANIFEST.a2ml delete mode 100644 features/boj-server/0.2-AI-MANIFEST.a2ml delete mode 100644 features/panic-attacker/0.2-AI-MANIFEST.a2ml delete mode 100644 features/ssg/0.2-AI-MANIFEST.a2ml delete mode 100644 k9iser.toml create mode 100644 scripts/campaigns/www-bundle.campaign create mode 100644 scripts/check-action-pinning.js create mode 100755 scripts/check-adoc-renders.sh create mode 100755 scripts/check-invisible-characters.sh create mode 100755 scripts/check-lock-sync.sh create mode 100644 scripts/check-no-md-in-docs.sh create mode 100755 scripts/check-no-placeholders.sh create mode 100755 scripts/check-no-vlang.sh create mode 100755 scripts/check-proofs.sh create mode 100755 scripts/check-root-shape.sh create mode 100755 scripts/check-variant-drift.sh create mode 100755 scripts/gen-repo-map.sh create mode 100755 scripts/invariant-path.sh create mode 100755 scripts/migrate-wellknown-to-www.sh create mode 100644 scripts/prune-dependabot-ecosystems.rs create mode 100644 scripts/rsr-campaign.sh create mode 100644 scripts/rust-tool.sh create mode 100755 scripts/scan-dangerous.sh create mode 100644 scripts/strip-instruction-blocks.rs create mode 100755 scripts/sweep-wellknown.sh create mode 100644 scripts/validate-session-contracts.sh create mode 100755 scripts/validate-template.sh delete mode 100644 selur-compose.toml create mode 100644 session/README.adoc create mode 100644 session/custom-checks.k9.ncl create mode 100755 session/dispatch.sh create mode 100755 session/local-hooks.sh delete mode 100755 setup.sh create mode 100644 sonar-project.properties delete mode 100644 src/0.1-AI-MANIFEST.a2ml delete mode 100644 src/aspects/0.2-AI-MANIFEST.a2ml delete mode 100644 src/aspects/integrity/0.3-AI-MANIFEST.a2ml delete mode 100644 src/aspects/observability/0.3-AI-MANIFEST.a2ml delete mode 100644 src/aspects/security/0.3-AI-MANIFEST.a2ml rename {docs/practice => src/bridges}/.gitkeep (100%) delete mode 100644 src/bridges/0.2-AI-MANIFEST.a2ml delete mode 100644 src/contracts/0.2-AI-MANIFEST.a2ml rename {docs/theory => src/core}/.gitkeep (100%) delete mode 100644 src/core/0.2-AI-MANIFEST.a2ml delete mode 100644 src/definitions/0.2-AI-MANIFEST.a2ml rename {configs => src/definitions}/config.ncl (100%) rename {schemas => src/definitions/schemas}/candidate-intent.json (94%) rename {schemas => src/definitions/schemas}/config.cue (100%) rename {schemas => src/definitions/schemas}/tasks.sql (98%) delete mode 100644 src/errors/0.2-AI-MANIFEST.a2ml delete mode 100644 src/interface/0.2-AI-MANIFEST.a2ml create mode 100644 src/interface/Abi/Foreign.idr create mode 100644 src/interface/Abi/Layout.idr create mode 100644 src/interface/Abi/README.adoc rename src/interface/{abi => Abi}/Task.idr (98%) create mode 100644 src/interface/Abi/Types.idr create mode 100644 src/interface/abi.ipkg delete mode 100644 src/interface/abi/0.3-AI-MANIFEST.a2ml delete mode 100644 src/interface/abi/README.adoc delete mode 100644 src/interface/ffi/0.3-AI-MANIFEST.a2ml delete mode 100644 src/interface/ffi/src/0.4-AI-MANIFEST.a2ml delete mode 100644 src/interface/ffi/test/0.4-AI-MANIFEST.a2ml delete mode 100644 src/interface/generated/0.3-AI-MANIFEST.a2ml delete mode 100644 src/interface/generated/abi/.gitkeep delete mode 100644 src/interface/generated/abi/0.4-AI-MANIFEST.a2ml rename src/interface/generated/{dictask_ffi.h => dicta_task_ffi.h} (51%) rename src/parse/{dictask-parse.cabal => dicta-task-parse.cabal} (71%) rename src/parse/src/{Dictask => DictaTask}/Parse/Confidence.hs (98%) rename src/parse/src/{Dictask => DictaTask}/Parse/DateResolver.hs (98%) rename src/parse/src/{Dictask => DictaTask}/Parse/Dedup.hs (97%) rename src/parse/src/{Dictask => DictaTask}/Parse/Intent.hs (98%) rename src/parse/src/{Dictask => DictaTask}/Parse/Internal/Patterns.hs (94%) rename src/parse/src/{Dictask => DictaTask}/Parse/Internal/Tokeniser.hs (88%) rename src/parse/src/{Dictask => DictaTask}/Parse/Priority.hs (97%) rename src/parse/src/{Dictask => DictaTask}/Parse/Types.hs (96%) rename src/parse/test/Test/{Dictask => DictaTask}/Parse/ConfidenceSpec.hs (92%) rename src/parse/test/Test/{Dictask => DictaTask}/Parse/DateResolverSpec.hs (89%) rename src/parse/test/Test/{Dictask => DictaTask}/Parse/DedupSpec.hs (85%) rename src/parse/test/Test/{Dictask => DictaTask}/Parse/IntentSpec.hs (93%) create mode 100755 tests/aspect_tests.sh create mode 100755 tests/e2e.sh create mode 100644 tests/e2e/julia_mint_test.sh create mode 100755 tests/e2e/template_instantiation_test.sh create mode 100644 tests/fuzz/README.adoc delete mode 100644 tests/fuzz/placeholder.txt create mode 100755 tests/invisible-characters-test.sh create mode 100755 tests/shape/check_root_shape_test.sh create mode 100755 tests/shape/repo_map_determinism_test.sh create mode 100755 tests/workflows/check_adoc_renders_test.sh create mode 100755 tests/workflows/check_no_vlang_test.sh create mode 100644 tests/workflows/k9_typecheck_test.sh create mode 100644 tests/workflows/mint_cleanup_test.sh create mode 100644 tests/workflows/session_contracts_test.sh create mode 100755 tests/workflows/validate_workflows_test.sh delete mode 100644 verification/0.1-AI-MANIFEST.a2ml delete mode 100644 verification/benchmarks/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/coverage/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/fuzzing/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/proofs/0.2-AI-MANIFEST.a2ml create mode 100644 verification/proofs/agda/MANIFEST create mode 100644 verification/proofs/agda/Properties.agda create mode 100644 verification/proofs/coq/MANIFEST create mode 100644 verification/proofs/coq/TypeSafety.v create mode 100644 verification/proofs/idris2/ABI/Compliance.idr create mode 100644 verification/proofs/idris2/ABI/Foreign.idr create mode 100644 verification/proofs/idris2/ABI/Layout.idr create mode 100644 verification/proofs/idris2/ABI/Platform.idr create mode 100644 verification/proofs/idris2/ABI/Pointers.idr create mode 100644 verification/proofs/idris2/MANIFEST create mode 100644 verification/proofs/idris2/Types.idr create mode 100644 verification/proofs/lean4/ApiTypes.lean create mode 100644 verification/proofs/lean4/MANIFEST create mode 100644 verification/proofs/lean4/lean-toolchain create mode 100644 verification/proofs/tlaplus/StateMachine.tla delete mode 100644 verification/safety_case/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/simulations/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/tests/0.2-AI-MANIFEST.a2ml delete mode 100644 verification/traceability/0.2-AI-MANIFEST.a2ml create mode 100644 www/.well-known/aibdp.json create mode 100644 www/.well-known/aibdp.json.license create mode 100644 www/README.adoc create mode 100644 www/dns/bind9/README.adoc create mode 100644 www/dns/bind9/named.conf.example create mode 100644 www/dns/privacy/ENCRYPTED-DNS.adoc create mode 100644 www/dns/records/2.0.192.in-addr.arpa.zone create mode 100644 www/dns/records/README.adoc create mode 100644 www/dns/records/example.invalid.zone create mode 100644 www/errors/403.html create mode 100644 www/errors/404.html create mode 100644 www/errors/429.html create mode 100644 www/errors/500.html create mode 100644 www/errors/502.html create mode 100644 www/errors/503.html create mode 100644 www/policies/acceptable-use.adoc create mode 100644 www/policies/ai-use.adoc create mode 100644 www/policies/privacy.adoc create mode 100644 www/profiles/README.adoc create mode 100644 www/profiles/authoritative-dns.toml create mode 100644 www/profiles/baseline-site.toml create mode 100644 www/profiles/consent-aware-web.toml create mode 100644 www/profiles/full-expert.toml create mode 100644 www/profiles/privacy-enhanced.toml create mode 100644 www/public/index.html create mode 100644 www/public/styles/site.css create mode 100644 www/runbooks/cert-rotation.adoc create mode 100644 www/runbooks/deploy.adoc create mode 100644 www/runbooks/dns-change.adoc create mode 100644 www/runbooks/rollback.adoc create mode 100644 www/runbooks/stage5-wellknown-sweep.adoc create mode 100644 www/schemas/aibdp-schema-v0.2.json create mode 100644 www/schemas/aibdp-schema-v0.2.json.license create mode 100644 www/schemas/publishable-paths.txt create mode 100644 www/security_headers/README.adoc create mode 100644 www/security_headers/csp.conf create mode 100755 www/tests/check-aibdp.sh create mode 100755 www/tests/check-bind-safety.sh create mode 100755 www/tests/check-migration.sh create mode 100755 www/tests/check-profiles.sh create mode 100755 www/tests/check-publication-boundary.sh create mode 100755 www/tests/check-wellknown.sh create mode 100644 www/tests/controls/aibdp-bad-status.control.json create mode 100644 www/tests/controls/aibdp-bad-status.control.json.license create mode 100644 www/tests/controls/aibdp-enforce-430.control.json create mode 100644 www/tests/controls/aibdp-enforce-430.control.json.license create mode 100644 www/tests/controls/bad-deploy/dns/named.conf create mode 100644 www/tests/controls/bad-deploy/index.html create mode 100644 www/tests/controls/bad-deploy/tests/run-all.sh create mode 100644 www/tests/controls/caddy-serve-everything.control create mode 100644 www/tests/controls/good-deploy/.well-known/security.txt create mode 100644 www/tests/controls/good-deploy/errors/404.html create mode 100644 www/tests/controls/good-deploy/index.html create mode 100644 www/tests/controls/good-deploy/policies/privacy.html create mode 100644 www/tests/controls/named.conf.open-recursion.control create mode 100644 www/tests/controls/profile-enforce-430.control.toml create mode 100644 www/tests/controls/profile-hidden-start.control.toml create mode 100755 www/tests/run-all.sh create mode 100644 www/tls/POLICY.adoc create mode 100644 www/webservers/README.adoc create mode 100644 www/webservers/apache/vhost.conf.example create mode 100644 www/webservers/caddy/Caddyfile.example create mode 100644 www/webservers/nginx/site.conf.example diff --git a/.cicd-hygiene-allow b/.cicd-hygiene-allow new file mode 100644 index 0000000..1513822 --- /dev/null +++ b/.cicd-hygiene-allow @@ -0,0 +1,12 @@ +# Code-hygiene gate allowlist (consumed by cicd-suite actions/code-hygiene-check). +# Patterns are git pathspec excludes, applied to both the debt-marker scan and +# the proof-circumvention scan. +# +# The two entries below are TEMPLATE SCAFFOLDS, not implementation debt: +# their TODOs are the instantiation seams every minted repo is meant to fill +# in (entrypoint command, e2e sections). Excluding them keeps the gate's +# debt-tracking signal clean for real source in instantiated repos, which +# inherit this file. If an instantiated repo later owns genuine debt in +# these paths, resolve or issue-link it there — do not widen this list. +build/container/entrypoint.sh +tests/e2e.sh diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index ef29514..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,47 +0,0 @@ -# dictask — Project AI Instructions - -## Overview - -dictask is a speech-to-do pipeline that converts voice recordings from a portable -recorder into structured, prioritised, auditable tasks stored in SQLite. - -## Build Commands - -```bash -just build # Build all components -just test # Run all tests -just build-rust # Rust only (ingest, transcribe, store) -just build-haskell # Haskell parser only -just build-ffi # Zig FFI only -just db-init # Initialise SQLite from schemas/tasks.sql -``` - -## Language Map - -| Directory | Language | Purpose | -|-----------|----------|---------| -| `src/ingest/` | Rust | udev detection, audio archival, cloud backup | -| `src/transcribe/` | Rust | Whisper/Vosk ASR integration | -| `src/parse/` | Haskell (megaparsec) | NLP task extraction from transcripts | -| `src/store/` | Rust (rusqlite) | SQLite canonical store + view generation | -| `src/review/` | Rust | Review queue + notifications | -| `src/interface/abi/` | Idris2 | Formal ABI proofs for task schema | -| `src/interface/ffi/` | Zig | C-compatible FFI bridge | -| `schemas/` | SQL + JSON | SQLite schema, JSON schemas for intermediate formats | -| `deploy/ansible/` | YAML | Local machine setup | -| `deploy/terraform/` | HCL | Cloud provisioning | - -## Key Invariants - -1. **Confidence thresholds**: >= 0.8 auto-apply, 0.3–0.8 review, < 0.3 log only -2. **Deletions always review**: DeleteTask confidence capped at 0.7 (never auto-apply) -3. **Idempotent stages**: Same input → same output for all pipeline stages -4. **No Python**: Use Rust, Haskell, Julia, or Zig -5. **Priority formula**: `urgency * 0.5 + importance * 0.3 + deadline_proximity * 0.2` - -## Testing - -- Haskell: `cd src/parse && cabal test` (HSpec + QuickCheck) -- Rust: `cd src/ && cargo test` -- Zig: `cd src/interface/ffi && zig build test` -- Idris2: Compile-time verification (proofs, not runtime tests) diff --git a/.machine_readable/ai/.clinerules b/.clinerules similarity index 50% rename from .machine_readable/ai/.clinerules rename to .clinerules index 462d31b..138414b 100644 --- a/.machine_readable/ai/.clinerules +++ b/.clinerules @@ -1,14 +1,14 @@ # SPDX-License-Identifier: MPL-2.0 -# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> -# Authoritative source: docs/AI-CONVENTIONS.md +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# Authoritative source: docs/practice/AI-CONVENTIONS.adoc -# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/STATE.a2ml. +# STARTUP: Read the repo deed (*_chora.deed at the repo root) first, then .machine_readable/descriptiles/STATE.a2ml. # LICENSE # All original code: MPL-2.0. # Never AGPL-3.0. MPL-2.0 only as platform-required fallback. # SPDX header required on every source file. -# Copyright: {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> +# Copyright: Jonathan D.A. Jewell (hyperpolymath) # STATE FILES (.machine_readable/ ONLY) # Never create in repo root: STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, @@ -23,14 +23,22 @@ # Lean: sorry # Rust: transmute (unless FFI with // SAFETY: comment) +# JS/TS RUNTIMES — ordered preference, reach for the first that can do the job +# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) +# 1. Bun default for all new work; runs .ts directly, no build step +# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm +# 3. pnpm only where an upstream toolchain needs a node_modules layout +# 4. npm last resort; permitted, never preferred — a noted decision +# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is +# RETIRED: ReScript is no longer used in this estate, so that rule named a dead +# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. + # BANNED LANGUAGES -# TypeScript -> ReScript -# Node.js / npm / bun -> Deno # Go -> Rust # Python -> Julia or Rust # CONTAINERS -# Runtime: Podman (never Docker). +# Runtime: Podman first. # File: Containerfile (never Dockerfile). # Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. @@ -39,5 +47,5 @@ # FFI: Zig with C ABI (src/interface/ffi/). # Headers: src/interface/generated/. -# BUILD: Use just (justfile) for all tasks. +# BUILD: Use just (Justfile) for all tasks. # STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/.conflow.yaml b/.conflow.yaml deleted file mode 100644 index 3e42c6f..0000000 --- a/.conflow.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# conflow pipeline - Nickel generation -version: "1" -name: "dictask" - -stages: - - name: "generate" - description: "Generate configuration from Nickel" - tool: - type: nickel - command: export - file: configs/config.ncl - format: json - input: "configs/config.ncl" - output: generated/config.json - - - name: "validate" - description: "Validate generated configuration" - tool: - type: cue - command: vet - schemas: - - schemas/config.cue - input: - from_stage: generate - depends_on: - - generate diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 0000000..4d99929 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# Authoritative source: docs/practice/AI-CONVENTIONS.adoc + +# Read the repo deed (*_chora.deed in the repo root) FIRST: canonical file locations +# live in its (ply ...) canonical-locations clauses. + +# LICENSE +# All original code: MPL-2.0 (SPDX header required on every file). +# Copyright: Jonathan D.A. Jewell (hyperpolymath) + +# STATE FILES +# .deed metadata files go in .machine_readable/ ONLY. +# Never create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, or PLAYBOOK.a2ml. + +# BANNED PATTERNS +# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO +# Haskell: unsafeCoerce, unsafePerformIO, undefined, error +# OCaml: Obj.magic, Obj.repr, Obj.obj +# Coq: Admitted +# Lean: sorry +# Rust: transmute (unless FFI with // SAFETY: comment) + +# JS RUNTIMES — ordered preference, reach for the first that can do the job +# (standards/3-practice/LANGUAGE-POLICY.adoc section 1, ruled 2026-07-29) +# 1. Bun default for all new work; runs .ts directly, no build step +# 2. Deno existing Deno projects are grandfathered; prefer over pnpm/npm +# 3. pnpm only where an upstream toolchain needs a node_modules layout +# 4. npm last resort; permitted, never preferred — a noted decision +# TypeScript IS PERMITTED under Bun. The old "use ReScript instead" rule is +# RETIRED: ReScript is no longer used in this estate, so that rule named a dead +# alternative. Do NOT migrate Bun to Deno — that inverts the current ruling. + +# BANNED LANGUAGES +# Go -> use Rust +# Python -> use Julia or Rust + +# CONTAINERS +# Runtime: Podman (never Docker) +# File: Containerfile (never Dockerfile) +# Base: cgr.dev/chainguard/wolfi-base:latest + +# ABI/FFI STANDARD +# ABI definitions: Idris2 with dependent types (src/interface/abi/) +# FFI implementation: Zig with C ABI (src/interface/ffi/) +# Generated C headers: src/interface/generated/ + +# BUILD SYSTEM +# Use just (Justfile) for all build, test, lint, and format tasks. + +# CODE STYLE +# Use descriptive variable names. +# Annotate and document all files. +# Add SPDX-License-Identifier headers to every source file. diff --git a/.devcontainer/Containerfile b/.devcontainer/Containerfile index 928a042..1d9b1b0 100644 --- a/.devcontainer/Containerfile +++ b/.devcontainer/Containerfile @@ -1,9 +1,11 @@ # SPDX-License-Identifier: MPL-2.0 -# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Dev Container image for {{PROJECT_NAME}} +# Dev Container image for Dicta-Task # Base: Chainguard Wolfi (minimal, supply-chain-secure) -# Build: podman build -t {{PROJECT_NAME}}-dev -f .devcontainer/Containerfile . +# Build: podman build -t Dicta-Task-dev -f .devcontainer/Containerfile . +# `:latest` suits a dev container (rebuilt daily); pin by digest +# (...@sha256:) if you need a reproducible dev environment. FROM cgr.dev/chainguard/wolfi-base:latest @@ -24,7 +26,7 @@ RUN groupadd -g 1000 nonroot || true \ && useradd -m -u 1000 -g 1000 -s /bin/bash nonroot || true # Set workspace directory -WORKDIR /workspaces/{{PROJECT_NAME}} +WORKDIR /workspaces/Dicta-Task # Default shell ENV SHELL=/bin/bash diff --git a/.devcontainer/README.adoc b/.devcontainer/README.adoc index c94767d..5b0a718 100644 --- a/.devcontainer/README.adoc +++ b/.devcontainer/README.adoc @@ -1,4 +1,5 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Dev Container Usage :author: Jonathan D.A. Jewell @@ -24,4 +25,4 @@ This dev container uses `cgr.dev/chainguard/wolfi-base` with git, curl, bash, an == Customization -Replace `Dictask` placeholders in both `devcontainer.json` and `Containerfile` with your actual project name. Run `just deps` to verify the environment after first launch. +Replace `Dicta-Task` placeholders in both `devcontainer.json` and `Containerfile` with your actual project name. Run `just deps` to verify the environment after first launch. diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ac12c4a..e2109a8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -// Dev Container configuration for dictask +// Dev Container configuration for Dicta-Task // Works with: VS Code Dev Containers, GitHub Codespaces, Gitpod // Container runtime: Podman (recommended) or any OCI-compliant runtime { - "name": "dictask", + "name": "Dicta-Task", "build": { "dockerfile": "Containerfile", diff --git a/.editorconfig b/.editorconfig index c9980a0..df40e5b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,34 +1,46 @@ -# dictask - Editor Configuration +# SPDX-License-Identifier: MPL-2.0 +# Hyperpolymath estate canonical .editorconfig (standards#343 phase 1) +# Canon lives in dicta-task; do not add a per-repo name to this header. # https://editorconfig.org root = true +# --- Estate baseline ------------------------------------------------------- [*] charset = utf-8 end_of_line = lf -indent_size = 2 indent_style = space +indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true +# --- Prose: trailing whitespace is significant ----------------------------- [*.md] trim_trailing_whitespace = false [*.adoc] trim_trailing_whitespace = false +# --- 4-space languages ----------------------------------------------------- [*.rs] indent_size = 4 -[*.ex] -indent_size = 2 +[*.zig] +indent_size = 4 -[*.exs] -indent_size = 2 +[*.jl] +indent_size = 4 -[*.zig] +[*.c] +indent_size = 4 + +[*.h] +indent_size = 4 + +[*.php] indent_size = 4 +# --- 3-space languages (Ada / GNAT house style) ---------------------------- [*.ada] indent_size = 3 @@ -38,13 +50,17 @@ indent_size = 3 [*.ads] indent_size = 3 +[*.gpr] +indent_size = 3 + +# --- 2-space languages (explicit; matches the [*] default) ----------------- [*.hs] indent_size = 2 -[*.res] +[*.ex] indent_size = 2 -[*.resi] +[*.exs] indent_size = 2 [*.ncl] @@ -56,10 +72,69 @@ indent_size = 2 [*.scm] indent_size = 2 -[*.nix] +[*.idr] +indent_size = 2 + +[*.ipkg] +indent_size = 2 + +[*.k9] +indent_size = 2 + +[*.agda] indent_size = 2 +[*.lean] +indent_size = 2 + +[*.ebnf] +indent_size = 2 + +# --- Tab-mandatory formats ------------------------------------------------- + +# --- Task runners ---------------------------------------------------------- [Justfile] indent_style = space indent_size = 4 +[*.just] +indent_style = space +indent_size = 4 + +[Mustfile] +indent_style = space +indent_size = 4 + +# --- Platform-mandated line endings ---------------------------------------- +# Windows batch/PowerShell hosts require CRLF; keep in step with .gitattributes. +[*.bat] +end_of_line = crlf + +[*.cmd] +end_of_line = crlf + +[*.ps1] +end_of_line = crlf + +# --- Legacy / retired toolchains (retained for byte hygiene only) ---------- +# ReScript (LANGUAGE-POLICY 1.2) and Nix (retired 2026-06-01) are no longer +# adopted for new work. These sections are inert where the files are absent and +# keep surviving files from drifting. Removal is a per-repo judgement. + +[*.a2ml] +indent_size = 2 + +[*.go] +indent_style = tab + +[*.nix] +indent_size = 2 + +[*.res] +indent_size = 2 + +[*.resi] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.envrc b/.envrc index ebba6e1..5018506 100644 --- a/.envrc +++ b/.envrc @@ -3,8 +3,8 @@ # Install direnv: https://direnv.net/ # Load .tool-versions if asdf is available -if has asdf; then - use asdf +if has mise; then + use mise fi # Load Guix shell if build/guix.scm exists @@ -12,15 +12,11 @@ if has guix && [ -f build/guix.scm ]; then use guix fi -# Load Nix flake if flake.nix exists -if has nix && [ -f flake.nix ]; then -fi - # Project environment variables -export PROJECT_NAME="{{PROJECT_NAME}}" +export PROJECT_NAME="Dicta-Task" export RSR_TIER="infrastructure" -# export DATABASE_URL="..." -# export API_KEY="..." +# Add non-secret project env vars above (e.g. DATABASE_URL, service endpoints). +# Real secrets belong in .env (gitignored) — never commit them to .envrc. # Source .env if it exists (gitignored) dotenv_if_exists diff --git a/.gitattributes b/.gitattributes index f8f9072..c3ff23d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,54 +1,117 @@ # SPDX-License-Identifier: MPL-2.0 -# RSR-compliant .gitattributes +# Hyperpolymath estate canonical .gitattributes (standards#343 phase 1) +# Canon lives in dicta-task; do not add a per-repo name to this header. +# +# Only diff drivers that git actually ships are used here. MEASURED on git +# 2.47.3: diff=go and diff=zig are NOT drivers (git silently falls back to the +# default heuristic); the Go driver is named `golang`. * text=auto eol=lf -# Source +# --- Source ---------------------------------------------------------------- *.rs text eol=lf diff=rust *.ex text eol=lf diff=elixir *.exs text eol=lf diff=elixir -*.jl text eol=lf -*.res text eol=lf -*.resi text eol=lf *.ada text eol=lf diff=ada *.adb text eol=lf diff=ada *.ads text eol=lf diff=ada +*.gpr text eol=lf diff=ada +*.go text eol=lf diff=golang +*.scm text eol=lf diff=scheme linguist-language=Scheme +*.rkt text eol=lf +*.jl text eol=lf *.hs text eol=lf +*.zig text eol=lf *.chpl text eol=lf -*.scm text eol=lf +*.idr text eol=lf linguist-language=Idris +*.ipkg text eol=lf linguist-language=Idris +*.agda text eol=lf linguist-language=Agda +*.lagda text eol=lf linguist-language=Agda +*.lean text eol=lf +# `.v` is ambiguous (Coq / Verilog / V). This estate's `.v` files are Coq +# proofs; 49 repos currently mislabel them `linguist-language=V`. +*.v text eol=lf linguist-language=Coq *.ncl text eol=lf -*.nix text eol=lf +*.k9 text eol=lf linguist-language=Nickel +*.deed text eol=lf linguist-language=SCM +*.ebnf text eol=lf +*.js text eol=lf +*.sh text eol=lf diff=bash +*.bash text eol=lf diff=bash + +# --- Windows hosts require CRLF -------------------------------------------- +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf -# Docs +# --- Docs ------------------------------------------------------------------ *.md text eol=lf diff=markdown *.adoc text eol=lf *.txt text eol=lf +*.tex text eol=lf diff=tex +*.bib text eol=lf diff=bibtex -# Data +# --- Data / config --------------------------------------------------------- *.json text eol=lf +*.jsonl text eol=lf *.yaml text eol=lf *.yml text eol=lf *.toml text eol=lf +*.svg text eol=lf +*.csv text eol=lf +*.html text eol=lf diff=html +*.css text eol=lf diff=css -# Config +# --- Repo control files ---------------------------------------------------- .gitignore text eol=lf .gitattributes text eol=lf +.editorconfig text eol=lf +.tool-versions text eol=lf Justfile text eol=lf -Makefile text eol=lf +*.just text eol=lf +Mustfile text eol=lf Containerfile text eol=lf -# Scripts -*.sh text eol=lf - -# Binary +# --- Binary ---------------------------------------------------------------- *.png binary *.jpg binary +*.jpeg binary *.gif binary +*.webp binary +*.ico binary *.pdf binary +*.woff binary *.woff2 binary +*.ttf binary +*.otf binary +*.eot binary *.zip binary +*.tar binary *.gz binary +*.xz binary +*.bz2 binary +*.so binary +*.dylib binary +*.dll binary +*.exe binary +*.wasm binary +*.rlib binary +*.beam binary + +# --- Generated lockfiles: no diff noise, not counted as source ------------- +Cargo.lock text eol=lf -diff linguist-generated=true +mix.lock text eol=lf -diff linguist-generated=true +bun.lock text eol=lf -diff linguist-generated=true +bun.lockb binary -diff linguist-generated=true +pnpm-lock.yaml text eol=lf -diff linguist-generated=true +package-lock.json text eol=lf -diff linguist-generated=true -# Lock files -Cargo.lock text eol=lf -diff -flake.lock text eol=lf -diff +# --- Legacy / retired toolchains (byte hygiene only) ----------------------- +# ReScript is retired (LANGUAGE-POLICY 1.2); Nix was retired 2026-06-01. These +# lines keep surviving files normalised and keep retired tech out of the +# GitHub primary-language badge. Removal is a per-repo judgement, never a sweep. +*.res text eol=lf +*.resi text eol=lf +**/*.res linguist-detectable=false +*.nix text eol=lf +flake.lock text eol=lf -diff linguist-generated=true diff --git a/.github/.mailmap b/.github/.mailmap deleted file mode 100644 index 0ada9de..0000000 --- a/.github/.mailmap +++ /dev/null @@ -1 +0,0 @@ -{{AUTHOR}} <{{AUTHOR_EMAIL}}> <{{AUTHOR_EMAIL_ALT}}> diff --git a/.github/.nojekyll b/.github/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/.github/0.1-AI-MANIFEST.a2ml b/.github/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index 85b2f0f..0000000 --- a/.github/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1 +0,0 @@ -# AI Manifest - Level 1: .github diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 6cbb0be..0e9d8e3 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -1,28 +1,12 @@ -# Code of Conduct - - +# Code of Conduct ## Our Pledge -We as members, contributors, and leaders pledge to make participation in Dictask a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation. +We as members, contributors, and leaders pledge to make participation in dicta-task a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, colour, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. @@ -136,9 +120,8 @@ If you experience or witness unacceptable behaviour, or have any other concerns, | Method | Details | Best For | |--------|---------|----------| -| **Email** | {{CONDUCT_EMAIL}} | Detailed reports, sensitive matters | +| **Email** | j.d.a.jewell@open.ac.uk | Detailed reports, sensitive matters | | **Private Message** | Contact any maintainer directly | Quick questions, minor issues | -| **Anonymous Form** | [Link to form if available] | When you need anonymity | **What to Include** @@ -152,8 +135,8 @@ If you experience or witness unacceptable behaviour, or have any other concerns, **What Happens Next** -1. You will receive acknowledgment within **{{RESPONSE_TIME}}** -2. The {{CONDUCT_TEAM}} will review the report +1. You will receive acknowledgment within **48 hours** +2. The Code of Conduct Committee will review the report 3. We may ask for additional information 4. We will determine appropriate action 5. We will inform you of the outcome (respecting others' privacy) @@ -169,7 +152,7 @@ All reports will be handled with discretion: ### Conflicts of Interest -If a {{CONDUCT_TEAM}} member is involved in an incident: +If the Code of Conduct Committee are themselves involved in an incident: - They will recuse themselves from the process - Another maintainer or external party will handle the report @@ -179,7 +162,7 @@ If a {{CONDUCT_TEAM}} member is involved in an incident: ## Enforcement Guidelines -The {{CONDUCT_TEAM}} will follow these guidelines in determining consequences: +The Code of Conduct Committee will follow these guidelines in determining consequences: ### 1. Correction @@ -215,6 +198,14 @@ The {{CONDUCT_TEAM}} will follow these guidelines in determining consequences: ### Enforcement Across Perimeters +This project uses the estate's **Tri-Perimeter Contribution Framework (TPCF)**, a +graduated trust model defined in `hyperpolymath/standards` +(`rhodium-standard-repositories/README.adoc`): + +- 🔒 **Perimeter 1 (Core)** — maintainers only; shell runtime, build systems +- 🧠 **Perimeter 2 (Expert)** — trusted contributors; protocol extensions, validators +- 🌱 **Perimeter 3 (Community)** — open to all; docs, tests, proposals + For contributors with elevated access (Perimeter 2 or 1): | Level | Additional Consequence | @@ -231,13 +222,13 @@ For contributors with elevated access (Perimeter 2 or 1): If you believe an enforcement decision was made in error: 1. **Wait 7 days** after the decision (cooling-off period) -2. **Email** {{CONDUCT_EMAIL}} with subject line "Appeal: [Original Report ID]" +2. **Email** j.d.a.jewell@open.ac.uk with subject line "Appeal: [Original Report ID]" 3. **Explain** why you believe the decision should be reconsidered 4. **Provide** any new information not previously available **Appeals Process** -- Appeals are reviewed by a different {{CONDUCT_TEAM}} member than the original +- Appeals are reviewed by the Code of Conduct Committee, excluding anyone involved in the original - You will receive a response within 14 days - The appeals decision is final - You may only appeal once per incident @@ -310,8 +301,8 @@ We thank these communities for their leadership in creating welcoming spaces. If you have questions about this Code of Conduct: -- Open a [Discussion](https://github.com/hyperpolymath/dictask/discussions) (for general questions) -- Email {{CONDUCT_EMAIL}} (for private questions) +- Open a [Discussion](https://github.com/hyperpolymath/dicta-task/discussions) (for general questions) +- Email j.d.a.jewell@open.ac.uk (for private questions) - Contact any maintainer directly --- diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 7874d1e..fc34f6e 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,13 +1,18 @@ + +``` # Clone the repository -git clone https://github.com/hyperpolymath/dictask.git -cd dictask +git clone https://github.com/hyperpolymath/dicta-task.git +cd dicta-task # Using Guix (recommended for reproducibility) -guix develop +guix shell -D -f build/guix.scm # Or using toolbox/distrobox -toolbox create dictask-dev -toolbox enter dictask-dev +toolbox create dicta-task-dev +toolbox enter dicta-task-dev # Install dependencies manually # Verify setup @@ -16,39 +21,15 @@ just test # Run test suite ``` ### Repository Structure -``` -dictask/ -├── src/ # Source code (Perimeter 1-2) -├── lib/ # Library code (Perimeter 1-2) -├── extensions/ # Extensions (Perimeter 2) -├── plugins/ # Plugins (Perimeter 2) -├── tools/ # Tooling (Perimeter 2) -├── docs/ # Documentation (Perimeter 3) -│ ├── architecture/ # ADRs, specs (Perimeter 2) -│ └── proposals/ # RFCs (Perimeter 3) -├── examples/ # Examples (Perimeter 3) -├── spec/ # Spec tests (Perimeter 3) -├── tests/ # Test suite (Perimeter 2-3) -├── .machine_readable/ # ALL machine-readable content (Perimeter 1) -│ ├── *.a2ml # State files (STATE, META, ECOSYSTEM, etc.) -│ ├── bot_directives/ # Bot configs -│ └── contractiles/ # Policy contracts (k9, dust, lust, must, trust) -├── .well-known/ # Protocol files (Perimeter 1-3) -├── .github/ # GitHub config (Perimeter 1) -│ ├── CONTRIBUTING.md # This file -│ ├── ISSUE_TEMPLATE/ -│ └── workflows/ -├── CHANGELOG.md -├── CODE_OF_CONDUCT.md -├── GOVERNANCE.md -├── LICENSE -├── MAINTAINERS.md -├── README.adoc -├── SECURITY.md -├── flake.guix # Guix flake — fallback (Perimeter 1) -├── guix.scm # Guix package — primary (Perimeter 1) -└── Justfile # Task runner -``` + +The authoritative map is **generated** from the tree and checked in CI, so it +cannot drift: +[`docs/architecture/REPOSITORY-MAP.adoc`](../docs/architecture/REPOSITORY-MAP.adoc). +Regenerate it with `just repo-map`. + +A hand-written tree used to live here. It described `lib/`, `extensions/`, +`plugins/` and `spec/` directories that this repository has never contained, +which is precisely why the map is now generated rather than typed. --- @@ -74,7 +55,7 @@ Use the [bug report template](.github/ISSUE_TEMPLATE/bug_report.md) and include: ### Suggesting Features **Before suggesting**: -1. Check the [roadmap](ROADMAP.md) if available +1. Check the [roadmap](../docs/status/ROADMAP.adoc) if available 2. Search existing issues and discussions 3. Consider which perimeter the feature belongs to @@ -91,10 +72,10 @@ Use the [feature request template](.github/ISSUE_TEMPLATE/feature_request.md) an Look for issues labelled: -- [`good first issue`](https://github.com/hyperpolymath/dictask/labels/good%20first%20issue) — Simple Perimeter 3 tasks -- [`help wanted`](https://github.com/hyperpolymath/dictask/labels/help%20wanted) — Community help needed -- [`documentation`](https://github.com/hyperpolymath/dictask/labels/documentation) — Docs improvements -- [`perimeter-3`](https://github.com/hyperpolymath/dictask/labels/perimeter-3) — Community sandbox scope +- [`good first issue`](https://github.com/hyperpolymath/dicta-task/labels/good%20first%20issue) — Simple Perimeter 3 tasks +- [`help wanted`](https://github.com/hyperpolymath/dicta-task/labels/help%20wanted) — Community help needed +- [`documentation`](https://github.com/hyperpolymath/dicta-task/labels/documentation) — Docs improvements +- [`perimeter-3`](https://github.com/hyperpolymath/dicta-task/labels/perimeter-3) — Community sandbox scope --- @@ -119,3 +100,4 @@ We follow [Conventional Commits](https://www.conventionalcommits.org/): [optional body] [optional footer] +``` diff --git a/.github/DIRECTORY.adoc b/.github/DIRECTORY.adoc deleted file mode 100644 index a97d220..0000000 --- a/.github/DIRECTORY.adoc +++ /dev/null @@ -1 +0,0 @@ -= .github Pillar diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 688a442..557149b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,17 @@ -# SPDX-License-Identifier: MPL-2.0 -# Funding platforms for hyperpolymath projects -# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/displaying-a-sponsor-button-in-your-repository +# SPDX-License-Identifier: MPL-2.0 for code +# SPDX-License-Identifier: CC-BY-SA-4.0 for documentation +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# These are supported funding model platforms + +buy_me_a_coffee: jonathan.jewell +community_bridge: jonathan-jewell github: hyperpolymath +issuehunt: hyperpolymath ko_fi: hyperpolymath +lfx_crowdfunding: hyperpolymath liberapay: hyperpolymath +open_collective: jonathan-jewell +patreon: cc_studio +polar: hyperpolymath +thanks_dev: hyperpolymath diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md new file mode 100644 index 0000000..53729ce --- /dev/null +++ b/.github/GOVERNANCE.md @@ -0,0 +1,160 @@ + +# Project Governance + +This document describes the governance model for **Dicta-Task**. + +--- + +## Project Governance Model + +Dicta-Task follows a **Benevolent Dictator For Life (BDFL)** governance model. +This model is well-suited for solo maintainers and small project teams where rapid, +consistent decision-making is more valuable than formal consensus processes. + +The BDFL has final authority on all project decisions, including technical direction, +release schedules, contributor access, and community standards. + +> **Transition clause:** When the core team exceeds three active maintainers, this +> project should transition to a **consensus-based governance model** with documented +> voting procedures. That transition should itself be recorded as an Architecture +> Decision Record (ADR) in `docs/decisions/`. + +--- + +## Decision Making + +### Day-to-day decisions + +- The BDFL makes final decisions on all matters. +- Routine decisions (bug fixes, dependency updates, minor improvements) may be made + by any maintainer with commit access. +- Maintainers are expected to use good judgement and seek input on non-trivial changes. + +### Proposing changes + +- Contributors can propose changes by opening issues or pull requests. +- Significant changes (new features, breaking changes, architectural shifts) should + be discussed in an issue before implementation begins. +- The BDFL will provide a clear accept/reject decision with reasoning. + +### Architecture Decision Records (ADRs) + +- Significant technical decisions are documented as ADRs in `docs/decisions/`. +- ADR statuses: `proposed`, `accepted`, `deprecated`, `superseded`, `rejected`. +- ADRs provide a historical record of why decisions were made and what alternatives + were considered. +- See `.machine_readable/descriptiles/META.a2ml` for the machine-readable ADR index. + +--- + +## Roles + +### BDFL (Benevolent Dictator For Life) + +- The project creator and ultimate decision-maker. +- Sets the project's technical direction and long-term vision. +- Has final say on all matters, including maintainer appointments and removals. +- Responsible for ensuring the project adheres to RSR standards. + +### Maintainer + +- Has commit access to the repository. +- Reviews and merges pull requests. +- Triages issues and manages releases. +- Upholds code quality, security standards, and the Code of Conduct. +- Listed in [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc). + +### Contributor + +- Anyone who submits pull requests, opens issues, or participates in discussions. +- Does not have direct commit access. +- Contributions are reviewed by maintainers before merging. +- All contributors must follow the [Code of Conduct](CODE_OF_CONDUCT.md). + +### Bot + +- Automated agents managed via your bot orchestration system. +- Perform automated code review, security scanning, dependency updates, and + standards enforcement. +- Bot actions are subject to the same quality and review standards as human + contributions. +- Configure your bots in `.machine_readable/bot_directives/`. + +--- + +## Becoming a Maintainer + +A contributor may be nominated to become a maintainer when they demonstrate: + +1. **Sustained quality contributions** -- a track record of well-crafted pull requests + that follow project conventions and require minimal revision. +2. **Understanding of RSR standards** -- familiarity with the Repository Structure + Requirements, security policies, and CI/CD workflows used across the project. +3. **Constructive participation** -- helpful issue triage, thoughtful code review + comments, and mentoring of other contributors. +4. **Reliability** -- consistent engagement over a meaningful period (typically 3+ + months of active contribution). + +### Process + +1. An existing maintainer nominates the candidate by opening a private discussion + with the BDFL. +2. The BDFL reviews the candidate's contribution history and community interactions. +3. The BDFL approves or declines the nomination, with reasoning provided to the + nominator. +4. If approved, the new maintainer is added to [MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) and + granted appropriate repository access. + +--- + +## Removing a Maintainer + +A maintainer may be removed under the following circumstances: + +- **Inactivity**: No meaningful contributions or reviews for 12 or more consecutive + months. The maintainer will be contacted before removal and offered the option to + move to emeritus status voluntarily. +- **Code of Conduct violation**: Behaviour that violates the + [Code of Conduct](CODE_OF_CONDUCT.md), as determined through the enforcement + process described therein. +- **BDFL discretion**: The BDFL may remove a maintainer for other reasons (e.g., + repeated disregard for project standards, loss of trust). Reasoning will be + documented privately. + +Removed maintainers are moved to the Emeritus section of +[MAINTAINERS.adoc](../docs/MAINTAINERS.adoc) unless removal was due to a serious Code of Conduct +violation. + +--- + +## Code of Conduct + +All participants in this project are expected to follow the +[Code of Conduct](CODE_OF_CONDUCT.md). The Code of Conduct applies to all project +spaces, including issues, pull requests, discussions, and any forum where the project +is represented. + +Enforcement of the Code of Conduct is described in that document. The BDFL serves as +the final arbiter in conduct disputes. + +--- + +## Amendments + +This governance document may be amended by the BDFL at any time. All amendments will +be: + +1. Documented as an ADR in `docs/decisions/` explaining the rationale for the change. +2. Committed to the repository with a clear commit message. +3. Communicated to existing maintainers and contributors via the project's usual + channels. + +Substantive changes (e.g., changing the governance model itself) should be discussed +with the community before adoption, even though the BDFL retains final authority. + +--- + +Copyright (c) 2026 hyperpolymath. Licensed under MPL-2.0. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..41699b6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Bug report +description: Something behaves differently from what it claims to do. +labels: ["bug", "needs-triage"] +body: + - type: markdown + attributes: + value: | + Please report what you **observed**, not what you inferred. A command and + its actual output is worth more than a description of the problem. + - type: textarea + id: what-happened + attributes: + label: What happened + description: The observed behaviour, with the exact command and its output. + placeholder: | + $ just verify + error: ... + render: shell + validations: + required: true + - type: textarea + id: expected + attributes: + label: What you expected instead + validations: + required: true + - type: textarea + id: repro + attributes: + label: Minimal reproduction + description: The shortest sequence that reproduces it from a clean checkout. + validations: + required: true + - type: input + id: version + attributes: + label: Version / commit + description: Output of `git rev-parse --short HEAD`. + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: OS, and the output of `just --version` and `mise current` if relevant. + validations: + required: false + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I have reported observed output rather than a summary of it. + required: true + - label: This is not a security vulnerability (those go via a private advisory). + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..85ee2a2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# Issue chooser configuration. Blank issues stay enabled so that reports which +# fit neither form are not silently discouraged. +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/hyperpolymath/dicta-task/security/advisories/new + about: Report privately via a security advisory. Do NOT open a public issue. + - name: Question or support request + url: https://github.com/hyperpolymath/dicta-task/discussions + about: Ask a question. See .github/SUPPORT.md for what to expect. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..068c5c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Feature request +description: Propose a capability this project does not yet have. +labels: ["enhancement", "needs-triage"] +body: + - type: textarea + id: problem + attributes: + label: The problem + description: What are you unable to do today? Describe the situation, not the solution. + validations: + required: true + - type: textarea + id: proposal + attributes: + label: Proposed change + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Including doing nothing — say why that is insufficient. + validations: + required: false + - type: dropdown + id: scope + attributes: + label: Scope + description: Would this change the template's shape, and therefore every repo minted from it? + options: + - Repo-local — affects only this project + - Template-wide — would propagate to minted repos + - Not sure + validations: + required: true diff --git a/.github/MAINTAINERS b/.github/MAINTAINERS deleted file mode 100644 index 145c4e9..0000000 --- a/.github/MAINTAINERS +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# MAINTAINERS - Project maintainers and contact information -# -# Format: Name (role) -# Replace placeholders with actual maintainer information. - -{{AUTHOR}} <{{AUTHOR_EMAIL}}> (Lead Maintainer) - -# Additional maintainers: -# Name (role) diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 46c3e4b..512cff4 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -1,5 +1,16 @@ + # Security Policy + + We take security seriously. We appreciate your efforts to responsibly disclose vulnerabilities and will make every effort to acknowledge your contributions. ## Table of Contents @@ -22,7 +33,7 @@ We take security seriously. We appreciate your efforts to responsibly disclose v The preferred method for reporting security vulnerabilities is through GitHub's Security Advisory feature: -1. Navigate to [Report a Vulnerability](https://github.com/hyperpolymath/dictask/security/advisories/new) +1. Navigate to [Report a Vulnerability](https://github.com/hyperpolymath/dicta-task/security/advisories/new) 2. Click **"Report a vulnerability"** 3. Complete the form with as much detail as possible 4. Submit — we'll receive a private notification @@ -34,15 +45,17 @@ This method ensures: - Coordinated disclosure tooling - Automatic credit when the advisory is published -### Alternative: Encrypted Email +### Alternative: Email If you cannot use GitHub Security Advisories, you may email us directly: | | | |---|---| -| **Email** | 6759885+hyperpolymath@users.noreply.github.com | -| **Fingerprint** | `[PGP fingerprint not set]` | +| **Email** | j.d.a.jewell@open.ac.uk | +This mailbox is not encrypted. For anything sensitive enough to need +encryption, prefer GitHub Security Advisories above — the report stays private +to the maintainers until an advisory is published. > **⚠️ Important:** Do not report security vulnerabilities through public GitHub issues, pull requests, discussions, or social media. @@ -174,7 +187,7 @@ If we cannot reach agreement on disclosure timing, we default to 90 days from yo The following are within scope for security research: -- This repository (`hyperpolymath/dictask`) and all its code +- This repository (`hyperpolymath/dicta-task`) and all its code - Official releases and packages published from this repository - Documentation that could lead to security issues - Build and deployment configurations in this repository @@ -293,8 +306,8 @@ Recognition includes: To stay informed about security updates: - **Watch this repository**: Click "Watch" → "Custom" → Select "Security alerts" -- **GitHub Security Advisories**: Published at [Security Advisories](https://github.com/hyperpolymath/dictask/security/advisories) -- **Release notes**: Security fixes noted in [CHANGELOG](CHANGELOG.md) +- **GitHub Security Advisories**: Published at [Security Advisories](https://github.com/hyperpolymath/dicta-task/security/advisories) +- **Release notes**: Security fixes noted in [CHANGELOG](../CHANGELOG.md) ### Update Policy @@ -319,7 +332,7 @@ To stay informed about security updates: ## Security Best Practices -When using Dictask, we recommend: +When using dicta-task, we recommend: ### General @@ -341,8 +354,8 @@ When using Dictask, we recommend: ## Additional Resources -- [Security Advisories](https://github.com/hyperpolymath/dictask/security/advisories) -- [Changelog](CHANGELOG.md) +- [Security Advisories](https://github.com/hyperpolymath/dicta-task/security/advisories) +- [Changelog](../CHANGELOG.md) - [Contributing Guidelines](CONTRIBUTING.md) - [CVE Database](https://cve.mitre.org/) - [CVSS Calculator](https://www.first.org/cvss/calculator/3.1) @@ -353,9 +366,9 @@ When using Dictask, we recommend: | Purpose | Contact | |---------|---------| -| **Security issues** | [Report via GitHub](https://github.com/hyperpolymath/dictask/security/advisories/new) or 6759885+hyperpolymath@users.noreply.github.com | -| **General questions** | [GitHub Discussions](https://github.com/hyperpolymath/dictask/discussions) | -| **Other enquiries** | See [README](README.md) for contact information | +| **Security issues** | [Report via GitHub](https://github.com/hyperpolymath/dicta-task/security/advisories/new) or j.d.a.jewell@open.ac.uk | +| **General questions** | [GitHub Discussions](https://github.com/hyperpolymath/dicta-task/discussions) | +| **Other enquiries** | See [README](../README.adoc) for contact information | --- @@ -369,7 +382,7 @@ This security policy may be updated from time to time. Significant changes will --- -*Thank you for helping keep Dictask and its users safe.* 🛡️ +*Thank you for helping keep dicta-task and its users safe.* 🛡️ --- diff --git a/.github/SUPPORT b/.github/SUPPORT deleted file mode 100644 index e643d77..0000000 --- a/.github/SUPPORT +++ /dev/null @@ -1,7 +0,0 @@ -# Support - -For questions, help, and community discussion: - -- GitHub Discussions: https://github.com/hyperpolymath/dictask/discussions -- GitHub Issues: https://github.com/hyperpolymath/dictask/issues -- Documentation: See README.adoc in the root directory. diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000..d9238ba --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,7 @@ +# Support + +For questions, help, and community discussion: + +- GitHub Discussions: https://github.com/hyperpolymath/dicta-task/discussions +- GitHub Issues: https://github.com/hyperpolymath/dicta-task/issues +- Documentation: See README.adoc in the root directory. diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 678e4a4..cbcc064 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,12 +1,16 @@ - + - + # Copilot Instructions ## Before Writing Code -- Read `0-AI-MANIFEST.a2ml` in the repo root for canonical file locations. +- Read the repo deed (`*_chora.deed` in the repo root) for canonical file locations + (its `(ply ...)` clauses carry the canonical-locations tables). - State files (.a2ml) live in `.machine_readable/` ONLY, never the root. ## License @@ -31,12 +35,37 @@ - Lean: no `sorry` - Rust: no `transmute` unless FFI with `// SAFETY:` comment +## JavaScript / TypeScript runtimes + +Ordered preference (`standards/3-practice/LANGUAGE-POLICY.adoc` §1) — reach for the first +that can do the job: + +1. **Bun** — default for all new work. Runs compiled ESM/JS directly, no bundler + step. Uses an npm-compatible `package.json` plus `bun.lock`; both are + expected, not anti-patterns. +2. **pnpm** — only where an upstream toolchain requires `node_modules`. +3. **npm** — last resort. Permitted, never preferred; a deliberate, noted choice. + +**Deno is being removed**, not grandfathered. Owner ruling 2026-08-26: *"deno is +to go and bun is the way we are going, put it first everywhere unless not +possible and explain why if not."* Existing Deno projects migrate to Bun; where +Bun genuinely cannot be used, document the reason in the repo. + +**TypeScript is not the language for new application code — AffineScript is.** +`LANGUAGE-POLICY.adoc` §1.2, ruled 2026-08-25, separates two questions the older +text ran together: *runtime* is Bun (where `.ts` runs at all, Bun runs it), while +the *language* target is AffineScript. TypeScript is permitted only where +AffineScript cannot reach — the same narrow, transitional carve-out JavaScript +holds for MCP protocol glue and runtime APIs. ReScript remains banned; its +migration destination is AffineScript. + ## Banned Languages -- No TypeScript (use AffineScript) -- No Node.js / npm / bun (use Deno) - No Go (use Rust) - No Python (use Julia or Rust) +- No Nix (use Guix) +- No Deno for new work — being removed estate-wide; use Bun (owner ruling 2026-08-26) +- No ReScript (`LANGUAGE-POLICY.adoc` §3) — migrate to AffineScript ## Containers diff --git a/.github/copilot/coding-agent.yml b/.github/copilot/coding-agent.yml new file mode 100644 index 0000000..a719a77 --- /dev/null +++ b/.github/copilot/coding-agent.yml @@ -0,0 +1,6 @@ +mcp_servers: + boj-server: + command: npx + args: ["-y", "@hyperpolymath/boj-server@latest"] + env: + BOJ_URL: http://localhost:7700 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9710d86..d310428 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -16,38 +16,22 @@ updates: open-pull-requests-limit: 2 # Rust/Cargo - - package-ecosystem: "cargo" - directory: "/" - schedule: - interval: "weekly" + # + # `open-pull-requests-limit: 0` suppresses routine version-update PRs + # (no weekly patch-bump noise) while leaving Dependabot SECURITY PRs + # flowing. Under GitHub's current Dependabot behaviour (2024+), using + # an `ignore:` rule with `version-update:semver-patch` would ALSO + # silence security PRs that happen to be patch-level — historically + # the cause of estate-wide vulns sitting un-PR'd for weeks. + # `open-pull-requests-limit: 0` is the GitHub-endorsed way to say + # "security only, not routine bumps". Pair with the + # dependabot-automerge.yml workflow for low-touch security + # maintenance. ignore: - - dependency-name: "*" - update-types: ["version-update:semver-patch"] - open-pull-requests-limit: 0 - - # Elixir/Mix - - package-ecosystem: "mix" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - - # Node.js/npm - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 + # HOLD: github/codeql-action at v4.38.0 (SHA-pinned). v4.38.1 fails + # GitHub workflow-startup validation estate-wide (nexia-list#100; + # SHA-form re-bump bypassed versions-scoped ignores - see + # nexia-list#101/#104). Hold until upstream clears 4.38.1 or a + # newer release verifies green; revisit deliberately. + - dependency-name: "github/codeql-action" - # Python/pip - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "weekly" - open-pull-requests-limit: 3 - - # Guix flakes - - package-ecosystem: "guix" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/hooks/install.sh b/.github/hooks/install.sh new file mode 100755 index 0000000..b853bb4 --- /dev/null +++ b/.github/hooks/install.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Point this clone's git hooks at .github/hooks/ so the local Dogfood Gate runs +# on push. Idempotent; safe to re-run. +set -euo pipefail +cd "$(git rev-parse --show-toplevel)" +git config core.hooksPath .github/hooks +git config commit.template .gitmessage +chmod +x .github/hooks/pre-push .github/hooks/validate-deed.sh .github/hooks/validate-k9.sh 2>/dev/null || true +echo "Installed: core.hooksPath -> .github/hooks (pre-push DEED+K9 gate active), commit.template -> .gitmessage." diff --git a/.github/hooks/pre-push b/.github/hooks/pre-push new file mode 100755 index 0000000..7e00686 --- /dev/null +++ b/.github/hooks/pre-push @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# pre-push — local Dogfood Gate. +# +# Runs the SAME DEED + K9 validators the CI Dogfood Gate runs, but here on +# your machine before the push leaves, so format drift is caught in seconds +# instead of surfacing as red CI minutes later. The vendored validators in +# this directory are byte-identical to the pinned CI action scripts and are +# refreshed by the estate `refresh-githooks` sweep. +# +# Enable once per clone: +# git config core.hooksPath .github/hooks +# (or run: .github/hooks/install.sh) +# +# Override for an emergency push: git push --no-verify +# +# The DEED/K9 validators are skipped gracefully if their tooling is absent. +# The SECRET gate is not: see .github/hooks/scan-secrets.sh for why it fails closed. + +set -euo pipefail + +HOOK_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(git rev-parse --show-toplevel)" +status=0 + +# git feeds pre-push one " " +# line per ref on stdin. Capture it HERE, before any validator runs, and hand it +# to the scanners in K9_PUSH_RANGES so they can scan exactly the commits being +# pushed rather than the whole history. Guarded on `! -t 0`: when the hook is +# run by hand from a terminal there are no ref lines, and an unguarded `cat` +# would block forever on the tty. +K9_PUSH_RANGES="" +if [ ! -t 0 ]; then K9_PUSH_RANGES="$(cat)"; fi +export K9_PUSH_RANGES + +run() { + local label="$1" script="$2" + if [ ! -f "$HOOK_DIR/$script" ]; then + echo "[pre-push] ($label) validator missing: $script — skipping" >&2 + return 0 + fi + echo "[pre-push] $label…" + if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then + status=1 + fi +} + +# Same as run(), but a MISSING script is a failure rather than a skip. Used for +# the secret gate only: "validator absent" and "no secrets found" produce the +# same silence, so a skip here is a gate that reports success having examined +# nothing. The DEED/K9 validators keep the lenient behaviour — a missing +# formatter costs a red CI run, a missing secret scanner costs a leaked key. +run_required() { + local label="$1" script="$2" + if [ ! -f "$HOOK_DIR/$script" ]; then + echo "[pre-push] ($label) BLOCKED: required validator missing: $script" >&2 + echo "[pre-push] Restore it from the template, or override: git push --no-verify" >&2 + status=1 + return 0 + fi + echo "[pre-push] $label…" + if ! INPUT_PATH="$REPO_ROOT" INPUT_STRICT=false bash "$HOOK_DIR/$script"; then + status=1 + fi +} + +run "DEED manifests" "validate-deed.sh" +run "K9 contracts" "validate-k9.sh" +run_required "Secrets" "scan-secrets.sh" + +if [ "$status" -ne 0 ]; then + echo "" >&2 + echo "[pre-push] BLOCKED: validation failed (see errors above)." >&2 + echo "[pre-push] Fix the files, or override with: git push --no-verify" >&2 + exit 1 +fi + +echo "[pre-push] Dogfood Gate passed." +exit 0 diff --git a/.github/hooks/scan-secrets.sh b/.github/hooks/scan-secrets.sh new file mode 100755 index 0000000..b26dd40 --- /dev/null +++ b/.github/hooks/scan-secrets.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# scan-secrets.sh — local pre-push secret gate (TruffleHog). +# +# ── Why this is NOT duplicated work ───────────────────────────────────────── +# The estate runs a TWO-TIER secret defence: +# +# CI gitleaks, via the standards `secret-scanner-reusable.yml` +# (gitleaks + rust-secrets + shell-secrets). Catches what reaches the +# remote, on every pull_request and push to main. +# LOCAL TruffleHog — this script — before the push leaves the machine. +# +# Different detection engines at different checkpoints. The reusable's header +# retires TruffleHog "as redundant with gitleaks"; that judgement is scoped to +# CI, where running both cost twice over one population. It is not a reason to +# leave the local tier unguarded. DO NOT delete this script as duplication — +# see .github/workflows/README.adoc, "Secret scanning is single-sourced". +# +# ── Fail closed ───────────────────────────────────────────────────────────── +# A missing or non-functional scanner BLOCKS the push and prints an install +# hint. The sibling validators skip gracefully when their tooling is absent; a +# SECRET gate must not, because "skipped" and "clean" are indistinguishable in +# the output, and the machines least likely to carry the toolchain are exactly +# the ones whose pushes nobody has vetted. +# The escape hatch is unchanged and documented: git push --no-verify +# +# ── Two measured instrument traps this script is written around ───────────── +# 1. TRUFFLEHOG EXITS 0 WHEN IT FINDS SECRETS. `--fail` is mandatory, and with +# it the found-secrets status is 183 — NOT 1. Every test here is for +# non-zero; never compare against 1. +# 2. PIPING A SCANNER DESTROYS ITS EXIT CODE — a pipeline reports the LAST +# command's status. Measured: `gitleaks … | tail` returns rc=0 while +# printing "leaks found: 1". Nothing here pipes the scanner; output is +# captured to a file and printed after the status is read. +# +# Env overrides: +# TRUFFLEHOG explicit path to the binary (used by the tests) +# K9_SECRETS_MAX_DEPTH commits to scan for a brand-new branch (default 500) +# K9_SECRETS_VERIFY=1 enable live credential verification (see below) + +set -euo pipefail + +REPO_ROOT="${INPUT_PATH:-$(git rev-parse --show-toplevel)}" +MAX_DEPTH="${K9_SECRETS_MAX_DEPTH:-500}" +ZERO="0000000000000000000000000000000000000000" + +# Verification OFF by default. `--no-verification` keeps the hook offline and +# fast, and — the real reason — verification transmits candidate credentials to +# the issuing provider's API on every push. A detected-but-unverified secret +# must still block, so verification buys only false-positive reduction at the +# cost of egress. Opt in with K9_SECRETS_VERIFY=1. +VERIFY_ARGS=(--no-verification) +if [ "${K9_SECRETS_VERIFY:-0}" = "1" ]; then VERIFY_ARGS=(); fi + +# ── The SonarCloud detector must be excluded, and why ─────────────────────── +# TruffleHog's SonarCloud detector matches ANY bare 40-hex string. This estate +# SHA-pins every GitHub Action by doctrine, so every pin is a 40-hex string and +# every pin reads as a secret. MEASURED on a clean clone of this template: +# 24 findings, 0 verified, 100% SonarCloud, 12 distinct values, ALL exactly +# 40 hex, all in .github/workflows/ — and .github/workflows/actions.lock +# carries one of them verbatim as `commit: 'sha1-ede1191ef…'`. +# Left in, the gate blocks every push on this repo and every inheritor of the +# template: a gate that always fires is uninstallable, and an uninstallable gate +# gets deleted. With the detector excluded the same history scans rc=0 (5,103 +# chunks, 0 findings), and a planted AWS/Slack credential still returns 183. +# +# What this gives up, and what covers it: a genuine SonarCloud token is also +# 40-hex, so it is indistinguishable from a pin BY SHAPE — no path or context +# filter recovers it. CI's gitleaks tier catches it instead; measured, gitleaks +# under the estate baseline (`useDefault = true`) flags a SonarCloud token as +# `generic-api-key`, rc=1. The two tiers are complementary here by design. +DETECTOR_ARGS=(--exclude-detectors SonarCloud) + +# ── Resolve the binary by INVOKING it ─────────────────────────────────────── +# `command -v` is not enough: a mise shim with no pinned global version sits on +# PATH, answers `command -v` happily, and fails every actual invocation with +# "No version is set for shim". Installed is not invokable. Each candidate is +# therefore probed with `--version` and only accepted on exit 0. +probe() { [ -n "${1:-}" ] && [ -x "$1" ] && "$1" --version >/dev/null 2>&1; } + +TH="" +if probe "${TRUFFLEHOG:-}"; then + TH="$TRUFFLEHOG" +elif c="$(command -v trufflehog 2>/dev/null)" && probe "$c"; then + TH="$c" +else + # mise installs tree, newest version first. MISE_DATA_DIR when exported, + # else mise's documented default. No machine-specific path is hardcoded. + _md="${MISE_DATA_DIR:-$HOME/.local/share/mise}" + while IFS= read -r c; do + if probe "$c"; then TH="$c"; break; fi + done < <(find "$_md/installs" -mindepth 3 -maxdepth 3 -type f -name trufflehog \ + -path '*trufflehog*' 2>/dev/null | sort -rV) +fi + +if [ -z "$TH" ]; then + cat >&2 <<'HINT' +[scan-secrets] BLOCKED: TruffleHog is not installed, or is a dead shim. + + This gate fails closed on purpose — a secret scanner that skips silently is + indistinguishable from one that found nothing. + + Install (mise, as used by this estate): + mise use -g aqua:trufflesecurity/trufflehog@3.96.0 + + Or point the hook at an existing binary: + TRUFFLEHOG=/path/to/trufflehog git push + + Emergency override (you are asserting the push carries no secrets): + git push --no-verify +HINT + exit 1 +fi + +# ── Build the scan ranges from the pre-push stdin lines ───────────────────── +# pre-push receives " " per ref +# and forwards them in K9_PUSH_RANGES. Scanning only the commits actually being +# pushed is both the correct population for this gate and far faster than the +# whole history (which CI already covers). +# ── Findings and scanner errors BOTH block, but are NOT the same thing ─────── +# TruffleHog returns 183 for "secrets found" and other non-zero codes for "I +# could not scan" (a broken ref, an unreadable repo, a bad flag). Both must fail +# closed — an unscanned push is exactly as unvetted as an unchecked one — but +# they must be REPORTED apart. Measured here: a stale remote ref pointing at +# 0000…0 made every scan exit 1 with "upload-pack: not our ref", and a handler +# that said "Secrets detected" for any non-zero told the developer to go hunting +# for a credential that did not exist. A gate that misnames its own failure +# sends people looking in the wrong place, which is how gates get switched off. +status=0 +scanned=0 +found=0 +errored=0 +out="$(mktemp)" +trap 'rm -f "$out"' EXIT + +# Run TruffleHog against a git ref/range with the configured verify/detector args. +# Takes a human-readable label ($1) and any extra trufflehog arguments, captures +# output to a file (not piped, to preserve the exit code), and sets the shared +# found/errored/status variables based on the result: 0 = clean, 183 = secrets +# found, anything else = scanner error. +scan() { + local label="$1"; shift + local rc=0 + # NOT piped — see trap 2 in the header. + "$TH" git "file://$REPO_ROOT" "$@" "${VERIFY_ARGS[@]}" "${DETECTOR_ARGS[@]}" \ + --fail --no-update >"$out" 2>&1 || rc=$? + scanned=$((scanned + 1)) + if [ "$rc" -eq 183 ]; then + echo "[scan-secrets] FINDINGS while scanning $label:" >&2 + cat "$out" >&2 + found=1 + status=1 + elif [ "$rc" -ne 0 ]; then + echo "[scan-secrets] SCANNER ERROR (exit $rc) while scanning $label —" >&2 + echo "[scan-secrets] this is a failure to scan, NOT a detected secret:" >&2 + cat "$out" >&2 + errored=1 + status=1 + fi +} + +if [ -n "${K9_PUSH_RANGES:-}" ]; then + while read -r local_ref local_sha remote_ref remote_sha; do + [ -z "${local_sha:-}" ] && continue + [ "$local_sha" = "$ZERO" ] && continue # branch deletion + if [ "${remote_sha:-$ZERO}" = "$ZERO" ]; then + scan "new branch ${local_ref:-HEAD} (last $MAX_DEPTH commits)" \ + --branch "$local_sha" --max-depth "$MAX_DEPTH" + else + scan "${local_ref:-HEAD} since ${remote_sha:0:12}" \ + --branch "$local_sha" --since-commit "$remote_sha" + fi + done <<< "$K9_PUSH_RANGES" +fi + +# No ranges (hook invoked directly, or an empty push): scan the bounded tail of +# HEAD rather than reporting a pass over nothing. A gate that reports success +# having examined zero commits is the vacuity this estate keeps re-learning. +if [ "$scanned" -eq 0 ]; then + scan "HEAD (no push ranges; last $MAX_DEPTH commits)" --max-depth "$MAX_DEPTH" +fi + +if [ "$found" -ne 0 ]; then + echo "[scan-secrets] Secrets detected in the commits being pushed." >&2 + echo "[scan-secrets] Remove them and rewrite the offending commits." >&2 + echo "[scan-secrets] Override (only if these are false positives): git push --no-verify" >&2 +fi + +if [ "$errored" -ne 0 ]; then + echo "[scan-secrets] TruffleHog could not complete the scan (see above)." >&2 + echo "[scan-secrets] BLOCKED because an unscanned push is an unvetted push." >&2 + echo "[scan-secrets] Common cause: a broken local ref. Check with: git fsck" >&2 + echo "[scan-secrets] Override (you are asserting this push carries no secrets):" >&2 + echo "[scan-secrets] git push --no-verify" >&2 +fi + +if [ "$status" -ne 0 ]; then + exit 1 +fi + +echo "[scan-secrets] TruffleHog clean ($scanned range(s) scanned, $("$TH" --version 2>&1 | head -1))." +exit 0 diff --git a/.github/hooks/validate-deed.sh b/.github/hooks/validate-deed.sh new file mode 100755 index 0000000..2c63102 --- /dev/null +++ b/.github/hooks/validate-deed.sh @@ -0,0 +1,393 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# validate-deed.sh — DEED manifest validation script +# +# Scans for .a2ml and .deed files (dual-accept per owner ruling R-H2) and validates: +# 1. Required fields: agent-id or pedigree name, version +# 2. SPDX-License-Identifier header presence +# 3. Attestation block structure (if present) +# 4. Section heading syntax ([section] or ## section) +# +# Environment variables: +# INPUT_PATH — Directory to scan (default: .) +# INPUT_STRICT — Promote warnings to errors (default: false) +# +# Exit codes: +# 0 — All files valid (or only warnings in non-strict mode) +# 1 — Validation errors found + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +SCAN_PATH="${INPUT_PATH:-.}" +STRICT="${INPUT_STRICT:-false}" +PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" +GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" + +# Parse paths-ignore: newline-separated fragments, blank lines and # comments +# stripped. Each fragment is a substring match against the file path. Pattern +# adopted from hyperpolymath/hypatia#243 — content-pattern validators must +# distinguish a target from a vendored / fixture file that legitimately +# contains the very pattern being checked. +PATHS_IGNORE=() +while IFS= read -r _frag; do + # Strip leading and trailing whitespace (canonical bash idiom). + _frag="${_frag#"${_frag%%[![:space:]]*}"}" + _frag="${_frag%"${_frag##*[![:space:]]}"}" + [[ -z "$_frag" || "$_frag" == \#* ]] && continue + PATHS_IGNORE+=("$_frag") +done <<< "$PATHS_IGNORE_RAW" + +# Returns 0 if path should be skipped (matches any ignore fragment) +path_ignored() { + local p="$1" frag + for frag in "${PATHS_IGNORE[@]}"; do + [[ "$p" == *"$frag"* ]] && return 0 + done + return 1 +} + +# Counters +FILES_SCANNED=0 +ERRORS=0 +WARNINGS=0 + +# --------------------------------------------------------------------------- +# Helper: emit GitHub annotation +# --------------------------------------------------------------------------- +# Usage: annotate +# level: error | warning | notice +annotate() { + local level="$1" file="$2" line="$3" message="$4" + echo "::${level} file=${file},line=${line}::${message}" +} + +# --------------------------------------------------------------------------- +# Helper: report issue (respects strict mode) +# --------------------------------------------------------------------------- +# Usage: report_issue +# severity: error | warning +report_issue() { + local severity="$1" file="$2" line="$3" message="$4" + + if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then + severity="error" + fi + + annotate "$severity" "$file" "$line" "$message" + + if [[ "$severity" == "error" ]]; then + ERRORS=$((ERRORS + 1)) + else + WARNINGS=$((WARNINGS + 1)) + fi +} + +# --------------------------------------------------------------------------- +# Validator: check a single .a2ml file +# --------------------------------------------------------------------------- +validate_deed() { + local file="$1" + FILES_SCANNED=$((FILES_SCANNED + 1)) + + # --- Check 1: SPDX header --- + # The SPDX-License-Identifier should appear in the first 10 lines + local has_spdx=false + local line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ $line_num -gt 10 ]]; then + break + fi + if [[ "$line" == *"SPDX-License-Identifier"* ]]; then + has_spdx=true + break + fi + done < "$file" + + if [[ "$has_spdx" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing SPDX-License-Identifier in first 10 lines" + fi + + # --- Check 2: Required identity fields --- + # DEED files must contain either: + # - agent-id = "..." or agent_id = "..." + # - pedigree block with name field + # - name = "..." at top level (for AI manifests) + # - project = "..." (for STATE.a2ml) + local has_identity=false + local has_version=false + local first_form_seen=false + line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Check for identity fields (various DEED patterns) + # TOML/kv form: `name = "..."`, `project = "..."`, `agent-id = "..."`. + # + # `archetype` is the identity key of the ARCHETYPE.a2ml shape — the + # `just repo-init` scaffolding descriptors under archetypes/. It names the + # archetype exactly as `name` names a manifest, and is the fourth + # dialect this recogniser accommodates alongside TOML, s-expression and + # brace-block. Without it, archetypes/julia-library/ARCHETYPE.a2ml + # failed with "Missing required identity field" — on main, in this repo + # and in every repo instantiated from it. + # + # Recognising the shape is the right fix rather than adding a redundant + # `name = "julia-library"` beside `archetype = "julia-library"`: that + # file's own header states an archetype "must not write fiction", and + # duplicating its identity to satisfy a grep is exactly that. + if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|archetype)[[:space:]]*= ]]; then + has_identity=true + fi + # S-expression form: `(name "...")`, `(project "...")`, + # `(agent-id "...")`. Some DEED dialects (audit registries, + # classification stores) use Lisp-style s-expressions for the + # metadata block instead of TOML. Identity carries the same + # semantics; only the syntax differs. Match at any indent so it + # also picks up entries nested under `(metadata ...)`. + if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(agent[-_]id|name|project)[[:space:]]+\" ]]; then + has_identity=true + fi + # Colon / brace-block form: `name: "..."`, `id: "..."`, `project: "..."`. + # YAML-ish and brace-block DEED dialects (e.g. `Trust { name: "..." }`, + # `id: "tsdm-standard"`) carry the same identity semantics; only the + # delimiter (`:` vs `=`) differs. `id` is the brace-block spelling of an + # identity key. + if [[ "$line" =~ ^[[:space:]]*(agent[-_]id|name|project|id)[[:space:]]*: ]]; then + has_identity=true + fi + # DEED s-expression head form: `(estate-deed`, `(repo-deed`, + # `(estate-atlas-deed`, `(praxis-deed`. Per DEED-GRAMMAR-SPEC + # <>, a file whose first form is one of the four declared + # heads is a deed of that kind, and the head satisfies the structural + # half of identity. Only the FIRST form is eligible — checking every + # line would let a malformed file open with some other form and append + # a deed head lower down to buy identity. Ported from + # hyperpolymath/deed-ecosystem validate-action/validate-a2ml.sh so the + # local hook and the CI action agree on what a deed is. + if [[ "$first_form_seen" == "false" && "$line" =~ ^[[:space:]]*\( ]]; then + first_form_seen=true + if [[ "$line" =~ ^[[:space:]]*\((estate-deed|repo-deed|estate-atlas-deed|praxis-deed)([[:space:]]|$) ]]; then + has_identity=true + fi + fi + # DEED keyword identity form: `:canonical-name "..."` and the two other + # identity keywords the spec names. Leading colon: none of the forms + # above match it, because they test the bare words. + if [[ "$line" =~ ^[[:space:]]*:(canonical-name|estate-authority|agent-id)[[:space:]] ]]; then + has_identity=true + fi + # Check for version field — TOML form + if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*= ]]; then + has_version=true + fi + # Version field — s-expression form + if [[ "$line" =~ ^[[:space:]]*\([[:space:]]*(version|schema_version)[[:space:]]+\" ]]; then + has_version=true + fi + # Version field — colon / brace-block form + if [[ "$line" =~ ^[[:space:]]*(version|schema_version)[[:space:]]*: ]]; then + has_version=true + fi + # DEED keyword version form: `:schema-version "1.0.0"` — leading colon, + # hyphenated, REQUIRED on all four deed heads. The three patterns above + # spell it `schema_version` with no leading colon, so a conforming deed + # matched none of them. `:registry-version` is a distinct, optional + # atlas field and never satisfies the version requirement. + if [[ "$line" =~ ^[[:space:]]*:schema-version[[:space:]] ]]; then + has_version=true + fi + done < "$file" + + # AI manifest files (0-AI-MANIFEST.a2ml, 0.1-AI-MANIFEST.a2ml, etc.) + # use markdown-style headers and free text, so identity check is relaxed + local basename + basename="$(basename "$file")" + local is_manifest=false + if [[ "$basename" == *"AI-MANIFEST"* ]]; then + is_manifest=true + fi + # Canonical typed manifests under .machine_readable/descriptiles/ — identity comes + # from the enclosing directory + filename, not an in-file field. Sibling + # files in the same directory (ECOSYSTEM.a2ml, STATE.a2ml) DO carry their + # own $name/project and continue to be validated normally. + case "$basename" in + AGENTIC.a2ml|META.a2ml|NEUROSYM.a2ml|PLAYBOOK.a2ml|AI.a2ml) + # AI.a2ml = free-text "AI Assistant Instructions" manifest, the same + # doc type as 0-AI-MANIFEST.a2ml but with the bare name; identity is + # carried by the enclosing repo/plugin dir, not an in-file field. + is_manifest=true + ;; + # Dockerfile-style top-level typed manifests (Intentfile, Trustfile, …) + # use markdown-flavoured DEED; identity is carried by the parent repo. + *file.a2ml) + is_manifest=true + ;; + esac + + # Contractile-shape DEED files use `@directive:` syntax instead of + # TOML `key = value`. Trustfile.a2ml, Intentfile.a2ml, Mustfile.a2ml, + # Adjustfile.a2ml etc. are policy / trust / intent / abstract files + # whose identity is implicit in their @-prefixed directives + # (`@trust-level`, `@intent`, ...) rather than a TOML name/version + # pair. Treating them as manifest-shape produces 100% false positives — + # they're a different DEED doc type. Detected by the presence of any + # contractile directive in the file body. + local is_contractile_shape=false + if grep -qE '^@(abstract|trust-level|trust-boundary|trust-actions|trust-deny|intent|must|adjust|end)([[:space:]]*:|$)' "$file"; then + is_contractile_shape=true + fi + + # Canonical structured DEED tree. Everything under a `.machine_readable/` + # directory is a typed agent-readable doc (CLADE, ANCHOR, STATE, + # ECOSYSTEM, bot_directives/{debt,coverage,methodology}, ai/AI, + # policies/*, integrations/*, …). Per the RSR convention these carry + # identity structurally — owning repo + path + filename — not via an + # in-file `name`/`agent-id`. This generalises the `.machine_readable/descriptiles/` + # rationale above to the whole tree: dicta-task itself ships these + # files without an in-file identity key, so requiring one produces + # estate-wide false positives on every repo built from the canonical + # template. Files outside `.machine_readable/` are still validated. + local is_structural_identity=false + if [[ "$file" == *"/.machine_readable/"* || "$file" == "./.machine_readable/"* || "$file" == ".machine_readable/"* ]]; then + is_structural_identity=true + fi + + if [[ "$has_identity" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Missing required identity field (agent-id, name, or project)" + fi + + if [[ "$has_version" == "false" && "$is_manifest" == "false" && "$is_contractile_shape" == "false" && "$is_structural_identity" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing version or schema_version field" + fi + + # --- Check 3: Attestation block structure --- + # If file contains [attestation] or ## ATTESTATION, validate it has + # required sub-fields: proof or signature + local in_attestation=false + local attestation_line=0 + local attestation_has_content=false + line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Detect attestation section start + if [[ "$line" =~ ^\[attestation\] ]] || [[ "$line" =~ ^##[[:space:]]+[Aa]ttestation ]] || [[ "$line" =~ ^##[[:space:]]+ATTESTATION ]]; then + in_attestation=true + attestation_line=$line_num + continue + fi + + # Detect next section (ends attestation block) + if [[ "$in_attestation" == "true" ]]; then + if [[ "$line" =~ ^\[.+\] ]] || [[ "$line" =~ ^##[[:space:]] ]]; then + in_attestation=false + continue + fi + # Check for content in attestation block + if [[ "$line" =~ (proof|signature|verified|hash)[[:space:]]*= ]]; then + attestation_has_content=true + fi + fi + done < "$file" + + if [[ $attestation_line -gt 0 && "$attestation_has_content" == "false" ]]; then + report_issue "warning" "$file" "$attestation_line" \ + "Attestation block found but missing proof/signature/hash fields" + fi + + # --- Check 4: Section heading syntax --- + # Validate that [section] headings are well-formed (no unclosed brackets) + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + # Lines starting with [ should have a matching ] + if [[ "$line" =~ ^\[ && ! "$line" =~ ^\[.+\] ]]; then + # Exclude markdown-style links and multi-line values + if [[ ! "$line" =~ ^\[.*\]\( && ! "$line" =~ ^\[TODO && ! "$line" =~ ^\[YOUR ]]; then + report_issue "warning" "$file" "$line_num" \ + "Possibly malformed section heading: unclosed bracket" + fi + fi + done < "$file" +} + +# --------------------------------------------------------------------------- +# Main: discover and validate .a2ml files +# --------------------------------------------------------------------------- + +echo "::group::DEED Manifest Validation" +echo "Scanning ${SCAN_PATH} for .a2ml files..." +echo "" + +# Find all manifest files (.a2ml legacy + .deed — dual-accept; extension migration = standards #837, the DEED conversion campaign), excluding .git +mapfile -t deed_candidates < <(find "$SCAN_PATH" \( -name '*.a2ml' -o -name '*.deed' \) -not -path '*/.git/*' -type f | sort) + +# Apply paths-ignore filter +deed_files=() +SKIPPED=0 +for _f in "${deed_candidates[@]}"; do + if path_ignored "$_f"; then + SKIPPED=$((SKIPPED + 1)) + continue + fi + deed_files+=("$_f") +done + +if [[ $SKIPPED -gt 0 ]]; then + echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" +fi + +if [[ ${#deed_files[@]} -eq 0 ]]; then + echo "::notice::No .a2ml files found in ${SCAN_PATH}" + echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "::endgroup::" + exit 0 +fi + +echo "Found ${#deed_files[@]} .a2ml file(s)" +echo "" + +for file in "${deed_files[@]}"; do + echo " Validating: ${file}" + validate_deed "$file" +done + +echo "" +echo "────────────────────────────────────────" +echo "Files scanned: ${FILES_SCANNED}" +echo "Errors: ${ERRORS}" +echo "Warnings: ${WARNINGS}" +echo "Strict mode: ${STRICT}" +echo "────────────────────────────────────────" + +# Write outputs for GitHub Actions +{ + echo "files_scanned=${FILES_SCANNED}" + echo "errors=${ERRORS}" + echo "warnings=${WARNINGS}" +} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + +echo "::endgroup::" + +# Exit with failure if errors were found +if [[ $ERRORS -gt 0 ]]; then + echo "::error::DEED validation failed with ${ERRORS} error(s)" + exit 1 +fi + +echo "DEED validation passed." +exit 0 diff --git a/.github/hooks/validate-k9.sh b/.github/hooks/validate-k9.sh new file mode 100755 index 0000000..02845a4 --- /dev/null +++ b/.github/hooks/validate-k9.sh @@ -0,0 +1,389 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# validate-k9.sh — K9 configuration file validation script +# +# Scans for .k9 and .k9.ncl files and validates: +# 1. K9! magic number on line 1 +# 2. Pedigree block presence with required fields (name, version) +# 3. Security level is one of: kennel, yard, hunt (case-insensitive) +# 4. Hunt-level files must have a signature or signature_required field +# 5. SPDX-License-Identifier header presence +# +# Environment variables: +# INPUT_PATH — Directory to scan (default: .) +# INPUT_STRICT — Promote warnings to errors (default: false) +# +# Exit codes: +# 0 — All files valid (or only warnings in non-strict mode) +# 1 — Validation errors found + +set -euo pipefail + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +SCAN_PATH="${INPUT_PATH:-.}" +STRICT="${INPUT_STRICT:-false}" +PATHS_IGNORE_RAW="${INPUT_PATHS_IGNORE:-}" +GITHUB_OUTPUT_FILE="${GITHUB_OUTPUT:-/dev/null}" + +# Parse paths-ignore: newline-separated fragments, blank lines and # comments +# stripped. Each fragment is a substring match against the file path. Pattern +# adopted from hyperpolymath/hypatia#243 — content-pattern validators must +# distinguish a target from a vendored / fixture file that legitimately +# contains the very pattern being checked. +PATHS_IGNORE=() +while IFS= read -r _frag; do + # Strip leading and trailing whitespace (canonical bash idiom). + _frag="${_frag#"${_frag%%[![:space:]]*}"}" + _frag="${_frag%"${_frag##*[![:space:]]}"}" + [[ -z "$_frag" || "$_frag" == \#* ]] && continue + PATHS_IGNORE+=("$_frag") +done <<< "$PATHS_IGNORE_RAW" + +# Returns 0 if path should be skipped (matches any ignore fragment) +path_ignored() { + local p="$1" frag + for frag in "${PATHS_IGNORE[@]}"; do + [[ "$p" == *"$frag"* ]] && return 0 + done + return 1 +} + +# Counters +FILES_SCANNED=0 +ERRORS=0 +WARNINGS=0 + +# Valid security levels (the leash metaphor) +VALID_LEVELS="kennel yard hunt" + +# --------------------------------------------------------------------------- +# Helper: emit GitHub annotation +# --------------------------------------------------------------------------- +annotate() { + local level="$1" file="$2" line="$3" message="$4" + echo "::${level} file=${file},line=${line}::${message}" +} + +# --------------------------------------------------------------------------- +# Helper: report issue (respects strict mode) +# --------------------------------------------------------------------------- +report_issue() { + local severity="$1" file="$2" line="$3" message="$4" + + if [[ "$severity" == "warning" && "$STRICT" == "true" ]]; then + severity="error" + fi + + annotate "$severity" "$file" "$line" "$message" + + if [[ "$severity" == "error" ]]; then + ERRORS=$((ERRORS + 1)) + else + WARNINGS=$((WARNINGS + 1)) + fi +} + +# --------------------------------------------------------------------------- +# Helper: normalise a security level string +# --------------------------------------------------------------------------- +# Strips quotes, leading/trailing whitespace, Nickel enum tick prefix +normalise_level() { + local raw="$1" + # Remove surrounding quotes, tick prefix ('Kennel -> Kennel), whitespace + raw="${raw#*=}" # Remove everything before = + raw="${raw//\"/}" # Remove double quotes + raw="${raw//\'/}" # Remove single quotes (Nickel tick) + raw="${raw//,/}" # Remove trailing commas + raw="${raw## }" # Trim leading space + raw="${raw%% }" # Trim trailing space + raw="${raw%%#*}" # Remove inline comments + raw="${raw## }" # Trim again + raw="${raw%% }" + echo "${raw,,}" # Lowercase +} + +# --------------------------------------------------------------------------- +# Validator: check a single K9 file +# --------------------------------------------------------------------------- +validate_k9() { + local file="$1" + FILES_SCANNED=$((FILES_SCANNED + 1)) + + # --- Check 1: K9! magic number on first non-empty line --- + local first_content_line="" + local first_content_line_num=0 + local line_num=0 + + while IFS= read -r line; do + line_num=$((line_num + 1)) + # Skip empty lines + if [[ -z "${line// /}" ]]; then + continue + fi + first_content_line="$line" + first_content_line_num=$line_num + break + done < "$file" + + if [[ "$first_content_line" != "K9!" ]]; then + report_issue "error" "$file" "$first_content_line_num" \ + "Missing K9! magic number. First non-empty line must be exactly 'K9!'" + fi + + # --- Check 2: SPDX header --- + local has_spdx=false + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + if [[ $line_num -gt 10 ]]; then + break + fi + if [[ "$line" == *"SPDX-License-Identifier"* ]]; then + has_spdx=true + break + fi + done < "$file" + + if [[ "$has_spdx" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Missing SPDX-License-Identifier in first 10 lines" + fi + + # --- Check 3: Pedigree block with required fields --- + local has_pedigree=false + local has_pedigree_name=false + local has_pedigree_version=false + local has_security_level=false + local security_level_value="" + local security_level_line=0 + local has_signature_field=false + local in_pedigree=false + local pedigree_depth=0 + + # Resolve a one-hop `let` indirection before scanning. + # + # Nickel lets you build the pedigree separately and attach it by name: + # + # let component_pedigree = { + # metadata = { name = "verisimdb-test-infra", ... }, + # } in + # { pedigree = component_pedigree, ... } + # + # The brace-depth scanner below only sees `pedigree = component_pedigree,` + # — a line with no braces — so depth never rises, the block appears empty, + # and `name` is invisible. The file is valid; the grep could not follow the + # reference. Observed on verisimdb/connectors/test-infra/deploy.k9.ncl, + # which does declare metadata.name at its line 23. + # + # If `pedigree = ` names a binding, treat `let = {` + # as the block opener too. One hop only: chased aliases would need a real + # Nickel evaluator, and `nickel typecheck` cannot be used here because the + # mandatory `K9!` magic line on line 1 is not valid Nickel. + local pedigree_alias="" + pedigree_alias=$(sed -nE 's/^[[:space:]]*pedigree[[:space:]]*=[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*,?[[:space:]]*$/\1/p' "$file" | head -1) + + line_num=0 + while IFS= read -r line; do + line_num=$((line_num + 1)) + + # Detect pedigree block start. Note: do NOT `continue` here — the + # `pedigree = {` line itself contains the opening brace that + # establishes the block. Falling through to the brace counter + # below makes depth start at 1, so a subsequent `security = {…},` + # closing brace correctly takes depth to 1 (not 0), keeping us + # inside the pedigree block when later fields (name/version/leash) + # are checked. Previously the `continue` skipped this opening + # brace, depth started at 0, and the first nested block's close + # prematurely terminated the validator's view of the pedigree — + # making `pedigree.metadata.name` invisible. + if [[ "$line" =~ ^[[:space:]]*pedigree[[:space:]]*= ]]; then + has_pedigree=true + in_pedigree=true + pedigree_depth=0 + # fall through + fi + + # `let = {` where is what `pedigree =` points at. + # See the pedigree_alias note above. + if [[ -n "$pedigree_alias" ]] && \ + [[ "$line" =~ ^[[:space:]]*let[[:space:]]+${pedigree_alias}[[:space:]]*= ]]; then + has_pedigree=true + in_pedigree=true + pedigree_depth=0 + # fall through — this line carries the opening brace + fi + + if [[ "$in_pedigree" == "true" ]]; then + # Track brace depth to know when pedigree block ends + local opens closes + opens="${line//[^\{]/}" + closes="${line//[^\}]/}" + pedigree_depth=$(( pedigree_depth + ${#opens} - ${#closes} )) + + if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" ]]; then + # Check this final line too before leaving + : + fi + + # Check for name field within pedigree.metadata or pedigree directly. + # Two patterns cover both multi-line and single-line pedigrees: + # 1. ^[[:space:]]+name[[:space:]]*= — the normal multi-line case where + # `name = "..."` appears on its own indented line. + # 2. [[:space:]]name[[:space:]]*= — inline within a single-line + # pedigree assignment such as: + # pedigree = component_pedigree & { name = "foo" } + # (root cause: developer-ecosystem@baab1534 — single-line form + # was missed entirely because the pedigree block opened and + # closed in one line, never reaching the ^[[:space:]]+ check on + # a subsequent iteration.) + if [[ "$line" =~ ^[[:space:]]+name[[:space:]]*= ]] || \ + [[ "$line" =~ [[:space:]]name[[:space:]]*= ]]; then + has_pedigree_name=true + fi + + # Check for version field + if [[ "$line" =~ ^[[:space:]]+(version|schema_version)[[:space:]]*= ]] || \ + [[ "$line" =~ [[:space:]](version|schema_version)[[:space:]]*= ]]; then + has_pedigree_version=true + fi + + # Check for security level (leash field) + if [[ "$line" =~ ^[[:space:]]+(leash|security_level)[[:space:]]*= ]]; then + has_security_level=true + security_level_value="$(normalise_level "$line")" + security_level_line=$line_num + fi + + # Check for signature fields + if [[ "$line" =~ ^[[:space:]]+(signature|signature_required)[[:space:]]*= ]]; then + has_signature_field=true + fi + + # End of pedigree block + if [[ $pedigree_depth -le 0 && "$has_pedigree" == "true" && "$line" == *"}"* ]]; then + in_pedigree=false + fi + fi + + # Also check for signature fields outside pedigree (top-level) + if [[ "$line" =~ ^[[:space:]]*(signature)[[:space:]]*= ]]; then + has_signature_field=true + fi + done < "$file" + + if [[ "$has_pedigree" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Missing pedigree block. K9 files must contain a 'pedigree = { ... }' section" + else + if [[ "$has_pedigree_name" == "false" ]]; then + report_issue "error" "$file" 1 \ + "Pedigree block missing 'name' field (in pedigree.metadata.name or pedigree.name)" + fi + + if [[ "$has_pedigree_version" == "false" ]]; then + report_issue "warning" "$file" 1 \ + "Pedigree block missing 'version' or 'schema_version' field" + fi + fi + + # --- Check 4: Security level validation --- + if [[ "$has_security_level" == "true" ]]; then + local level_valid=false + for valid in $VALID_LEVELS; do + if [[ "$security_level_value" == "$valid" ]]; then + level_valid=true + break + fi + done + + if [[ "$level_valid" == "false" ]]; then + report_issue "error" "$file" "$security_level_line" \ + "Invalid security level '${security_level_value}'. Must be one of: kennel, yard, hunt" + fi + else + if [[ "$has_pedigree" == "true" ]]; then + report_issue "warning" "$file" 1 \ + "No security level (leash/security_level) found in pedigree block" + fi + fi + + # --- Check 5: Hunt-level signature requirement --- + if [[ "$security_level_value" == "hunt" && "$has_signature_field" == "false" ]]; then + report_issue "error" "$file" "$security_level_line" \ + "Hunt-level K9 file must include a 'signature' or 'signature_required' field" + fi +} + +# --------------------------------------------------------------------------- +# Main: discover and validate K9 files +# --------------------------------------------------------------------------- + +echo "::group::K9 Configuration Validation" +echo "Scanning ${SCAN_PATH} for K9 files (.k9, .k9.ncl)..." +echo "" + +# Find all K9 files, excluding .git directory +mapfile -t k9_candidates < <(find "$SCAN_PATH" \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path '*/.git/*' -type f | sort) + +# Apply paths-ignore filter +k9_files=() +SKIPPED=0 +for _f in "${k9_candidates[@]}"; do + if path_ignored "$_f"; then + SKIPPED=$((SKIPPED + 1)) + continue + fi + k9_files+=("$_f") +done + +if [[ $SKIPPED -gt 0 ]]; then + echo "::notice::Skipped ${SKIPPED} file(s) matching paths-ignore" +fi + +if [[ ${#k9_files[@]} -eq 0 ]]; then + echo "::notice::No K9 files found in ${SCAN_PATH}" + echo "files_scanned=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "errors=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "warnings=0" >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + echo "::endgroup::" + exit 0 +fi + +echo "Found ${#k9_files[@]} K9 file(s)" +echo "" + +for file in "${k9_files[@]}"; do + echo " Validating: ${file}" + validate_k9 "$file" +done + +echo "" +echo "────────────────────────────────────────" +echo "Files scanned: ${FILES_SCANNED}" +echo "Errors: ${ERRORS}" +echo "Warnings: ${WARNINGS}" +echo "Strict mode: ${STRICT}" +echo "────────────────────────────────────────" + +# Write outputs for GitHub Actions +{ + echo "files_scanned=${FILES_SCANNED}" + echo "errors=${ERRORS}" + echo "warnings=${WARNINGS}" +} >> "$GITHUB_OUTPUT_FILE" 2>/dev/null || true + +echo "::endgroup::" + +# Exit with failure if errors were found +if [[ $ERRORS -gt 0 ]]; then + echo "::error::K9 validation failed with ${ERRORS} error(s)" + exit 1 +fi + +echo "K9 validation passed." +exit 0 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3a8accd..2cca1e1 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,7 @@ - + ## Summary @@ -18,7 +21,7 @@ - [ ] Tests pass (`just test` or equivalent) - [ ] Code is formatted (`just fmt` or equivalent) - [ ] Linter is clean (no new warnings or errors) -- [ ] No banned language patterns (no TypeScript, no npm/bun, no Go/Python) +- [ ] No banned language patterns (no , no npm/bun, no Go/Python) - [ ] No `unsafe` blocks without `// SAFETY:` comments - [ ] No banned functions (`believe_me`, `unsafeCoerce`, `Obj.magic`, `Admitted`, `sorry`) - [ ] SPDX license headers present on all new/modified source files @@ -26,9 +29,9 @@ ### As Applicable -- [ ] `.machine_readable/STATE.a2ml` updated (if project state changed) -- [ ] `.machine_readable/ECOSYSTEM.a2ml` updated (if integrations changed) -- [ ] `.machine_readable/META.a2ml` updated (if architectural decisions changed) +- [ ] `.machine_readable/descriptiles/STATE.a2ml` updated (if project state changed) +- [ ] `.machine_readable/descriptiles/ECOSYSTEM.a2ml` updated (if integrations changed) +- [ ] `.machine_readable/descriptiles/META.a2ml` updated (if architectural decisions changed) - [ ] Documentation updated for user-facing changes - [ ] `TOPOLOGY.md` updated (if architecture changed) - [ ] `CHANGELOG` or release notes updated diff --git a/.github/rulesets/Immutable-Tags.json b/.github/rulesets/Immutable-Tags.json new file mode 100644 index 0000000..75fe73a --- /dev/null +++ b/.github/rulesets/Immutable-Tags.json @@ -0,0 +1,28 @@ +{ + "name": "Immutable-Tags", + "target": "tag", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "~ALL" + ], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "update" + }, + { + "type": "required_signatures" + } + ] +} diff --git a/.github/rulesets/README.adoc b/.github/rulesets/README.adoc new file mode 100644 index 0000000..1b79726 --- /dev/null +++ b/.github/rulesets/README.adoc @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Repository rulesets + +[IMPORTANT] +==== +**GitHub does not read this directory.** Unlike `.github/workflows/`, +`.github/dependabot.yml` or `.github/settings.yml`, ruleset JSON in a repository +path is *not* auto-applied by any GitHub feature. These files are +**documentation of intent plus a ready-to-POST payload** — nothing more. + +A ruleset only takes effect once someone applies it via the REST API or imports +it in the web UI. Until then the protection described here **is not in force**. +==== + +== Applying a ruleset + +Per ADR-0003 the *Configure* stage is an operator stage: identity, visibility and +branch/tag protection are set out of band. Automation is future work and must not +be described as existing. + +[source,bash] +---- +# Apply (create) a ruleset on the current repo: +gh api --method POST \ + -H "Accept: application/vnd.github+json" \ + "/repos/{owner}/{repo}/rulesets" \ + --input .github/rulesets/base.json + +# List what is actually in force — the only authoritative answer: +gh api "/repos/{owner}/{repo}/rulesets" --jq '.[] | "\(.id)\t\(.name)\t\(.enforcement)"' + +# Update an existing ruleset in place (needs its numeric id from the list above): +gh api --method PUT \ + "/repos/{owner}/{repo}/rulesets/" \ + --input .github/rulesets/base.json +---- + +== Files + +`base.json`:: Canonical branch protection ruleset for the default branch (`~DEFAULT_BRANCH`). +Enforces required signatures, linear history, squash merge, and deletion/force-push protection +without unsatisfiable coverage or deadlocking approval requirements. + +`Immutable-Tags.json`:: Makes release tags immutable — blocks tag deletion, +non-fast-forward updates, and overwrites, and requires signed tags. + +== Verifying + +Do not infer that a ruleset is active because the JSON is present. Check with the +`gh api ... /rulesets` list above. A ruleset that exists as a file but was never +POSTed is the protection equivalent of a check that cannot fail. diff --git a/.github/rulesets/base.json b/.github/rulesets/base.json new file mode 100644 index 0000000..136c0a0 --- /dev/null +++ b/.github/rulesets/base.json @@ -0,0 +1,102 @@ +{ + "name": "Base", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": [ + "~DEFAULT_BRANCH" + ], + "exclude": [] + } + }, + "bypass_actors": [ + { + "actor_id": 5, + "actor_type": "RepositoryRole", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1236702, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 29110, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 15368, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 347564, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 46505, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1143301, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 1144995, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 12526, + "actor_type": "Integration", + "bypass_mode": "pull_request" + }, + { + "actor_id": 2538504, + "actor_type": "Integration", + "bypass_mode": "pull_request" + } + ], + "rules": [ + { + "type": "deletion" + }, + { + "type": "non_fast_forward" + }, + { + "type": "required_signatures" + }, + { + "type": "required_linear_history" + }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": true, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": true, + "require_extra_approval_for_unattributed_changes": false, + "required_reviewers": [], + "allowed_merge_methods": [ + "squash" + ] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": true, + "do_not_enforce_on_create": false, + "required_status_checks": [] + } + } + ] +} diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..775c181 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,160 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Repository settings for probot/settings GitHub App. +# https://github.com/probot/settings +# +# This file defines repository-level configuration that is automatically +# applied by the probot/settings app when changes are pushed to the default +# branch. Install the app at: https://github.com/apps/settings +# +# ─── THIS FILE MUST NEVER DECLARE REPOSITORY IDENTITY ───────────────────────── +# +# It carries NO `name`, `description`, `homepage` or `private` key, and it must +# never gain one. The reason is a real incident, not a hypothetical: +# +# This file previously read `name: "dicta-task"`. probot/settings applies it on +# every push to the default branch, so it submitted the literal string +# `dicta-task` as the repository name. GitHub sanitises an invalid name by +# collapsing each run of illegal characters to a dash — `dicta-task` became +# `-REPO-`. The template renamed itself on every push, its old URL 404'd, and +# it was mistaken for a deleted repository. `description` was likewise left +# reading the literal `Canonical RSR (Rhodium Standard Repository) template — governance, CI/CD, machine-readable metadata, ABI/FFI seam and formal-verification scaffolding that hyperpolymath projects are instantiated from.` on the live repo. +# +# Two properties make identity keys unsafe here specifically: +# +# 1. This is a TEMPLATE. `just repo-init` fills placeholders in repos minted by the +# scaffolder — but GitHub's "Use this template" button copies the default +# branch verbatim and never runs `just repo-init`. Any placeholder left in a +# probot-managed file therefore reaches children unrendered. +# 2. Identity is not shareable. The template must be public while children +# default private; a child cannot inherit either `name` or `private` from +# its parent without being wrong. +# +# Repository identity and visibility are therefore set OUT OF BAND: once per +# repo, at creation time, by the operator (the Configure stage of ADR-0003). +# `just repo-init` deliberately runs NO `gh` commands — it prints the exact +# `gh repo edit` commands as next steps instead. Fail-closed default: repos +# stay private unless the owner flips visibility deliberately; the template's +# own name and visibility are set deliberately by the owner. +# +# Everything below is safe to inherit: it is true of every RSR repo regardless +# of that repo's name, purpose or visibility. +# +# Enforced by `scripts/check-no-placeholders.sh`, which fails if this file +# contains a `{{` token or declares any of the four identity keys. + +# ─── Repository Settings ─────────────────────────────────────────────────────── + +repository: + has_issues: true + has_projects: true + has_wiki: false + has_downloads: true + default_branch: main + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + delete_branch_on_merge: true + enable_automated_security_fixes: true + enable_vulnerability_alerts: true + +# ─── Labels ──────────────────────────────────────────────────────────────────── + +labels: + - name: "bug" + color: "d73a4a" + description: "Something isn't working" + + - name: "enhancement" + color: "a2eeef" + description: "New feature or request" + + - name: "documentation" + color: "0075ca" + description: "Improvements or additions to documentation" + + - name: "security" + color: "e4e669" + description: "Security-related issue or vulnerability" + + - name: "good first issue" + color: "7057ff" + description: "Good for newcomers" + + - name: "help wanted" + color: "008672" + description: "Extra attention is needed" + + - name: "question" + color: "d876e3" + description: "Further information is requested" + + - name: "duplicate" + color: "cfd3d7" + description: "This issue or pull request already exists" + + - name: "invalid" + color: "e4e669" + description: "This doesn't seem right" + + - name: "wontfix" + color: "ffffff" + description: "This will not be worked on" + + - name: "dependencies" + color: "0366d6" + description: "Pull requests that update a dependency file" + + - name: "ci/cd" + color: "fbca04" + description: "Continuous integration and deployment" + + - name: "rsr" + color: "006b75" + description: "Rhodium Standard Repository compliance" + + - name: "hypatia" + color: "5319e7" + description: "Hypatia neurosymbolic scanner finding" + + - name: "bot" + color: "b4a8d1" + description: "Automated action by gitbot-fleet" + + - name: "breaking-change" + color: "b60205" + description: "Introduces a breaking change" + + - name: "performance" + color: "f9d0c4" + description: "Performance improvement" + + - name: "refactor" + color: "c5def5" + description: "Code refactoring with no functional change" + +# ─── Branch Protection ───────────────────────────────────────────────────────── + +# A required context must name a check that is actually EMITTED, or the branch +# deadlocks: the check never reports, so it stays permanently pending, and with +# enforce_admins even the owner cannot merge or push. This block previously +# required three contexts, two of which no repo has ever emitted — +# +# "codeql" is emitted as `analyze (actions, none)` (job id + matrix) +# "hypatia-scan" is emitted as `scan / Hypatia Neurosymbolic Analysis` +# (a reusable-workflow call always reports `caller / called`) +# +# — while "openssf-compliance" resolved only because its job *id* is literally +# `openssf-compliance` and it declares no `name:` and no matrix. That is the +# rule: pin the job **id** to the context string. Note this file is applied by +# probot on every push to the default branch, so a wrong context here does not +# merely describe protection, it re-imposes the deadlock on every push. +# +# `contexts` is deliberately EMPTY rather than aspirational. Requiring a check +# that cannot pass is the same defect as requiring one that cannot report, and +# on this repo no check can currently run at all: GitHub Actions is billing- +# blocked for PRIVATE repositories on this account ("The job was not started +# because recent account payments have failed or your spending limit needs to be +# increased"), which is why every workflow here fails with zero steps while the +# estate's public repos run normally. Repopulate this list — one context at a +# time, each pinned to a job id, each verified green — once Actions can run. diff --git a/.github/workflows/README.adoc b/.github/workflows/README.adoc new file mode 100644 index 0000000..c7a2328 --- /dev/null +++ b/.github/workflows/README.adoc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 += GitHub Workflows — what runs, and why some look duplicated + +This directory holds the repo's CI/CD. A few workflows *look* like they overlap +but are deliberately distinct — documented here so the apparent duplication is +not "tidied away" into a real coverage gap. + +== Hypatia runs in two places (on purpose) + +* `hypatia-scan.yml` — the **standalone security scan** (push / PR / weekly). + Independent; this is the repo's own Hypatia coverage. +* `static-analysis-gate.yml` -> `hypatia-scan` job — runs Hypatia as part of the + **gate**, then the `deposit-findings` job hands the results to the + **gitbot-fleet learning** pipeline. This job is also a *required status check*. + +Same tool, two different consumers (security scan vs fleet learning). Removing +either loses real function — keep both. + +== Secret scanning is single-sourced *in CI* — and two-tier overall + +`secret-scanner.yml` calls the standards reusable (gitleaks + a Rust-secrets +check). An inline TruffleHog job was removed: the reusable deliberately retired +TruffleHog as redundant with gitleaks, so re-adding it was duplicated work, not +extra coverage. + +**That retirement is scoped to CI.** The estate runs a deliberate *two-tier* +secret defence, and the second tier is not in this directory: + +[cols="1,2,3"] +|=== +| Tier | Engine | Where + +| CI +| gitleaks (+ rust-secrets, shell-secrets) +| `secret-scanner.yml` -> standards reusable. Runs on pull_request and on push + to main — i.e. on what has *already reached* the remote. + +| Local +| TruffleHog +| `.github/hooks/scan-secrets.sh`, called by `.github/hooks/pre-push`. Runs *before* the + push leaves the machine, on exactly the commits being pushed. +|=== + +Different engines at different checkpoints — the local tier is the only one that +can stop a key before it exists on a server, where revocation, not deletion, is +the only real remedy. Ratified by the owner, 2026-09-14: *"we [run] gitleaks on +github regularly but [run] trufflehog prior to pushes using the hooks system."* + +*Do not delete `.github/hooks/scan-secrets.sh` as duplication of `secret-scanner.yml`.* +It is the other half of this design, not a second copy of this half. The two +tiers also cover for each other's blind spots: the hook excludes TruffleHog's +SonarCloud detector (it matches any 40-hex string, so every SHA-pinned action +reads as a secret — measured, 24 findings on a clean clone, all of them action +pins), and gitleaks in CI catches a genuine SonarCloud token as `generic-api-key`. + +== SAST tools are complementary, not redundant + +* `codeql.yml` — CodeQL (matrix defaults to `actions`; every repo has workflows). +* `sonarqube.yml` — SonarCloud (shell / JS surface; see `sonar-project.properties`). +* `static-analysis-gate.yml` — panic-attack + Hypatia gate (see above). + +Each targets a different language/surface; together they cover what no single +analyser does. diff --git a/.github/workflows/actions.lock b/.github/workflows/actions.lock new file mode 100644 index 0000000..f0aea84 --- /dev/null +++ b/.github/workflows/actions.lock @@ -0,0 +1,287 @@ +# This file is machine-generated by `gh actions-lock`. +# Do not edit by hand; run `gh actions-lock` to update. +# Docs: https://gh.io/actions-lockfile +version: 'v0.0.2' +workflows: + '.github/workflows/build-notification.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/codeql.yml': + - 'actions/checkout@v7.0.1' + - 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63' + '.github/workflows/deed-validate.yml': + - 'actions/checkout@v7.0.1' + - 'hyperpolymath/deed-ecosystem@main' + '.github/workflows/dependabot-automerge.yml': + - 'dependabot/fetch-metadata@v3.1.0' + '.github/workflows/docker-build.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/dogfood-gate.yml': + - 'actions/checkout@v7.0.1' + - 'erlef/setup-beam@v1.24.1' + - 'hyperpolymath/deed-ecosystem@main' + - 'hyperpolymath/k9-ecosystem@main' + '.github/workflows/dogfood-summary.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/dot-wellknown-enforcement.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/eclexiaiser-validate.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/empty-linter.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/estate-rules.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/governance.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/groove-check.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/guix-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/hypatia-scan.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/k9-validate.yml': + - 'actions/checkout@v7.0.1' + - 'hyperpolymath/k9-ecosystem@main' + '.github/workflows/label-triage.yml': [] + '.github/workflows/labels.yml': [] + '.github/workflows/lock-sync-gate.yml': [] + '.github/workflows/main-estate-audit.yml': + - 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d' + '.github/workflows/mirror.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/ossf-best-practices.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/pages.yml': + - 'actions/cache@v6.1.0' + - 'actions/checkout@v7.0.1' + - 'actions/configure-pages@v6.0.0' + - 'actions/deploy-pages@v5.0.1' + - 'actions/upload-pages-artifact@v5.0.0' + - 'haskell-actions/setup@v2.12.0' + '.github/workflows/push-email-notify.yml': + - 'hyperpolymath/smtp-notify-action@v0.3.0' + '.github/workflows/quality.yml': + - 'actions/checkout@v7.0.1' + - 'editorconfig-checker/action-editorconfig-checker@v3.0.0' + '.github/workflows/release.yml': + - 'actions/attest-build-provenance@v4.2.2' + - 'actions/checkout@v7.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'softprops/action-gh-release@v3.0.3' + '.github/workflows/rsr-compliance-canary.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/runtime-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/rust-ci.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/scorecard.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/secret-scanner.yml': + - 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540' + '.github/workflows/security-policy.yml': + - 'actions/checkout@v7.0.1' + '.github/workflows/sonarqube.yml': + - 'actions/checkout@v7.0.1' + - 'sonarsource/sonarqube-scan-action@v8.2.2' + '.github/workflows/static-analysis-gate.yml': + - 'actions/checkout@v7.0.1' + - 'actions/download-artifact@v8.0.1' + - 'actions/upload-artifact@v7.0.1' + - 'erlef/setup-beam@v1.24.1' + '.github/workflows/workflow-linter.yml': + - 'actions/checkout@v7.0.1' + - 'oven-sh/setup-bun@v2.2.0' +dependencies: + 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6': + ref: '6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + commit: 'sha1-6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + owner_id: 580492 + repo_id: 298565987 + 'actions/attest-build-provenance@v4.2.2': + ref: 'v4.2.2' + commit: 'sha1-4d101475d8b20a2381f78447822ac1eab6504dd8' + owner_id: 44036562 + repo_id: 760702757 + uses: + - 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d' + 'actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d': + ref: 'v4.2.1' + commit: 'sha1-508db95dd578ae2727ebd6217d5ba78e4fbda05d' + owner_id: 44036562 + repo_id: 760701061 + 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9': + ref: '55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + owner_id: 44036562 + repo_id: 215566462 + 'actions/cache@v6.1.0': + ref: 'v6.1.0' + commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + owner_id: 44036562 + repo_id: 215566462 + 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1': + ref: '3d3c42e5aac5ba805825da76410c181273ba90b1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/checkout@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1' + owner_id: 44036562 + repo_id: 197814629 + 'actions/configure-pages@v6.0.0': + ref: 'v6.0.0' + commit: 'sha1-45bfe0192ca1faeb007ade9deae92b16b8254a0d' + owner_id: 44036562 + repo_id: 513659658 + 'actions/deploy-pages@v5.0.1': + ref: 'v5.0.1' + commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346' + owner_id: 44036562 + repo_id: 438112499 + 'actions/download-artifact@v8.0.1': + ref: 'v8.0.1' + commit: 'sha1-3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c' + owner_id: 44036562 + repo_id: 192626254 + 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a': + ref: '043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f': + ref: 'v7.0.0' + commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-artifact@v7.0.1': + ref: 'v7.0.1' + commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + owner_id: 44036562 + repo_id: 192625955 + 'actions/upload-pages-artifact@v5.0.0': + ref: 'v5.0.0' + commit: 'sha1-fc324d3547104276b827a68afc52ff2a11cc49c9' + owner_id: 44036562 + repo_id: 496012378 + uses: + - 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f' + 'dependabot/fetch-metadata@v3.1.0': + ref: 'v3.1.0' + commit: 'sha1-25dd0e34f4fe68f24cc83900b1fe3fe149efef98' + owner_id: 27347476 + repo_id: 371068214 + 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772': + ref: '6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + commit: 'sha1-6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + owner_id: 1940490 + repo_id: 260749683 + 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393': + ref: '51f63319f592f97930c73d9c46184d20bd206393' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'editorconfig-checker/action-editorconfig-checker@v3.0.0': + ref: 'v3.0.0' + commit: 'sha1-51f63319f592f97930c73d9c46184d20bd206393' + owner_id: 26415196 + repo_id: 297874902 + 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124': + ref: '54075bcc5e249e4758d363f27d099f55d843f124' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'erlef/setup-beam@v1.24.1': + ref: 'v1.24.1' + commit: 'sha1-54075bcc5e249e4758d363f27d099f55d843f124' + owner_id: 47606891 + repo_id: 331103973 + 'github/codeql-action@b96794f015dfd88f77b49b1c93e0fa7110f94c63': + ref: 'v4.38.0' + commit: 'sha1-b96794f015dfd88f77b49b1c93e0fa7110f94c63' + owner_id: 9919 + repo_id: 259445878 + 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938': + ref: 'cdf488f595d80d6e07e03d4674febd5ab45fa938' + commit: 'sha1-cdf488f595d80d6e07e03d4674febd5ab45fa938' + owner_id: 9919 + repo_id: 259445878 + 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406': + ref: 'abea47f85e598557f500fa1fd2ab7464fcb39406' + commit: 'sha1-abea47f85e598557f500fa1fd2ab7464fcb39406' + owner_id: 1006268 + repo_id: 212984112 + 'haskell-actions/setup@v2.12.0': + ref: 'v2.12.0' + commit: 'sha1-6037f33647c3f17758a2356c80fc4a53d7e0685d' + owner_id: 75048950 + repo_id: 623796603 + 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5': + ref: '0da816c05ae7486671d863e07935f93981c0c2d5' + commit: 'sha1-0da816c05ae7486671d863e07935f93981c0c2d5' + owner_id: 6759885 + repo_id: 1326697643 + 'hyperpolymath/cicd-suite@55556cf5ba71ba744695861503c13148d3915a5d': + ref: '55556cf5ba71ba744695861503c13148d3915a5d' + commit: 'sha1-55556cf5ba71ba744695861503c13148d3915a5d' + owner_id: 6759885 + repo_id: 1326697643 + uses: + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'hyperpolymath/cicd-suite@0da816c05ae7486671d863e07935f93981c0c2d5' + 'hyperpolymath/deed-ecosystem@main': + ref: 'main' + commit: 'sha1-44f9c9fc42901a55e0f489a40eabf4173e550d1d' + owner_id: 6759885 + repo_id: 1275649586 + 'hyperpolymath/k9-ecosystem@main': + ref: 'main' + commit: 'sha1-2155aa26a21758f2ba119f61bc7e0e1981c106fb' + owner_id: 6759885 + repo_id: 1275650185 + 'hyperpolymath/smtp-notify-action@v0.3.0': + ref: 'v0.3.0' + commit: 'sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be' + owner_id: 6759885 + repo_id: 1352485172 + 'hyperpolymath/standards@da2c748aad55c1a1dcba00b60fe4a35017bc6540': + ref: 'da2c748aad55c1a1dcba00b60fe4a35017bc6540' + commit: 'sha1-da2c748aad55c1a1dcba00b60fe4a35017bc6540' + owner_id: 6759885 + repo_id: 1116521501 + uses: + - 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9' + - 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' + - 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' + - 'dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772' + - 'editorconfig-checker/action-editorconfig-checker@51f63319f592f97930c73d9c46184d20bd206393' + - 'erlef/setup-beam@54075bcc5e249e4758d363f27d099f55d843f124' + - 'github/codeql-action@cdf488f595d80d6e07e03d4674febd5ab45fa938' + - 'goto-bus-stop/setup-zig@abea47f85e598557f500fa1fd2ab7464fcb39406' + - 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc' + - 'Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6' + - 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555' + 'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc': + ref: '2d1146689b8cda280b9bc96326124645441f03bc' + commit: 'sha1-2d1146689b8cda280b9bc96326124645441f03bc' + owner_id: 67707773 + repo_id: 421101922 + 'oven-sh/setup-bun@v2.2.0': + ref: 'v2.2.0' + commit: 'sha1-0c5077e51419868618aeaa5fe8019c62421857d6' + owner_id: 108928776 + repo_id: 512644635 + 'softprops/action-gh-release@v3.0.3': + ref: 'v3.0.3' + commit: 'sha1-efb35369e0ad2afab669f228072c1b0d510eae64' + owner_id: 2242 + repo_id: 204253808 + 'sonarsource/sonarqube-scan-action@v8.2.2': + ref: 'v8.2.2' + commit: 'sha1-ba9859eae8dd6bd29e412f25ddbbef3d032000f4' + owner_id: 545988 + repo_id: 366408409 + 'webfactory/ssh-agent@e83874834305fe9a4a2997156cb26c5de65a8555': + ref: 'e83874834305fe9a4a2997156cb26c5de65a8555' + commit: 'sha1-e83874834305fe9a4a2997156cb26c5de65a8555' + owner_id: 135788 + repo_id: 208510314 diff --git a/.github/workflows/boj-build.yml b/.github/workflows/boj-build.yml deleted file mode 100644 index 6c50439..0000000 --- a/.github/workflows/boj-build.yml +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: BoJ Server Build Trigger -on: - push: - branches: [main, master] - workflow_dispatch: -jobs: - trigger-boj: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - name: Trigger BoJ Server (Casket/ssg-mcp) - run: | - # Send a secure trigger to boj-server to build this repository - curl -X POST "http://boj-server.local:7700/cartridges/ssg-mcp/invoke" -H "Content-Type: application/json" -d "{\"repo\": \"${{ github.repository }}\", \"branch\": \"${{ github.ref_name }}\", \"engine\": \"casket\\"}"} - continue-on-error: true -permissions: - actions: read - contents: read diff --git a/.github/workflows/build-notification.yml b/.github/workflows/build-notification.yml new file mode 100644 index 0000000..e43da5b --- /dev/null +++ b/.github/workflows/build-notification.yml @@ -0,0 +1,50 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# +# OPTIONAL: BoJ Server Build Trigger +# This workflow notifies a BoJ Server instance when code is pushed. +# It is a no-op if BOJ_SERVER_URL is not set or the server is unreachable. +# To enable: set BOJ_SERVER_URL as a repository variable (secrets cannot gate +# a job-level if:). A BOJ_SERVER_URL secret is still honoured at run time. +# To disable: delete this file or leave BOJ_SERVER_URL unset. +name: BoJ Server Build Trigger +on: + push: + branches: [main, master] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + trigger-boj: + runs-on: ubuntu-latest + timeout-minutes: 15 + if: ${{ vars.BOJ_SERVER_URL != '' }} + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + - name: Trigger BoJ Server (Casket/ssg-mcp) + env: + BOJ_URL: ${{ secrets.BOJ_SERVER_URL || vars.BOJ_SERVER_URL }} + REPO_NAME: ${{ github.repository }} + BRANCH_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + + if [ -z "$BOJ_URL" ]; then + echo "BOJ_SERVER_URL not configured - skipping" + exit 0 + fi + + payload="$(jq -cn \ + --arg repo "$REPO_NAME" \ + --arg branch "$BRANCH_NAME" \ + --arg engine "casket" \ + '{repo:$repo, branch:$branch, engine:$engine}')" + + curl -sf -X POST "${BOJ_URL}/cartridges/ssg-mcp/invoke" \ + -H "Content-Type: application/json" \ + --data "$payload" \ + || echo "BoJ server unreachable - skipping (non-fatal)" diff --git a/.github/workflows/casket-pages.yml b/.github/workflows/casket-pages.yml deleted file mode 100644 index 96650f7..0000000 --- a/.github/workflows/casket-pages.yml +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -name: GitHub Pages - -on: - push: - branches: [main, master] - workflow_dispatch: - -permissions: - actions: read - contents: read - pages: write - id-token: write - -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - - - name: Checkout casket-ssg - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 - with: - repository: hyperpolymath/casket-ssg - path: .casket-ssg - - - name: Setup GHCup - uses: haskell-actions/setup@0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d # v2 - with: - ghc-version: '9.8.2' - cabal-version: '3.10' - - - name: Cache Cabal - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: | - ~/.cabal/packages - ~/.cabal/store - .casket-ssg/dist-newstyle - key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} - - - name: Build casket-ssg - working-directory: .casket-ssg - run: cabal build - - - name: Prepare site source - shell: bash - run: | - set -euo pipefail - rm -rf .site-src _site - - if [ -d site ]; then - cp -R site .site-src - else - mkdir -p .site-src - TODAY="$(date +%Y-%m-%d)" - REPO_NAME="${{ github.event.repository.name }}" - REPO_URL="https://github.com/${{ github.repository }}" - README_URL="" - - if [ -f README.md ]; then - README_URL="${REPO_URL}/blob/${{ github.ref_name }}/README.md" - elif [ -f README.adoc ]; then - README_URL="${REPO_URL}/blob/${{ github.ref_name }}/README.adoc" - fi - - { - echo "---" - echo "title: ${REPO_NAME}" - echo "date: ${TODAY}" - echo "---" - echo - echo "# ${REPO_NAME}" - echo - echo "Static documentation site for ${REPO_NAME}." - echo - echo "- Source repository: [${{ github.repository }}](${REPO_URL})" - if [ -n "${README_URL}" ]; then - echo "- README: [project README](${README_URL})" - fi - if [ -d docs ]; then - echo "- Docs directory: [docs/](${REPO_URL}/tree/${{ github.ref_name }}/docs)" - fi - echo - echo "Project-specific site content can be added later under site/." - } > .site-src/index.md - fi - - - name: Build site - run: | - mkdir -p _site - cd .casket-ssg && cabal run casket-ssg -- build ../.site-src ../_site - touch ../_site/.nojekyll - - - name: Setup Pages - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 - - - name: Upload artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - with: - path: '_site' - - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b2e2bed..0836912 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,29 +1,22 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: CodeQL Security Analysis - on: push: branches: [main, master] pull_request: 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 -# (no publish/mutation), so cancelling a superseded run is always safe. + - cron: '0 6 1 * *' # monthly 1st 06:00 UTC (Actions burn cut, standards#288) concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: - actions: read contents: read - jobs: analyze: runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read security-events: write @@ -31,23 +24,24 @@ jobs: fail-fast: false matrix: include: + # Default to `actions` — scaffolded repos rarely have JS/TS + # failures on every CodeQL run. The `actions` extractor scans + # workflow files which every repo has. Override per-repo if + # the scaffolded project actually contains JS/TS code. + # Per hypatia rule `codeql_language_matrix_mismatch`. - language: actions build-mode: none - - language: c-cpp - build-mode: none - - language: rust - build-mode: none steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - + uses: actions/checkout@v7.0.1 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v3 + uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v3 + uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 (4.38.1 blocked estate-wide; nexia-list#100) with: category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/deed-validate.yml b/.github/workflows/deed-validate.yml new file mode 100644 index 0000000..32dc767 --- /dev/null +++ b/.github/workflows/deed-validate.yml @@ -0,0 +1,59 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: DEED Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + deed-validate: + name: Validate DEED manifests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for A2ML files + id: detect + run: | + COUNT=$(find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | wc -l) + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ]; then + echo "::warning::No .a2ml/.deed manifest files found. Every RSR repo should have a repo deed (_chora.deed); legacy 0-AI-MANIFEST.a2ml accepted mid-migration — standards #837" + fi + + - name: "🟡 CHECK: Validate DEED manifests" + if: steps.detect.outputs.count > 0 + uses: hyperpolymath/deed-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + MANIFEST_COUNT="${{ steps.detect.outputs.count }}" + if [ "$MANIFEST_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## DEED Manifest Validation + + :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + + Copy it from [dicta-task](https://github.com/hyperpolymath/dicta-task). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. + EOF + else + echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Scanned **${MANIFEST_COUNT}** .a2ml/.deed manifest(s). See step output for details." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/dependabot-automerge.yml b/.github/workflows/dependabot-automerge.yml new file mode 100644 index 0000000..31a7b6a --- /dev/null +++ b/.github/workflows/dependabot-automerge.yml @@ -0,0 +1,137 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# +# dependabot-automerge.yml — enable GitHub's native auto-merge on +# Dependabot pull requests that match a declared severity / ecosystem +# policy. Pairs with `.github/dependabot.yml`'s +# `open-pull-requests-limit: 0` + security-only pattern (see the +# cargo block there). +# +# What this does: +# - Triggers on every Dependabot PR. +# - Reads the PR's update-type metadata via the dependabot/fetch-metadata +# action (no free-text parsing). +# - Requires CI to be green before merge (GitHub's auto-merge enforces +# required status checks). +# - Gates merge behind a severity+ecosystem policy table. Default is +# low+medium security updates only. +# +# Why auto-merge on GitHub (not via a bot like rhodibot) is the right +# layer: GitHub enforces branch protection + required checks natively, +# and the PR author is already `dependabot[bot]`. Rhodibot doesn't need +# to know anything about ecosystems — GitHub handles the merge mechanics +# once we approve. +# +# Threat model: +# - A compromised upstream package with a bogus security advisory +# could propose a malicious version bump. Mitigation: require at +# least one non-automated reviewer for HIGH+CRITICAL severity +# (done below — we explicitly refuse to auto-approve those). +# - A compromised Dependabot itself is an Akerlof claim-grounder +# problem. Not in scope here; track under +# `project_claim_grounders_dual_use_akerlof.md`. +# +# Dogfooding: this workflow template is itself subject to the same +# Dependabot config via the github-actions ecosystem block, so SHA +# bumps for dependabot/fetch-metadata flow through the same path. + +name: Dependabot Auto-Merge +on: + pull_request: + types: [opened, reopened, synchronize] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false +permissions: + contents: read + pull-requests: write # needed to approve + # NB: keep narrow — do NOT add secrets: read or id-token: write here. + # The write scope auto-merge needs is elevated PER JOB below, not here: a + # job-level permissions: block REPLACES this one, so the job restates both. +jobs: + automerge: + # Only run for PRs actually authored by Dependabot. + if: github.actor == 'dependabot[bot]' && github.event.pull_request.user.login == 'dependabot[bot]' + permissions: + contents: write # needed to enable auto-merge (gh pr merge --auto) + pull-requests: write # needed to approve + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Fetch Dependabot metadata + id: meta + uses: dependabot/fetch-metadata@v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + # --- Policy gate ------------------------------------------------------- + # Outputs from fetch-metadata we care about: + # update-type → version-update:semver-{patch,minor,major} + # dependency-type → direct:{development,production} | indirect + # alert-state → AUTO_DISMISSED | DISMISSED | FIXED | OPEN + # ghsa-id → GHSA-... if this is a security PR + # --- Policy ------------------------------------------------------------- + # AUTO-APPROVE + AUTO-MERGE when: + # 1. This is a SECURITY update (ghsa-id present), AND + # 2. Update is patch or minor, AND + # 3. Severity ≤ moderate (Dependabot doesn't expose severity + # directly in fetch-metadata; infer from the absence of + # HIGH/CRITICAL labels added by Dependabot). + # Otherwise: do nothing. Human reviews HIGH+CRITICAL security + # updates and all non-security bumps. + - name: Decide policy outcome + id: policy + env: + GHSA_ID: ${{ steps.meta.outputs.ghsa-id }} + UPDATE_TYPE: ${{ steps.meta.outputs.update-type }} + PR_LABELS: ${{ toJson(github.event.pull_request.labels.*.name) }} + run: | + set -euo pipefail + + is_security=false + [ -n "$GHSA_ID" ] && is_security=true + + # Owner policy (deliberate: velocity over caution). Auto-merge EVERY + # Dependabot update — patch, minor AND major, security or routine. + # Safe because GitHub's auto-merge only COMPLETES once the required + # checks (secret-scanner, codeql, hypatia-scan, openssf-compliance, + # build/test) are green: a bump that breaks fails CI and the PR stays + # OPEN with an email ("oi, it broke") instead of landing on a red + # main; a bump that passes lands within minutes with no chasing. + # This is preferred over making Dependabot a ruleset BYPASS actor, + # which would let bumps skip those very checks (no gate, no signal). + echo "action=automerge" >> "$GITHUB_OUTPUT" + echo "security=$is_security" >> "$GITHUB_OUTPUT" + echo "update_type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT" + echo "ghsa=$GHSA_ID" >> "$GITHUB_OUTPUT" + - name: Approve PR (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr review --approve "$PR_URL" \ + --body "Auto-approving Dependabot security update (${{ steps.policy.outputs.ghsa }}, ${{ steps.policy.outputs.update_type }}). Policy: low/moderate security patches/minors only." + - name: Enable auto-merge (if policy allows) + if: steps.policy.outputs.action == 'automerge' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_URL: ${{ github.event.pull_request.html_url }} + run: | + gh pr merge --auto --squash "$PR_URL" + - name: Write decision to step summary + env: + ACTION: ${{ steps.policy.outputs.action }} + IS_SECURITY: ${{ steps.policy.outputs.security }} + UPDATE_TYPE: ${{ steps.policy.outputs.update_type }} + GHSA: ${{ steps.policy.outputs.ghsa }} + run: | + { + echo "## Dependabot Auto-Merge Decision" + echo "" + echo "| Field | Value |" + echo "|-------|-------|" + echo "| Policy action | \`$ACTION\` |" + echo "| Security update | \`$IS_SECURITY\` |" + echo "| Update type | \`$UPDATE_TYPE\` |" + echo "| GHSA ID | \`${GHSA:-n/a}\` |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..f795d2e --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,58 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: container build +on: + pull_request: + paths: + - 'build/container/**' + - 'build/just/container.just' + - '.github/workflows/container-build.yml' + push: + tags: ['v*'] + workflow_dispatch: + +# Scope + cancel superseded runs (estate guardrail). +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + container: + # OFF by default — costs nothing in derived repos. A repo opts in by setting + # the repository/organisation variable CONTAINER_CI=true. When enabled it + # runs on OWNED self-hosted runners (no metered GitHub Actions minutes); + # override the labels with the CONTAINER_RUNNER variable (a JSON array). + if: vars.CONTAINER_CI == 'true' + runs-on: ${{ fromJSON(vars.CONTAINER_RUNNER || '["self-hosted","owned","container"]') }} + timeout-minutes: 30 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + + - name: Tooling check + run: | + command -v just >/dev/null 2>&1 || { echo "::error::just not found on this runner"; exit 1; } + # The container recipes auto-detect the engine (podman | nerdctl | docker). + for e in podman nerdctl docker; do command -v "$e" >/dev/null 2>&1 && { echo "engine: $e"; break; }; done + + - name: Build image + run: just container-build + + - name: Verify compose configuration + run: just container-verify + + - name: Scan image (trivy, best-effort) + run: | + if command -v trivy >/dev/null 2>&1; then + trivy image --severity HIGH,CRITICAL --exit-code 0 "${{ github.event.repository.name }}:latest" || true + else + echo "trivy not installed on this runner — skipping image scan" + fi + + - name: Sign & verify .ctp bundle (tags only) + if: startsWith(github.ref, 'refs/tags/v') + run: just container-sign # cerro-torre: build + pack + Ed25519 sign + verify diff --git a/.github/workflows/dogfood-gate.yml b/.github/workflows/dogfood-gate.yml index b6e4926..32d367f 100644 --- a/.github/workflows/dogfood-gate.yml +++ b/.github/workflows/dogfood-gate.yml @@ -1,4 +1,6 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate @@ -12,8 +14,10 @@ on: push: branches: [main, master] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: - actions: read contents: read jobs: @@ -23,10 +27,11 @@ jobs: deed-validate: name: Validate DEED manifests runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Check for manifest files (.a2ml/.deed) id: detect @@ -39,7 +44,7 @@ jobs: - name: Validate DEED manifests if: steps.detect.outputs.count > 0 - uses: hyperpolymath/deed-ecosystem/validate-action@aa4b836bd969df2bc58128cb8e3d20bbc88d5e79 # main + uses: hyperpolymath/deed-ecosystem/validate-action@main with: path: '.' strict: 'false' @@ -53,7 +58,7 @@ jobs: :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. - Copy it from [rsr-template-repo](https://github.com/hyperpolymath/rsr-template-repo). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. + Copy it from [dicta-task](https://github.com/hyperpolymath/dicta-task). Manifests are validated against the DEED grammar (standards `1-formats/deed/`); the `.a2ml` → `.deed` extension migration is tracked in standards #837 — the deed validator scans both. EOF else echo "## DEED Manifest Validation" >> "$GITHUB_STEP_SUMMARY" @@ -67,10 +72,11 @@ jobs: k9-validate: name: Validate K9 contracts runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Check for K9 files id: detect @@ -87,7 +93,7 @@ jobs: - name: Validate K9 contracts if: steps.detect.outputs.k9_count > 0 - uses: hyperpolymath/k9-ecosystem/validate-action@89f3c2702f4f650a92aa7411502f38da06abd562 # main + uses: hyperpolymath/k9-ecosystem/validate-action@main with: path: '.' strict: 'false' @@ -100,7 +106,7 @@ jobs: cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" ## K9 Contract Validation - :warning: **No .a2ml/.deed manifest files found.** Every RSR-compliant repo should have a repo deed (`_chora.deed`) at its root. + :warning: **No K9 contract files found.** Run `k9iser` to generate contracts. Generate contracts with: `k9iser generate .` EOF @@ -114,46 +120,57 @@ jobs: # Job 3: Empty-linter — invisible character detection # --------------------------------------------------------------------------- empty-lint: - name: Empty-linter (invisible characters) + name: "🔴 GATE: Empty-linter (invisible characters)" runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Scan for invisible characters id: lint run: | - # Inline invisible character detection (from empty-linter's core patterns). - # Checks for: zero-width spaces, zero-width joiners, BOM, soft hyphens, - # non-breaking spaces, null bytes, and other invisible Unicode in source files. - set +e - PATTERNS='(*UTF)[\x00-\x08\x0B\x0C\x0E-\x1F\x{a0}\x{ad}\x{200b}-\x{200f}\x{202a}-\x{202f}\x{2060}\x{2066}-\x{2069}\x{feff}]' - find "$GITHUB_WORKSPACE" \ - -not -path '*/.git/*' -not -path '*/node_modules/*' \ - -not -path '*/.deno/*' -not -path '*/target/*' \ - -not -path '*/_build/*' -not -path '*/deps/*' \ - -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ - -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ - -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ - -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ - -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ - -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \) \ - -exec grep -aPrl "$PATTERNS" {} \; > /tmp/empty-lint-results.txt 2>/dev/null - EL_EXIT=$? - set -e - - FINDINGS=$(wc -l < /tmp/empty-lint-results.txt 2>/dev/null || echo 0) + RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" + BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" + if ! scripts/check-invisible-characters.sh \ + "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then + echo "::error::Invisible-character scanner failed; refusing a partial pass" + exit 2 + fi + + FINDINGS=0 + while IFS= read -r -d '' filepath; do + FINDINGS=$((FINDINGS + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" + done < "$RESULTS_FILE" + + BLOCKING=0 + while IFS= read -r -d '' filepath; do + BLOCKING=$((BLOCKING + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" + done < "$BLOCKING_FILE" + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" - echo "exit_code=$EL_EXIT" >> "$GITHUB_OUTPUT" + echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" echo "ready=true" >> "$GITHUB_OUTPUT" - # Emit annotations for each file with invisible chars - while IFS= read -r filepath; do - [ -z "$filepath" ] && continue - REL_PATH="${filepath#$GITHUB_WORKSPACE/}" - echo "::warning file=${REL_PATH}::Invisible Unicode characters detected (zero-width space, BOM, NBSP, etc.)" - done < /tmp/empty-lint-results.txt + if [ "$BLOCKING" -gt 0 ]; then + echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi - name: Write summary run: | @@ -178,12 +195,13 @@ jobs: # Job 4: Groove manifest check (for repos that should expose services) # --------------------------------------------------------------------------- groove-check: - name: Groove manifest check + name: "🟡 CHECK: Groove manifest check" runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Check for Groove manifest id: groove @@ -192,13 +210,29 @@ jobs: HAS_MANIFEST="false" HAS_GROOVE_CODE="false" - if [ -f ".well-known/groove/manifest.json" ]; then + # Canonical manifest location is www/.well-known/groove/ (issue #53); + # the repository-root path is accepted, with a warning, during the + # migration window. + MANIFEST="" + if [ -f "www/.well-known/groove/manifest.json" ]; then + MANIFEST="www/.well-known/groove/manifest.json" + elif [ -f ".well-known/groove/manifest.json" ]; then + MANIFEST=".well-known/groove/manifest.json" + echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -n "$MANIFEST" ]; then HAS_MANIFEST="true" # Validate the manifest JSON - if ! jq empty .well-known/groove/manifest.json 2>/dev/null; then - echo "::error file=.well-known/groove/manifest.json::Invalid JSON in Groove manifest" + if ! jq empty "$MANIFEST" 2>/dev/null; then + # Gate, don't annotate: an unparseable manifest is a real error, + # not a warning — same behaviour as the standalone + # groove-check.yml (this job had drifted to annotation-only, + # the class of "check that cannot fail" from nexia-list#49). + echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" + exit 1 else - SVC_ID=$(jq -r '.service_id // "unknown"' .well-known/groove/manifest.json) + SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" fi fi @@ -208,9 +242,15 @@ jobs: HAS_GROOVE_CODE="true" fi - # Check if this repo likely serves HTTP (has server/listener code) + # Check if this repo likely serves HTTP in production. Key on + # production HTTP-server framework markers only. A bare `TcpListener` + # is deliberately NOT a signal: it is dominated by test/utility use + # (e.g. a #[cfg(test)] loopback fake), so matching it produced a + # spurious groove nudge on client-only apps. A real hand-rolled Rust + # server still pairs the listener with `axum::serve`/`hyper::Server`, + # which are matched here. HAS_SERVER="false" - if grep -rl 'TcpListener\|Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then HAS_SERVER="true" fi @@ -219,7 +259,7 @@ jobs: echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then - echo "::warning::This repo has server code but no Groove endpoint. Add .well-known/groove/manifest.json for service discovery." + echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." fi - name: Write summary @@ -228,7 +268,7 @@ jobs: echo "" >> "$GITHUB_STEP_SUMMARY" echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" - echo "| Static manifest (.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" @@ -238,10 +278,11 @@ jobs: eclexiaiser-validate: name: Validate eclexiaiser manifest runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Check and validate eclexiaiser manifest id: eclex @@ -257,31 +298,17 @@ jobs: echo "has_manifest=true" >> "$GITHUB_OUTPUT" - # Validate TOML structure using Python 3.11+ tomllib - python3 -c " - import tomllib, sys - with open('eclexiaiser.toml', 'rb') as f: - data = tomllib.load(f) - project = data.get('project', {}) - if not project.get('name', '').strip(): - print('ERROR: project.name is required', file=sys.stderr) - sys.exit(1) - functions = data.get('functions', []) - if not functions: - print('ERROR: at least one [[functions]] entry is required', file=sys.stderr) - sys.exit(1) - for fn in functions: - if not fn.get('name', '').strip(): - print('ERROR: function name cannot be empty', file=sys.stderr) - sys.exit(1) - if not fn.get('source', '').strip(): - print(f'ERROR: function {fn[\"name\"]} has no source path', file=sys.stderr) - sys.exit(1) - print(f'Valid: {project[\"name\"]} ({len(functions)} function(s))') - " || { - echo "::error file=eclexiaiser.toml::Invalid eclexiaiser.toml — see step output for details" + # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). + # Structural presence checks only — deep schema validation is eclexiaiser's own job. + err=0 + grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } + grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } + grep -qE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } + if [ "$err" -ne 0 ]; then exit 1 - } + fi + fns=$(grep -cE '^[[:space:]]*\[\[functions\]\]' eclexiaiser.toml) + echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" - name: Write summary run: | @@ -296,24 +323,204 @@ jobs: fi # --------------------------------------------------------------------------- - # Job 6: Dogfooding summary + # Job 6: Canon lockstep — does this template still implement the canon? + # + # The canon<->spine binding. hyperpolymath/standards publishes canon.lock: the + # released identity of the law, with a sha256 per law artefact. This repo + # declares the SAME hashes in .machine_readable/rsr-profile.a2ml [canon]. + # + # Before this job, the declaration was two free-text strings + # (`spec = "rsr-criteria-v2"`, `declares-against = "2.0.0-draft"`) and nothing + # failed when the canon changed. This makes it a comparison. + # + # Deliberately a bare `run:` step with NO `uses:`. Adding an action reference + # would have required an actions.lock row, and this repo's lock records + # FLOATING refs ('actions/checkout@v7.0.1') where the canon's records SHAs - + # so a pinned `uses:` here would mismatch the lock and fail at LOAD time with + # `jobs=0` and no annotation. Read-only over the public internet needs no auth. + # --------------------------------------------------------------------------- + canon-lockstep: + name: Canon lockstep + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + steps: + - name: Fetch the canon's released identity + id: canon + run: | + set -euo pipefail + LOCK="$RUNNER_TEMP/canon.lock" + URL="https://raw.githubusercontent.com/hyperpolymath/standards/main/canon.lock" + if ! curl -fsSL --retry 3 --max-time 30 "$URL" -o "$LOCK"; then + echo "::error::could not fetch canon.lock from $URL" + echo "The canon does not publish a released identity, so this repo" + echo "cannot be shown to implement it. Fix at source: add canon.lock." + exit 1 + fi + # Read (not compute) the criteria hash canon.lock asserts. + # + # awk only JOINS the record — the artefact entries are inline tables + # that span lines — then grep -oE extracts the hash. Deliberately the + # same two-step the canon's own check-canon-lockstep.sh uses, so + # there is one technique across both repos. + # + # Two traps this avoids, both found by running it rather than reading + # it. (1) An earlier version used `gsub(/.*"|"/,"",s)` to strip the + # quotes; the `.*"` is GREEDY, so it ate the entire string and the + # hash came out EMPTY. (2) awk interval expressions are not portable + # across mawk/gawk builds; grep -oE is. + rec="$(awk ' + /^[[:space:]]*#/ { next } + /^criteria[[:space:]]*=/ { on=1 } + on { + line=$0; sub(/#.*/,"",line); rec = rec " " line + if (line ~ /}/) { on=0 } + } + END { print rec }' "$LOCK")" + want="$(printf '%s\n' "$rec" \ + | grep -oE 'sha256[[:space:]]*=[[:space:]]*"[0-9a-f]{64}"' \ + | grep -oE '[0-9a-f]{64}' | head -1)" + if [ -z "$want" ]; then + echo "::error::canon.lock carries no criteria sha256 — malformed, or" + echo "the criteria record no longer starts a line with 'criteria ='." + exit 1 + fi + echo "want=$want" >> "$GITHUB_OUTPUT" + + - name: Compare this repo's declared pin + env: + WANT: ${{ steps.canon.outputs.want }} + # This job has NO `uses:` steps, deliberately: any added action would + # have to be pinned in actions.lock, and `uses ⊆ actions.lock` is already + # failing on main, so this job must not make it worse. That means no + # actions/checkout — and therefore NO WORKING TREE. Which is what + # broke this job: it read "machine-readable/rsr-profile.a2ml" from a + # directory that was never populated, so it failed 100% of the time + # with "no rsr-profile.a2ml", and would have whatever the pin said. + # + # Fetch the profile by SHA instead, exactly as canon.lock is fetched + # above. Deliberately NOT github.sha: on pull_request that is the + # ephemeral MERGE commit, which is not on the remote and would 404. + # The event's head sha is the commit that actually exists there. + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: | + set -euo pipefail + PROFILE="$RUNNER_TEMP/rsr-profile.a2ml" + fetched="" + for p in ".machine_readable/rsr-profile.a2ml" "machine-readable/rsr-profile.a2ml"; do + URL="https://raw.githubusercontent.com/hyperpolymath/dicta-task/${HEAD_SHA}/${p}" + if curl -fsSL --retry 3 --max-time 30 "$URL" -o "$PROFILE" 2>/dev/null; then + fetched="$p"; break + fi + done + if [ -z "$fetched" ]; then + echo "::error::no rsr-profile.a2ml at ${HEAD_SHA:0:7} — this repo cannot declare a canon pin" + echo "Looked for .machine_readable/rsr-profile.a2ml and machine-readable/rsr-profile.a2ml." + exit 1 + fi + echo "profile: $fetched @ ${HEAD_SHA:0:7}" + + # ALL THREE PINS, BOTH SIDES. + # + # This step used to compare only criteria_sha256, while this repo's own + # rsr-profile.a2ml stated that "these values ... equal canon.lock + # [canon.artifacts]" — plural. The claim was wider than the check, so + # `version` and `gates_sha256` could sit at stale values and nothing + # reported it. They did: canon 2.0.1 changed gates_sha256, and this repo + # kept asserting 2.0.0 and the superseded hash. A pin nothing verifies + # is not a binding. See hyperpolymath/standards docs/AUDIT.adoc F8. + # + # Both readers below are the technique the canon's own + # check-canon-lockstep.sh uses, so there is one idiom across both repos. + canon_key() { + awk -v key="$1" ' + /^[[:space:]]*#/ { next } + /^\[/ { f = ($0 == "[canon]") ? 1 : 0; next } + f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { + sub(/^[^=]*=[[:space:]]*/, ""); gsub(/^["[:space:]]+|["[:space:]]+$/, ""); print; exit + }' "$2" + } + # The artefact records are inline tables spanning lines, so awk JOINS the + # record and grep extracts the hash. Deliberately not anchored to the + # sha256 being on any particular line of the record. + artefact_hash() { + awk -v key="$1" ' + $0 ~ "^" key "[[:space:]]*=" { f = 1 } + f { buf = buf " " $0 } + f && /sha256[[:space:]]*=/ { print buf; exit } + ' "$2" | grep -oE '[0-9a-f]{64}' | head -1 + } + + want_crit="$WANT" + want_gates="$(artefact_hash gates "$RUNNER_TEMP/canon.lock")" + want_ver="$(canon_key version "$RUNNER_TEMP/canon.lock")" + got_crit="$(canon_key criteria_sha256 "$PROFILE")" + got_gates="$(canon_key gates_sha256 "$PROFILE")" + got_ver="$(canon_key version "$PROFILE")" + + # An unreadable pin must not pass. Hashing or parsing nothing yields an + # empty string, and `"" = ""` is a green build with nothing behind it — + # the same class of bug as this job reading a file that was never there. + for pair in "want_crit:$want_crit" "want_gates:$want_gates" "want_ver:$want_ver" \ + "got_crit:$got_crit" "got_gates:$got_gates" "got_ver:$got_ver"; do + if [ -z "${pair#*:}" ]; then + echo "::error::could not read ${pair%%:*} — refusing to compare an empty value" + exit 1 + fi + done + + ok=1 + row() { + mark=":x:" + if [ "$2" = "$3" ]; then mark=":white_check_mark:"; else ok=0; fi + printf '| `%s` | `%s` | `%s` | %s |\n' "$1" "${2:0:16}" "${3:0:16}" "$mark" + } + { + echo "## Canon lockstep" + echo "" + echo "| pin | canon.lock asserts | this repo declares | |" + echo "|---|---|---|---|" + row version "$want_ver" "$got_ver" + row criteria_sha256 "$want_crit" "$got_crit" + row gates_sha256 "$want_gates" "$got_gates" + } >> "$GITHUB_STEP_SUMMARY" + if [ "$ok" = "1" ]; then + echo ":white_check_mark: This template implements the canon at HEAD." + exit 0 + fi + echo "::error::canon lockstep broken — the template does not implement the canon at HEAD" + { + echo "" + echo ":x: **Lockstep broken.**" + echo "" + echo "The canon's law changed and this template has not adopted it." + echo "Order matters (\`canon.lock [canon.lockstep].order\` is" + echo "\`spine-adopts-then-canon-releases\`): re-pin \`[canon]\` in" + echo "\`.machine_readable/rsr-profile.a2ml\` to the new hashes and fix any" + echo "criteria this repo now fails, BEFORE the canon release lands." + } >> "$GITHUB_STEP_SUMMARY" + exit 1 + # --------------------------------------------------------------------------- + # Job 7: Dogfooding summary # --------------------------------------------------------------------------- dogfood-summary: - name: Dogfooding compliance summary + name: "ℹ️ ADVISORY: Dogfooding compliance summary (non-gating)" runs-on: ubuntu-latest - needs: [deed-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate] + timeout-minutes: 15 + needs: [deed-validate, k9-validate, empty-lint, groove-check, eclexiaiser-validate, canon-lockstep] if: always() steps: - name: Checkout repository - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4 + uses: actions/checkout@v7.0.1 - name: Generate dogfooding scorecard run: | SCORE=0 MAX=6 - # DEED manifest present? + # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then SCORE=$((SCORE + 1)) DEED_STATUS=":white_check_mark:" @@ -338,7 +545,7 @@ jobs: fi # Groove manifest or code? - if [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then SCORE=$((SCORE + 1)) GROOVE_STATUS=":white_check_mark:" else @@ -376,7 +583,50 @@ jobs: | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | --- - *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/rsr-template-repo) workflow.* + *Generated by the [Dogfood Gate](https://github.com/hyperpolymath/dicta-task) workflow.* *Dogfooding is guinea pig fooding — we test our tools on ourselves.* EOF + rsr-score-self: + name: RSR oracle — dogfood this repo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/standards + path: _standards + - uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/hypatia + path: _hypatia + - name: Setup Elixir for the oracle + uses: erlef/setup-beam@v1.24.1 + with: + elixir-version: '1.19.4' + otp-version: '28.3' + - name: Compile the oracle + working-directory: _hypatia + run: mix deps.get && mix compile + - name: Score this repo against the canon + working-directory: _hypatia + run: | + # --ssot is explicit until the hypatia M-3 fix lands (the + # --standards default still points at the pre-reorg path). + mix hypatia.rsr_score .. \ + --ssot ../_standards/0-canon/rsr/rsr-criteria-v2.a2ml \ + --write + - name: Show the scorecard + if: always() + run: | + if [ -f .machine_readable/descriptiles/SCORECARD.a2ml ]; then + cat .machine_readable/descriptiles/SCORECARD.a2ml + else + echo "::warning::no scorecard written — the oracle failed; see the step log" + fi + # Deliberately NO --fail-under: the point is to publish the honest + # scorecard (provisional=true until the coverage worklist is triaged), + # not to gate the spine on itself. Tightens in the PR that lands the + # first coverage tranche. diff --git a/.github/workflows/dogfood-summary.yml b/.github/workflows/dogfood-summary.yml new file mode 100644 index 0000000..2df5c3d --- /dev/null +++ b/.github/workflows/dogfood-summary.yml @@ -0,0 +1,99 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "ℹ️ ADVISORY: Dogfooding Compliance Scorecard" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + dogfood-summary: + name: "Dogfooding compliance summary" + runs-on: ubuntu-latest + timeout-minutes: 15 + if: always() + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Generate dogfooding scorecard + run: | + SCORE=0 + MAX=6 + + # DEED manifest present? (.a2ml legacy extension accepted during standards #837 migration) + if find . \( -name '*.a2ml' -o -name '*.deed' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + DEED_STATUS=":white_check_mark:" + else + DEED_STATUS=":x:" + fi + + # K9 contracts present? + if find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + K9_STATUS=":white_check_mark:" + else + K9_STATUS=":x:" + fi + + # .editorconfig present? + if [ -f ".editorconfig" ]; then + SCORE=$((SCORE + 1)) + EC_STATUS=":white_check_mark:" + else + EC_STATUS=":x:" + fi + + # Groove manifest or code? + if [ -f "www/.well-known/groove/manifest.json" ] || [ -f ".well-known/groove/manifest.json" ] || grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + GROOVE_STATUS=":white_check_mark:" + else + GROOVE_STATUS=":ballot_box_with_check:" + fi + + # VeriSimDB integration? + if grep -rl 'verisimdb\|VeriSimDB' --include='*.toml' --include='*.yaml' --include='*.yml' --include='*.json' --include='*.rs' --include='*.ex' . 2>/dev/null | head -1 | grep -q .; then + SCORE=$((SCORE + 1)) + VSDB_STATUS=":white_check_mark:" + else + VSDB_STATUS=":ballot_box_with_check:" + fi + + # eclexiaiser energy tracking? + if [ -f "eclexiaiser.toml" ]; then + SCORE=$((SCORE + 1)) + ECLEX_STATUS=":white_check_mark:" + else + ECLEX_STATUS=":ballot_box_with_check:" + fi + + cat <> "$GITHUB_STEP_SUMMARY" + ## Dogfooding Scorecard + + **Score: ${SCORE}/${MAX}** + + | Tool/Format | Status | Notes | + |-------------|--------|-------| + | DEED manifest (repo deed `*_chora.deed`) | ${DEED_STATUS} | Required for all RSR repos | + | K9 contracts | ${K9_STATUS} | Required for repos with config files | + | .editorconfig | ${EC_STATUS} | Required for all repos | + | Groove endpoint | ${GROOVE_STATUS} | Required for service repos | + | VeriSimDB integration | ${VSDB_STATUS} | Required for stateful repos | + | eclexiaiser | ${ECLEX_STATUS} | Energy/carbon budgets for container services | + + --- + *Generated by the Dogfooding Compliance workflow.* + *Dogfooding is guinea pig fooding — we test our tools on ourselves.* + EOF diff --git a/.github/workflows/dot-wellknown-enforcement.yml b/.github/workflows/dot-wellknown-enforcement.yml new file mode 100644 index 0000000..7ce166e --- /dev/null +++ b/.github/workflows/dot-wellknown-enforcement.yml @@ -0,0 +1,157 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Well-Known Standards (RFC 9116 + RSR) +on: + push: + branches: [main, master] + paths: + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) + - 'security.txt' + pull_request: + paths: + - 'www/.well-known/**' + - 'www/tests/**' + - 'www/schemas/**' + - '.well-known/**' # legacy location — migration window (issue #53) + schedule: + # Weekly expiry check (Mondays 09:00 UTC) + - cron: '0 9 * * 1' + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + validate: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: RFC 9116 security.txt validation + run: | + SECTXT="" + if [ -f "www/.well-known/security.txt" ]; then + SECTXT="www/.well-known/security.txt" + elif [ -f ".well-known/security.txt" ]; then + SECTXT=".well-known/security.txt" + echo "::warning::security.txt at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + elif [ -f "security.txt" ]; then + SECTXT="security.txt" + echo "::warning::security.txt at repository root — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -z "$SECTXT" ]; then + echo "::error::No security.txt found — required for OpenSSF Best Practices. See https://github.com/hyperpolymath/well-known-ecosystem" + exit 1 + fi + + # Required: Contact + grep -q "^Contact:" "$SECTXT" || { echo "::error::Missing Contact field"; exit 1; } + + # Required: Expires + if ! grep -q "^Expires:" "$SECTXT"; then + echo "::error::Missing Expires field" + exit 1 + fi + + # Check expiry + EXPIRES=$(grep "^Expires:" "$SECTXT" | cut -d: -f2- | tr -d ' ' | head -1) + if date -d "$EXPIRES" > /dev/null 2>&1; then + DAYS=$(( ($(date -d "$EXPIRES" +%s) - $(date +%s)) / 86400 )) + if [ $DAYS -lt 0 ]; then + echo "::error::security.txt EXPIRED" + exit 1 + elif [ $DAYS -lt 30 ]; then + echo "::warning::security.txt expires in $DAYS days" + else + echo "✅ security.txt valid ($DAYS days)" + fi + fi + - name: RSR well-known compliance + run: | + MISSING="" + LEGACY="" + for f in security.txt ai.txt humans.txt; do + if [ -f "www/.well-known/$f" ]; then + : + elif [ -f ".well-known/$f" ] || { [ "$f" = security.txt ] && [ -f security.txt ]; }; then + LEGACY="$LEGACY $f" + else + MISSING="$MISSING $f" + fi + done + if [ -n "$LEGACY" ]; then + echo "::warning::legacy .well-known location (canonical is www/.well-known/):$LEGACY — run scripts/migrate-wellknown-to-www.sh" + fi + + if [ -n "$MISSING" ]; then + echo "::warning::Missing RSR recommended files:$MISSING" + echo "Reference: https://github.com/hyperpolymath/well-known-ecosystem/.well-known/" + else + echo "✅ RSR well-known compliant" + fi + # Stage 5 (#119) sweeps ~270 repositories. #106 claimed CI "drives the + # migrator"; until now it only printed advice, so nothing verified that a + # repo would actually survive the migration. This step runs the real + # migrator and turns every conflict into an annotation. + # + # --dry-run because this job holds contents: read and cannot push: the + # point is to surface divergence early in the migration window, not to + # mutate a checkout that will be thrown away. The sweep driver + # (scripts/sweep-wellknown.sh) is what performs the real migration. + - name: Legacy root .well-known/ migration report + run: | + if [ ! -d .well-known ]; then + echo "No root .well-known/ — nothing to migrate." + exit 0 + fi + if [ ! -f scripts/migrate-wellknown-to-www.sh ]; then + echo "::warning::root .well-known/ present but no migrator — template is behind #53 stage 5." + exit 0 + fi + bash scripts/migrate-wellknown-to-www.sh --dry-run --report wellknown-migration.tsv + echo "--- planned migration ---" + cat wellknown-migration.tsv + # Conflicts are warnings, not errors, for the duration of the + # migration window (#106): a repo must not go red for a condition + # the sweep exists to fix. It must not be silent either. + while IFS=$'\t' read -r action path detail; do + case "$action" in + quarantined|conflict|left) + echo "::warning title=well-known migration::$action $path — $detail" + ;; + esac + done < <(tail -n +2 wellknown-migration.tsv) + - name: www bundle self-test + run: | + if [ -d www/tests ]; then + bash www/tests/run-all.sh + else + echo "www/tests absent — site-operations bundle not present; skipping." + fi + - name: Mixed content check + run: | + MIXED=$(grep -rE 'src="http://|href="http://' --include="*.html" --include="*.htm" . 2>/dev/null | grep -vE 'localhost|127\.0\.0\.1|example\.com' | head -5 || true) + if [ -n "$MIXED" ]; then + echo "::error::Mixed content (HTTP in HTML)" + echo "$MIXED" + exit 1 + fi + echo "✅ No mixed content" + - name: DNS security records check + if: hashFiles('CNAME') != '' + run: | + DOMAIN=$(cat CNAME 2>/dev/null | tr -d '\n') + if [ -n "$DOMAIN" ]; then + echo "Checking DNS for $DOMAIN..." + # CAA record + dig +short CAA "$DOMAIN" | grep -q "issue" && echo "✅ CAA record" || echo "::warning::No CAA record" + # DNSSEC + dig +dnssec +short "$DOMAIN" | grep -q "RRSIG" && echo "✅ DNSSEC" || echo "::warning::No DNSSEC" + fi diff --git a/.github/workflows/e2e.yml.template b/.github/workflows/e2e.yml.template new file mode 100644 index 0000000..9842451 --- /dev/null +++ b/.github/workflows/e2e.yml.template @@ -0,0 +1,224 @@ +# ⚠ THIS FILE IS A SCAFFOLD, NOT A WORKFLOW. +# +# It is named .yml.template deliberately: GitHub Actions reads only *.yml and +# *.yaml under .github/workflows/, so this file is ignored until someone +# instantiates it. +# +# It used to be called e2e.yml, and because every job block below is commented +# out, `jobs:` parsed as null — an invalid workflow. It therefore FAILED on +# every run, in every repository minted from this template, without ever +# executing a single check. 29 repos inherited that. +# +# A scaffold that is invalid until edited WILL be merged unedited. Naming it +# so the platform ignores it removes the failure without removing the guidance. +# +# TO USE: copy to .github/workflows/e2e.yml, uncomment the block matching your +# stack, delete the rest, and confirm it can FAIL before treating it as a gate +# (standards docs/language-testing-standards.md R10). +# +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard E2E + Aspect + Benchmark Workflow Template +# +# Covers ALL merge requirement test categories: +# - E2E (end-to-end pipeline tests) +# - Aspect (cross-cutting concern validation) +# - Benchmarks (performance regression detection) +# - Readiness (Component Readiness Grade: D/C/B) +# +# INSTRUCTIONS: Uncomment and customise the section matching your stack. +# Delete sections that don't apply. See examples in each job. + +name: E2E + Aspect + Bench +on: + push: + branches: [main, master, develop] + paths: + - 'src/**' + - 'src/interface/ffi/**' + - 'tests/**' + - '.github/workflows/e2e.yml' + pull_request: + branches: [main, master] + paths: + - 'src/**' + - 'src/interface/ffi/**' + - 'tests/**' + workflow_dispatch: +permissions: read-all +concurrency: + group: e2e-${{ github.ref }} + cancel-in-progress: true +jobs: + # ─── Default: template smoke (always runs — not a silent no-op) ───── + # Runs the template's own E2E harness (tests/e2e.sh). On the bare template + # this is a clean pass (no stack-specific checks enabled yet); downstream + # repos either extend tests/e2e.sh or uncomment a stack block below. + e2e: + name: E2E — template smoke + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + - name: Run E2E harness + run: | + if [ -f tests/e2e.sh ]; then + bash tests/e2e.sh + else + echo "::notice::no tests/e2e.sh present — nothing to run" + fi + +# ─── End-to-End Tests (downstream stack-specific examples) ───────── +# 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@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 +# with: +# version: 0.15.1 +# - 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@fc68ffb90438ef2936bbb3251622353b3dcb2f93 # v1.24.0 +# 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/ E2E === +# e2e: +# name: E2E — Full Pipeline +# runs-on: ubuntu-latest +# timeout-minutes: 15 +# steps: +# - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 +# - uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 +# 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@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4 +# 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@cd0d9bdd65b20557f41bea4dbe43d0b5fbbfe553 # v2.11.0 +# 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@abea47f85e598557f500fa1fd2ab7464fcb39406 # v2.2.1 +# with: +# version: 0.15.1 +# - 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/eclexiaiser-validate.yml b/.github/workflows/eclexiaiser-validate.yml new file mode 100644 index 0000000..aae7481 --- /dev/null +++ b/.github/workflows/eclexiaiser-validate.yml @@ -0,0 +1,64 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: Eclexiaiser Manifest Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + eclexiaiser-validate: + name: "🟡 CHECK: Validate eclexiaiser manifest" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check and validate eclexiaiser manifest + id: eclex + run: | + if [ ! -f "eclexiaiser.toml" ]; then + # Check if repo has a Containerfile — if so, recommend eclexiaiser + if [ -f "Containerfile" ]; then + echo "::warning::Containerfile present but no eclexiaiser.toml. Run \`eclexiaiser init\` to scaffold energy/carbon budgets." + fi + echo "has_manifest=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "has_manifest=true" >> "$GITHUB_OUTPUT" + + # Validate eclexiaiser.toml structure (bash + grep; NO Python per estate policy). + # Structural presence checks only — deep schema validation is eclexiaiser's own job. + err=0 + grep -qE '^[[:space:]]*\[project\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::[project] section is required"; err=1; } + grep -qE '^[[:space:]]*name[[:space:]]*=[[:space:]]*"[^"]+"' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::a non-empty name is required"; err=1; } + grep -qE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml || { echo "::error file=eclexiaiser.toml::at least one [[functions]] entry is required"; err=1; } + if [ "$err" -ne 0 ]; then + exit 1 + fi + fns=$(grep -cE '^[[:space:]]*\[\[(functions)\]\]' eclexiaiser.toml) + echo "Valid: eclexiaiser.toml structure present (${fns} function block(s))" + + - name: "Write summary" + run: | + if [ "${{ steps.eclex.outputs.has_manifest }}" = "true" ]; then + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: **eclexiaiser.toml** present and valid." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Eclexiaiser Manifest" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":ballot_box_with_check: No eclexiaiser.toml. Add one with \`eclexiaiser init\` for energy/carbon tracking." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/empty-linter.yml b/.github/workflows/empty-linter.yml new file mode 100644 index 0000000..7bdf237 --- /dev/null +++ b/.github/workflows/empty-linter.yml @@ -0,0 +1,89 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🔴 GATE: Invisible Character Detection" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + empty-lint: + name: "🔴 GATE: Empty-linter (invisible characters)" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Scan for invisible characters + id: lint + run: | + RESULTS_FILE="$RUNNER_TEMP/empty-lint-results.bin" + BLOCKING_FILE="$RUNNER_TEMP/empty-lint-blocking-results.bin" + if ! scripts/check-invisible-characters.sh \ + "$GITHUB_WORKSPACE" "$RESULTS_FILE" "$BLOCKING_FILE"; then + echo "::error::Invisible-character scanner failed; refusing a partial pass" + exit 2 + fi + + FINDINGS=0 + while IFS= read -r -d '' filepath; do + FINDINGS=$((FINDINGS + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::warning file=${SAFE_PATH}::Invisible Unicode or C0 characters detected" + done < "$RESULTS_FILE" + + BLOCKING=0 + while IFS= read -r -d '' filepath; do + BLOCKING=$((BLOCKING + 1)) + REL_PATH="${filepath#"$GITHUB_WORKSPACE"/}" + SAFE_PATH="${REL_PATH//'%'/'%25'}" + SAFE_PATH="${SAFE_PATH//$'\r'/'%0D'}" + SAFE_PATH="${SAFE_PATH//$'\n'/'%0A'}" + SAFE_PATH="${SAFE_PATH//':'/'%3A'}" + SAFE_PATH="${SAFE_PATH//','/'%2C'}" + echo "::error file=${SAFE_PATH}::C0 control character or NUL byte detected" + done < "$BLOCKING_FILE" + + echo "findings=$FINDINGS" >> "$GITHUB_OUTPUT" + echo "blocking=$BLOCKING" >> "$GITHUB_OUTPUT" + echo "ready=true" >> "$GITHUB_OUTPUT" + + if [ "$BLOCKING" -gt 0 ]; then + echo "## Empty-linter: BLOCKED — $BLOCKING file(s) contain C0/NUL corruption" >> "$GITHUB_STEP_SUMMARY" + exit 1 + fi + + - name: "Write summary" + run: | + if [ "${{ steps.lint.outputs.ready }}" = "true" ]; then + FINDINGS="${{ steps.lint.outputs.findings }}" + if [ "$FINDINGS" -gt 0 ] 2>/dev/null; then + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Found **${FINDINGS}** invisible character issue(s). See annotations above." >> "$GITHUB_STEP_SUMMARY" + else + echo "## Empty-Linter Results" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo ":white_check_mark: No invisible character issues found." >> "$GITHUB_STEP_SUMMARY" + fi + else + echo "## Empty-Linter" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Skipped: empty-linter not available." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/estate-rules.yml b/.github/workflows/estate-rules.yml new file mode 100644 index 0000000..9dd2741 --- /dev/null +++ b/.github/workflows/estate-rules.yml @@ -0,0 +1,85 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Estate Rules — enforces hyperpolymath estate-wide conventions: +# * root shape (allowlist of permitted root entries) +# * AsciiDoc-by-default (no .md files under docs/) +# * AsciiDoc that renders (every tracked .adoc parses with no diagnostics) +# * V-lang is banned (no V-lang scaffolding or references; Zig is the FFI default) +# +# Each rule is enforced by an executable check script under scripts/. Failures +# are surfaced as workflow errors so the rule can't silently regress. + +name: Estate Rules +on: + push: + branches: [main] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + estate-rules: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + # Prove the instrument works before trusting its reading. This gate was + # one-directional for months, which is indistinguishable from a working + # gate until you try to make it fail. + - name: Root-shape gate self-test (must fail on drift) + run: bash tests/shape/check_root_shape_test.sh + - name: Root shape allowlist + run: bash scripts/check-root-shape.sh . + # The map is generated. Five hand-written predecessors all rotted because + # nothing diffed them. + # The freshness check below is only meaningful if the generator is + # environment-independent. It wasn't: sort is locale-dependent, so the + # map generated differently in CI than locally. Vary the locale first. + - name: Repository map generates identically across locales + run: bash tests/shape/repo_map_determinism_test.sh + - name: Repository map is not stale + run: | + cp docs/architecture/REPOSITORY-MAP.adoc /tmp/map.before + bash scripts/gen-repo-map.sh . + diff -u /tmp/map.before docs/architecture/REPOSITORY-MAP.adoc \ + || { echo "::error::REPOSITORY-MAP.adoc is stale - run 'just repo-map'"; exit 1; } + - name: AsciiDoc by default (no .md under docs/) + run: bash scripts/check-no-md-in-docs.sh . + # The rule above checks the file EXTENSION only, so a Markdown body + # renamed to .adoc passes it. This one checks the file actually parses. + # asciidoctor's --failure-level defaults to FATAL, so a document emitting + # WARNING or ERROR still exits 0; the version is pinned because the + # verdict rests on a stderr comparison. + - name: Install asciidoctor + run: sudo gem install asciidoctor -v 2.0.26 --no-document + - name: AsciiDoc render gate self-test (must catch what bare asciidoctor misses) + run: bash tests/workflows/check_adoc_renders_test.sh + - name: Every tracked .adoc renders clean + run: bash scripts/check-adoc-renders.sh . + - name: No V-language references + run: bash scripts/check-no-vlang.sh . + # Was written but wired to nothing, so it had never run in CI. + - name: V-language gate self-test + run: bash tests/workflows/check_no_vlang_test.sh + - name: Install verified Nickel 1.17.0 + run: | + mkdir -p "$RUNNER_TEMP/nickel-bin" + curl --proto '=https' --proto-redir '=https' --fail --silent --show-error --location \ + https://github.com/nickel-lang/nickel/releases/download/1.17.0/nickel-x86_64-linux \ + --output "$RUNNER_TEMP/nickel-bin/nickel" + echo "afcdfa6e0fff31760cf229e85997456c02c00b8b3b84ff38f897ac7b3f39ae34 $RUNNER_TEMP/nickel-bin/nickel" | sha256sum --check --strict + chmod +x "$RUNNER_TEMP/nickel-bin/nickel" + echo "$RUNNER_TEMP/nickel-bin" >> "$GITHUB_PATH" + - name: Evaluate session contracts + run: bash scripts/validate-session-contracts.sh + - name: Check Nickel typecheck controls + run: bash tests/workflows/k9_typecheck_test.sh + - name: Verify session envelope controls + run: bash tests/workflows/session_contracts_test.sh + - name: Verify mint cleanup controls + run: bash tests/workflows/mint_cleanup_test.sh diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 5922883..73366ea 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -1,35 +1,21 @@ +# This workflow is managed by gh actions-lock. # 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-guix-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). - 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: - actions: read + actions: read # required by the reusable workflow (staleness check reads workflow runs) contents: read jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/.github/workflows/groove-check.yml b/.github/workflows/groove-check.yml new file mode 100644 index 0000000..f0e8785 --- /dev/null +++ b/.github/workflows/groove-check.yml @@ -0,0 +1,91 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: Groove Protocol Compliance" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + groove-check: + name: "🟡 CHECK: Groove manifest check" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for Groove manifest + id: groove + run: | + # Check for static or dynamic Groove endpoints + HAS_MANIFEST="false" + HAS_GROOVE_CODE="false" + + # Canonical manifest location is www/.well-known/groove/ (issue #53); + # the repository-root path is accepted, with a warning, during the + # migration window. + MANIFEST="" + if [ -f "www/.well-known/groove/manifest.json" ]; then + MANIFEST="www/.well-known/groove/manifest.json" + elif [ -f ".well-known/groove/manifest.json" ]; then + MANIFEST=".well-known/groove/manifest.json" + echo "::warning::Groove manifest at legacy root .well-known/ — canonical location is www/.well-known/ (run scripts/migrate-wellknown-to-www.sh)" + fi + + if [ -n "$MANIFEST" ]; then + HAS_MANIFEST="true" + # Validate the manifest JSON + if ! jq empty "$MANIFEST" 2>/dev/null; then + echo "::error file=$MANIFEST::Invalid JSON in Groove manifest" + exit 1 + else + SVC_ID=$(jq -r '.service_id // "unknown"' "$MANIFEST") + echo "service_id=$SVC_ID" >> "$GITHUB_OUTPUT" + fi + fi + + # Check for Groove endpoint code (Rust, Elixir, Zig, V) + if grep -rl 'well-known/groove' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' --include='*.res' . 2>/dev/null | head -1 | grep -q .; then + HAS_GROOVE_CODE="true" + fi + + # Check if this repo likely serves HTTP in production. Key on + # production HTTP-server framework markers only. A bare `TcpListener` + # is deliberately NOT a signal: it is dominated by test/utility use + # (e.g. a #[cfg(test)] loopback fake), so matching it produced a + # spurious groove nudge on client-only apps. A real hand-rolled Rust + # server still pairs the listener with `axum::serve`/`hyper::Server`, + # which are matched here. + HAS_SERVER="false" + if grep -rl 'Bandit\|Plug.Cowboy\|httpz\|vweb\|axum::serve\|actix_web\|hyper::Server\|rocket::build\|warp::serve' --include='*.rs' --include='*.ex' --include='*.zig' --include='*.v' . 2>/dev/null | head -1 | grep -q .; then + HAS_SERVER="true" + fi + + echo "has_manifest=$HAS_MANIFEST" >> "$GITHUB_OUTPUT" + echo "has_groove_code=$HAS_GROOVE_CODE" >> "$GITHUB_OUTPUT" + echo "has_server=$HAS_SERVER" >> "$GITHUB_OUTPUT" + + if [ "$HAS_SERVER" = "true" ] && [ "$HAS_MANIFEST" = "false" ] && [ "$HAS_GROOVE_CODE" = "false" ]; then + echo "::warning::This repo has server code but no Groove endpoint. Add www/.well-known/groove/manifest.json for service discovery." + fi + + - name: "Write summary" + run: | + echo "## Groove Protocol Check" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Check | Status |" >> "$GITHUB_STEP_SUMMARY" + echo "|-------|--------|" >> "$GITHUB_STEP_SUMMARY" + echo "| Static manifest (www/.well-known/groove/manifest.json) | ${{ steps.groove.outputs.has_manifest }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Groove endpoint in code | ${{ steps.groove.outputs.has_groove_code }} |" >> "$GITHUB_STEP_SUMMARY" + echo "| Has HTTP server code | ${{ steps.groove.outputs.has_server }} |" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/guix-policy.yml b/.github/workflows/guix-policy.yml new file mode 100644 index 0000000..6363018 --- /dev/null +++ b/.github/workflows/guix-policy.yml @@ -0,0 +1,49 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Guix Package Policy +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Enforce Guix-only package policy + run: | + # Guix is the sole package manager estate-wide. Nix is BANNED. + HAS_GUIX=$(find . -path ./.git -prune -o \( -name "*.scm" -o -name ".guix-channel" -o -name "guix.scm" \) -print 2>/dev/null | head -1) + HAS_NIX=$(find . -path ./.git -prune -o -name "*.nix" -print 2>/dev/null | head -1) + + # Hard-fail on any Nix file — Nix is banned, migrate to Guix. + if [ -n "$HAS_NIX" ]; then + echo "::error::Nix is banned estate-wide (Guix only). Remove flake.nix/*.nix and use guix.scm: $HAS_NIX" + exit 1 + fi + + # Warn on non-reproducible lock files; prefer Guix manifests. + NEW_LOCKS=$(git diff --name-only --diff-filter=A HEAD~1 2>/dev/null | grep -E 'package-lock\.json|yarn\.lock|Gemfile\.lock|Pipfile\.lock|poetry\.lock|cargo\.lock' || true) + if [ -n "$NEW_LOCKS" ]; then + echo "⚠️ Lock files detected. Prefer Guix manifests for reproducibility." + fi + + if [ -n "$HAS_GUIX" ]; then + echo "✅ Guix package management detected" + else + echo "ℹ️ Consider adding guix.scm / .guix-channel for reproducible builds" + fi + + echo "✅ Guix package policy check passed" diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index d8e00ae..997e008 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -1,7 +1,11 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -# Thin wrapper around hyperpolymath/standards hypatia-scan-reusable.yml. -# See standards#191 for the reusable's purpose and design. - +# +# Standalone Hypatia security scan (push / PR / weekly). This is NOT a duplicate +# of the `hypatia-scan` job in `static-analysis-gate.yml`: that job exists to +# feed the gitbot-fleet LEARNING pipeline (via `deposit-findings`), whereas this +# workflow is the repo's independent security scan. Same tool, two consumers. +# See .github/workflows/README.adoc. name: Hypatia Security Scan on: @@ -13,18 +17,14 @@ on: - cron: '0 0 * * 0' workflow_dispatch: -# Estate guardrail: cancel superseded runs so re-pushes don't pile up. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: - actions: read + actions: read # required by the reusable workflow (staleness check reads workflow runs) contents: read security-events: write - pull-requests: write jobs: hypatia: - uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c - secrets: inherit + uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/.github/workflows/instant-sync.yml b/.github/workflows/instant-sync.yml deleted file mode 100644 index e612d96..0000000 --- a/.github/workflows/instant-sync.yml +++ /dev/null @@ -1,39 +0,0 @@ -# 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: - actions: read - contents: read - -jobs: - dispatch: - runs-on: ubuntu-latest - steps: - - name: Trigger Propagation - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v3 - with: - token: ${{ secrets.FARM_DISPATCH_TOKEN }} - repository: hyperpolymath/.git-private-farm - event-type: propagate - client-payload: |- - { - "repo": "${{ github.event.repository.name }}", - "ref": "${{ github.ref }}", - "sha": "${{ github.sha }}", - "forges": "" - } - - - name: Confirm - run: echo "::notice::Propagation triggered for ${{ github.event.repository.name }}" - - - name: K9-SVC Validation - run: | - echo "K9-SVC validation" - [ -d .machine_readable/contractiles ] && echo "Contractiles present" || echo "No contractiles" diff --git a/.github/workflows/k9-validate.yml b/.github/workflows/k9-validate.yml new file mode 100644 index 0000000..548c3bb --- /dev/null +++ b/.github/workflows/k9-validate.yml @@ -0,0 +1,64 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: "🟡 CHECK: K9 Contract Validation" + +on: + pull_request: + branches: ['**'] + push: + branches: [main, master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + k9-validate: + name: "🟡 CHECK: Validate K9 contracts" + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + + - name: Check for K9 files + id: detect + run: | + COUNT=$(find . \( -name '*.k9' -o -name '*.k9.ncl' \) -not -path './.git/*' | wc -l) + CONFIG_COUNT=$(find . \( -name '*.toml' -o -name '*.yaml' -o -name '*.yml' -o -name '*.json' \) \ + -not -path './.git/*' -not -path './node_modules/*' -not -path './.deno/*' \ + -not -name 'package-lock.json' -not -name 'Cargo.lock' -not -name 'deno.lock' | wc -l) + echo "k9_count=$COUNT" >> "$GITHUB_OUTPUT" + echo "config_count=$CONFIG_COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" -eq 0 ] && [ "$CONFIG_COUNT" -gt 0 ]; then + echo "::warning::Found $CONFIG_COUNT config files but no K9 contracts. Run k9iser to generate contracts." + fi + + - name: "🟡 CHECK: Validate K9 contracts" + if: steps.detect.outputs.k9_count > 0 + uses: hyperpolymath/k9-ecosystem/validate-action@main + with: + path: '.' + strict: 'false' + + - name: Write summary + run: | + K9_COUNT="${{ steps.detect.outputs.k9_count }}" + CFG_COUNT="${{ steps.detect.outputs.config_count }}" + if [ "$K9_COUNT" -eq 0 ]; then + cat <<'EOF' >> "$GITHUB_STEP_SUMMARY" + ## K9 Contract Validation + + :warning: **No K9 contract files found.** Repos with configuration files should have K9 contracts. + + Generate contracts with: `k9iser generate .` + EOF + else + echo "## K9 Contract Validation" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Validated **${K9_COUNT}** K9 contract(s) against **${CFG_COUNT}** config file(s)." >> "$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/label-triage.yml b/.github/workflows/label-triage.yml index 9886e92..b752226 100644 --- a/.github/workflows/label-triage.yml +++ b/.github/workflows/label-triage.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Label Triage @@ -46,6 +47,7 @@ permissions: jobs: triage: runs-on: ubuntu-latest + timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #68) steps: - name: Classify and label env: diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index c80b676..31618e4 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Labels @@ -32,6 +33,7 @@ permissions: jobs: sync: runs-on: ubuntu-latest + timeout-minutes: 10 # Hypatia workflow_audit/missing_timeout_minutes (alert #69) steps: - name: Apply canonical labels env: diff --git a/.github/workflows/lock-sync-gate.yml b/.github/workflows/lock-sync-gate.yml new file mode 100644 index 0000000..db046cb --- /dev/null +++ b/.github/workflows/lock-sync-gate.yml @@ -0,0 +1,64 @@ +# SPDX-License-Identifier: MPL-2.0 +name: Lock Sync Gate + +# Fails any pull request whose .github/workflows/actions.lock has drifted from +# the workflow YAML. That drift is not cosmetic: GitHub refuses such a run at +# startup, creating ZERO jobs, and reports only "This run likely failed because +# of a workflow file issue." A single grouped Dependabot bump can take out most +# of a repository's CI that way, because Dependabot rewrites `uses:` refs in the +# YAML and cannot touch the lockfile. Measured across 200 repositories on +# 2026-09-22: 39 had silently dead CI from exactly this cause. +# See hyperpolymath/standards#968. +# +# This workflow deliberately carries NO `uses:` of its own. It checks out by +# calling git in a `run:` step instead of using actions/checkout, so it has no +# lockfile entry to go stale and is structurally immune to the very failure it +# detects. Do not add a `uses:` to this file. +# +# There is also no `paths:` filter, on purpose: a filtered workflow never +# reports on pull requests that miss the filter, which deadlocks any branch +# ruleset that requires this check. + +on: + workflow_dispatch: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: lock-sync-gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + lock-sync: + name: actions.lock is in sync with the workflow YAML + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check out without actions/checkout + env: + REPO: ${{ github.repository }} + SHA: ${{ github.event.pull_request.head.sha || github.sha }} + TOKEN: ${{ github.token }} + run: | + set -euo pipefail + # Authenticate the fetch. An anonymous clone works only for public + # repositories; this gate must also run on private ones. The header + # form is used rather than a token in the remote URL so the + # credential is never written into .git/config. + AUTH="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${TOKEN}" | base64 -w0)" + git init -q . + git remote add origin "https://github.com/${REPO}.git" + git -c http.extraheader="${AUTH}" fetch -q --depth 1 origin "${SHA}" + git checkout -q FETCH_HEAD + echo "checked out ${SHA}" + + - name: Verify lockfile synchronisation + run: | + set -euo pipefail + test -x scripts/check-lock-sync.sh \ + || { echo "::error::scripts/check-lock-sync.sh missing or not executable"; exit 1; } + ./scripts/check-lock-sync.sh diff --git a/.github/workflows/main-estate-audit.yml b/.github/workflows/main-estate-audit.yml new file mode 100644 index 0000000..c856ee3 --- /dev/null +++ b/.github/workflows/main-estate-audit.yml @@ -0,0 +1,20 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# This workflow is managed by gh actions-lock. +name: Central Estate CI/CD Audit + +# The called reusable declares `permissions: contents: read`; a caller must grant +# at least what the reusable declares, and this grants exactly that — no more. +permissions: + contents: read + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_call: + +jobs: + call-estate-audit: + uses: hyperpolymath/cicd-suite/.github/workflows/main-estate-audit.yml@55556cf5ba71ba744695861503c13148d3915a5d # cicd-suite MAIN (branch-reachable): remote-form callee — 27 full-SHA refs, de-onboarded; witnessed cross-repo in rsr runs 35282081912 + 35283168495 (jobs spawn; gates audit THIS repo). Prior f9e173a pin (via #137) = deleted PR head: pull-ref-only reachability -> 0-job creation rejection; its onboarded @main/tag form was separately proven startup_failure cross-repo (35280642055). Repin on callee updates; never to PR-head commits. diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index cde47d6..35c5683 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -1,16 +1,28 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Mirror to Git Forges - on: push: branches: [main] workflow_dispatch: - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false permissions: - actions: read + actions: read # required by the reusable workflow (staleness check reads workflow runs) contents: read - jobs: mirror: - uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c - secrets: inherit + uses: hyperpolymath/standards/.github/workflows/mirror-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 + # Explicit secrets map — no `secrets: inherit` (Hypatia WH008, alert #131). + # All seven are the callee's complete optional contract (standards + # mirror-reusable.yml@da2c748); behaviour is unchanged, future secrets + # are no longer shared implicitly. + secrets: + GITLAB_SSH_KEY: ${{ secrets.GITLAB_SSH_KEY }} + BITBUCKET_SSH_KEY: ${{ secrets.BITBUCKET_SSH_KEY }} + CODEBERG_SSH_KEY: ${{ secrets.CODEBERG_SSH_KEY }} + SOURCEHUT_SSH_KEY: ${{ secrets.SOURCEHUT_SSH_KEY }} + DISROOT_SSH_KEY: ${{ secrets.DISROOT_SSH_KEY }} + GITEA_SSH_KEY: ${{ secrets.GITEA_SSH_KEY }} + RADICLE_KEY: ${{ secrets.RADICLE_KEY }} diff --git a/.github/workflows/ossf-best-practices.yml b/.github/workflows/ossf-best-practices.yml new file mode 100644 index 0000000..b3aa90e --- /dev/null +++ b/.github/workflows/ossf-best-practices.yml @@ -0,0 +1,97 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# 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: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + openssf-compliance: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + with: + persist-credentials: false + - name: Check SECURITY.md exists and has substance + run: | + SECFILE="" + [ -f "SECURITY.md" ] && SECFILE="SECURITY.md" + [ -f "SECURITY.adoc" ] && SECFILE="SECURITY.adoc" + [ -f ".github/SECURITY.md" ] && SECFILE=".github/SECURITY.md" + + if [ -z "$SECFILE" ]; then + echo "::error::SECURITY.md (or SECURITY.adoc) is required for OpenSSF Best Practices" + exit 1 + fi + + LINES=$(wc -l < "$SECFILE") + if [ "$LINES" -lt 10 ]; then + echo "::error::$SECFILE has only $LINES lines — must have >10 lines of substantive content" + 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 + echo "::error::LICENSE file is required for OpenSSF Best Practices" + exit 1 + fi + echo "LICENSE: OK" + - name: Check CONTRIBUTING exists + run: | + if [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] \ + && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ]; then + echo "::error::CONTRIBUTING file is required for OpenSSF Best Practices" + 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 + echo "::error::README file is required for OpenSSF Best Practices" + exit 1 + fi + echo "README: OK" + - name: Check .machine_readable directory and STATE.a2ml + run: | + if [ ! -d ".machine_readable" ]; then + echo "::error::.machine_readable/ directory is required" + exit 1 + fi + + if [ ! -f ".machine_readable/descriptiles/STATE.a2ml" ]; then + echo "::error::.machine_readable/descriptiles/STATE.a2ml is required" + exit 1 + fi + echo ".machine_readable/descriptiles/STATE.a2ml: OK" + - name: Check CHANGELOG exists + run: | + if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG.adoc" ] && [ ! -f "CHANGES.md" ]; then + echo "::error::CHANGELOG.md is required for OpenSSF Best Practices" + exit 1 + fi + echo "CHANGELOG: OK" + - name: Check no unfilled placeholder tokens + # Delegates to the same script tests/e2e/template_instantiation_test.sh + # runs, so the gate and the test can never disagree about the rule. + # This step used to check a hand-maintained list of required files that + # omitted .github/settings.yml and ANCHOR.a2ml — the two places the + # leaks actually were. The script scans everything and allow-lists the + # legitimate carriers instead. + run: bash scripts/check-no-placeholders.sh . + - name: Summary + run: | + echo "=== OpenSSF Best Practices Compliance: PASS ===" + echo "All required files present and placeholder-free." diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..a84b495 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,91 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# GitHub Pages via casket-ssg (hyperpolymath's pure-Haskell static site generator). +# Replaces the orphan one-off Pages deployment with a reproducible build. +name: GitHub Pages + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Serialise Pages deploys; never cancel an in-flight deploy. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + build: + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Checkout casket-ssg + uses: actions/checkout@v7.0.1 + with: + repository: hyperpolymath/casket-ssg + path: .casket-ssg + + - name: Setup GHCup + uses: haskell-actions/setup@v2.12.0 + with: + ghc-version: '9.8.2' + cabal-version: '3.10' + + - name: Cache Cabal + uses: actions/cache@v6.1.0 + with: + path: | + ~/.cabal/packages + ~/.cabal/store + .casket-ssg/dist-newstyle + key: ${{ runner.os }}-casket-${{ hashFiles('.casket-ssg/casket-ssg.cabal') }} + + - name: Build casket-ssg + working-directory: .casket-ssg + run: cabal build + + - name: Build site + run: | + mkdir -p site _site + # Seed site/index.md from README if the author hasn't provided one. + if [ ! -f site/index.md ]; then + { + echo "---" + echo "title: $(basename "$PWD")" + echo "date: $(date +%Y-%m-%d)" + echo "---" + if [ -f README.adoc ]; then cat README.adoc + elif [ -f README.md ]; then cat README.md + else printf '\n# %s\n\nDocumentation coming soon.\n' "$(basename "$PWD")" + fi + } > site/index.md + fi + cd .casket-ssg && cabal run casket-ssg -- build ../site ../_site + + - name: Setup Pages + uses: actions/configure-pages@v6.0.0 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v5.0.0 + with: + path: '_site' + + deploy: + timeout-minutes: 20 + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5.0.1 diff --git a/.github/workflows/push-email-notify.yml b/.github/workflows/push-email-notify.yml index 7946625..6414b39 100644 --- a/.github/workflows/push-email-notify.yml +++ b/.github/workflows/push-email-notify.yml @@ -1,3 +1,4 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Dormant push-email notification. ARMED by setting the repo variable # PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled; @@ -11,7 +12,7 @@ name: Push email notification on: push: - # Branch pushes only: tag and deletion payloads mislabel Branch:/head_commit. + # Branch pushes only; the job condition separately excludes branch deletions. branches: ['**'] concurrency: # Deliberately per-RUN, so no run is ever queued behind another and none is @@ -27,23 +28,23 @@ concurrency: # is still a cap whereas a per-run group needs none. # Verified with zizmor 1.30.0: deleting this block raises concurrency-limits; # this form silences it exactly as a shared group would. - group: push-email-${{ github.run_id }} + group: push-email-${{ github.run_id }}-${{ github.run_attempt }} cancel-in-progress: false -permissions: - contents: read +permissions: {} jobs: notify: name: Email on push - if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' }} + if: ${{ vars.PUSH_EMAIL_ENABLED == 'true' && github.event.deleted != true }} runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Send push notification email - uses: hyperpolymath/smtp-notify-action@22e7bdb322c430c1d0dac6b3bb307f4bb139d0be # v0.3.0 + uses: hyperpolymath/smtp-notify-action@v0.3.0 # NOSONAR — pin authority is actions.lock (sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be) with: server_address: ${{ secrets.SMTP_HOST }} server_port: ${{ secrets.SMTP_PORT }} - secure: true + # Standard submission uses mandatory STARTTLS; other ports retain implicit TLS. + secure: ${{ secrets.SMTP_PORT == '587' && 'starttls' || 'implicit' }} username: ${{ secrets.SMTP_USER }} password: ${{ secrets.SMTP_PASS }} from: "GitHub Push <${{ secrets.SMTP_USER }}>" diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml new file mode 100644 index 0000000..c9c638c --- /dev/null +++ b/.github/workflows/quality.yml @@ -0,0 +1,79 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Code Quality +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + +permissions: + contents: read +jobs: + lint: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Check file permissions + run: | + find . -type f -perm /111 -name "*.sh" | head -10 || true + - name: ShellCheck (error severity) + run: | + # Error severity only: parse failures, unterminated find -exec, shebang + # not on line 1. Warnings are deliberately not gated — a gate that fires + # on style gets switched off, and these are the findings that break + # scripts at runtime. + # + # Vendored/upstream trees are excluded: patching a third party's test + # fixtures creates permanent divergence and conflicts on every sync. + fail=0 + while IFS= read -r f; do + case "/$f" in + */node_modules/*|*/vendor/*|*/third_party/*|*/rescript-ecosystem/*) continue ;; + esac + shellcheck -S error "$f" || fail=1 + done < <(git ls-files '*.sh' '*.bash') + if [ "$fail" -ne 0 ]; then + echo "::error::shellcheck reported error-severity findings (see above)" + exit 1 + fi + echo "✅ shellcheck: no error-severity findings" + + - name: Check TODO/FIXME + run: | + echo "=== TODOs ===" + grep -rn "TODO\|FIXME\|HACK\|XXX" --include="*.rs" --include="*.res" --include="*.py" --include="*.ex" . | head -20 || echo "None found" + - name: Check for large files + run: | + find . -type f -size +1M -not -path "./.git/*" | head -10 || echo "No large files" + - name: EditorConfig check + uses: editorconfig-checker/action-editorconfig-checker@v3.0.0 + continue-on-error: true + docs: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Check documentation + run: | + MISSING="" + [ ! -f "README.md" ] && [ ! -f "README.adoc" ] && MISSING="$MISSING README" + [ ! -f "LICENSE" ] && [ ! -f "LICENSE.txt" ] && [ ! -f "LICENSE.md" ] && MISSING="$MISSING LICENSE" + [ ! -f "CONTRIBUTING.md" ] && [ ! -f "CONTRIBUTING.adoc" ] && [ ! -f ".github/CONTRIBUTING.md" ] && [ ! -f ".github/CONTRIBUTING.adoc" ] && MISSING="$MISSING CONTRIBUTING" + + if [ -n "$MISSING" ]; then + echo "::warning::Missing docs:$MISSING" + else + echo "✅ Core documentation present" + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 19b73e1..b85b408 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,67 +1,100 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # # Release workflow — triggered by version tags (v*). # Builds artifacts, generates changelog via git-cliff, creates a GitHub Release, -# and produces SLSA provenance attestations. +# and produces GitHub native build-provenance attestations (OIDC + Sigstore). name: Release - on: push: tags: - 'v*' - +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false permissions: - actions: read contents: read - jobs: build: name: Build Artifacts runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - - name: Build + - uses: actions/checkout@v7.0.1 + - name: Detect project type and build + id: build run: | - echo "Build your artifacts here" - # TODO: Replace with your build commands - # Examples: - # cargo build --release - # zig build -Doptimize=ReleaseFast - # gleam build - # mix release - - # TODO: Upload build artifacts if needed - # - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - # with: - # name: release-artifacts - # path: target/release/ - + # Auto-detect build system from project files. + # Order matters: more specific markers checked first. + if [ -f "mix.exs" ]; then + echo "::notice::Detected Elixir/Gleam project (mix.exs)" + echo "build_type=mix" >> "$GITHUB_OUTPUT" + mix local.hex --force --if-missing + mix local.rebar --force --if-missing + mix deps.get --only prod + MIX_ENV=prod mix release + elif [ -f "Cargo.toml" ]; then + echo "::notice::Detected Rust project (Cargo.toml)" + echo "build_type=cargo" >> "$GITHUB_OUTPUT" + cargo build --release + elif [ -f "build.zig" ]; then + echo "::notice::Detected Zig project (build.zig)" + echo "build_type=zig" >> "$GITHUB_OUTPUT" + zig build -Doptimize=ReleaseSafe + elif [ -f "deno.json" ] || [ -f "deno.jsonc" ]; then + echo "::notice::Detected Deno project (deno.json)" + echo "build_type=deno" >> "$GITHUB_OUTPUT" + deno task build + elif [ -f "gossamer.conf.json" ]; then + echo "::notice::Detected Gossamer project (gossamer.conf.json)" + echo "build_type=gossamer" >> "$GITHUB_OUTPUT" + gossamer build + elif [ -f "gleam.toml" ]; then + echo "::notice::Detected Gleam project (gleam.toml)" + echo "build_type=gleam" >> "$GITHUB_OUTPUT" + gleam build + elif [ -f "rebar.config" ]; then + echo "::notice::Detected Erlang/Rebar project (rebar.config)" + echo "build_type=rebar" >> "$GITHUB_OUTPUT" + rebar3 as prod release + elif [ -f "Justfile" ] || [ -f "justfile" ]; then + echo "::notice::Detected Justfile — running 'just build'" + echo "build_type=just" >> "$GITHUB_OUTPUT" + just build + else + echo "::error::No recognised build system found." + 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 (pin actions/upload-artifact + # to a full commit SHA when enabling, per the SHA-pin policy): + # - uses: actions/upload-artifact@ # vX.Y.Z + # with: + # name: release-artifacts + # path: target/release/ changelog: name: Generate Changelog runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: read outputs: changelog: ${{ steps.cliff.outputs.content }} version: ${{ steps.version.outputs.version }} steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@v7.0.1 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: | @@ -73,35 +106,34 @@ 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@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + uses: actions/upload-artifact@v7.0.1 with: name: changelog path: CHANGELOG.md retention-days: 5 - release: name: Create GitHub Release needs: [build, changelog] runs-on: ubuntu-latest + timeout-minutes: 15 permissions: contents: write + id-token: write # mint the OIDC token attestation provenance is signed with + attestations: write # write the build-provenance attestation (the "claim") steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - - # TODO: Download build artifacts if uploading to the release - # - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 + - uses: actions/checkout@v7.0.1 + # TODO: Download build artifacts if uploading to the release (pin + # actions/download-artifact to a full commit SHA when enabling): + # - uses: actions/download-artifact@ # vX.Y.Z # with: # name: release-artifacts # path: artifacts/ - - name: Create GitHub Release - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v2 + uses: softprops/action-gh-release@v3.0.3 with: body: ${{ needs.changelog.outputs.changelog }} draft: false @@ -112,19 +144,16 @@ jobs: # artifacts/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - provenance: - name: SLSA Provenance - needs: [build] - permissions: - actions: read - id-token: write - contents: write - # SLSA generator must run in a separate, isolated workflow - # See: https://slsa.dev/spec/v1.0/requirements#build-l3 - uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@f7dd8c54c2067bafc12ca7a55595d5ee9b75204a # v2.1.0 - with: - base64-subjects: "" - # TODO: Replace with actual artifact hashes - # Generate with: sha256sum artifact | base64 -w0 - # base64-subjects: "${{ needs.build.outputs.hashes }}" + # GitHub native artifact attestation (build provenance). Generates a + # signed, verifiable claim binding each released artifact to this build + # (commit, workflow, runner) via OIDC + Sigstore — verify with + # `gh attest verify --repo ${{ github.repository }}`. + # Replaces the older SLSA-generator job; native attestations need no + # separate isolated workflow. + # TODO: point subject-path at the artifacts this release actually ships + # (must match the `files:` uploaded above, e.g. artifacts/*). + - name: Attest build provenance + if: ${{ hashFiles('artifacts/*') != '' }} # skip until real artifacts are wired + uses: actions/attest-build-provenance@v4.2.2 + with: + subject-path: 'artifacts/*' diff --git a/.github/workflows/rsr-compliance-canary.yml b/.github/workflows/rsr-compliance-canary.yml new file mode 100644 index 0000000..13e0a55 --- /dev/null +++ b/.github/workflows/rsr-compliance-canary.yml @@ -0,0 +1,95 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# rhodibot.yml — RSR compliance CANARY (report-only) +# +# Rhodibot does NOT mutate this repository. It never deletes, renames, +# rewrites SPDX headers, creates files, or opens PRs. Instead it DETECTS +# what an auto-fixer would have changed and reports it. +# +# Design intent (owner): if rhodibot "feels the desire to edit" — i.e. it +# detects something it considers non-compliant — that is itself a MAJOR +# WARNING. Either the repo has drifted, OR rhodibot's own rules have +# diverged from the normative style it is meant to enforce. Both warrant +# a human look, so the canary FAILS the run when it finds would-mutate +# drift. Dangerous-pattern hits are advisory warnings only. +# +# Licence note: SPDX/licence drift is reported for MANUAL, owner-only +# correction. Rhodibot must never edit a licence header (estate directive). + +name: "\U0001F916 Rhodibot — RSR Compliance Canary" +on: + schedule: + - cron: '0 6 * * 1' # Every Monday at 06:00 UTC + workflow_dispatch: # Manual trigger + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + canary: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 1 + - name: Rhodibot — detect drift (no mutations) + run: | + set -uo pipefail + DRIFT=0 + warn() { echo "::warning title=Rhodibot canary::$*"; DRIFT=$((DRIFT+1)); } + note() { echo "::warning title=Rhodibot advisory::$*"; } + + echo "## 🤖 Rhodibot canary — report only (no edits made)" >> "$GITHUB_STEP_SUMMARY" + + # --- would-DELETE: banned files --- + for f in AI.djot NEXT_STEPS.md TODO.md NOTES.md TASKS.md; do + [ -f "$f" ] && warn "banned file present: $f (an auto-fixer would delete it)" + done + # would-DELETE: stale snapshots + for f in *-STATUS-*.md *-COMPLETION-*.md *-COMPLETE.md *-VERIFIED-*.md; do + [ -f "$f" ] && warn "stale snapshot present: $f (would be deleted)" + done + # would-RENAME: legacy manifest name (a repo deed at root is the + # deed-era equivalent — standards#837) + if [ -f "AI.a2ml" ] && [ ! -f "0-AI-MANIFEST.a2ml" ] && ! ls *_chora.deed >/dev/null 2>&1; then + warn "AI.a2ml present without 0-AI-MANIFEST.a2ml or a repo deed (would be renamed)" + fi + # would-DELETE: duplicate community files + [ -f "CONTRIBUTING.md" ] && [ -f "CONTRIBUTING.adoc" ] && warn "duplicate CONTRIBUTING.md + CONTRIBUTING.adoc (one would be removed)" + if [ -f "README.md" ] && [ -f "README.adoc" ] && [ "$(wc -l < README.md)" -lt 5 ]; then + warn "stub README.md alongside README.adoc (would be removed)" + fi + # SPDX drift — MANUAL owner-only fix, never auto-edited + for dotfile in .gitignore .gitattributes .editorconfig; do + if [ -f "$dotfile" ] && grep "AGPL-3.0" "$dotfile" 2>/dev/null | grep -v "AGPL-3.0-or-later" | grep -q .; then + warn "$dotfile carries an AGPL-3.0 SPDX header; estate policy is MPL-2.0 — fix MANUALLY (owner-only, never auto-edited)" + fi + done + # would-CREATE: missing required files + [ -f "SECURITY.md" ] || [ -f ".github/SECURITY.md" ] || warn "no SECURITY.md (would be created)" + [ -f "CONTRIBUTING.md" ] || [ -f ".github/CONTRIBUTING.md" ] || warn "no CONTRIBUTING.md (would be created)" + + # --- unfixable compliance gaps (also drift) --- + [ -f "0-AI-MANIFEST.a2ml" ] || [ -f "AI.a2ml" ] || ls *_chora.deed >/dev/null 2>&1 || warn "missing AI manifest (0-AI-MANIFEST.a2ml or repo deed *_chora.deed)" + [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ] || warn "missing LICENSE file" + [ -f "README.adoc" ] || [ -f "README.md" ] || warn "missing README" + + # --- advisory only: dangerous verification-bypass patterns --- + for pattern in believe_me assert_total Admitted sorry unsafeCoerce Obj.magic; do + count=$(grep -rl "$pattern" --include='*.idr' --include='*.v' --include='*.lean' --include='*.hs' --include='*.ml' --include='*.res' . 2>/dev/null | grep -v node_modules | wc -l || true) + [ "$count" -gt 0 ] && note "verification-bypass pattern '$pattern' in $count file(s) (advisory)" + done + + echo "" >> "$GITHUB_STEP_SUMMARY" + if [ "$DRIFT" -gt 0 ]; then + echo "🔴 **Canary tripped: $DRIFT would-mutate finding(s).** Either the repo drifted or rhodibot's rules diverged from the norm — investigate (no edits were made)." >> "$GITHUB_STEP_SUMMARY" + echo "::error title=Rhodibot canary::$DRIFT would-mutate finding(s) detected — rhodibot wants to edit. Investigate; nothing was changed." + exit 1 + fi + echo "✅ Canary clean — rhodibot has no desire to edit. Repository matches the norm." >> "$GITHUB_STEP_SUMMARY" + echo "✅ Rhodibot canary clean — no drift, no mutations." diff --git a/.github/workflows/runtime-policy.yml b/.github/workflows/runtime-policy.yml new file mode 100644 index 0000000..51ec04f --- /dev/null +++ b/.github/workflows/runtime-policy.yml @@ -0,0 +1,72 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Runtime and package-manager policy check. +# +# Authority: hyperpolymath/standards LANGUAGE-POLICY.adoc §1. +# Ordering: Bun (1st) > Deno (2nd) > pnpm (3rd) > npm (last resort). +# +# REPLACES npm-bun-blocker.yml, which failed any build carrying `bun.lockb` with +# the message "npm/bun artifacts detected. Use Deno instead." That gate blocked +# what is now the FIRST-choice runtime and mandated the second. It was present in +# 55 repositories. +# +# What this fails on, deliberately: +# MIXED TOOLCHAINS -- two different package managers' lockfiles in one repo. +# That is real, actionable drift: two dependency graphs that can disagree. +# What it does NOT fail on: +# Using bun, pnpm or npm. Deno is RETIRED (2026-08-26). npm is LAST but PERMITTED; the check reports +# the tier in use so drift is visible without blocking legitimate work. +name: Runtime Policy +on: + push: + branches: [main, master] + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + runtime-policy: + name: Runtime Policy + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + + - name: Report runtime tier and reject mixed toolchains + run: | + set -euo pipefail + + bun=0; deno=0; pnpm=0; npm=0 + [ -f bun.lockb ] || [ -f bun.lock ] && bun=1 || true + [ -f deno.lock ] || [ -f deno.json ] || [ -f deno.jsonc ] && deno=1 || true + [ -f pnpm-lock.yaml ] && pnpm=1 || true + [ -f package-lock.json ] && npm=1 || true + + total=$((bun + deno + pnpm + npm)) + + if [ "$total" -eq 0 ]; then + echo "::notice::No JS/TS package manager in use — nothing to check." + exit 0 + fi + + # Report the tier actually in use (LANGUAGE-POLICY.adoc §1). + [ "$bun" -eq 1 ] && echo "Bun — tier 1 (preferred)" + [ "$deno" -eq 1 ] && echo "::warning::Deno lockfile/config present. Deno is BEING REMOVED, not grandfathered (owner ruling 2026-08-26, canon standards#655). Migrate to Bun — package.json + bun.lock. This is a warning rather than an error only while the estate migration is in flight; it escalates to an error when that completes." + [ "$pnpm" -eq 1 ] && echo "pnpm — tier 3" + [ "$npm" -eq 1 ] && echo "::warning::npm lockfile present. npm is tier 4, the last resort — permitted, never preferred. See LANGUAGE-POLICY.adoc §1." + + if [ "$total" -gt 1 ]; then + echo "::error::Mixed toolchains: $total package managers have lockfiles in this repository." + echo "Two dependency graphs that can disagree is real drift. Pick one — preferring the" + echo "highest tier present — and delete the others' lockfiles." + exit 1 + fi + + echo "✅ Single package manager in use." diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml new file mode 100644 index 0000000..05369dd --- /dev/null +++ b/.github/workflows/rust-ci.yml @@ -0,0 +1,19 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# Rust CI — thin wrapper calling the shared estate reusable in +# hyperpolymath/standards. Configure once, propagate everywhere. +# See: docs/CI-REUSABLE-WORKFLOWS.adoc in standards. +name: Rust CI +on: + push: + branches: [main, master] + pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + actions: read + contents: read +jobs: + rust-ci: + uses: hyperpolymath/standards/.github/workflows/rust-ci-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index bfb0fcb..f2ce703 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,22 +1,26 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 -name: Scorecards supply-chain security +name: OSSF Scorecard on: - branch_protection_rule: schedule: - - cron: '23 4 * * 1' + - cron: '0 4 * * *' + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true permissions: + actions: read # required by the reusable workflow (staleness check reads workflow runs) contents: read - security-events: write - id-token: write - actions: read jobs: - analysis: - uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c + scorecard: + uses: hyperpolymath/standards/.github/workflows/scorecard-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 + # Reusable called-workflow permissions are CAPPED by the caller's grants; + # without security-events: write here the scorecard SARIF upload fails with + # startup_failure (hypatia WF018). id-token: write enables OIDC publish. permissions: contents: read security-events: write id-token: write - secrets: inherit diff --git a/.github/workflows/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 23f0444..9a7b136 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -1,22 +1,29 @@ +# This workflow is managed by gh actions-lock. # SPDX-License-Identifier: MPL-2.0 name: Secret Scanner - on: pull_request: push: branches: [main] - concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true - permissions: actions: read contents: read - +# Single secret scanner. The standards reusable runs gitleaks (+ a Rust-secrets +# check). An inline TruffleHog job previously lived here, but the reusable +# DELIBERATELY retired TruffleHog as redundant (gitleaks gives sufficient +# coverage at lower cost — see the reusable's header). Re-adding it was +# duplicated work, not extra coverage, so it has been removed. See +# .github/workflows/README.adoc. jobs: scan: + # The reusable installs and executes a pinned gitleaks binary directly. + # Since standards#500 it neither comments on PRs nor reads workflow-run + # metadata, so contents: read is the complete permission contract. Keep + # this job-level block explicit: it replaces the workflow-level grant and + # is therefore the cap GitHub applies to the called workflow. permissions: contents: read - uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@8f2ee50841e216cd8c192eeb68953118190f105c - secrets: inherit + uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@da2c748aad55c1a1dcba00b60fe4a35017bc6540 diff --git a/.github/workflows/security-policy.yml b/.github/workflows/security-policy.yml new file mode 100644 index 0000000..3c64369 --- /dev/null +++ b/.github/workflows/security-policy.yml @@ -0,0 +1,54 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +name: Security Policy +on: + push: + branches: [main, master] + pull_request: + +# Estate guardrail: scope push to default branches so a PR fires once (not +# push+PR), and cancel superseded runs. Safe — read-only PR-triggered check. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + steps: + - uses: actions/checkout@v7.0.1 + - name: Security checks + run: | + FAILED=false + + # Block MD5/SHA1 for security (allow for checksums/caching) + WEAK_CRYPTO=$(grep -rE 'md5\(|sha1\(' --include="*.py" --include="*.rb" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" . 2>/dev/null | grep -v 'checksum\|cache\|test\|spec' | head -5 || true) + if [ -n "$WEAK_CRYPTO" ]; then + echo "⚠️ Weak crypto (MD5/SHA1) detected. Use SHA256+ for security:" + echo "$WEAK_CRYPTO" + fi + + # Block HTTP URLs (except localhost) + HTTP_URLS=$(grep -rE 'http://[^l][^o][^c]' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.yaml" --include="*.yml" . 2>/dev/null | grep -v 'localhost\|127.0.0.1\|example\|test\|spec' | head -5 || true) + if [ -n "$HTTP_URLS" ]; then + echo "⚠️ HTTP URLs found. Use HTTPS:" + echo "$HTTP_URLS" + fi + + # Block hardcoded secrets patterns + SECRETS=$(grep -rEi '(api_key|apikey|secret_key|password)\s*[=:]\s*["\x27][A-Za-z0-9+/=]{20,}' --include="*.py" --include="*.js" --include="*.ts" --include="*.go" --include="*.rs" --include="*.env" . 2>/dev/null | grep -v 'example\|sample\|test\|mock\|placeholder' | head -3 || true) + if [ -n "$SECRETS" ]; then + echo "❌ Potential hardcoded secrets detected!" + FAILED=true + fi + + if [ "$FAILED" = true ]; then + exit 1 + fi + + echo "✅ Security policy check passed" diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml new file mode 100644 index 0000000..1e42137 --- /dev/null +++ b/.github/workflows/sonarqube.yml @@ -0,0 +1,69 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# SonarQube Cloud (SonarCloud) static analysis. Analysis scope + exclusions live +# in sonar-project.properties. Requires the SONAR_TOKEN repository secret +# (Settings -> Secrets and variables -> Actions) and a SonarCloud project: +# https://sonarcloud.io/project/overview?id=hyperpolymath_dicta-task +# Mirrors the boj-server arrangement. +# +# WHY THE TOKEN GUARD (added 2026-07-28) +# -------------------------------------- +# This repo has NO Actions secrets configured at all, so `secrets.SONAR_TOKEN` +# expanded to the empty string and the scanner failed every run with +# "Not authorized or project not found" -- a permanent red that blocked merges +# through the ruleset's code_quality rule. It was never a code-quality signal: +# the scan never ran. +# +# SonarCloud's AUTOMATIC analysis is separately enabled here and does report +# (check name "SonarCloud Code Analysis"), so no coverage is lost by skipping. +# +# The guard makes the state honest rather than red: with no token the job +# reports that it is unconfigured and exits 0; the moment a SONAR_TOKEN secret +# is added it runs for real, with no further edit needed. Deleting the workflow +# would have discarded a correct configuration over a missing secret. +name: SonarQube +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + sonarqube: + name: SonarQube + runs-on: ubuntu-latest + timeout-minutes: 15 + # Secrets cannot be referenced from a job-level `if:`, so the presence test + # is carried through an env var set at job scope and read in a first step. + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + steps: + - name: Report configuration state + id: cfg + run: | + if [ -z "${SONAR_TOKEN}" ]; then + echo "configured=false" >> "$GITHUB_OUTPUT" + echo "::notice::SONAR_TOKEN is not configured for this repository - skipping the CI-based scan." + echo "SonarCloud automatic analysis (check: 'SonarCloud Code Analysis') is unaffected." + echo "To enable this scan: Settings > Secrets and variables > Actions > new secret SONAR_TOKEN." + else + echo "configured=true" >> "$GITHUB_OUTPUT" + echo "SONAR_TOKEN present - running the CI-based scan." + fi + + - name: Checkout + if: steps.cfg.outputs.configured == 'true' + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 # full history for accurate new-code detection + + - name: SonarQube Scan + if: steps.cfg.outputs.configured == 'true' + uses: SonarSource/sonarqube-scan-action@v8.2.2 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml new file mode 100644 index 0000000..62134f3 --- /dev/null +++ b/.github/workflows/static-analysis-gate.yml @@ -0,0 +1,454 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# 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] +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read +jobs: + # --------------------------------------------------------------------------- + # Job 1: panic-attack assail + # --------------------------------------------------------------------------- + panic-attack-assail: + name: panic-attack assail + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Install panic-attack (if available) + id: install + run: | + # Try to fetch the latest release binary from the org + PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" + if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then + curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" + chmod +x /usr/local/bin/panic-attack + echo "installed=true" >> "$GITHUB_OUTPUT" + else + 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' + run: | + set +e + panic-attack assail --format json . > panic-attack-findings.json + PA_EXIT=$? + set -e + + # Same defect class as the Hypatia job below: `2>&1` folded the + # scanner's stderr into the JSON payload, so every jq parse failed, + # every count silently became 0 via `|| echo 0`, and "Fail on critical + # findings" could never fire on any input. Keep stderr on the log. + if [ ! -s panic-attack-findings.json ]; then + echo "[]" > panic-attack-findings.json + fi + + # Deliberately a WARNING, not a failure. panic-attack is a downloaded + # release binary whose exit-code and output contract are not verified + # here, and it has no confirmed --exit-zero equivalent, so we surface a + # malformed payload in the log rather than block on an unverified tool. + # Promote to `exit 1` (as the Hypatia job does) once that contract is + # confirmed -- see the follow-up issue linked from this PR. + if ! jq -e 'type == "array"' panic-attack-findings.json >/dev/null 2>&1; then + echo "::warning::panic-attack output is not a JSON array (exit ${PA_EXIT}); counts below are unreliable" + fi + + # Parse finding counts + TOTAL=$(jq '. | length' panic-attack-findings.json 2>/dev/null || echo 0) + CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + HIGH=$(jq '[.[] | select(.severity == "high")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + LOW=$(jq '[.[] | select(.severity == "low")] | length' panic-attack-findings.json 2>/dev/null || echo 0) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + echo "high=$HIGH" >> "$GITHUB_OUTPUT" + 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: | + # Convert JSON findings into GitHub Actions annotations + # Findings carry no `.message` (keys: action,file,line,reason,rule_module, + # severity,type), so every annotation read "null". `.file` is an absolute + # runner path, which GitHub cannot anchor to the diff, so it is made + # workspace-relative here. + jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | + (.file | ltrimstr($ws + "/")) as $f | + (.reason // .message // .type // "finding") as $m | + if .severity == "critical" then + "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + elif .severity == "high" then + "::error file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + else + "::warning file=\($f),line=\(.line // 1)::[panic-attack] \($m)" + end + ' panic-attack-findings.json || true + - name: Write step summary + if: steps.install.outputs.installed == 'true' + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## panic-attack assail Results + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.assail.outputs.critical }} | + | High | ${{ steps.assail.outputs.high }} | + | Medium | ${{ steps.assail.outputs.medium }} | + | 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: | + echo "[]" > panic-attack-findings.json + 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@v7.0.1 + 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 + # + # NOTE — this is NOT a duplicate of the standalone `hypatia-scan.yml`. + # * THIS job runs Hypatia inside the gate and hands its findings to the + # `deposit-findings` job below, which feeds the gitbot-fleet LEARNING + # pipeline (artifact -> fleet). It is a REQUIRED status check. + # * `hypatia-scan.yml` runs Hypatia standalone as the repo's security scan, + # independent of the fleet-learning deposit. + # Same tool, two different consumers — keep both. See .github/workflows/README.adoc. + # --------------------------------------------------------------------------- + hypatia-scan: + name: Hypatia neurosymbolic scan + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Setup Elixir for Hypatia scanner + id: beam + continue-on-error: true + uses: erlef/setup-beam@v1.24.1 + with: + elixir-version: '1.19.4' + otp-version: '28.3' + - name: Clone and build Hypatia + id: build + continue-on-error: true + run: | + git clone https://github.com/hyperpolymath/hypatia.git "$HOME/hypatia" 2>/dev/null || true + if [ -f "$HOME/hypatia/mix.exs" ]; then + cd "$HOME/hypatia" + # Build escript if neither hypatia nor hypatia-v2 exists + if [ ! -f hypatia ] && [ ! -f hypatia-v2 ]; then + mix deps.get + mix escript.build + fi + echo "ready=true" >> "$GITHUB_OUTPUT" + else + 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' + run: | + set +e + HYPATIA_FORMAT=json "$HOME/hypatia/hypatia-cli.sh" scan . --exit-zero > hypatia-findings.json + HYP_EXIT=$? + set -e + + # --exit-zero is Hypatia's own documented CI recipe (lib/hypatia/cli.ex), + # for exactly this case: "use in CI when a downstream step gates on + # severity counts". Findings go to stdout, the one-line summary to + # stderr, and the process exits 0 unless the SCANNER itself failed. + # + # Do NOT redirect stderr into the payload with `2>&1`: that folds the + # summary line into the JSON, so every parse fails, the old `[]` + # fallback substituted a clean result, CRITICAL was always 0, and the + # gate below could never fire on any input. Keep stderr on the log. + if [ "$HYP_EXIT" -ne 0 ]; then + echo "::error::Hypatia scanner execution failed with exit ${HYP_EXIT}" + exit "$HYP_EXIT" + fi + # `jq empty` is NOT sufficient -- it succeeds on any valid JSON, + # including a bare string, object or null. Assert the array. + if [ ! -s hypatia-findings.json ] || ! jq -e 'type == "array"' hypatia-findings.json >/dev/null; then + echo "::error::Hypatia did not produce a valid JSON findings array" + exit 1 + fi + + TOTAL=$(jq '. | length' hypatia-findings.json 2>/dev/null || echo 0) + CRITICAL=$(jq '[.[] | select(.severity == "critical")] | length' hypatia-findings.json 2>/dev/null || echo 0) + HIGH=$(jq '[.[] | select(.severity == "high")] | length' hypatia-findings.json 2>/dev/null || echo 0) + MEDIUM=$(jq '[.[] | select(.severity == "medium" or .severity == "warn")] | length' hypatia-findings.json 2>/dev/null || echo 0) + LOW=$(jq '[.[] | select(.severity == "low")] | length' hypatia-findings.json 2>/dev/null || echo 0) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + 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: | + # Findings carry no `.message` (keys: action,file,line,reason,rule_module, + # severity,type), so every annotation read "null". `.file` is an absolute + # runner path, which GitHub cannot anchor to the diff, so it is made + # workspace-relative here. + jq -r --arg ws "$GITHUB_WORKSPACE" '.[] | select(.file != null) | + (.file | ltrimstr($ws + "/")) as $f | + (.reason // .message // .type // "finding") as $m | + if .severity == "critical" then + "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" + elif .severity == "high" then + "::error file=\($f),line=\(.line // 1)::[hypatia] \($m)" + else + "::warning file=\($f),line=\(.line // 1)::[hypatia] \($m)" + end + ' hypatia-findings.json || true + - name: Write step summary + if: steps.build.outputs.ready == 'true' + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Hypatia Scan Results + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.scan.outputs.critical }} | + | High | ${{ steps.scan.outputs.high }} | + | Medium | ${{ steps.scan.outputs.medium }} | + | 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: | + echo "[]" > hypatia-findings.json + 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@v7.0.1 + 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) + # --------------------------------------------------------------------------- + patch-bridge-triage: + name: Patch Bridge CVE triage + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout repository + uses: actions/checkout@v7.0.1 + with: + fetch-depth: 0 + - name: Install panic-attack (if available) + id: install + run: | + PA_URL="https://github.com/hyperpolymath/panic-attack/releases/latest/download/panic-attack-linux-x86_64" + if curl -fsSL --head "$PA_URL" >/dev/null 2>&1; then + curl -fsSL -o /usr/local/bin/panic-attack "$PA_URL" + chmod +x /usr/local/bin/panic-attack + echo "installed=true" >> "$GITHUB_OUTPUT" + else + 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' + run: | + set +e + panic-attack bridge triage --format json . > bridge-report.json 2>&1 + PB_EXIT=$? + set -e + + if [ ! -s bridge-report.json ] || ! jq empty bridge-report.json 2>/dev/null; then + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json + fi + + UNMITIGABLE=$(jq '.unmitigable // 0' bridge-report.json) + MITIGATED=$(jq '.mitigated // 0' bridge-report.json) + CONCATENATIVE=$(jq '.concatenative // 0' bridge-report.json) + INFORMATIONAL=$(jq '.informational // 0' bridge-report.json) + + echo "unmitigable=$UNMITIGABLE" >> "$GITHUB_OUTPUT" + 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: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Patch Bridge CVE Triage + + | Classification | Count | + |----------------|-------| + | Unmitigable | ${{ steps.triage.outputs.unmitigable }} | + | Mitigated | ${{ steps.triage.outputs.mitigated }} | + | Concatenative | ${{ steps.triage.outputs.concatenative }} | + | Informational | ${{ steps.triage.outputs.informational }} | + + Unmitigable CVEs require dependency replacement or rearchitecture. + 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: | + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > bridge-report.json + 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@v7.0.1 + 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) + # --------------------------------------------------------------------------- + deposit-findings: + name: Deposit findings for gitbot-fleet + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [panic-attack-assail, hypatia-scan, patch-bridge-triage] + if: always() + steps: + - name: Download panic-attack findings + uses: actions/download-artifact@v8.0.1 + with: + name: panic-attack-findings + path: findings/ + - name: Download hypatia findings + uses: actions/download-artifact@v8.0.1 + with: + name: hypatia-findings + path: findings/ + - name: Download bridge report + uses: actions/download-artifact@v8.0.1 + with: + name: bridge-report + path: findings/ + - name: Combine findings into unified report + id: combine + run: | + PA_FILE="findings/panic-attack-findings.json" + HYP_FILE="findings/hypatia-findings.json" + + # Ensure both files exist and are valid JSON arrays + for f in "$PA_FILE" "$HYP_FILE"; do + if [ ! -s "$f" ] || ! jq empty "$f" 2>/dev/null; then + echo "[]" > "$f" + fi + done + + # Tag each finding with its source scanner + jq '[.[] | . + {"scanner": "panic-attack"}]' "$PA_FILE" > /tmp/pa-tagged.json + jq '[.[] | . + {"scanner": "hypatia"}]' "$HYP_FILE" > /tmp/hyp-tagged.json + + # Read bridge report (CVE triage, not findings array) + BRIDGE_FILE="findings/bridge-report.json" + if [ ! -s "$BRIDGE_FILE" ] || ! jq empty "$BRIDGE_FILE" 2>/dev/null; then + echo '{"cves":[],"mitigated":0,"unmitigable":0,"concatenative":0,"informational":0}' > "$BRIDGE_FILE" + fi + + # Build unified report envelope + jq -n \ + --arg repo "${{ github.repository }}" \ + --arg sha "${{ github.sha }}" \ + --arg ref "${{ github.ref }}" \ + --arg run_id "${{ github.run_id }}" \ + --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ + --slurpfile pa /tmp/pa-tagged.json \ + --slurpfile hyp /tmp/hyp-tagged.json \ + --slurpfile bridge "$BRIDGE_FILE" \ + '{ + schema_version: "1.1.0", + repository: $repo, + commit_sha: $sha, + ref: $ref, + run_id: $run_id, + timestamp: $ts, + findings: ($pa[0] + $hyp[0]), + patch_bridge: $bridge[0] + }' > findings/unified-findings.json + + TOTAL=$(jq '.findings | length' findings/unified-findings.json) + CRITICAL=$(jq '[.findings[] | select(.severity == "critical")] | length' findings/unified-findings.json) + HIGH=$(jq '[.findings[] | select(.severity == "high")] | length' findings/unified-findings.json) + MEDIUM=$(jq '[.findings[] | select(.severity == "medium" or .severity == "warn")] | length' findings/unified-findings.json) + LOW=$(jq '[.findings[] | select(.severity == "low")] | length' findings/unified-findings.json) + + echo "total=$TOTAL" >> "$GITHUB_OUTPUT" + echo "critical=$CRITICAL" >> "$GITHUB_OUTPUT" + 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@v7.0.1 + with: + name: unified-findings + path: findings/unified-findings.json + retention-days: 90 + - name: Write deposit summary + run: | + cat <> "$GITHUB_STEP_SUMMARY" + ## Unified Findings Deposit + + **Repository:** ${{ github.repository }} + **Commit:** \`${{ github.sha }}\` + **Deposited at:** $(date -u +"%Y-%m-%d %H:%M:%S UTC") + + | Severity | Count | + |----------|-------| + | Critical | ${{ steps.combine.outputs.critical }} | + | High | ${{ steps.combine.outputs.high }} | + | Medium | ${{ steps.combine.outputs.medium }} | + | Low | ${{ steps.combine.outputs.low }} | + | **Total**| ${{ steps.combine.outputs.total }} | + + Findings saved as \`unified-findings\` artifact. + The gitbot-fleet scanner will ingest these on its next pass. + EOF diff --git a/.github/workflows/workflow-linter.yml b/.github/workflows/workflow-linter.yml new file mode 100644 index 0000000..35dfd9d --- /dev/null +++ b/.github/workflows/workflow-linter.yml @@ -0,0 +1,178 @@ +# This workflow is managed by gh actions-lock. +# SPDX-License-Identifier: MPL-2.0 +# workflow-linter.yml - Validates GitHub workflows against RSR security standards +# This workflow can be copied to other repos for consistent enforcement +name: Workflow Security Linter + +on: + push: + paths: + - '.github/workflows/**' + pull_request: + paths: + - '.github/workflows/**' + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +permissions: + contents: read + +jobs: + lint-workflows: + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v7.0.1 + + - name: Check SPDX Headers + run: | + echo "=== Checking SPDX License Headers ===" + failed=0 + for file in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$file" ] || continue + # actions-lock may prepend its own comment. Require the licence + # in the leading comment block, before the workflow body. + if ! awk '/^# SPDX-License-Identifier:/ { found=1 } /^[^#[:space:]]/ { exit } END { exit !found }' "$file"; then + echo "ERROR: $file missing SPDX header" + failed=1 + fi + done + if [ $failed -eq 1 ]; then + echo "Add '# SPDX-License-Identifier: MPL-2.0' to the leading comment block" + exit 1 + fi + echo "All workflows have SPDX headers" + + - name: Check Permissions Declaration + run: | + echo "=== Checking Permissions ===" + failed=0 + for file in .github/workflows/*.yml .github/workflows/*.yaml; do + [ -f "$file" ] || continue + if ! grep -q "^permissions:" "$file"; then + echo "ERROR: $file missing top-level 'permissions:' declaration" + failed=1 + fi + done + if [ $failed -eq 1 ]; then + echo "Add a top-level 'permissions:' block (e.g. contents: read)" + exit 1 + fi + echo "All workflows have permissions declared" + + # Bun is the estate's first-choice runtime (LANGUAGE-POLICY.adoc §1: + # Bun > Deno > pnpm > npm) and executes .ts directly (§1.2). Tag form + # matches every other ref in this repo and is resolved by actions.lock. + - name: Set up Bun + uses: oven-sh/setup-bun@v2.2.0 + with: + bun-version: latest + + - name: Check SHA-Pinned Actions + run: | + echo "=== Checking Action Pinning ===" + # Find any uses: lines that don't have @SHA format + # Pattern: uses: owner/repo@<40-char-hex> + # Delegated to scripts/check-action-pinning.js. The rule it enforces is + # unchanged in spirit — every action ref must resolve to an immutable + # commit — but "pinned" now includes refs the workflow lockfile + # resolves, which is how this repo pins them. Kept as a script rather + # than inline because the inline form needs a heredoc inside a YAML + # block scalar, and that is a well-known way to ship a gate that + # silently does nothing. + if ! bun scripts/check-action-pinning.js; then + unpinned="see above" + else + unpinned="" + fi + + if [ -n "$unpinned" ]; then + echo "ERROR: Found unpinned actions:" + echo "" + echo "Replace version tags with SHA pins, e.g.:" + echo " uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6.0.1" + exit 1 + fi + echo "All actions are SHA-pinned" + + - name: Check for Duplicate Workflows + run: | + echo "=== Checking for Duplicates ===" + # Known duplicate patterns + if [ -f .github/workflows/codeql.yml ] && [ -f .github/workflows/codeql-analysis.yml ]; then + echo "ERROR: Duplicate CodeQL workflows found" + echo "Delete codeql-analysis.yml (keep codeql.yml)" + exit 1 + fi + if [ -f .github/workflows/rust.yml ] && [ -f .github/workflows/rust-ci.yml ]; then + echo "WARNING: Potential duplicate Rust workflows" + echo "Consider consolidating rust.yml and rust-ci.yml" + fi + echo "No critical duplicates found" + + - name: Check CodeQL Language Matrix + run: | + echo "=== Checking CodeQL Configuration ===" + if [ ! -f .github/workflows/codeql.yml ]; then + echo "No CodeQL workflow found (optional)" + exit 0 + fi + + # Detect repo languages + has_js=$(find . -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) + has_py=$(find . -name "*.py" -path "*/src/*" -o -path "*/lib/*" 2>/dev/null | head -1) + has_go=$(find . -name "*.go" -path "*/src/*" -o -path "*/cmd/*" -o -path "*/pkg/*" 2>/dev/null | head -1) + has_rs=$(find . -name "*.rs" -path "*/src/*" 2>/dev/null | head -1) + has_java=$(find . -name "*.java" -path "*/src/*" 2>/dev/null | head -1) + has_rb=$(find . -name "*.rb" -path "*/lib/*" -o -path "*/app/*" 2>/dev/null | head -1) + + echo "Detected languages:" + [ -n "$has_py" ] && echo " - python" + [ -n "$has_go" ] && echo " - go" + [ -n "$has_rs" ] && echo " - rust (note: CodeQL rust is limited)" + [ -n "$has_java" ] && echo " - java-kotlin" + [ -n "$has_rb" ] && echo " - ruby" + + # Check for over-reach + if grep -q "language:.*'go'" .github/workflows/codeql.yml && [ -z "$has_go" ]; then + echo "WARNING: CodeQL configured for Go but no Go files found" + fi + if grep -q "language:.*'python'" .github/workflows/codeql.yml && [ -z "$has_py" ]; then + echo "WARNING: CodeQL configured for Python but no Python files found" + fi + if grep -q "language:.*'java'" .github/workflows/codeql.yml && [ -z "$has_java" ]; then + echo "WARNING: CodeQL configured for Java but no Java files found" + fi + if grep -q "language:.*'ruby'" .github/workflows/codeql.yml && [ -z "$has_rb" ]; then + echo "WARNING: CodeQL configured for Ruby but no Ruby files found" + fi + + echo "CodeQL check complete" + + - name: Check Secrets Guards + run: | + echo "=== Checking Secrets Usage ===" + # Look for secrets without conditional guards in mirror workflows + if [ -f .github/workflows/mirror.yml ]; then + if grep -q "secrets\." .github/workflows/mirror.yml; then + if ! grep -q "if:.*vars\." .github/workflows/mirror.yml; then + echo "WARNING: mirror.yml uses secrets without vars guard" + echo "Add 'if: vars.FEATURE_ENABLED == true' to jobs" + fi + fi + fi + echo "Secrets check complete" + + - name: Summary + run: | + echo "" + echo "=== Workflow Linter Summary ===" + echo "All critical checks passed." + echo "" + echo "For more info, see: robot-repo-bot/ERROR-CATALOG.scm" diff --git a/.gitignore b/.gitignore index 9d54af1..316e08d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,15 +11,17 @@ Thumbs.db .vscode/ .direnv/ +# Agent / local session artifacts (Claude Code worktrees, scratch) — never commit +.claude/ + # Build -/target/ -/_build/ -/build/ +target/ +_build/ /dist/ /out/ # Dependencies -/node_modules/ +node_modules/ /vendor/ /deps/ /.elixir_ls/ @@ -38,29 +40,18 @@ erl_crash.dump *.jl.mem /Manifest.toml -# ReScript +# /lib/bs/ /.bsb.lock -# Python (SaltStack only) -__pycache__/ -*.py[cod] -.venv/ - # Ada/SPARK *.ali /obj/ /bin/ -# Nix -# flake.lock is ignored in the template repo because each project should -# generate its own lock file on first use. In derived projects, REMOVE this -# line and track flake.lock for reproducible builds. -flake.lock - # Haskell /.stack-work/ -/dist-newstyle/ +dist-newstyle/ # Chapel *.chpl.tmp.* @@ -84,6 +75,9 @@ htmlcov/ .maintenance-perms-state.tsv docs/reports/maintenance/*.json +# Machine-readable locks +.machine_readable/.locks/ + # Temp /tmp/ *.tmp @@ -100,3 +94,60 @@ sync_report*.txt # Hypatia scan cache (local-only) .hypatia/ +.zig-cache/ +target/ +node_modules/ +_build/ +deps/ +.elixir_ls/ +.cache/ +build/ +dist/ + +# /build/ is a tracked config directory (build orchestration: contractile.just, +# guix.scm, just/*.just, etc.) introduced in chore/root-cleanup. Whitelist +# root-level /build/ so its contents are tracked. The blanket `build/` rule +# above still ignores any nested `build/` directories (e.g. Rust crate +# target/build/) — only the root-level path is exempt. +!/build/ +!/build/** + +# ...but never track Idris2 typecheck output. `idris2 --typecheck src/interface/abi.ipkg` +# writes compiled .ttc/.ttm under build/ttc/; these are generated artifacts. +/build/ttc/ + +# Arrival-pack build artifact (regenerated from deed) +.machine_readable/arrival-pack/claude-md-data.json + +# Coaptation atomiser artifacts (regenerated from contractiles + descriptiles); +# the receipt itself (receipts/latest.a2ml) IS committed as the drift baseline. +.machine_readable/coaptation/clauses.json +.machine_readable/coaptation/facts.json + +# Coaptation Idris2 core typecheck artifacts +.machine_readable/coaptation/core/build/ + +# Coaptation re-anchor basis (occasional, generated on --reanchor when red; not a baseline) +.machine_readable/coaptation/receipts/reanchor-basis.a2ml + +# Generated by `just cookbook` (has timestamp; non-deterministic) +docs/just-cookbook.adoc + +# Generated man pages (`just man`) +docs/man/*.1 +# Coq compiled proof artifacts +verification/proofs/coq/*.vo +verification/proofs/coq/*.vok +verification/proofs/coq/*.vos +verification/proofs/coq/*.glob +verification/proofs/coq/.*.aux +# Agda compiled proof artifacts +verification/proofs/agda/*.agdai + + +# dicta-task runtime data (never commit task stores or recordings) +*.sqlite +*.sqlite3 +*.db-wal +*.db-shm +/data/ diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 0000000..76765a3 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,4 @@ +874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CHORA.deed.in:generic-api-key:21 +8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CHORA.deed.in:generic-api-key:21 +874cfd89ae9e1567275202e829a187d5d2e465c3:build/templates/CLADE.a2ml.in:generic-api-key:21 +8f8cc39824c23b1badc8cc04862755fcb2c6f8d5:build/templates/CLADE.a2ml.in:generic-api-key:21 diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 0000000..ef6021d --- /dev/null +++ b/.gitmessage @@ -0,0 +1,18 @@ +# (): (Max 50 chars) +# |<------------------------------------------------>| + +# Explain WHY this change is being made (Max 72 chars per line) +# |<---------------------------------------------------------------------->| + +# Explain HOW this change was implemented (if not obvious) + +# [ ] Tests added/updated +# [ ] Documentation updated +# [ ] ABI/FFI boundaries verified (if applicable) + +# Issue tracking: +# Resolves: # +# See also: # +# +# --- +# Allowed Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert diff --git a/.guix-channel b/.guix-channel deleted file mode 100644 index 783f593..0000000 --- a/.guix-channel +++ /dev/null @@ -1,22 +0,0 @@ -;; SPDX-License-Identifier: MPL-2.0 -;; Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> -;; -;; Guix channel definition for {{PROJECT_NAME}} -;; -;; To use this channel, add to ~/.config/guix/channels.scm: -;; -;; (channel -;; (name '{{PROJECT_NAME}}) -;; (url "https://github.com/hyperpolymath/{{PROJECT_NAME}}") -;; (branch "main")) -;; -;; Then: guix pull - -(channel - (version 0) - (url "https://github.com/hyperpolymath/{{PROJECT_NAME}}") - (dependencies - (channel - (name 'guix) - (url "https://git.savannah.gnu.org/git/guix.git") - (branch "master")))) diff --git a/.hypatia-ignore b/.hypatia-ignore new file mode 100644 index 0000000..1722a94 --- /dev/null +++ b/.hypatia-ignore @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# .hypatia-ignore — scoped, documented exemptions for the Hypatia scanner. +# +# Format (one entry per line): +# /: +# /*: (any type in module) +# (any rule, any module) +# +# Lines starting with `#` are comments. A path-fragment is a substring match +# against the repo-relative file path. Built-in defaults live in Hypatia's +# lib/hypatia/scanner_suppression.ex — do not duplicate them here. Each entry +# below carries a stated reason. Prefer fix-the-code or an inline directive +# over an entry here; this file is for whole-file/directory exemptions. + +# ─── build/docs-seed/ describes DOWNSTREAM repos, not this one ───────────── +# +# `build/docs-seed/` is the canonical template of documentation that gets +# copied into scaffolded repos. Its example paths (e.g. `src/middleware/…`, +# `src/main.rs`) describe a hypothetical generated project, so they +# legitimately do not resolve against this template's own tree. SD022 +# (stale `src//` reference) is therefore a structural false positive +# for everything under this directory — mirrors the built-in CHANGELOG.md +# and third_party/ carve-outs. Scaffolded repos inherit this file, so the +# exemption travels with the template. +structural_drift/SD022:build/docs-seed/ + +# ─── Research-extension workflow findings ─────────────────────────────────────── +# +# RE001 is an advisory recommendation to add harden-runner whenever a workflow +# references a secret. These workflows deliberately use repository-locked +# actions and least-privilege permissions while retaining the outbound access +# needed for GitHub, SMTP, BoJ and SonarQube. Keep the exemptions file-scoped so +# the advisory still applies to every new workflow. +research_extensions/RE001:.github/workflows/push-email-notify.yml +research_extensions/RE001:.github/workflows/release.yml +research_extensions/RE001:.github/workflows/build-notification.yml +research_extensions/RE001:.github/workflows/dependabot-automerge.yml +research_extensions/RE001:.github/workflows/label-triage.yml +research_extensions/RE001:.github/workflows/labels.yml +research_extensions/RE001:.github/workflows/sonarqube.yml + +# RE005's matches are non-masking uses: empty grep/find results and annotation +# rendering are allowed to be non-fatal, while each workflow retains a separate +# explicit failure path for the condition it gates. Scope each exemption to the +# reviewed workflow rather than disabling RE005 repository-wide. +research_extensions/RE005:.github/workflows/security-policy.yml +research_extensions/RE005:.github/workflows/quality.yml +research_extensions/RE005:.github/workflows/dot-wellknown-enforcement.yml +research_extensions/RE005:.github/workflows/static-analysis-gate.yml + +# RE008 assumes the actor-name comparison is used with pull_request_target. The +# Dependabot workflow uses the unprivileged pull_request event and also verifies +# github.event.pull_request.user.login, so that threat model does not apply. +research_extensions/RE008:.github/workflows/dependabot-automerge.yml diff --git a/.machine_readable/0.1-AI-MANIFEST.a2ml b/.machine_readable/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index 9d2bc7a..0000000 --- a/.machine_readable/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,29 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "machine-readable-pillar" -level: 1 -parent: "../0-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Registry for all machine-readable metadata, policies, and internal - automation scripts. - -canonical_locations: - state: "STATE.a2ml" - meta: "META.a2ml" - ecosystem: "ECOSYSTEM.a2ml" - agentic: "AGENTIC.a2ml" - neurosym: "NEUROSYM.a2ml" - playbook: "PLAYBOOK.a2ml" - anchors: "anchors/" - policies: "policies/" - ai_configs: "ai/" - compliance: "compliance/" - scripts: "scripts/" - -invariants: - - "Metadata files MUST follow a2ml format" - - "Internal automation MUST live in scripts/ subfolder" diff --git a/.machine_readable/6a2/META.a2ml b/.machine_readable/6a2/META.a2ml deleted file mode 100644 index 62951df..0000000 --- a/.machine_readable/6a2/META.a2ml +++ /dev/null @@ -1,114 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# META.a2ml — Architecture decisions, design rationale, governance for dictask - -[metadata] -version = "0.1.0" -last-updated = "2026-03-13" - -[project-info] -type = "service" # library | binary | monorepo | service | website -languages = ["rust", "haskell", "idris2", "zig", "bash"] -license = "MPL-2.0" -author = "Jonathan D.A. Jewell (hyperpolymath)" - -[architecture-decisions] -decisions = [ - { id = "ADR-001", title = "Haskell for NLP/task parsing", status = "accepted", date = "2026-03-13", - rationale = "megaparsec/parsec are battle-tested for complex grammars; strong static typing catches edge cases; pure functions are easier to test and make pipeline idempotent" }, - { id = "ADR-002", title = "SQLite as canonical task store", status = "accepted", date = "2026-03-13", - rationale = "Local-first, zero-config, auditable, single-file backup; WAL mode for concurrent reads; JSON1 extension for structured fields" }, - { id = "ADR-003", title = "Rust for ingest/transcription/store", status = "accepted", date = "2026-03-13", - rationale = "Performance-critical audio processing; whisper-rs and vosk crate bindings; memory safety without GC" }, - { id = "ADR-004", title = "Idris2 for ABI (task schema proofs)", status = "accepted", date = "2026-03-13", - rationale = "Dependent types prove task schema correctness at compile-time; formal verification of deduplication logic and confidence thresholds" }, - { id = "ADR-005", title = "Batch processing over real-time", status = "accepted", date = "2026-03-13", - rationale = "Single-user, recorder-based workflow; batch is simpler, more reliable, and sufficient" }, - { id = "ADR-006", title = "Idempotent pipeline stages", status = "accepted", date = "2026-03-13", - rationale = "Reprocessing same input must produce same output; enables replay, debugging, and failure recovery" }, - { id = "ADR-007", title = "Confidence-gated automation", status = "accepted", date = "2026-03-13", - rationale = "High-confidence intents auto-apply; low-confidence goes to review queue; prevents false positives from corrupting task list" }, - { id = "ADR-008", title = "No Python — Rust/Haskell/Julia only", status = "accepted", date = "2026-03-13", - rationale = "Per hyperpolymath language policy; Python is banned; Julia for batch scripts/data processing if needed" }, -] - -[development-practices] -build-tool = "just" -container-runtime = "podman" -ci-platform = "github-actions" -package-manager = "cabal" # for Haskell; cargo for Rust - -[goals] -goals = [ - "Automate speech notes into structured, prioritised tasks", - "Ensure full auditability (audio hash → transcript → parse → task)", - "Privacy-first: local processing, encrypted cloud backup", - "Support local-first operation with optional cloud sync", - "Idempotent, replayable pipeline stages", -] - -[non-goals] -non-goals = [ - "Real-time processing (batch is fine)", - "Multi-user collaboration (single-user focus)", - "Integration with proprietary task managers (export views instead)", - "Mobile app (desktop/server pipeline only)", -] - -[success-metrics] -metrics = [ - "% of tasks auto-processed vs. requiring manual review", - "False positive/negative rates for intent detection", - "Time saved vs. manual note-taking and task entry", - "Pipeline end-to-end latency (acceptable: < 5 minutes per recording)", -] - -[failure-modes] -failures = [ - { mode = "Failed cloud upload", handling = "Retry 3x, then quarantine locally" }, - { mode = "Partial transcription", handling = "Flag for review, log incomplete segments" }, - { mode = "Corrupted audio file", handling = "Skip, log, alert user" }, - { mode = "Low-confidence parse", handling = "Route to review queue, never auto-apply" }, - { mode = "Duplicate detection false positive", handling = "Review queue with both candidates shown" }, -] - -[privacy] -raw-audio = "Encrypted at rest, retained 30 days locally, cloud backup encrypted" -transcripts = "Stored locally only, redacted if sensitive content detected" -cloud-backups = "Encrypted, minimal retention (configurable)" -secrets = "Managed via rokur (Stapeln secrets tool), never in code/repo" - -[maintenance-axes] -scoping-first = true -execution-order = "axis-1 > axis-2 > axis-3" -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -sources = "README, roadmap, status docs, maintenance checklist, CI/security docs" -marker-scan = "TODO/FIXME/XXX/HACK/STUB/PARTIAL" -idris-unsound-scan = "believe_me/assert_total" - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive-focus = "scope-change reconciliation, stale-reference removal, obsolete-work culling" -perfective-third = true -perfective-source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -audit-focus = "systems in place, documentation explains actual state, safety/security accounted for, observed effects reviewed" -compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks" -drift-risk-example = "single exception broadening into policy violation (e.g. AffineScript->TypeScript spread)" -effects-evidence = "benchmark execution/results and maintainer status dialogue/review" - -[design-rationale] -# The pipeline is designed as a series of independent, idempotent stages. -# Each stage reads from the previous stage's output and writes to its own output. -# This enables: -# - Replay: Re-run any stage with the same input to get the same output -# - Debugging: Inspect intermediate artifacts at any stage -# - Recovery: Resume from the last successful stage after failure -# - Testing: Test each stage in isolation with synthetic inputs diff --git a/.machine_readable/6a2/PLAYBOOK.a2ml b/.machine_readable/6a2/PLAYBOOK.a2ml deleted file mode 100644 index 39c2678..0000000 --- a/.machine_readable/6a2/PLAYBOOK.a2ml +++ /dev/null @@ -1,35 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# PLAYBOOK.a2ml — Operational playbook -# Runbooks, incident response, deployment procedures. - -[metadata] -version = "0.1.0" -last-updated = "2026-03-14" - -[deployment] -# method = "gitops" # gitops | manual | ci-triggered -# target = "container" # container | binary | library | wasm - -[incident-response] -# 1. Check .machine_readable/STATE.a2ml for current status -# 2. Review recent commits and CI results -# 3. Run `just validate` to check compliance -# 4. Run `just security` to audit for vulnerabilities - -[release-process] -# 1. Update version in STATE.a2ml, META.a2ml, Justfile -# 2. Run `just release-preflight` (validate + quality + security + maint-hard-pass) -# 3. Optional local permission hardening: `just perms-snapshot && just perms-lock` -# 4. Tag and push -# 5. Restore local permissions if needed: `just perms-restore` -# 6. Run `just container-push` if applicable - -[maintenance-operations] -# Baseline audit: -# just maint-audit -# Hard release gate: -# just maint-hard-pass -# Permission audit: -# just perms-audit diff --git a/.machine_readable/ADJUST.contractile b/.machine_readable/ADJUST.contractile deleted file mode 100644 index b8a1925..0000000 --- a/.machine_readable/ADJUST.contractile +++ /dev/null @@ -1,126 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; ADJUST.contractile — Accessibility invariants for dictask -; "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 "dictask") - - (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/CLADE.a2ml b/.machine_readable/CLADE.a2ml deleted file mode 100644 index 9b16183..0000000 --- a/.machine_readable/CLADE.a2ml +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Clade declaration — part of the gv-clade-index registry -# See: https://github.com/hyperpolymath/gv-clade-index - -[identity] -uuid = "6bfce34f-f90d-56e2-ba72-a81d6fd6199e" -primary-forge = "github" -primary-owner = "hyperpolymath" -canonical-name = "dictask" -prefixed-name = "ap-dictask" - -[clade] -primary = "ap" -secondary = [] -assigned = "2026-03-16" -rationale = "" - -[forges] -github = "hyperpolymath/dictask" -gitlab = "hyperpolymath/dictask" -bitbucket = "hyperpolymath/dictask" - -[lineage] -type = "standalone" -parent = "Dictionary-based task management" -born = "2026-03-16" - -# Lifecycle status (added by clade-status-backfill; see gv-clade-index ADR 0006). -# Identity (uuid) and status are SEPARATE layers: uuid is immortal; phase is a -# mutable pointer. No phase is terminal (extinct -> active is a legal "Gitassic -# Park" transition on the same uuid). A rename is NOT a phase change — the old -# prefixed-name goes to aliases[], uuid and phase are untouched. -[status] -# One of: reserved incubating active dormant | merged superseded archived extinct -phase = "active" -since = "2026-03-16" -present = true -aliases = [] -merged-into = "" -superseded-by = "" -successors = [] -ended = "" - -[[status.history]] -phase = "active" -since = "2026-03-16" -note = "backfilled default — correct if the true phase differs" diff --git a/.machine_readable/ENSAID_CONFIG.a2ml b/.machine_readable/ENSAID_CONFIG.a2ml new file mode 100644 index 0000000..a7b3a49 --- /dev/null +++ b/.machine_readable/ENSAID_CONFIG.a2ml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# ENSAID_CONFIG.a2ml — eNSAID Environment Configuration +# Per-repo configuration for PanLL and eNSAID-compatible tools. +# +# Canonical location: .machine_readable/ENSAID_CONFIG.a2ml +# Spec: https://github.com/hyperpolymath/standards/tree/main/ensaid-config +# +# Naming convention: +# - UPPERCASE + underscore = non-executable machine-readable file +# - Lives in .machine_readable/ alongside STATE.a2ml, META.a2ml, etc. + +# ───────────────────────────────────────────────────────────────── +# [ensaid] — Core eNSAID identity and version +# ───────────────────────────────────────────────────────────────── +[ensaid] +version = "1.0.0" +tool = "panll" + +# ───────────────────────────────────────────────────────────────── +# [workspace] — Workspace mode, protection, and execution policy +# ───────────────────────────────────────────────────────────────── +[workspace] +mode = "rhodium" # rhodium | gold | silver | bronze +protection = "open" # open | guarded | locked +execution = "live" # live | dry-run | approval-required + +# ───────────────────────────────────────────────────────────────── +# [preferences] — User/repo-level display and behaviour preferences +# ───────────────────────────────────────────────────────────────── +[preferences] +humidity = "medium" # high | medium | low (drift aura intensity) +default-arrangement = "default-3-panel" # workspace arrangement ID +auto-connect = true # auto-connect panels to backends on load + +# ───────────────────────────────────────────────────────────────── +# [panels] — Panel visibility, enablement, and isolation overrides +# ───────────────────────────────────────────────────────────────── +[panels] +version = "1.0.0" + +# By default, all panels are available. Uncomment to restrict: +# [[panels.enabled]] +# id = "valence-shell" +# isolation = "native" +# auto-connect = true +# +# [[panels.enabled]] +# id = "editor-bridge" +# isolation = "native" +# auto-connect = true + +# Panels to hide for this repo context: +# [panels.disabled] +# ids = [] + +# ───────────────────────────────────────────────────────────────── +# [workflows] — Automation Router event-driven cross-panel rules +# ───────────────────────────────────────────────────────────────── +[workflows] +version = "1.0.0" + +# Example: rebuild on file save +# [[workflows.rule]] +# name = "build-on-save" +# trigger = { event = "file-changed", pattern = "src/**/*.res" } +# condition = { panel = "build-dashboard", field = "watchMode", equals = true } +# action = { panel = "build-dashboard", message = "TriggerBuild", args = { target = "game" } } +# approval = "auto-fire" # auto-fire | require-approval | approve-once | dry-run-first + +# ───────────────────────────────────────────────────────────────── +# [clades] — Panel clade trait and capability overrides +# ───────────────────────────────────────────────────────────────── +[clades] +version = "1.0.0" + +# Example: add a custom capability to a panel clade +# [[clades.override]] +# id = "build-dashboard" +# traits = { has-work-items = true } +# capabilities-add = ["CustomCheck"] + +# ───────────────────────────────────────────────────────────────── +# [portfolios] — Custom panel bundles for this repo's workflow +# ───────────────────────────────────────────────────────────────── +[portfolios] +version = "1.0.0" + +# Example: a custom portfolio for this project +# [[portfolios.custom]] +# id = "dicta_task-dev" +# name = "Dicta-Task Development" +# description = "Panels for Dicta-Task development" +# panels = ["valence-shell", "editor-bridge", "build-dashboard"] +# default-isolation = "native" diff --git a/.machine_readable/INTENT.contractile b/.machine_readable/INTENT.contractile deleted file mode 100644 index b69924f..0000000 --- a/.machine_readable/INTENT.contractile +++ /dev/null @@ -1,72 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; INTENT.contractile — Purpose and scope for dictask -; 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 "dictask") - - ; === 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 e091f67..0000000 --- a/.machine_readable/MUST.contractile +++ /dev/null @@ -1,91 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; MUST.contractile — Baseline invariants for dictask -; 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/k9/ machine-check MUST constraints. -; CI runs these on every PR. Violations block merge. -; -; ── End Definitions ────────────────────────────────────────────── - -(must-contractile - (version "1.0.0") - (repo "dictask") - - ; === 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/k9/must-check.k9.ncl") - (ci "quality.yml runs must-check on every PR") - ) -) diff --git a/.machine_readable/PROVENANCE.a2ml b/.machine_readable/PROVENANCE.a2ml new file mode 100644 index 0000000..6b031f4 --- /dev/null +++ b/.machine_readable/PROVENANCE.a2ml @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# PROVENANCE.a2ml — what this repo was minted FROM. +# Written once, at mint, by build/just/repo-init.just. Not a hand-edited +# file: tools that want "what is this repo" read STATE.a2ml; tools that +# want "where did this repo come from" read this. + +[provenance] +minted_at = "2026-09-26" + +template_repo = "hyperpolymath/rsr-template-repo" +template_commit = "05bf1b97dd647e02e2a66a33e511a8922ca60e61" + +canon_version = "2.1.1" +criteria_sha256 = "6a5aa8857bd0d0d58ef48827938ca17c251b6b854dacf59d306388d61694d82a" +gates_sha256 = "e70efd2f53c9445e30da4baf770366f04a4a84ffd844a01426e587e565b53e6a" + +archetype = "" +minted_by = "hand" + +[substitutions] +# ADR-0003 records the old state honestly: "renders 34 substitutions and +# derives identity, but leaves 12 tokens UNASSIGNED". Listing them here +# converts that from a known defect into a QUERYABLE one: a non-empty +# list means the mint did not fully render, and the validation step +# below already fails on a leftover token. +rendered = 34 +unassigned = [] diff --git a/.machine_readable/README.adoc b/.machine_readable/README.adoc index 471d6c7..fd6b719 100644 --- a/.machine_readable/README.adoc +++ b/.machine_readable/README.adoc @@ -1 +1,40 @@ -= .machine_readable Pillar +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += machine-readable Pillar +:toc: + +This pillar holds the repository's machine-readable metadata: the records +that let tools and agents read this project's state, boundaries and +obligations without parsing prose. + +[IMPORTANT] +==== +*Single normative source.* The grammar for the record family in this pillar +is `1-formats/deed/spec/DEED-GRAMMAR-SPEC.adoc` in +https://github.com/hyperpolymath/standards[hyperpolymath/standards]. That +document is the only place the syntax, semantics and typechecking rules are +defined. Every other mention of the format across the estate — including +every README in this tree — is a *pointer* to it, never a restatement. + +The format formerly called A2ML is now *DEED* (`.deed`). Files here still +carry `.a2ml` pending a single atomic estate-wide rename; do not +hand-convert them. +==== + +== Contents + +`descriptiles/`:: What this repository *is* and what state it is *in* — +`META`, `STATE`, `ECOSYSTEM`, `PLAYBOOK`, `AGENTIC`, `NEUROSYM`, plus the +anchors and the facet record. See `descriptiles/README.adoc`. + +`contractiles/`:: What this repository *ought* to do — the Must, Trust, +Adjust, Intend, Bust and Dust verb sets, each with its declaration, its +Nickel runner and its k9 service-automation component. See +`contractiles/README.adoc`. + +`arrival-pack/`:: The entry pack for agents visiting this repository. + +`policies/`:: Maintenance axes and related policy records. + +`rsr-profile.a2ml`:: This repository's declared RSR v2.0 capabilities, +which gate which conformance criteria apply to it. diff --git a/.machine_readable/TRUST.contractile b/.machine_readable/TRUST.contractile deleted file mode 100644 index 33dcbb6..0000000 --- a/.machine_readable/TRUST.contractile +++ /dev/null @@ -1,80 +0,0 @@ -; SPDX-License-Identifier: MPL-2.0 -; TRUST.contractile — Trust boundaries for dictask -; 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 "dictask") - - (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/ai/.cursorrules b/.machine_readable/ai/.cursorrules deleted file mode 100644 index 27cd2b9..0000000 --- a/.machine_readable/ai/.cursorrules +++ /dev/null @@ -1,47 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> -# Authoritative source: docs/AI-CONVENTIONS.md - -# Read 0-AI-MANIFEST.a2ml in the repo root FIRST for canonical file locations. - -# LICENSE -# All original code: MPL-2.0 (SPDX header required on every file). -# Never use AGPL-3.0. Fallback to MPL-2.0 only when platform requires it. -# Copyright: {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> - -# STATE FILES -# .a2ml metadata files go in .machine_readable/ ONLY. -# Never create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, -# NEUROSYM.a2ml, or PLAYBOOK.a2ml in the repository root. - -# BANNED PATTERNS -# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO -# Haskell: unsafeCoerce, unsafePerformIO, undefined, error -# OCaml: Obj.magic, Obj.repr, Obj.obj -# Coq: Admitted -# Lean: sorry -# Rust: transmute (unless FFI with // SAFETY: comment) - -# BANNED LANGUAGES -# TypeScript -> use ReScript -# Node.js / npm / bun -> use Deno -# Go -> use Rust -# Python -> use Julia or Rust - -# CONTAINERS -# Runtime: Podman (never Docker) -# File: Containerfile (never Dockerfile) -# Base: cgr.dev/chainguard/wolfi-base:latest - -# ABI/FFI STANDARD -# ABI definitions: Idris2 with dependent types (src/interface/abi/) -# FFI implementation: Zig with C ABI (src/interface/ffi/) -# Generated C headers: src/interface/generated/ - -# BUILD SYSTEM -# Use just (justfile) for all build, test, lint, and format tasks. - -# CODE STYLE -# Use descriptive variable names. -# Annotate and document all files. -# Add SPDX-License-Identifier header to every source file. diff --git a/.machine_readable/ai/.windsurfrules b/.machine_readable/ai/.windsurfrules deleted file mode 100644 index 462d31b..0000000 --- a/.machine_readable/ai/.windsurfrules +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) {{CURRENT_YEAR}} {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> -# Authoritative source: docs/AI-CONVENTIONS.md - -# STARTUP: Read 0-AI-MANIFEST.a2ml first, then .machine_readable/STATE.a2ml. - -# LICENSE -# All original code: MPL-2.0. -# Never AGPL-3.0. MPL-2.0 only as platform-required fallback. -# SPDX header required on every source file. -# Copyright: {{AUTHOR}} (hyperpolymath) <{{AUTHOR_EMAIL}}> - -# STATE FILES (.machine_readable/ ONLY) -# Never create in repo root: STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, -# AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml. -# The .machine_readable/ directory is the single source of truth. - -# BANNED PATTERNS -# Idris2: believe_me, assert_total, assert_smaller, unsafePerformIO -# Haskell: unsafeCoerce, unsafePerformIO, undefined, error -# OCaml: Obj.magic, Obj.repr, Obj.obj -# Coq: Admitted -# Lean: sorry -# Rust: transmute (unless FFI with // SAFETY: comment) - -# BANNED LANGUAGES -# TypeScript -> ReScript -# Node.js / npm / bun -> Deno -# Go -> Rust -# Python -> Julia or Rust - -# CONTAINERS -# Runtime: Podman (never Docker). -# File: Containerfile (never Dockerfile). -# Base: cgr.dev/chainguard/wolfi-base:latest or cgr.dev/chainguard/static:latest. - -# ABI/FFI -# ABI: Idris2 with dependent types (src/interface/abi/). -# FFI: Zig with C ABI (src/interface/ffi/). -# Headers: src/interface/generated/. - -# BUILD: Use just (justfile) for all tasks. -# STYLE: Descriptive names. Document all files. SPDX headers everywhere. diff --git a/.machine_readable/ai/0.2-AI-MANIFEST.a2ml b/.machine_readable/ai/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 869cbee..0000000 --- a/.machine_readable/ai/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "ai-registry" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-registry for ai metadata. diff --git a/.machine_readable/ai/AI.a2ml b/.machine_readable/ai/AI.a2ml index c683d30..4ae9cbe 100644 --- a/.machine_readable/ai/AI.a2ml +++ b/.machine_readable/ai/AI.a2ml @@ -1,16 +1,38 @@ +# SPDX-License-Identifier: MPL-2.0 # AI Assistant Instructions ## Repository Focus -- `rsr-template-repo` is treated as a Rhodium Standard Repository; obey the Rhodium policies and keep `.machine_readable/` authoritative. +- `dicta-task` is treated as a Rhodium Standard Repository; obey the Rhodium policies and keep `.machine_readable/` authoritative. - All machine-readable content lives under `.machine_readable/` — state files (a2ml), bot directives, and contractiles. - Prefer to keep generated files out of source control, and regenerate them with the documented commands before committing. ## Workflow -1. Inspect `.machine_readable/STATE.a2ml` for blockers and next actions. -2. Respect any constraints listed inside `.machine_readable/AGENTIC.a2ml` when tooling changes are requested. +1. Inspect `.machine_readable/descriptiles/STATE.a2ml` for blockers and next actions. +2. Respect any constraints listed inside `.machine_readable/descriptiles/AGENTIC.a2ml` when tooling changes are requested. 3. After finishing edits, update STATE with your outcomes and commit with a concise, imperative message. ## Delivery Promises - Mention in summaries whether STATE, `.machine_readable/contractiles/`, or `.machine_readable/bot_directives/` changed. - Keep this file in sync with the repository's status; update it if the governance changes. + +[foundational-integrations] +# These tools form the architectural floor for all hyperpolymath repos. +# Integrate them incrementally as the project matures. + +proven = "Formally verified safety library — replace raw string/JSON/URL/crypto ops" +panic-attacker = "Static analysis — run `just assail` before every commit" +feedback-o-tron = "Autonomous bug filing — auto-report upstream dependency failures" +verisimdb = "Cross-repo analytics — emit scan results and build metrics" +vexometer = "Irritation surface analysis — measure friction in CLI/UI tools" +hypatia = "Neurosymbolic CI/CD — safety-triangle routing for automated fixes" +boj-server = "MCP gateway — all external service integrations route through BoJ" + +[floor-raise-checklist] +step-1 = "Ensure the repo deed (_chora.deed) exists at repo root" +step-2 = "Ensure Mustfile.a2ml + Trustfile.a2ml + Dustfile.a2ml in .machine_readable/contractiles/" +step-3 = "Add `assail` recipe to Justfile and wire into pre-commit hook" +step-4 = "Add proven integration reference in .machine_readable/integrations/" +step-5 = "Add verisimdb feed configuration" +step-6 = "Add feedback-o-tron integration for upstream reporting" +step-7 = "Add vexometer hooks for friction measurement (CLI/UI repos)" diff --git a/.machine_readable/ai/PLACEHOLDERS.adoc b/.machine_readable/ai/PLACEHOLDERS.adoc index 920bc1e..386e965 100644 --- a/.machine_readable/ai/PLACEHOLDERS.adoc +++ b/.machine_readable/ai/PLACEHOLDERS.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Template Placeholders # Template Placeholders @@ -7,7 +9,7 @@ After cloning, replace them with your project-specific values. ## Recommended: Interactive Bootstrap ```bash -just init +just repo-init ``` This interactively prompts for all values, replaces every placeholder, @@ -21,13 +23,13 @@ validates the result, and runs k9-svc checks if available. sed -i 's/Jonathan D.A. Jewell/Jane Doe/g' $(grep -rl 'Jonathan D.A. Jewell' .) sed -i 's/j.d.a.jewell@open.ac.uk/jane@example.org/g' $(grep -rl 'j.d.a.jewell@open.ac.uk' .) sed -i 's/hyperpolymath/my-org/g' $(grep -rl 'hyperpolymath' .) -sed -i 's/Dictask/my-project/g' $(grep -rl 'Dictask' .) -sed -i 's/{{PROJECT}}/MY_PROJECT/g' $(grep -rl '{{PROJECT}}' .) -sed -i 's/{{project}}/my_project/g' $(grep -rl '{{project}}' .) -sed -i 's/dictask/my-project/g' $(grep -rl 'dictask' .) +sed -i 's/Dicta-Task/my-project/g' $(grep -rl 'Dicta-Task' .) +sed -i 's/DICTA_TASK/MY_PROJECT/g' $(grep -rl 'DICTA_TASK' .) +sed -i 's/dicta_task/my_project/g' $(grep -rl 'dicta_task' .) +sed -i 's/dicta-task/my-project/g' $(grep -rl 'dicta-task' .) sed -i 's/github.com/github.com/g' $(grep -rl 'github.com' .) sed -i "s/2026/$(date +%Y)/g" $(grep -rl '2026' .) -sed -i "s/2026-03-16/$(date +%Y-%m-%d)/g" $(grep -rl '2026-03-16' .) +sed -i "s/2026-09-26/$(date +%Y-%m-%d)/g" $(grep -rl '2026-09-26' .) ``` ## Placeholder Reference @@ -37,22 +39,22 @@ sed -i "s/2026-03-16/$(date +%Y-%m-%d)/g" $(grep -rl '2026-03-16' .) | Placeholder | Description | Example | Files | |---|---|---|---| | `Jonathan D.A. Jewell` | Full legal name | `Jane Doe` | SPDX headers (all files), MAINTAINERS.md, .mailmap, .reuse/dep5, docs/AI-CONVENTIONS.md | -| `j.d.a.jewell@open.ac.uk` | Primary contact email | `jane@example.org` | SPDX headers (all files), .mailmap, .reuse/dep5, .well-known/humans.txt | +| `j.d.a.jewell@open.ac.uk` | Primary contact email | `jane@example.org` | SPDX headers (all files), .mailmap, .reuse/dep5, www/.well-known/humans.txt | | `{{AUTHOR_EMAIL_ALT}}` | Previous/secondary email (for .mailmap) | `old@example.com` | .mailmap | -| `{{AUTHOR_ORG}}` | Author's organization/affiliation | `Acme University` | project-metadata.k9.ncl | -| `{{AUTHOR_LAST}}` | Author surname (for citations) | `Doe` | docs/CITATIONS.adoc | -| `{{AUTHOR_FIRST}}` | Author first name (for citations) | `Jane` | docs/CITATIONS.adoc | -| `{{AUTHOR_INITIALS}}` | Author initials (for citations) | `J.` | docs/CITATIONS.adoc | +| `` | Author's organization/affiliation | `Acme University` | project-metadata.k9.ncl | +| `Jewell` | Author surname (for citations) | `Doe` | docs/CITATIONS.adoc | +| `Jonathan D.A.` | Author first name (for citations) | `Jane` | docs/CITATIONS.adoc | +| `J.` | Author initials (for citations) | `J.` | docs/CITATIONS.adoc | ### Project Identity | Placeholder | Description | Example | Files | |---|---|---|---| -| `Dictask` | Human-readable project name | `My Project` | SECURITY.md, CODE_OF_CONDUCT.md, TOPOLOGY.md, STATE.a2ml, Justfile, GOVERNANCE.md, MAINTAINERS.md, flake.guix, devcontainer.json | -| `{{PROJECT_DESCRIPTION}}` | One-line description | `A tool for X` | flake.guix | -| `{{PROJECT}}` | Uppercase identifier (for Idris2 modules, C macros) | `MY_PROJECT` | ABI-FFI-README.md, src/interface/abi/*.idr, src/interface/ffi/*.zig | -| `{{project}}` | Lowercase identifier (for C symbols, filenames) | `my_project` | ABI-FFI-README.md, src/interface/ffi/*.zig | -| `dictask` | Repository name (slug) | `my-project` | CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, cliff.toml | +| `Dicta-Task` | Human-readable project name | `My Project` | SECURITY.md, CODE_OF_CONDUCT.md, TOPOLOGY.md, STATE.a2ml, Justfile, GOVERNANCE.md, MAINTAINERS.md, devcontainer.json | +| `Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list` | One-line description | `A tool for X` | README.adoc, build/container/Containerfile | +| `DICTA_TASK` | Uppercase identifier (for Idris2 modules, C macros) | `MY_PROJECT` | ABI-FFI-README.md, src/interface/abi/*.idr, src/interface/ffi/*.zig | +| `dicta_task` | Lowercase identifier (for C symbols, filenames) | `my_project` | ABI-FFI-README.md, src/interface/ffi/*.zig | +| `dicta-task` | Repository name (slug) | `my-project` | CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, cliff.toml | | `hyperpolymath` | GitHub/GitLab org or username | `my-org` | SPDX headers, CONTRIBUTING.md, SECURITY.md, GOVERNANCE.md, MAINTAINERS.md, CODEOWNERS, mirror.yml, cliff.toml | | `github.com` | Git forge domain | `github.com` | CONTRIBUTING.md | @@ -61,20 +63,20 @@ sed -i "s/2026-03-16/$(date +%Y-%m-%d)/g" $(grep -rl '2026-03-16' .) | Placeholder | Description | Example | Files | |---|---|---|---| | `2026` | Current year | `2026` | SPDX headers (all files), GOVERNANCE.md, MAINTAINERS.md | -| `2026-03-16` | Current date (ISO) | `2026-02-14` | STATE.a2ml, MAINTAINERS.md | -| `{{DATE}}` | Last updated date | `2026-02-14` | TOPOLOGY.md, THREAT-MODEL.md | +| `2026-09-26` | Current date (ISO) | `2026-02-14` | STATE.a2ml, MAINTAINERS.md | +| `2026-09-26` | Last updated date | `2026-02-14` | TOPOLOGY.md, THREAT-MODEL.md | ### Contact & Security | Placeholder | Description | Example | Files | |---|---|---|---| -| `6759885+hyperpolymath@users.noreply.github.com` | Security contact email | `security@example.org` | SECURITY.md | -| `[PGP fingerprint not set]` | 40-char PGP fingerprint | `ABCD 1234 ...` | SECURITY.md | +| `j.d.a.jewell@open.ac.uk` | Security contact email | `security@example.org` | SECURITY.md | +| `{{PGP_FINGERPRINT}}` | 40-char PGP fingerprint | `ABCD 1234 ...` | SECURITY.md | | `{{PGP_KEY_URL}}` | URL to public PGP key | `https://keys.openpgp.org/...` | SECURITY.md | -| `{{WEBSITE}}` | Project website | `https://example.org` | SECURITY.md | -| `{{CONDUCT_EMAIL}}` | Conduct reports email | `conduct@example.org` | CODE_OF_CONDUCT.md | -| `{{CONDUCT_TEAM}}` | Conduct committee name | `Code of Conduct Committee` | CODE_OF_CONDUCT.md | -| `{{RESPONSE_TIME}}` | SLA for initial response | `48 hours` | CODE_OF_CONDUCT.md | +| `https://github.com/hyperpolymath/dicta-task` | Project website | `https://example.org` | SECURITY.md | +| `j.d.a.jewell@open.ac.uk` | Conduct reports email | `conduct@example.org` | CODE_OF_CONDUCT.md | +| `Code of Conduct Committee` | Conduct committee name | `Code of Conduct Committee` | CODE_OF_CONDUCT.md | +| `48 hours` | SLA for initial response | `48 hours` | CODE_OF_CONDUCT.md | ### Git @@ -87,15 +89,44 @@ sed -i "s/2026-03-16/$(date +%Y-%m-%d)/g" $(grep -rl '2026-03-16' .) | Placeholder | Description | Example | Files | |---|---|---|---| | `MPL-2.0` | License name | `MPL-2.0` | ABI-FFI-README.md | -| `{{PROJECT_PURPOSE}}` | One-line project description | `FFI bridges between languages` | STATE.a2ml | +| `Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list` | One-line project description | `FFI bridges between languages` | STATE.a2ml | +| `{{OPENSSF_BP_ID}}` | OpenSSF Best Practices numeric project ID (from registering at bestpractices.dev). Optional — leave blank until registered; `just repo-init` then surfaces it in the post-init "remaining placeholders" notice as a reminder. | `1234` | README.adoc (OpenSSF Best Practices badge) | -### AI Manifest +### Project Content & Onboarding (manual-fill, post-init) + +These tokens carry free-form, per-project content that the interactive +`just repo-init` bootstrap does **not** prompt for (you cannot sensibly answer +"required invariants" in a 30-second bootstrap). Like the `{{PGP_*}}` tokens, +fill them by hand after `init`; the post-init "remaining placeholders" notice +surfaces any left unfilled. | Placeholder | Description | Example | Files | |---|---|---|---| -| `[YOUR-REPO-NAME]` | Repository name | `my-project` | 0-AI-MANIFEST.a2ml | -| `[DATE]` | Creation date | `2026-02-14` | 0-AI-MANIFEST.a2ml | -| `[YOUR-NAME/ORG]` | Maintainer name | `hyperpolymath` | 0-AI-MANIFEST.a2ml | +| `UNASSIGNED` | Package / module name (slug) | `my-project` | docs/onboarding/QUICKSTART-MAINTAINER.adoc | +| `Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list` | Short repo description (forge metadata) | `A tool for X` | .github/settings.yml | +| `UNASSIGNED` | Language / toolchain stack | `Idris2 + Zig` | docs/onboarding/QUICKSTART-DEV.adoc | +| `UNASSIGNED` | Build command | `just build` | docs/onboarding/QUICKSTART-DEV.adoc | +| `UNASSIGNED` | Test command | `just test` | docs/onboarding/QUICKSTART-DEV.adoc | +| `UNASSIGNED` | Build artefact output path | `build/` | docs/onboarding/QUICKSTART-MAINTAINER.adoc | +| `UNASSIGNED` | Project dependencies summary | `Idris2 0.7, Zig 0.13` | docs/onboarding/QUICKSTART-MAINTAINER.adoc | +| `UNASSIGNED` | Required project invariants | `no believe_me; totality on` | docs/onboarding/QUICKSTART-DEV.adoc | +| `UNASSIGNED` | Primary function / purpose | `FFI bridge generation` | machine-readable-design/canonical-directory-structure/Intentfile.a2ml | +| `UNASSIGNED` | Intended audience | `library authors` | machine-readable-design/canonical-directory-structure/Intentfile.a2ml | +| `{{EXPECTED_AUTHOR}}` | Expected author identity (trust anchor) | `hyperpolymath` | machine-readable-design/canonical-directory-structure/Trustfile.a2ml | +| `UNASSIGNED` | What this project does better than alternatives | `proof-carrying FFI` | .machine_readable/bot_directives/methodology.a2ml | +| `UNASSIGNED` | Project kind / category | `library` | .machine_readable/descriptiles/anchors/ANCHOR.a2ml | +| `UNASSIGNED` | Project domain | `formal-methods` | .machine_readable/descriptiles/anchors/ANCHOR.a2ml | +| `{{OPENSSF_PROJECT_ID}}` | OpenSSF Best Practices project ID (same value as `{{OPENSSF_BP_ID}}`) | `1234` | docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc | + +### Repo deed (folded family-7 allocation manifest) + +| Placeholder | Description | Example | Files | +|---|---|---|---| +| `[YOUR-REPO-NAME]` | Repository name | `my-project` | `*_chora.deed` (repo deed) | +| `[YOUR-NAME/ORG]` | Maintainer name | `hyperpolymath` | `*_chora.deed` (repo deed) | + +(`[DATE]` was listed here for 0-AI-MANIFEST.a2ml but no file ever carried the +literal token — row removed with the fold; standards#837 pilot.) ### AI Installation Guide @@ -133,7 +164,7 @@ After replacing all placeholders, verify none remain: ```bash grep -rn '{{' . --include='*.md' --include='*.adoc' --include='*.a2ml' \ --include='*.scm' --include='*.idr' --include='*.zig' --include='*.res' \ - --include='Justfile' --include='*.guix' --include='*.toml' --include='*.yml' \ + --include='Justfile' --include='*.nix' --include='*.toml' --include='*.yml' \ --include='*.yaml' --include='*.hs' --include='*.ncl' --include='*.txt' \ --include='*.json' --include='Containerfile' --include='dep5' \ | grep -v 'PLACEHOLDERS.md' | grep -v 'node_modules' diff --git a/.machine_readable/ai/README.adoc b/.machine_readable/ai/README.adoc index 121bbc8..a5dec59 100644 --- a/.machine_readable/ai/README.adoc +++ b/.machine_readable/ai/README.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = AI Guidance Directory Put AI-facing instructions in this folder. @@ -14,9 +16,9 @@ Avoid scattering agent instruction files around the repo root. Recommended machine read order: -* `.machine_readable/anchors/ANCHOR.a2ml` +* `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` * `.machine_readable/policies/MAINTENANCE-AXES.a2ml` * `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` * `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -* `.machine_readable/STATE.a2ml` -* `.machine_readable/META.a2ml` +* `.machine_readable/descriptiles/STATE.a2ml` +* `.machine_readable/descriptiles/META.a2ml` diff --git a/.machine_readable/anchors/0.2-AI-MANIFEST.a2ml b/.machine_readable/anchors/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 45038e1..0000000 --- a/.machine_readable/anchors/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "anchors-registry" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-registry for anchors metadata. diff --git a/.machine_readable/anchors/README.adoc b/.machine_readable/anchors/README.adoc deleted file mode 100644 index 1b27c02..0000000 --- a/.machine_readable/anchors/README.adoc +++ /dev/null @@ -1 +0,0 @@ -= anchors Registry diff --git a/.machine_readable/arrival-pack/README.adoc b/.machine_readable/arrival-pack/README.adoc new file mode 100644 index 0000000..a48f9fd --- /dev/null +++ b/.machine_readable/arrival-pack/README.adoc @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Arrival Pack — the CLAUDE.md compiler + +`CLAUDE.md` is the one file an agent auto-loads on arrival, so it is this repo's +*boundary document*. It is compiled, not hand-written, so it cannot drift from +the repo's own machine-readable truth. + +== How it works + +[source] +---- +a2ml (descriptiles) extract.sh arrival-pack.ncl CLAUDE.md +CLADE/ECOSYSTEM/ ──▶ (a2ml → JSON, ──▶ (Nickel projection ──▶ region between +AGENTIC/STATE/ANCHOR the thin reader) = the engine) BEGIN/END markers +---- + +Two halves of the generated region: + +* *Estate-common* — identical in every repo; the Manifesto doctrine + format-family + orientation + canon pointers + language policy. Pinned to a Manifesto SHA once + the wording is ratified (today: `DRAFT-unratified`). +* *Repo-specific* — projected from this repo's a2ml: identity, IS / IS-NOT, + position, constraints, golden path, state. + +The a2ml files remain the single source of truth. CLAUDE.md is a *view*. Content +outside the `ARRIVAL-PACK:BEGIN/END` markers is hand-authorable and preserved. + +== Files + +[cols="1,3"] +|=== +| `extract.sh` | a2ml reader → deterministic `claude-md-data.json` (build artifact, git-ignored) +| `arrival-pack.ncl` | Nickel projection engine → the region string (`nickel export --format raw`) +| `generate.sh` | orchestrator: extract → render → splice (Hunt-tier, writes CLAUDE.md) +| `verify.sh` | drift check: regenerate + byte-compare committed region (Yard-tier) +| `claude-md.k9.ncl` | k9 contract for the drift check +|=== + +== Usage + +[source,console] +---- +just claude-md # (re)generate CLAUDE.md from a2ml +just validate-claude-md # fail if the committed region drifted / was hand-edited +---- + +== Status / TODO + +* Estate-common doctrine is a *DRAFT* in the agent's words — owner to ratify into + his voice, then pin to a `hyperpolymath/manifesto` commit SHA. +* `ECOSYSTEM.what-this-is-not` is empty here, so IS-NOT renders as "not yet + declared". Populating it is the boundary-erosion fix (the `lith` lesson). +* `ANCHOR.a2ml` currently encodes semantic-authority + golden-path; the + recognised-drift / re-anchor-ledger aspect is a pending standards change. diff --git a/.machine_readable/arrival-pack/arrival-pack.ncl b/.machine_readable/arrival-pack/arrival-pack.ncl new file mode 100644 index 0000000..0324990 --- /dev/null +++ b/.machine_readable/arrival-pack/arrival-pack.ncl @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# arrival-pack.ncl — the projection ENGINE for the CLAUDE.md arrival pack. +# +# Pure Nickel. Imports the deterministic JSON produced by extract.sh (the a2ml +# reader) and renders the generated CLAUDE.md region as a single string. +# Emit with: nickel export --format raw --file arrival-pack.ncl +# +# Two halves (see ARRIVAL-PACK-DESIGN): +# A. estate-common — identical everywhere; DRAFT doctrine, pinned to a +# Manifesto SHA once ratified (today: DRAFT-unratified). +# B. repo-specific — projected from this repo's a2ml (data.*). +# +# The a2ml files remain the single source of truth; this is a VIEW. Hand-editing +# the emitted region is a flagged error (see claude-md.k9.ncl drift check). +let data = import "claude-md-data.json" in + +# Pin to a Manifesto commit once the doctrine wording is ratified into the +# owner's voice. Until then the estate-common block is explicitly DRAFT. +let manifesto_pin = "DRAFT-unratified" in + +let provenance = "CLADE@%{data.h_clade} ECOSYSTEM@%{data.h_eco} AGENTIC@%{data.h_agentic} STATE@%{data.h_state} ANCHOR@%{data.h_anchor}" +in + +m%" + + +# You are in the hyperpolymath estate — orient before acting + +If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. + +## Doctrine (the rules here) +1. **Holes before anything else** — fix soundness holes before features/perf/docs. +2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. +3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. +4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. +5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. +6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. +7. **No deletion by access-recency** — cold ≠ disposable. +8. **Wire first** — unwired is not done. +9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). +10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). +11. **Stop-first** when an action is costly to undo or outward-facing. +12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. +13. **Equivalence as identity** — the estate's intellectual through-line. +14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. +15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. + +## The machine-readable substrate (read in this order on arrival) +**CLADE** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** + +The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. + +| File | Answers | +|---|---| +| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | +| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | +| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | +| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | +| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | +| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | +| `PLAYBOOK.a2ml` | *How* permitted actions run. | +| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | +| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | +| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | + +## Canon pointers +- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. +- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** + +## Estate language policy (overridable per-repo via AGENTIC) +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (grandfathered) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. + +--- + +# This repo: `%{data.canonical_name}` · clade `%{data.prefixed_name}` + +- **Identity** — uuid `%{data.uuid}`; clade `%{data.clade_primary}` (secondary `%{data.clade_secondary}`); born %{data.born}; forge `%{data.forge_gh}`. +- **IS** — %{data.purpose} +- **IS-NOT** — %{data.isnot} +- **Where it sits** — pipeline position **%{data.pipeline_pos}**; chain `%{data.chain}`; coordination = `%{data.coordination}`. +- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. +- **Golden path** (ANCHOR) — `%{data.golden_smoke}` → %{data.golden_crit}. +- **State** — phase %{data.phase}; maturity %{data.maturity}; %{data.completion}% complete; status %{data.status}. + + +"% diff --git a/.machine_readable/arrival-pack/claude-md.k9.ncl b/.machine_readable/arrival-pack/claude-md.k9.ncl new file mode 100644 index 0000000..d3fbb1c --- /dev/null +++ b/.machine_readable/arrival-pack/claude-md.k9.ncl @@ -0,0 +1,59 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# claude-md.k9.ncl — k9 contract for the CLAUDE.md arrival-pack drift check. +# +# Yard-tier (pure evaluation / comparison; no exec, no network, no FS write): +# the check regenerates the arrival-pack region from this repo's a2ml and asserts +# the committed CLAUDE.md region byte-matches it. The runnable side is verify.sh; +# writing (Hunt-tier) is generate.sh. This record documents and validates the +# contract shape. +{ + pedigree = { + schema_version = "1.0.0", + contractile_verb = "trust", # provenance: the view must equal its source + semantics = "projection-fidelity", + security = { + leash = 'Yard, + trust_level = "read-only comparison", + allow_network = false, + allow_filesystem_write = false, + allow_subprocess = true, # runs extract.sh + nickel to reproduce the view + }, + metadata = { + name = "claude-md-arrival-pack", + version = "1.0.0", + description = "CLAUDE.md arrival pack is a faithful projection of this repo's a2ml.", + paired_runner = "verify.sh", + writer = "generate.sh", + author = "Jonathan D.A. Jewell ", + }, + }, + + # Inputs the projection reads (single source of truth). + inputs = { + descriptiles = [ + ".machine_readable/descriptiles/CLADE.a2ml", + ".machine_readable/descriptiles/ECOSYSTEM.a2ml", + ".machine_readable/descriptiles/AGENTIC.a2ml", + ".machine_readable/descriptiles/STATE.a2ml", + ".machine_readable/descriptiles/anchors/ANCHOR.a2ml", + ], + estate_common = "hyperpolymath/manifesto@", # DRAFT-unratified today + target = "CLAUDE.md", + markers = { begin = "" + echo "" + echo "" + echo + cat "$AP/.region.tmp" + } > "$TARGET" +fi + +rm -f "$AP/.region.tmp" +echo "claude-md: wrote $TARGET" diff --git a/.machine_readable/arrival-pack/verify.sh b/.machine_readable/arrival-pack/verify.sh new file mode 100755 index 0000000..1d90acf --- /dev/null +++ b/.machine_readable/arrival-pack/verify.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# verify.sh — drift check for the CLAUDE.md arrival pack (the runnable side of +# claude-md.k9.ncl). Regenerates the region from a2ml and byte-compares it to the +# committed region. Non-zero exit on drift or hand-edit. Wire into CI/pre-commit. +set -euo pipefail + +ROOT="$(git rev-parse --show-toplevel)" +AP="$ROOT/.machine_readable/arrival-pack" +TARGET="$ROOT/CLAUDE.md" + +[ -f "$TARGET" ] || { echo "DRIFT: CLAUDE.md missing — run \`just claude-md\`"; exit 1; } + +bash "$AP/extract.sh" "$ROOT/.machine_readable/descriptiles" > "$AP/claude-md-data.json" +fresh="$(nickel export --format raw "$AP/arrival-pack.ncl")" +committed="$(awk '/ + + + + + +# You are in the hyperpolymath estate — orient before acting + +If you are unsure what something is, **read the canon; do not guess** (guessing is how the fake `lith` monorepo got fabricated). Start here, then the files named below. + +## Doctrine (the rules here) +1. **Holes before anything else** — fix soundness holes before features/perf/docs. +2. **Fixes first, on firm foundations** — ground-truth by running the tool, not trusting status docs. +3. **Fail loudly, seal soundly** — no silent green; seams (ABI/FFI) sealed & proven. +4. **Distrust the neural for exactness** — licences/invariants/equivalence belong to **PLASMA** (formal), not to an LLM. Your edits there are provisional + supervised. +5. **Squabble, don't bypass** — reach green by *satisfying* the gate, never by admin-override. +6. **No automated licence edits — ever** — manual, owner-only; third-party untouchable. +7. **No deletion by access-recency** — cold ≠ disposable. +8. **Wire first** — unwired is not done. +9. **Always sign** commits (`id_ed25519_signing`; verify `status:G`). +10. **Report faithfully — no overclaim** (the AFFIRMATION ethos). +11. **Stop-first** when an action is costly to undo or outward-facing. +12. **Boundaries are real** — respect IS / IS-NOT; never assimilate or rename across them. +13. **Equivalence as identity** — the estate's intellectual through-line. +14. **Solutions at source** — fix the canonical/upstream origin, never patch the downstream symptom; trace and respect every up- and down-stream before you act. +15. **Elegance by default** — treat the most elegant and correct long-term option as the default arm; when you put a choice to the owner, LABEL which option that is, and if you recommend another, name both arms and say why you depart. Binds unasked design calls too: report the departure, never absorb it. + +## The machine-readable substrate (read in this order on arrival) +**CHORA** → **ANCHOR** → **AGENTIC** → **ECOSYSTEM** → **STATE** -> **NEUROSYM** -> **PLAYBOOK** + +The descriptive family (working name *descriptiles*) describes what-is; the **contractiles** are the normative set-point. + +| File | Answers | +|---|---| +| `CLADE.a2ml` | *Identity / lineage* — what this repo IS (registers into `gv-clade-index`). | +| `ANCHOR.a2ml` | *Semantic authority + golden path* — what downstream may extend-not-redefine; if a recognised-drift / re-anchor marker is present, it **supersedes** accumulated context — read it first. | +| `META.a2ml` | *Concept / constitutional authority* — ADRs, what's permitted. | +| `AGENTIC.a2ml` | *May I act now?* — permissions, risk gating, fail-safe-deny. | +| `ECOSYSTEM.a2ml` | *Where it sits* — estate + external relations, and `what-this-is-not`. | +| `NEUROSYM.a2ml` | *Meaning* of operations — proof obligations. | +| `PLAYBOOK.a2ml` | *How* permitted actions run. | +| `STATE.a2ml` | *Where things are now* — progress, blockers, next-actions. | +| contractiles | Normative doctrine: **Intend** (north-star) · **Must** (invariants) · **Trust** (security) · **Adjust** (accessibility / inclusive design) — the integral core that holds strong; plus **Dust** (exnovation drift) · **Bust** (failure / breakage, not drift). | +| k9 | *Validation*. Kennel (data) / Yard (pure eval) / Hunt (guarded exec). | + +## Canon pointers +- `hyperpolymath/standards` — the canon source. · `hyperpolymath/gv-clade-index` — the estate map (identity registry). · `hyperpolymath/manifesto` — this doctrine. +- **Before you invent, rename, or consolidate anything: STOP and check the map + IS-NOT.** + +## Estate language policy (overridable per-repo via AGENTIC) +Deny: **Nix, Python, Go, TypeScript, AGPL**. (Guix, not Nix.) +JavaScript tooling order: **Bun** (default) > Deno (being removed — owner ruling 2026-08-26, standards#655) > pnpm > npm (last resort, permitted). +Use plain JavaScript when this tooling is needed. The "use ReScript" rule is retired — ReScript is no longer used in this estate. Do not migrate Bun to Deno. + +--- + +# This repo: `dicta-task` · clade `ap-dicta-task` + +- **Identity** — uuid `7aff6bbd-b776-503f-ba55-3f67c7106886`; clade `ap` (secondary ``); born 2026-03-14; forge `hyperpolymath/dicta-task`. +- **IS** — Speech-to-do pipeline: portable recorder → transcription → NLP parsing → prioritised task list +- **IS-NOT** — a cloud dictation service (ASR runs offline; cloud is encrypted backup only) · a general note-taking app · a template (it is minted from rsr-template-repo) +- **Where it sits** — pipeline position **leaf**; chain `standards → rsr-template-repo → dicta-task`; coordination = `standards`. +- **Constraints here** (AGENTIC) — fail-closed; evidence-per-step; no-silent-skip; rerun-after-fix; release-claim-requires-hard-pass. Never: banned langs (above), secrets, state files in repo root, AGPL. Details: `.machine_readable/bot_directives/{methodology,coverage,debt}.a2ml`. +- **Golden path** (ANCHOR) — `just test && just quality` → Core tests pass; Quality gates pass; No unresolved critical security findings. +- **State** — phase implementation; maturity experimental; 15% complete; status active. + + diff --git a/CODE_OF_CONDUCT.adoc b/CODE_OF_CONDUCT.adoc deleted file mode 100644 index bd2a83c..0000000 --- a/CODE_OF_CONDUCT.adoc +++ /dev/null @@ -1,24 +0,0 @@ -== Contributor Covenant Code of Conduct - -=== Our Pledge - -We pledge to make participation a harassment-free experience for -everyone. - -=== Our Standards - -*Positive behavior:* * Using welcoming language * Being respectful of -differing viewpoints * Accepting constructive criticism * Focusing on -what is best for the community - -*Unacceptable behavior:* * Harassment, trolling, or personal attacks * -Publishing private information without permission - -=== Enforcement - -Report issues to the maintainers. All complaints will be reviewed. - -=== Attribution - -Adapted from https://www.contributor-covenant.org/[Contributor Covenant] -v2.1. diff --git a/CONTRIBUTING.adoc b/CONTRIBUTING.adoc new file mode 100644 index 0000000..1bdec05 --- /dev/null +++ b/CONTRIBUTING.adoc @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: Jonathan D.A. Jewell (hyperpolymath) += Contributing — dicta-task +:toc: left + +Contributors work on the *spine*: the template every RSR repo is minted +from. Changes here propagate to the whole estate at mint time, so the bar +is the estate's bar. + +== Local-dev setup + +[source,bash] +---- +# Clone the repository +git clone https://github.com/hyperpolymath/dicta-task.git +cd dicta-task + +# Using Guix (recommended for reproducibility) +guix shell -D -f build/guix.scm + +# Or using toolbox/distrobox +toolbox create dicta-task-dev +toolbox enter dicta-task-dev +# Install dependencies manually + +# Verify setup +just check # or: cargo check / mix compile / etc. +just test # Run test suite +---- + +== Repository structure + +The authoritative map is *generated* from the tree and checked in CI, so +it cannot drift: +link:docs/architecture/REPOSITORY-MAP.adoc[docs/architecture/REPOSITORY-MAP.adoc]. +Regenerate it with `just repo-map`. + +A hand-written tree used to live here. It described `lib/`, `extensions/`, +`plugins/` and `spec/` directories that this repository has never +contained, which is precisely why the map is now generated rather than +typed. + +== How to contribute + +=== Reporting bugs + +*Before reporting:* + +. Search existing issues. +. Check if it is already fixed in `main`. +. Determine which perimeter the bug affects. + +*When reporting*, use the +link:.github/ISSUE_TEMPLATE/bug_report.md[bug report template] and include: + +* Clear, descriptive title +* Environment details (OS, versions, toolchain) +* Steps to reproduce +* Expected vs actual behaviour +* Logs, screenshots, or minimal reproduction + +=== Suggesting features + +*Before suggesting:* + +. Check the link:docs/status/ROADMAP.adoc[roadmap] if available. +. Search existing issues and discussions. +. Consider which perimeter the feature belongs to. + +*When suggesting*, use the +link:.github/ISSUE_TEMPLATE/feature_request.md[feature request template] +and include: + +* Problem statement (what pain point does this solve?) +* Proposed solution +* Alternatives considered +* Which perimeter this affects + +=== Your first contribution + +Look for issues labelled: + +* https://github.com/hyperpolymath/dicta-task/labels/good%20first%20issue[`good first issue`] — Simple Perimeter 3 tasks +* https://github.com/hyperpolymath/dicta-task/labels/help%20wanted[`help wanted`] — Community help needed +* https://github.com/hyperpolymath/dicta-task/labels/documentation[`documentation`] — Docs improvements +* https://github.com/hyperpolymath/dicta-task/labels/perimeter-3[`perimeter-3`] — Community sandbox scope + +== Development workflow + +=== Branch naming + +[source,text] +---- +docs/short-description # Documentation (P3) +test/what-added # Test additions (P3) +feat/short-description # New features (P2) +fix/issue-number-description # Bug fixes (P2) +refactor/what-changed # Code improvements (P2) +security/what-fixed # Security fixes (P1-2) +---- + +=== Commit messages + +We follow https://www.conventionalcommits.org/[Conventional Commits]: + +[source,text] +---- +(): + +[optional body] + +[optional footer] +---- + +== Cross-references + +This root document exists because the estate docs gate +(`hyperpolymath/standards` `scripts/check-docs-presence.sh`) requires +`CONTRIBUTING.md`, `CONTRIBUTING.adoc`, or `3-practice/CONTRIBUTING.adoc` +at the repository root. The legacy copy at +link:.github/CONTRIBUTING.md[.github/CONTRIBUTING.md] predates that gate; +this document supersedes it. diff --git a/Containerfile b/Containerfile deleted file mode 100644 index d4d9ac8..0000000 --- a/Containerfile +++ /dev/null @@ -1,41 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Containerfile for dictask -# Build: podman build -t dictask:latest -f Containerfile . -# Run: podman run --rm -it dictask:latest -# Seal: selur seal dictask:latest - -# --- Build stage (Rust components) --- -FROM cgr.dev/chainguard/wolfi-base:latest AS build-rust - -RUN apk add --no-cache rust cargo sqlite-dev - -WORKDIR /build -COPY src/ingest/ src/ingest/ -COPY src/transcribe/ src/transcribe/ -COPY src/store/ src/store/ -COPY schemas/ schemas/ - -RUN cd src/ingest && cargo build --release -RUN cd src/store && cargo build --release - -# --- Runtime stage --- -FROM cgr.dev/chainguard/wolfi-base:latest - -RUN apk add --no-cache sqlite - -COPY --from=build-rust /build/src/ingest/target/release/dictask-ingest /usr/local/bin/ -COPY --from=build-rust /build/src/store/target/release/dictask-store /usr/local/bin/ -COPY schemas/ /opt/dictask/schemas/ - -# Data directory -RUN mkdir -p /data/dictask && chown nonroot:nonroot /data/dictask - -ENV DICTASK_DATA_DIR=/data/dictask -ENV DICTASK_DB_PATH=/data/dictask/tasks.db - -USER nonroot -VOLUME ["/data/dictask"] - -ENTRYPOINT ["/usr/local/bin/dictask-ingest"] diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..417391d --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,8 @@ +# Pointer + +This repository has no `AGENTS.md` yet. Until it does, the instructions +for every coding agent live in **[CLAUDE.md](./CLAUDE.md)**. Read that +file, and skip anything in it that is specific to Claude Code tooling. +Do not duplicate rules here. + +When `AGENTS.md` lands in this repository, retarget this pointer at it. diff --git a/GOVERNANCE.adoc b/GOVERNANCE.adoc deleted file mode 100644 index 9b836fb..0000000 --- a/GOVERNANCE.adoc +++ /dev/null @@ -1,60 +0,0 @@ -== Governance - -=== Overview - -This project is governed by the following principles and structures to -ensure transparent, inclusive, and effective decision-making. - -=== Roles and Responsibilities - -==== Maintainers - -Maintainers are responsible for: - Reviewing and merging pull requests - -Managing releases and versioning - Ensuring code quality and standards - -Triaging issues and bug reports - Community engagement and support - -==== Contributors - -Contributors are expected to: - Follow the code of conduct - Submit -well-documented pull requests - Write tests for new functionality - -Maintain existing tests - Update documentation as needed - -=== Decision Making - -==== Minor Changes - -* Can be made by any maintainer -* Include bug fixes, documentation updates, dependency updates - -==== Major Changes - -* Require discussion in issues or pull requests -* Include new features, architectural changes, API changes -* Need approval from at least 2 maintainers - -==== Breaking Changes - -* Require RFC (Request for Comments) process -* Need approval from majority of maintainers -* Must include migration guide - -=== Code of Conduct - -All participants are expected to follow our Code of Conduct. Violations -can be reported to the maintainers. - -=== Communication - -* *Issues*: For bug reports and feature requests -* *Discussions*: For questions and general discussion -* *Pull Requests*: For code contributions - -=== Licensing - -All contributions are made under the terms of the repository’s LICENSE -file. By submitting a pull request, you agree to license your -contributions accordingly. - -''''' - -_Last updated: 2026-07-18_ diff --git a/Justfile b/Justfile index ae2cff2..05e8ffc 100644 --- a/Justfile +++ b/Justfile @@ -1,311 +1,709 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# dictask — Speech-to-Do Pipeline +# 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 -import? "contractile.just" +# Import auto-generated contractile recipes (must-check, trust-verify, etc.) +# Re-generate with: contractile gen-just +import? "build/contractile.just" -project := "dictask" +# Project metadata — customize these +project := "dicta-task" +OWNER := "hyperpolymath" +REPO := "dicta-task" version := "0.1.0" -tier := "2" +tier := "infrastructure" # 1 | 2 | infrastructure # ═══════════════════════════════════════════════════════════════════════════════ # DEFAULT & HELP # ═══════════════════════════════════════════════════════════════════════════════ -# Show all available recipes +# Show all available recipes with descriptions default: @just --list --unsorted -# Show project info +# 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 "Project: dicta_task" @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 + @[ -f ".machine_readable/descriptiles/STATE.a2ml" ] && grep -oP 'phase\s*=\s*"\K[^"]+' .machine_readable/descriptiles/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 — see build/just/repo-init.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/repo-init.just" + +# >>> container-module (three-tier: OCI · portable engine · stapeln) >>> +# Self-contained. Remove the entire block — this and the import — with `just no-container`. +import? "build/just/container.just" +# <<< container-module <<< + +# ═══════════════════════════════════════════════════════════════════════════════ +# GROOVE PROTOCOL — see build/just/groove.just +# ═══════════════════════════════════════════════════════════════════════════════ + +import? "build/just/groove.just" # ═══════════════════════════════════════════════════════════════════════════════ -# BUILD +# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE — see build/just/assess.just # ═══════════════════════════════════════════════════════════════════════════════ -# Build all components -build: build-rust build-haskell build-ffi +import? "build/just/assess.just" -# Build Rust components (ingest, transcribe, store) -build-rust: - cd src/ingest && cargo build - cd src/transcribe && cargo build - cd src/store && cargo build +# ═══════════════════════════════════════════════════════════════════════════════ +# BUILD & COMPILE +# ═══════════════════════════════════════════════════════════════════════════════ -# Build Rust components in release mode -build-rust-release: - cd src/ingest && cargo build --release - cd src/transcribe && cargo build --release - cd src/store && cargo build --release +# Build the project (debug mode) +# dicta-task Rust crates (built individually; see ADR-0004 / README) +RUST_CRATES := "ingest store transcribe" -# Build Haskell parser -build-haskell: - cd src/parse && cabal build +build *args: + #!/usr/bin/env bash + set -euo pipefail + # Rust crates are independent (no root Cargo.toml: RSR root shape). + for c in {{RUST_CRATES}}; do (cd "src/$c" && cargo build {{args}}); done + (cd src/parse && cabal build all) -# Build Zig FFI bridge -build-ffi: - cd src/interface/ffi && zig build +# Build in release mode with optimizations +build-release *args: + #!/usr/bin/env bash + set -euo pipefail + for c in {{RUST_CRATES}}; do (cd "src/$c" && cargo build --release {{args}}); done + (cd src/parse && cabal build all -O2) + +# 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 + # + # `build/` is DELIBERATELY ABSENT from this list. It is not an artifact + # directory in an RSR repo: it holds 11 tracked files, including + # build/just/repo-init.just, which the root Justfile imports at line 65. + # Deleting it destroys `just repo-init`, `just verify` and the proof gates. + rm -rf target/ _build/ dist/ out/ obj/ bin/ + +# Deep clean including caches [reversible: rebuild] +clean-all: clean + rm -rf .cache .tmp # ═══════════════════════════════════════════════════════════════════════════════ -# TEST +# TEST & QUALITY # ═══════════════════════════════════════════════════════════════════════════════ # Run all tests -test: test-rust test-haskell test-ffi +test *args: + #!/usr/bin/env bash + set -euo pipefail + for c in {{RUST_CRATES}}; do (cd "src/$c" && cargo test {{args}}); done + (cd src/parse && cabal test all --test-show-details=direct) + (cd src/interface/ffi && zig build test) + +# 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 Rust tests -test-rust: - cd src/ingest && cargo test - cd src/transcribe && cargo test - cd src/store && cargo test +# Run with verbose output +run-verbose *args: build + # TODO: Replace with verbose run command + echo "Run not configured yet" -# Run Haskell parser tests -test-haskell: - cd src/parse && cabal test +# Install to user path +install: build-release + @echo "Installing dicta_task..." + # TODO: Replace with your install command + +# ═══════════════════════════════════════════════════════════════════════════════ +# DEPENDENCIES +# ═══════════════════════════════════════════════════════════════════════════════ -# Run Zig FFI tests -test-ffi: - cd src/interface/ffi && zig build test +# 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 + @echo "Audit complete" # ═══════════════════════════════════════════════════════════════════════════════ -# PIPELINE (manual invocation) +# ARRIVAL PACK — agent-facing CLAUDE.md, compiled from a2ml # ═══════════════════════════════════════════════════════════════════════════════ -# Run ingest from a mounted recorder path -ingest mount_point="/media/recorder": - cd src/ingest && cargo run -- "{{mount_point}}" +# Compile CLAUDE.md (the agent arrival pack) from this repo's a2ml +claude-md: + @bash .machine_readable/arrival-pack/generate.sh + +# Regenerate the single authoritative repository map +repo-map: + @bash scripts/gen-repo-map.sh . + +# Fail if the repository map is stale (the map is generated; CI diffs it) +validate-repo-map: + #!/usr/bin/env bash + set -euo pipefail + cd "{{justfile_directory()}}" + before=$(mktemp); cp docs/architecture/REPOSITORY-MAP.adoc "$before" 2>/dev/null || true + bash scripts/gen-repo-map.sh . >/dev/null + if ! diff -q "$before" docs/architecture/REPOSITORY-MAP.adoc >/dev/null 2>&1; then + echo "FAIL: docs/architecture/REPOSITORY-MAP.adoc is stale. Run: just repo-map" >&2 + diff -u "$before" docs/architecture/REPOSITORY-MAP.adoc | head -40 >&2 || true + cp "$before" docs/architecture/REPOSITORY-MAP.adoc + rm -f "$before"; exit 1 + fi + rm -f "$before" + echo "repository map: up to date" + +# Fail if CLAUDE.md's generated region drifted from a2ml or was hand-edited +validate-claude-md: + @bash .machine_readable/arrival-pack/verify.sh + +# ═══════════════════════════════════════════════════════════════════════════════ +# COAPTATION — typed descriptile↔contractile face-off (homeostasis reading) +# ═══════════════════════════════════════════════════════════════════════════════ -# Transcribe a specific audio file -transcribe file: - @echo "STUB: transcription not yet implemented" - @echo "Would transcribe: {{file}}" +# Emit the coaptation receipt: how the descriptiles coapt with the contractiles (SITREP) +coapt: + @bash .machine_readable/coaptation/coapt.sh --report -# Parse a transcript file into candidate intents -parse file: - @echo "STUB: parsing not yet integrated as CLI" - @echo "Would parse: {{file}}" +# Assemble a re-anchor basis IF the band is red (the drop itself is a human act) +coapt-reanchor: + @bash .machine_readable/coaptation/coapt.sh --reanchor -# Generate views from canonical SQLite store -views: - @echo "STUB: view generation not yet implemented" - @echo "Would generate: Markdown, JSON, CSV views" +# Fail if the committed coaptation receipt drifted from the contractiles/descriptiles +validate-coapt: + @bash .machine_readable/coaptation/verify.sh # ═══════════════════════════════════════════════════════════════════════════════ -# DATABASE +# DOCUMENTATION # ═══════════════════════════════════════════════════════════════════════════════ -# Initialise SQLite database from schema -db-init: +# 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 - DB_PATH="${DICTASK_DATA_DIR:-$HOME/.local/share/dictask}/tasks.db" - mkdir -p "$(dirname "$DB_PATH")" - sqlite3 "$DB_PATH" ".read schemas/tasks.sql" - echo "Database initialised at: $DB_PATH" + mkdir -p docs + OUTPUT="docs/just-cookbook.adoc" + echo "= dicta_task 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" -# Show database stats -db-stats: +# Generate man page +man: #!/usr/bin/env bash - DB_PATH="${DICTASK_DATA_DIR:-$HOME/.local/share/dictask}/tasks.db" - echo "=== dictask Database Stats ===" - echo "Tasks: $(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM tasks;' 2>/dev/null || echo 'N/A')" - echo "Audio: $(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM audio_files;' 2>/dev/null || echo 'N/A')" - echo "Transcripts:$(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM transcripts;' 2>/dev/null || echo 'N/A')" - echo "Intents: $(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM candidate_intents;' 2>/dev/null || echo 'N/A')" - echo "Audit log: $(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM audit_log;' 2>/dev/null || echo 'N/A')" - echo "Review Q: $(sqlite3 "$DB_PATH" 'SELECT COUNT(*) FROM review_queue WHERE resolution IS NULL;' 2>/dev/null || echo 'N/A')" + mkdir -p docs/man + cat > docs/man/dicta_task.1 << EOF + .TH dicta_task 1 "$(date +%Y-%m-%d)" "{{version}}" "dicta_task Manual" + .SH NAME + dicta_task \- 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/dicta_task.1" # ═══════════════════════════════════════════════════════════════════════════════ -# DEPLOYMENT +# CI & AUTOMATION # ═══════════════════════════════════════════════════════════════════════════════ -# Run Ansible local setup -deploy-local: - cd deploy/ansible && ansible-playbook setup.yml +# Run full CI pipeline locally +# proof-check-all is FATAL if any prover toolchain is absent (idris2/lean/agda/coqc): +# the full CI gate must not pass on a machine that cannot verify the proofs. +ci: deps quality proof-check-all + @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 +# ═══════════════════════════════════════════════════════════════════════════════ -# Initialise Terraform -deploy-cloud-init: - cd deploy/terraform && terraform init +# Run security audit +security: deps-audit + @echo "=== Security Audit ===" + @command -v trivy >/dev/null && trivy fs --severity HIGH,CRITICAL . || true + @echo "Security audit complete" -# Apply Terraform (provision cloud resources) -deploy-cloud-apply: - cd deploy/terraform && terraform apply +# 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" # ═══════════════════════════════════════════════════════════════════════════════ -# CONTAINER +# VALIDATION & COMPLIANCE — see build/just/validate.just # ═══════════════════════════════════════════════════════════════════════════════ -# Build OCI container image -container-build: - podman build -t dictask:{{version}} -f Containerfile . +import? "build/just/validate.just" -# Run container -container-run: - podman run --rm -it dictask:{{version}} +# ═══════════════════════════════════════════════════════════════════════════════ +# STATE MANAGEMENT +# ═══════════════════════════════════════════════════════════════════════════════ + +# Update STATE.a2ml timestamp +state-touch: + @if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ + sed -i 's/last-updated = "[^"]*"/last-updated = "'"$(date +%Y-%m-%d)"'"/' .machine_readable/descriptiles/STATE.a2ml && \ + echo "STATE.a2ml timestamp updated"; \ + fi + +# Show current phase from STATE.a2ml +state-phase: + @sed -n 's/^[[:space:]]*phase[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' .machine_readable/descriptiles/STATE.a2ml 2>/dev/null | head -1 || echo "unknown" # ═══════════════════════════════════════════════════════════════════════════════ -# LINT & FORMAT +# GUIX # ═══════════════════════════════════════════════════════════════════════════════ -# Lint all code -lint: lint-rust lint-haskell +# Enter Guix development shell (primary) +guix-shell: + guix shell -D -f build/guix.scm -# Lint Rust code -lint-rust: - cd src/ingest && cargo clippy -- -D warnings - cd src/transcribe && cargo clippy -- -D warnings - cd src/store && cargo clippy -- -D warnings +# Build with Guix +guix-build: + guix build -f build/guix.scm -# Lint Haskell code -lint-haskell: - cd src/parse && cabal build --ghc-options="-Wall -Werror" +# ═══════════════════════════════════════════════════════════════════════════════ +# HYBRID AUTOMATION +# ═══════════════════════════════════════════════════════════════════════════════ -# Format Rust code -fmt-rust: - cd src/ingest && cargo fmt - cd src/transcribe && cargo fmt - cd src/store && cargo fmt +# 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 # ═══════════════════════════════════════════════════════════════════════════════ -# CLEAN +# COMBINATORIC MATRIX RECIPES # ═══════════════════════════════════════════════════════════════════════════════ -# Clean all build artifacts -clean: - cd src/ingest && cargo clean - cd src/transcribe && cargo clean - cd src/store && cargo clean - cd src/parse && cabal clean - cd src/interface/ffi && rm -rf zig-out zig-cache +# Build matrix: [debug|release] x [target] x [features] +build-matrix mode="debug" target="" features="": + @echo "Build matrix: mode={{mode}} target={{target}} features={{features}}" -# Run panic-attacker pre-commit scan -assail: - @command -v panic-attack >/dev/null 2>&1 && panic-attack assail . || echo "panic-attack not found — install from https://github.com/hyperpolymath/panic-attacker" +# 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}}" + +# 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] (needs container module)" + @echo "CI Matrix: just ci-matrix [lint|test|build|security|all] [quick|full]" # ═══════════════════════════════════════════════════════════════════════════════ -# ONBOARDING & DIAGNOSTICS +# VERSION CONTROL # ═══════════════════════════════════════════════════════════════════════════════ -# Check all required toolchain dependencies and report health -doctor: +# Show git status +status: + @git status --short + +# Show recent commits +log count="20": + @git log --oneline -{{count}} + +# Generate CHANGELOG.adoc with git-cliff +changelog: + @command -v git-cliff >/dev/null || { echo "git-cliff not found — install: cargo install git-cliff"; exit 1; } + # AsciiDoc, not .md: CHANGELOG.adoc is what root-allow.txt permits, so a + # .md here would fail check-root-shape AND the estate's no-.md rule the + # moment anyone ran this recipe. + git cliff --config .machine_readable/configs/git-cliff/cliff.toml --output CHANGELOG.adoc + @echo "Generated CHANGELOG.adoc" + +# 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 - echo "═══════════════════════════════════════════════════" - echo " Dictask Doctor — Toolchain Health Check" - echo "═══════════════════════════════════════════════════" - echo "" - PASS=0; FAIL=0; WARN=0 - check() { - local name="$1" cmd="$2" min="$3" - if command -v "$cmd" >/dev/null 2>&1; then - VER=$("$cmd" --version 2>&1 | head -1) - echo " [OK] $name — $VER" - PASS=$((PASS + 1)) - else - echo " [FAIL] $name — not found (need $min+)" - FAIL=$((FAIL + 1)) - fi - } - check "just" just "1.25" - check "git" git "2.40" -# Optional tools -if command -v panic-attack >/dev/null 2>&1; then - echo " [OK] panic-attack — available" - PASS=$((PASS + 1)) -else - echo " [WARN] panic-attack — not found (pre-commit scanner)" - WARN=$((WARN + 1)) -fi - echo "" - echo " Result: $PASS passed, $FAIL failed, $WARN warnings" - if [ "$FAIL" -gt 0 ]; then - echo " Run 'just heal' to attempt automatic repair." + TAG="v{{version}}" + if git rev-parse "$TAG" >/dev/null 2>&1; then + echo "Tag $TAG already exists" exit 1 fi - echo " All required tools present." + 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" -# Attempt to automatically install missing tools -heal: - #!/usr/bin/env bash - echo "═══════════════════════════════════════════════════" - echo " Dictask Heal — Automatic Tool Installation" - echo "═══════════════════════════════════════════════════" - echo "" -if ! command -v just >/dev/null 2>&1; then - echo "Installing just..." - cargo install just 2>/dev/null || echo "Install just from https://just.systems" -fi - echo "" - echo "Heal complete. Run 'just doctor' to verify." - -# Guided tour of the project structure and key concepts +# ═══════════════════════════════════════════════════════════════════════════════ +# 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 dicta-task..." + @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: - #!/usr/bin/env bash - echo "═══════════════════════════════════════════════════" - echo " Dictask — Guided Tour" - echo "═══════════════════════════════════════════════════" - echo "" - echo '// SPDX-License-Identifier: MPL-2.0' - echo "" - echo "Key directories:" - echo " src/ Source code" - echo " docs/ Documentation" - echo " tests/ Test suite" - echo " .github/workflows/ CI/CD workflows" - echo " .machine_readable/ Machine-readable metadata" - echo " container/ Container configuration" - echo " examples/ Usage examples" - echo "" - echo "Quick commands:" - echo " just doctor Check toolchain health" - echo " just heal Fix missing tools" - echo " just help-me Common workflows" - echo " just default List all recipes" - echo "" - echo "Read more: README.adoc, EXPLAINME.adoc" - -# Show help for common workflows + @echo "=== dicta-task 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: - #!/usr/bin/env bash - echo "═══════════════════════════════════════════════════" - echo " Dictask — Common Workflows" - echo "═══════════════════════════════════════════════════" - echo "" -echo "FIRST TIME SETUP:" -echo " just doctor Check toolchain" -echo " just heal Fix missing tools" -echo "" -echo "PRE-COMMIT:" -echo " just assail Run panic-attacker scan" -echo "" -echo "LEARN:" -echo " just tour Guided project tour" -echo " just default List all recipes" + @echo "=== dicta-task Help ===" + @echo "Platform: $(uname -s) $(uname -m)" + @echo "Shell: $SHELL" + @echo "" + @echo "To report an issue:" + @echo " https://github.com/hyperpolymath/dicta-task/issues/new" + @echo "" + @echo "Include the output of 'just doctor' in your report." +# ═══════════════════════════════════════════════════════════════════════════════ +# FORMAL VERIFICATION (PROOFS) — see build/just/proofs.just +# ═══════════════════════════════════════════════════════════════════════════════ -# 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" +import? "build/just/proofs.just" -# Generate a shields.io badge markdown for the current CRG grade -# 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)" +# ═══════════════════════════════════════════════════════════════════════════════ +# 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}}" + +secret-scan-trufflehog: + @command -v trufflehog >/dev/null && trufflehog filesystem . --only-verified || true + +# ═══════════════════════════════════════════════════════════════════════════════ +# WINDOWS RGONOMICS (CLOAKING) +# ═══════════════════════════════════════════════════════════════════════════════╓ +# Hide all dotfiles and dot-folders from Windows Explorer. On POSIX systems, +# leading-dot names are already hidden by convention, so these recipes are +# intentionally harmless no-ops. +cloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -bor [System.IO.FileAttributes]::Hidden }" + echo "Cloak engaged." + else + echo "Dotfiles are natively cloaked on this OS. No action required." + fi + +# Reveal dotfiles in Windows Explorer; on POSIX, explain the native mechanism. +uncloak: + #!/usr/bin/env bash + set -euo pipefail + if command -v powershell.exe >/dev/null 2>&1; then + powershell.exe -NoProfile -Command "Get-ChildItem -Path . -Force -Filter '.*' | Where-Object { \$_.Name -match '^\\.' } | ForEach-Object { \$_.Attributes = \$_.Attributes -band -bnot [System.IO.FileAttributes]::Hidden }" + echo "Cloak lifted." + else + echo "Use 'ls -a' to view dotfiles on this OS." + fi diff --git a/LICENSES/AGPL-3.0-or-later.txt b/LICENSES/AGPL-3.0-or-later.txt deleted file mode 100644 index be3f7b2..0000000 --- a/LICENSES/AGPL-3.0-or-later.txt +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index 37f6411..0000000 --- a/MAINTAINERS +++ /dev/null @@ -1,43 +0,0 @@ -# Maintainers - -This file lists the current maintainers of this project. - -## Active Maintainers - -| Name | GitHub | Role | Since | -|------|--------|------|-------| -| Metadatastician | @metadatastician | Primary | Project Start | - -## Emeritus Maintainers - -None at this time. - -## Becoming a Maintainer - -To become a maintainer: - -1. Demonstrate consistent, high-quality contributions -2. Show understanding of the project's goals and architecture -3. Be active in code reviews and community discussions -4. Be nominated by an existing maintainer -5. Be approved by consensus of existing maintainers - -## Maintainer Responsibilities - -- Reviewing and merging pull requests -- Managing releases -- Triaging issues -- Enforcing code standards -- Mentoring new contributors -- Participating in decision-making - -## Maintainer Expectations - -- Respond to issues and PRs in a timely manner -- Follow the code of conduct -- Be transparent in decision-making -- Communicate clearly and respectfully - ---- - -*Last updated: 2026-07-18* diff --git a/README.adoc b/README.adoc index 05db546..9564a85 100644 --- a/README.adoc +++ b/README.adoc @@ -1,26 +1,36 @@ -____ -[!TIP] *AI-Assisted Install:* Just tell any AI assistant: + -`+Set+` `+up+` `+dictask+` `+from+` -https://github.com/hyperpolymath/dictask[`+https://github.com/hyperpolymath/dictask+`] + -The AI reads the manifest, asks you a few questions, and handles -everything. -____ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2024-2026 Jonathan D.A. Jewell (hyperpolymath) += Dicta-Task +:toc: +:toc-placement: preamble + +// ── Licensing ─────────────────────────────────────────────────────────────── +image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg?logo=mozilla[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] +image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg?logo=creativecommons[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] +image:https://img.shields.io/badge/Provenance-Quantum--Safe-blueviolet[Quantum-Safe Provenance,link="docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt"] + +// ── Standard & quality gates ──────────────────────────────────────────────── +image:https://img.shields.io/badge/RSR-Rhodium_Standard-9C27B0[Rhodium Standard Repository,link="https://github.com/hyperpolymath/rhodium-standard-repositories"] +image:https://api.scorecard.dev/projects/github.com/hyperpolymath/dicta-task/badge[OpenSSF Scorecard,link="https://scorecard.dev/viewer/?uri=github.com/hyperpolymath/dicta-task"] +image:https://sonarcloud.io/api/project_badges/measure?project=hyperpolymath_dicta-task&metric=alert_status[SonarQube Quality Gate,link="https://sonarcloud.io/summary/new_code?id=hyperpolymath_dicta-task"] +image:https://archive.softwareheritage.org/badge/origin/https://github.com/hyperpolymath/dicta-task/[Archived in Software Heritage,link="https://archive.softwareheritage.org/browse/origin/?origin_url=https://github.com/hyperpolymath/dicta-task"] + +// ── Provenance & ecosystem ────────────────────────────────────────────────── +image:https://img.shields.io/badge/Idris-Inside-5E5086?logo=idris&logoColor=white[Idris Inside,link="https://github.com/hyperpolymath/proven"] +image:https://img.shields.io/badge/Zig-Zero--overhead-F7A41D?logo=zig&logoColor=black[Zig Zero-overhead FFI,link="https://github.com/hyperpolymath/typed-wasm"] +image:https://api.thegreenwebfoundation.org/greencheckimage/github.com[Green Web,link="https://www.thegreenwebfoundation.org/green-web-check/?url=github.com"] == Overview -*dictask* automates the ingestion of speech notes from a portable voice +*dicta-task* automates the ingestion of speech notes from a portable voice recorder into a structured, prioritised, auditable task list. -Plug in your recorder → dictask detects it, archives the audio, +Plug in your recorder → dicta-task detects it, archives the audio, transcribes it, extracts tasks with confidence scores, deduplicates against your existing list, and either auto-applies high-confidence items or queues ambiguous ones for review. -This project follows a *Dual-Track* architecture: - -* *Root:* High-level orientation and rich documentation for humans. -* *Sub-directories:* Machine-readable metadata and technical -implementation. == Architecture @@ -143,7 +153,7 @@ Every task carries its full provenance chain: ---- original_audio_hash (SHA-256) → transcript_version (Whisper v3 / Vosk v0.3.45) - → parser_version (dictask-parse v0.1.0) + → parser_version (dicta-task-parse v0.1.0) → change_set { timestamp, action, user_confirmation_state } ---- @@ -171,104 +181,88 @@ locally All pipeline stages are idempotent — reprocessing the same input produces the same output. -== Repository Structure - -[width="100%",cols="50%,50%",options="header",] -|=== -|Directory |Purpose -|`+.github/+` |Forge-specific metadata (CODEOWNERS, SECURITY.md, -workflows). - -|`+.machine_readable/+` |Canonical project state (6 a2ml files), bot -directives, and AI guides. - -|`+src/ingest/+` |Rust crate: udev detection, file archival, cloud -upload. - -|`+src/transcribe/+` |Rust crate: Whisper/Vosk ASR integration. - -|`+src/parse/+` |Haskell package: megaparsec NLP task extraction. - -|`+src/store/+` |Rust crate: SQLite canonical store + view generation. +== Name -|`+src/review/+` |Rust crate: review queue + notification. +*Dicta-Task* (from _dictate_ + _task_) was previously called `dictask`. The +repository was renamed in September 2026; the old GitHub URL redirects. -|`+src/interface/+` |Verified Interface Seams (Idris2 ABI, Zig FFI, -generated C headers). +== Repository layout -|`+deploy/ansible/+` |Ansible playbooks for local machine setup. +The RSR layout applies (see link:docs/architecture/REPOSITORY-MAP.adoc[the repository map]). +Project-specific locations: -|`+deploy/terraform/+` |Terraform configs for cloud provisioning -(bucket, IAM). - -|`+schemas/+` |SQLite schema, JSON schemas for intermediate formats. - -|`+container/+` |Stapeln container ecosystem. - -|`+docs/+` |Technical documentation (architecture, theory, practice). +[cols="1,3",options="header"] +|=== +| Path | What lives there +| `src/ingest/` | Rust crate: udev detection, archival (SHA-256), encrypted backup. +| `src/transcribe/` | Rust crate: offline ASR (Whisper/Vosk). +| `src/parse/` | Haskell package `dicta-task-parse`: megaparsec task extraction, dates, priority, dedup. +| `src/store/` | Rust crate: SQLite canonical store + Markdown/JSON views. +| `src/interface/` | Idris2 ABI (`Abi/Task.idr`), Zig FFI, generated C header. +| `src/definitions/schemas/` | SQLite schema, JSON schema for candidate intents, CUE config schema. +| `src/definitions/config.ncl` | Default pipeline configuration (Nickel). +| `build/deploy/` | Ansible (local machine) and Terraform (cloud backup bucket). +| `build/container/` | Stapeln container build. +| `docs/` | Human documentation (architecture, onboarding, status, decisions). +| `.machine_readable/` | Descriptiles, contractiles, policies read by tools. |=== -== Quick Start +== Quick start [source,bash] ---- -just init # Interactive bootstrap -just build # Build all components -just test # Run all tests -just ingest # Run ingest pipeline manually -just transcribe FILE # Transcribe a specific audio file -just parse FILE # Parse a transcript file -just views # Generate Markdown/JSON/CSV views -just container-build # Build verified OCI image +just # list all recipes +just build # build all components +just test # run all tests +just validate # RSR structure + metadata checks ---- -== Deployment - -=== Local Setup (Ansible) +Per component, until the root recipes are wired to them: [source,bash] ---- -cd deploy/ansible -ansible-playbook setup.yml +(cd src/parse && cabal test) # Haskell parser + tests +(cd src/store && cargo test) # Rust store +(cd src/ingest && cargo run -- /media/recorder) ---- -Sets up: udev rules, systemd service, Rust/Haskell toolchains, SQLite. - -=== Cloud Provisioning (Terraform) +== Deployment [source,bash] ---- -cd deploy/terraform -terraform init -terraform apply +(cd build/deploy/ansible && ansible-playbook setup.yml) # udev, systemd, toolchains, SQLite +(cd build/deploy/terraform && terraform init && terraform apply) # encrypted bucket, IAM ---- -Provisions: encrypted storage bucket, IAM roles, lifecycle rules. - == Testing * *Haskell parser:* HSpec + QuickCheck property-based tests -* *Rust components:* `+cargo+` `+test+` with integration tests against -real SQLite -* *Pipeline replay:* Re-run historical audio through newer parser -versions -* *Idris2 proofs:* Compile-time verification (no runtime tests needed) +* *Rust components:* `cargo test` with integration tests against real SQLite +* *Pipeline replay:* re-run historical audio through newer parser versions +* *Idris2 proofs:* compile-time verification -== Success Metrics +== Success metrics -* % of tasks auto-processed vs. requiring manual review +* % of tasks auto-processed vs. requiring manual review * False positive/negative rates for intent detection -* Time saved vs. manual note-taking and task entry +* Time saved vs. manual note-taking and task entry * Pipeline end-to-end latency (target: < 5 minutes per recording) -== Documentation +== Where to go next -* link:CONTRIBUTING.adoc[Contributing Guide] -* link:ROADMAP.adoc[Roadmap] -* link:TOPOLOGY.md[Architecture Topology] -* link:docs/architecture/[Architecture Details] +* link:docs/architecture/REPOSITORY-MAP.adoc[The repository map] — generated; what every directory is for. +* link:docs/EXPLAINME.adoc[EXPLAINME] — the engineering deep-dive: how the pieces actually work. +* link:docs/AFFIRMATION.adoc[AFFIRMATION] — the dated, signed honesty snapshot of the repo's true state. +* link:docs/AUDIT.adoc[AUDIT] — the release audit gate. +* link:docs/status/ROADMAP.adoc[ROADMAP] — what is built and what comes next. +* link:docs/architecture/TOPOLOGY.adoc[TOPOLOGY] — pipeline topology and completion dashboard. +* link:docs/decisions/0004-storage-backend.adoc[ADR-0004] — why SQLite stays canonical (and where LMDB fits). -== License +== Licence -SPDX-License-Identifier: CC-BY-SA-4.0 + -See LICENSE and docs/legal/ for details. +Code, configuration and scripts are link:LICENSE[Mozilla Public License 2.0] +(`MPL-2.0`); prose documentation is `CC-BY-SA-4.0`. Both texts live in +`LICENSES/`, and per-file `SPDX-License-Identifier` headers are authoritative. +The GitHub-detected licence is MPL-2.0 (the root `LICENSE`). Long-term +attribution uses Quantum-Safe Provenance — see +link:docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt[the Quantum-Safe Provenance exhibit]. diff --git a/SECURITY.adoc b/SECURITY.adoc deleted file mode 100644 index e47fdd9..0000000 --- a/SECURITY.adoc +++ /dev/null @@ -1,16 +0,0 @@ -== Security Policy - -=== Reporting a Vulnerability - -If you discover a security vulnerability, please report it responsibly. - -*Email:* j.d.a.jewell@open.ac.uk - -*Please include:* - Description of the vulnerability - Steps to -reproduce - Potential impact - -*Response timeline:* - Acknowledgement within 48 hours - Initial -assessment within 7 days - Fix or mitigation within 90 days - -*Safe harbour:* We will not pursue legal action against security -researchers who follow responsible disclosure. diff --git a/benches/template_bench.sh b/benches/template_bench.sh new file mode 100755 index 0000000..5fb58a8 --- /dev/null +++ b/benches/template_bench.sh @@ -0,0 +1,227 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Template Benchmarks +# Measures performance characteristics of template validation and build system + +set -euo pipefail + +REPO_ROOT="${1:-.}" +OUTPUT_FORMAT="${2:-human}" # human | json | csv + +# ANSI colors +BLUE='\033[0;34m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +log_info() { + echo -e "${BLUE}→${NC} $*" +} + +log_pass() { + echo -e "${GREEN}✓${NC} $*" +} + +# Ensure we have required commands +command -v /usr/bin/time >/dev/null 2>&1 || { + echo "Warning: /usr/bin/time not available, using built-in time" + TIME_CMD="time" +} + +TIME_CMD="/usr/bin/time -f %e" 2>/dev/null || TIME_CMD="time" + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "Template Benchmarks" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" + +declare -A results + +#============================================================================== +# BENCHMARK 1: Template Validation +#============================================================================== + +log_info "Running template validation benchmark" + +# Warm-up run +if [ -f "$REPO_ROOT/scripts/validate-template.sh" ]; then + bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true +fi + +# Timed runs +BENCH_RUNS=3 +TOTAL_TIME=0 + +for i in $(seq 1 $BENCH_RUNS); do + START=$(date +%s%N) + bash "$REPO_ROOT/scripts/validate-template.sh" "$REPO_ROOT" 0 > /dev/null 2>&1 || true + END=$(date +%s%N) + + # Convert to milliseconds + RUN_TIME=$(( (END - START) / 1000000 )) + TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) + + [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" +done + +AVG_VALIDATION_TIME=$(( TOTAL_TIME / BENCH_RUNS )) +results[validation]=$AVG_VALIDATION_TIME +log_pass "Validation: ${AVG_VALIDATION_TIME}ms average (${BENCH_RUNS} runs)" + +#============================================================================== +# BENCHMARK 2: Zig Build +#============================================================================== + +log_info "Running Zig build benchmark" + +if ! command -v zig &> /dev/null; then + echo " ⚠ Zig compiler not found - skipping Zig build benchmark" + results[zig_build]="skipped" +else + cd "$REPO_ROOT/src/interface/ffi" + + # Warm-up + zig build --summary off > /dev/null 2>&1 || true + + # Clean build + BENCH_RUNS=2 + TOTAL_TIME=0 + + for i in $(seq 1 $BENCH_RUNS); do + rm -rf zig-cache + + START=$(date +%s%N) + zig build --summary off > /dev/null 2>&1 || true + END=$(date +%s%N) + + RUN_TIME=$(( (END - START) / 1000000 )) + TOTAL_TIME=$(( TOTAL_TIME + RUN_TIME )) + + [ "$OUTPUT_FORMAT" = "human" ] && echo " Run $i: ${RUN_TIME}ms" + done + + AVG_BUILD_TIME=$(( TOTAL_TIME / BENCH_RUNS )) + results[zig_build]=$AVG_BUILD_TIME + log_pass "Zig build: ${AVG_BUILD_TIME}ms average (clean build, ${BENCH_RUNS} runs)" + + cd - > /dev/null +fi + +#============================================================================== +# BENCHMARK 3: Zig Tests +#============================================================================== + +log_info "Running Zig test benchmark" + +if ! command -v zig &> /dev/null; then + echo " ⚠ Zig compiler not found - skipping Zig test benchmark" + results[zig_test]="skipped" +else + cd "$REPO_ROOT/src/interface/ffi" + + # Warm-up + zig build test --summary off > /dev/null 2>&1 || true + + START=$(date +%s%N) + TEST_OUTPUT=$(zig build test --summary off 2>&1 || true) + END=$(date +%s%N) + + TEST_TIME=$(( (END - START) / 1000000 )) + results[zig_test]=$TEST_TIME + log_pass "Zig tests: ${TEST_TIME}ms" + + # Count tests + TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -c "^test " || echo "unknown") + echo " Test count: $TEST_COUNT" + + cd - > /dev/null +fi + +#============================================================================== +# BENCHMARK 4: Workflow Validation +#============================================================================== + +log_info "Running workflow validation benchmark" + +if [ -f "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" ]; then + START=$(date +%s%N) + bash "$REPO_ROOT/tests/workflows/validate_workflows_test.sh" "$REPO_ROOT/.github/workflows" > /dev/null 2>&1 || true + END=$(date +%s%N) + + WORKFLOW_TIME=$(( (END - START) / 1000000 )) + results[workflow_validation]=$WORKFLOW_TIME + log_pass "Workflow validation: ${WORKFLOW_TIME}ms" +fi + +#============================================================================== +# BENCHMARK 5: Template Instantiation +#============================================================================== + +log_info "Running template instantiation benchmark" + +if [ -f "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" ]; then + START=$(date +%s%N) + bash "$REPO_ROOT/tests/e2e/template_instantiation_test.sh" "$REPO_ROOT" > /dev/null 2>&1 || true + END=$(date +%s%N) + + INSTANTIATION_TIME=$(( (END - START) / 1000000 )) + results[instantiation]=$INSTANTIATION_TIME + log_pass "Template instantiation: ${INSTANTIATION_TIME}ms" +fi + +#============================================================================== +# SUMMARY +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "BENCHMARK RESULTS" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" + +if [ "$OUTPUT_FORMAT" = "json" ]; then + echo "{" + echo " \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"," + echo " \"repo\": \"$REPO_ROOT\"," + echo " \"results\": {" + + count=0 + for key in "${!results[@]}"; do + value="${results[$key]}" + [ $count -gt 0 ] && echo "," + if [ "$value" = "skipped" ]; then + echo -n " \"$key\": \"skipped\"" + else + echo -n " \"$key\": $value" + fi + count=$((count + 1)) + done + echo "" + echo " }" + echo "}" +elif [ "$OUTPUT_FORMAT" = "csv" ]; then + echo "metric,value_ms,timestamp" + for key in "${!results[@]}"; do + value="${results[$key]}" + if [ "$value" != "skipped" ]; then + echo "$key,$value,$(date -u +%Y-%m-%dT%H:%M:%SZ)" + fi + done +else + # Human-readable format + for key in "${!results[@]}"; do + value="${results[$key]}" + if [ "$value" = "skipped" ]; then + printf " %-30s %s\n" "$key:" "SKIPPED" + else + printf " %-30s %5d ms\n" "$key:" "$value" + fi + done +fi + +echo "" +echo "Benchmark complete." +echo "" diff --git a/build/container/.containerignore b/build/container/.containerignore new file mode 100644 index 0000000..a4d6519 --- /dev/null +++ b/build/container/.containerignore @@ -0,0 +1,59 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Build-context exclusions for the OCI image build (Tier A). +# Honoured by podman, nerdctl and docker (all read .containerignore; +# docker also reads .dockerignore — this file is the portable name). +# +# Keeping the context lean speeds builds and prevents secrets, VCS +# history and local cruft from leaking into image layers. + +# Version control +.git/ +.gitignore +.gitattributes + +# CI / forge metadata +.github/ +ci/ + +# Editor / agent / local state +.claude/ +.devcontainer/ +.editorconfig +.envrc +.direnv/ +*.swp +*~ + +# Build outputs and caches +build/ttc/ +target/ +_build/ +deps/ +zig-out/ +zig-cache/ +node_modules/ +dist/ +result/ + +# Docs, tests, examples (not needed in the runtime image) +docs/ +build/docs-seed/ +benches/ +examples/ +tests/ + +# The container tooling itself (don't recurse the build files into the image) +build/container/stapeln/ +build/container/compose*.yaml +build/container/compose*.toml +build/container/.containerignore +build/container/README.adoc + +# Secrets and environment files — never ship these +*.env +.env +.env.* +*.pem +*.key +secrets/ diff --git a/container/Containerfile b/build/container/Containerfile similarity index 58% rename from container/Containerfile rename to build/container/Containerfile index b1cea9f..0619c70 100644 --- a/container/Containerfile +++ b/build/container/Containerfile @@ -1,18 +1,26 @@ # SPDX-License-Identifier: MPL-2.0 -# {{PROJECT_NAME}} Container Image +# Dicta-Task Container Image # # Multi-stage build template for Chainguard Wolfi base images. # Customise the builder stage for your language and copy the # resulting binary/release into the minimal runtime stage. # -# Build with Podman: -# podman build -t {{SERVICE_NAME}}:latest -f container/Containerfile . +# This is the Tier-A artifact: a plain OCI image, buildable by ANY +# OCI-compliant engine — podman (recommended), nerdctl, or docker. +# It carries no hard dependency on the stapeln tier; the cerro-torre / +# svalinn files under build/container/stapeln/ are referenced only as labels. +# +# Build (engine-agnostic — substitute podman | nerdctl | docker): +# podman build -t dicta-task:latest -f build/container/Containerfile . # # Run: -# podman run -p {{PORT}}:{{PORT}} {{SERVICE_NAME}}:latest +# podman run -p 8080:8080 dicta-task:latest # # Run with persistent volume: -# podman run -p {{PORT}}:{{PORT}} -v {{SERVICE_NAME}}-data:/data {{SERVICE_NAME}}:latest +# podman run -p 8080:8080 -v dicta-task-data:/data dicta-task:latest +# +# Via the Justfile (auto-detects engine, prefers cerro-torre when present): +# just container-build # ============================================================================ # Stage 1: Builder @@ -29,7 +37,7 @@ # COPY Cargo.toml Cargo.lock ./ # COPY src/ ./src/ # RUN cargo build --release -# # Output: /build/target/release/{{SERVICE_NAME}} +# # Output: /build/target/release/dicta-task # # --- Elixir --- # RUN apk add --no-cache erl27-elixir-1.18 erlang-27 erlang-27-dev git build-base @@ -39,15 +47,20 @@ # ENV MIX_ENV=prod # RUN mix local.hex --force && mix local.rebar --force && \ # mix deps.get --only prod && mix compile && mix release -# # Output: /build/_build/prod/rel/{{SERVICE_NAME}}/ +# # Output: /build/_build/prod/rel/dicta-task/ # # --- Zig --- # RUN apk add --no-cache zig build-base # COPY build.zig build.zig.zon ./ # COPY src/ ./src/ # RUN zig build -Doptimize=ReleaseFast -# # Output: /build/zig-out/bin/{{SERVICE_NAME}} +# # Output: /build/zig-out/bin/dicta-task # +# Base image: `:latest` is right for a template scaffold (Chainguard rebuilds +# Wolfi daily). For PRODUCTION builds, pin by digest for reproducibility/supply- +# chain integrity, e.g.: +# FROM cgr.dev/chainguard/wolfi-base:latest@sha256: AS builder +# (cerro-torre's .ctp signing in build/container/stapeln/ records the resolved digest.) FROM cgr.dev/chainguard/wolfi-base:latest AS builder # TODO: Install your language toolchain @@ -69,16 +82,16 @@ COPY . . FROM cgr.dev/chainguard/wolfi-base:latest # OCI image labels (compatible with cerro-torre .ctp bundle metadata) -LABEL org.opencontainers.image.title="{{PROJECT_NAME}}" \ - org.opencontainers.image.description="{{PROJECT_DESCRIPTION}}" \ - org.opencontainers.image.url="https://github.com/hyperpolymath/dictask" \ - org.opencontainers.image.source="https://github.com/hyperpolymath/dictask" \ +LABEL org.opencontainers.image.title="Dicta-Task" \ + org.opencontainers.image.description="Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list" \ + org.opencontainers.image.url="https://github.com/hyperpolymath/dicta-task" \ + org.opencontainers.image.source="https://github.com/hyperpolymath/dicta-task" \ org.opencontainers.image.vendor="hyperpolymath" \ - org.opencontainers.image.licenses="{{LICENSE}}" \ - org.opencontainers.image.authors="{{AUTHOR}} <{{AUTHOR_EMAIL}}>" \ - dev.cerrotorre.manifest="container/manifest.toml" \ - dev.cerrotorre.gatekeeper="container/.gatekeeper.yaml" \ - dev.stapeln.compose="container/compose.toml" + org.opencontainers.image.licenses="MPL-2.0" \ + org.opencontainers.image.authors="Jonathan D.A. Jewell " \ + dev.cerrotorre.manifest="build/container/stapeln/manifest.toml" \ + dev.cerrotorre.gatekeeper="build/container/stapeln/.gatekeeper.yaml" \ + dev.stapeln.compose="build/container/stapeln/compose.toml" # Install minimal runtime dependencies. # Adjust this list for your application: @@ -96,17 +109,20 @@ WORKDIR /app # TODO: Copy compiled binary/release from builder stage. # Examples: -# COPY --from=builder /build/target/release/{{SERVICE_NAME}} /app/{{SERVICE_NAME}} -# COPY --from=builder /build/_build/prod/rel/{{SERVICE_NAME}} /app/release/ -# COPY --from=builder /build/zig-out/bin/{{SERVICE_NAME}} /app/{{SERVICE_NAME}} +# COPY --from=builder /build/target/release/dicta-task /app/dicta-task +# COPY --from=builder /build/_build/prod/rel/dicta-task /app/release/ +# COPY --from=builder /build/zig-out/bin/dicta-task /app/dicta-task # Copy entrypoint script -COPY container/entrypoint.sh /app/entrypoint.sh +COPY build/container/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh -# Copy stapeln integration files (svalinn gatekeeper policy, cerro-torre manifest) -COPY container/.gatekeeper.yaml /etc/svalinn/gatekeeper.yaml -COPY container/manifest.toml /app/manifest.toml +# OPTIONAL (Tier C — stapeln). A pure OCI image does NOT need these baked in: +# svalinn reads .gatekeeper.yaml from a mounted volume (see stapeln/compose.toml), +# and cerro-torre reads manifest.toml at pack time. Uncomment only if you want +# the policy/manifest embedded in the image itself. +# COPY build/container/stapeln/.gatekeeper.yaml /etc/svalinn/gatekeeper.yaml +# COPY build/container/stapeln/manifest.toml /app/manifest.toml # Create data directory for persistent storage (mountable volume) RUN mkdir -p /data && chown appuser:appuser /data @@ -116,7 +132,7 @@ RUN chown -R appuser:appuser /app # Environment variables — customise for your application ENV APP_HOST=[::] -ENV APP_PORT={{PORT}} +ENV APP_PORT=8080 ENV APP_LOG_FORMAT=json ENV APP_DATA_DIR=/data @@ -127,7 +143,7 @@ VOLUME ["/data"] USER appuser # Expose the application port -EXPOSE {{PORT}} +EXPOSE 8080 # Health check — the application must respond 2xx at /health HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ diff --git a/build/container/README.adoc b/build/container/README.adoc new file mode 100644 index 0000000..0f9b5ce --- /dev/null +++ b/build/container/README.adoc @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Dicta-Task Containerisation +:toc: left +:toclevels: 3 +:sectnums: + +== Overview + +Containerisation in this template is organised in *three tiers*, from the most +portable to the most sovereign. You can use only the tier you need and ignore — +or delete — the rest. + +[cols="1,2,4"] +|=== +| Tier | What | Why + +| *A — OCI standard* +| `Containerfile`, `.containerignore` +| A plain, multi-stage OCI image on https://www.chainguard.dev/[Chainguard] + Wolfi. Builds with any OCI engine. No dependency on tiers B or C. + +| *B — Portable engine* +| `compose.yaml`, `compose.example.yaml` +| A https://compose-spec.io[compose-spec] stack that runs unchanged on + `podman`, `nerdctl` or `docker`. For everyday local and simple production use. + +| *C — stapeln (sovereign)* +| `stapeln/` directory +| Verified `.ctp` bundles, zero-copy IPC, edge-gateway trust policy, secrets + gating, runtime monitoring and signed deployment — the full + https://github.com/hyperpolymath/stapeln[stapeln] stack. +|=== + +All files use `{{PLACEHOLDER}}` tokens, replaced by `just container-init` +(or the top-level `just repo-init` during project bootstrap). + +The container engine is *auto-detected* (`podman`, then `nerdctl`, then +`docker`). Override with `CONTAINER_ENGINE=docker just container-build`. + +== File Reference + +=== Tier A + B (this directory) + +[cols="1,3"] +|=== +| File | Purpose + +| `Containerfile` +| Multi-stage OCI build. Stage 1 builds the app; Stage 2 is a minimal Wolfi + runtime image. Engine-agnostic. Carries the stapeln files only as labels, + so it has no hard dependency on tier C. + +| `.containerignore` +| Build-context exclusions (honoured by podman/nerdctl/docker). Keeps `.git`, + secrets, docs, tests and build caches out of image layers. + +| `compose.yaml` +| Portable compose-spec stack: app + `rokur` + `svalinn`. Use with + `just container-up` or `podman compose -f build/container/compose.yaml up -d`. + +| `compose.example.yaml` +| Concrete multi-service example (Rust API + Elixir worker + gate + gateway). + Copy to `compose.yaml` and customise. + +| `entrypoint.sh` +| Container entrypoint: signal handling (SIGTERM/SIGINT), startup logging, + `exec` into the main process. +|=== + +=== Tier C — `stapeln/` + +[cols="1,3"] +|=== +| File | Purpose + +| `compose.toml` +| *selur-compose* stack definition (TOML, selur-native — not parseable by + `podman compose`; use `compose.yaml` for that). Declares services, volumes, + the selur zero-copy network, and health checks. + +| `compose.example.toml` +| Concrete multi-service selur example. + +| `manifest.toml` +| *cerro-torre* `.ctp` bundle metadata: provenance, dependencies, + attestations, and the runtime security profile. Used by `ct pack` / `ct verify`. + +| `.gatekeeper.yaml` +| *svalinn* edge-gateway policy: authentication, rate limiting, container + trust, request validation, CORS, audit logging. + +| `rokur.toml` +| *rokur* secrets-gate configuration: required secrets, listener/backend, + rate limiting, policy engine, and audit log. + +| `vordr.toml` +| *vordr* runtime monitoring: health/readiness probes (app, rokur, svalinn), + crash detection, resource thresholds, structured logs. + +| `ct-build.sh` +| *cerro-torre* build → pack → sign (Ed25519) → verify → push pipeline. + Engine-agnostic; degrades gracefully when cerro-torre tools are absent. + +| `deploy.k9.ncl` +| *k9-svc* deployment component at Hunt trust level: full pedigree (L1–L5), + dev/staging/prod configs, rolling deployment strategy. +|=== + +== The stapeln ecosystem + +The sovereign tier wires together the stapeln container stack: + +*cerro-torre* (build + sign):: + Produces signed, minimal `.ctp` bundles from OCI images. Tools: `ct pack`, + `ct sign`, `ct verify`, `ct push`, `ct explain`. + +*selur* (compose + IPC):: + Orchestration with zero-copy IPC for co-located services. Reads + `stapeln/compose.toml`. + +*rokur* (secrets gate):: + A service that fronts the application and refuses to pass traffic unless + the declared secrets are present; rate-limits per client and writes a + structured audit log. Configured by `stapeln/rokur.toml`. In the stack it + sits between `svalinn` and the app. + +*svalinn* (edge gateway):: + Policy-driven reverse proxy: TLS termination, authentication, rate limiting, + CORS and container trust, from `stapeln/.gatekeeper.yaml`. + +*vordr* (monitoring):: + Runtime monitoring: health probes, crash detection, resource tracking, + structured logs, from `stapeln/vordr.toml`. + +*k9-svc* (deployment):: + Nickel-based deployment components with pedigree and trust levels + (Kennel / Yard / Hunt). + +The request path in the full stack is: +`svalinn` (edge) → `rokur` (secrets gate) → `dicta-task` (app). + +== Initialise + +[source,bash] +---- +just repo-init # full project bootstrap (all placeholders) +# or +just container-init # container-only: prompts for service/port/registry +---- + +== Everyday workflow (tiers A + B) + +[source,bash] +---- +just container-build # build image (auto-detected engine) +just container-verify # validate the compose config +just container-up -d # start the stack +just container-down # stop the stack + +# Explicit engine: +CONTAINER_ENGINE=docker just container-build +---- + +`just container-up` uses `selur-compose` automatically when it is installed, +otherwise it falls back to ` compose -f build/container/compose.yaml`. + +== Sovereign workflow (tier C) + +[source,bash] +---- +just container-sign # build → pack → sign → verify (.ctp) +just container-push # push the signed bundle +cd build/container/stapeln && selur-compose up --detach +---- + +k9-svc managed deployment: + +[source,bash] +---- +nickel typecheck build/container/stapeln/deploy.k9.ncl +k9-svc deploy build/container/stapeln/deploy.k9.ncl --env production +---- + +== Continuous integration + +A `container build` workflow ships in `.github/workflows/container-build.yml`. +It is **off by default** — it costs nothing in a repository created from this +template until you opt in. + +To enable it, set a repository (or organisation) variable: + +[cols="1,3"] +|=== +| Variable | Effect + +| `CONTAINER_CI=true` +| Enables the workflow. It builds the image (`just container-build`), verifies + the compose config, runs a best-effort `trivy` scan, and on `v*` tags signs + the `.ctp` bundle (`just container-sign`). + +| `CONTAINER_RUNNER` +| Optional JSON array of runner labels. Defaults to + `["self-hosted","owned","container"]` — owned compute, so **no metered + GitHub Actions minutes**. Set e.g. `["ubuntu-latest"]` to use GitHub-hosted + runners instead. +|=== + +[source,bash] +---- +gh variable set CONTAINER_CI --body true +# optional: gh variable set CONTAINER_RUNNER --body '["ubuntu-latest"]' +---- + +The owned runner is expected to have `just` and a container engine installed; +`trivy` and cerro-torre (`ct`/`cerro-sign`) are used when present and skipped +otherwise. + +== Removing containerisation + +If your project does not need containers (e.g. a pure library), strip the whole +apparatus in one command: + +[source,bash] +---- +just no-container +---- + +This removes `build/container/`, `.devcontainer/`, the `build/just/container.just` +module and its Justfile import. Review `.github/workflows/` afterwards for any +image build/publish jobs you no longer need. + +== Base images + +* Builder: `cgr.dev/chainguard/wolfi-base:latest` +* Runtime: `cgr.dev/chainguard/wolfi-base:latest` (or + `cgr.dev/chainguard/static:latest` for static binaries) + +Chainguard images are minimal, low-CVE, and rebuilt daily (`apk` package +manager, Alpine-compatible). + +== Container runtime + +Podman is recommended, but the OCI Containerfile and compose-spec files work +unchanged with `nerdctl` and `docker`. Set `CONTAINER_ENGINE` to choose. diff --git a/build/container/compose.example.yaml b/build/container/compose.example.yaml new file mode 100644 index 0000000..cbd43aa --- /dev/null +++ b/build/container/compose.example.yaml @@ -0,0 +1,108 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Example portable compose stack (Tier B) — multi-service +# +# A concrete, fully-commented example: a Rust API + Elixir worker behind the +# rokur secrets gate and the svalinn edge gateway. Copy to compose.yaml and +# customise. Works with podman / nerdctl / docker compose unchanged. +# +# cp build/container/compose.example.yaml build/container/compose.yaml +# # edit service names, ports, images +# just container-up # or: podman compose -f build/container/compose.yaml up -d +# +# For the sovereign selur path see build/container/stapeln/compose.example.toml. + +services: + # Rust API — primary HTTP/gRPC backend. + rust-api: + build: + context: .. + dockerfile: build/container/Containerfile + image: ghcr.io/hyperpolymath/myproject-api:latest + ports: + - "8080:8080" + environment: + RUST_LOG: "info" + APP_HOST: "[::]" + APP_PORT: "8080" + APP_LOG_FORMAT: "json" + APP_DATA_DIR: "/data" + volumes: + - api-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + + # Elixir worker — background processing, talks to the API over the network. + elixir-worker: + image: ghcr.io/hyperpolymath/myproject-worker:latest + ports: + - "4000:4000" + environment: + API_URL: "http://rust-api:8080/api/v1" + MIX_ENV: "prod" + APP_LOG_FORMAT: "json" + POOL_SIZE: "10" + depends_on: + rust-api: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:4000/health"] + interval: 30s + timeout: 5s + retries: 3 + + # rokur — secrets gate. Refuses to come up unless required secrets are present. + rokur: + image: ghcr.io/hyperpolymath/rokur:latest + ports: + - "8081:8081" + environment: + ROKUR_BACKEND: "http://rust-api:8080" + ROKUR_LISTEN: "[::]:8081" + ROKUR_REQUIRED_SECRETS: "DATABASE_URL,JWT_SIGNING_KEY" + ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" + ROKUR_LOG_FORMAT: "json" + volumes: + - rokur-audit:/var/log/rokur + depends_on: + rust-api: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] + interval: 30s + timeout: 5s + retries: 3 + + # svalinn — edge gateway. TLS, auth, rate limiting, audit. The only + # externally exposed service. + svalinn: + image: ghcr.io/hyperpolymath/svalinn:latest + ports: + - "443:443" + - "80:80" + environment: + SVALINN_BACKEND: "http://rokur:8081" + SVALINN_WORKER_BACKEND: "http://elixir-worker:4000" + SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" + SVALINN_TLS_AUTO: "true" + volumes: + - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro + depends_on: + - rokur + - elixir-worker + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:80/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + api-data: + rokur-audit: diff --git a/build/container/compose.yaml b/build/container/compose.yaml new file mode 100644 index 0000000..94d68a7 --- /dev/null +++ b/build/container/compose.yaml @@ -0,0 +1,99 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Dicta-Task — portable compose stack (Tier B) +# +# This is the runtime-agnostic, OCI compose-spec file. It works unchanged with: +# podman compose -f build/container/compose.yaml up -d +# nerdctl compose -f build/container/compose.yaml up -d +# docker compose -f build/container/compose.yaml up -d +# +# It has NO dependency on the stapeln tier. For the sovereign path +# (selur zero-copy IPC, signed .ctp bundles, svalinn trust policy) use +# build/container/stapeln/compose.toml with `selur-compose` instead. +# +# The Justfile picks the right one automatically: +# just container-up # selur-compose if installed, else this file +# +# See https://compose-spec.io for the full specification. + +services: + # ────────────────────────────────────────────────────────────────────── + # Primary application service + # ────────────────────────────────────────────────────────────────────── + "dicta-task": + build: + context: .. + dockerfile: build/container/Containerfile + image: "ghcr.io/hyperpolymath/dicta-task:latest" + ports: + - "8080:8080" + environment: + APP_HOST: "[::]" + APP_PORT: "8080" + APP_LOG_FORMAT: "json" + APP_DATA_DIR: "/data" + volumes: + - "dicta-task-data:/data" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + + # ────────────────────────────────────────────────────────────────────── + # rokur — secrets-management gate (stapeln). Fronts the app and refuses + # to start the stack unless required secrets are present. on Wolfi. + # Remove this service if you do not use secret gating. + # ────────────────────────────────────────────────────────────────────── + rokur: + image: ghcr.io/hyperpolymath/rokur:latest + environment: + ROKUR_BACKEND: "http://dicta-task:8080" + ROKUR_LISTEN: "[::]:8081" + ROKUR_REQUIRED_SECRETS: "" # comma-separated env names that MUST be set + ROKUR_AUDIT_LOG: "/var/log/rokur/audit.jsonl" + ROKUR_LOG_FORMAT: "json" + ports: + - "8081:8081" + volumes: + - rokur-audit:/var/log/rokur + depends_on: + "dicta-task": + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:8081/health"] + interval: 30s + timeout: 5s + retries: 3 + + # ────────────────────────────────────────────────────────────────────── + # svalinn — edge gateway (stapeln). TLS termination, auth, rate limiting. + # Reads the gatekeeper policy from build/container/stapeln/.gatekeeper.yaml. + # Remove this service for a bare app with no gateway. + # ────────────────────────────────────────────────────────────────────── + svalinn: + image: ghcr.io/hyperpolymath/svalinn:latest + ports: + - "443:443" + - "80:80" + environment: + SVALINN_BACKEND: "http://rokur:8081" + SVALINN_POLICY_FILE: "/etc/svalinn/gatekeeper.yaml" + SVALINN_TLS_AUTO: "true" + volumes: + - ./stapeln/.gatekeeper.yaml:/etc/svalinn/gatekeeper.yaml:ro + depends_on: + - rokur + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:80/health"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + "dicta-task-data": + rokur-audit: diff --git a/container/entrypoint.sh b/build/container/entrypoint.sh similarity index 85% rename from container/entrypoint.sh rename to build/container/entrypoint.sh index a7a0369..c649d23 100755 --- a/container/entrypoint.sh +++ b/build/container/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/sh # SPDX-License-Identifier: MPL-2.0 -# {{PROJECT_NAME}} container entrypoint +# Dicta-Task container entrypoint # # Handles signal propagation, startup logging, and health check # preparation before exec-ing into the main application process. @@ -15,7 +15,7 @@ set -e # when Podman sends stop signals (e.g. `podman stop`, `selur-compose down`). cleanup() { - echo "Received shutdown signal — stopping {{SERVICE_NAME}}..." + echo "Received shutdown signal — stopping dicta-task..." # If the main process is backgrounded, kill it here: # kill "$MAIN_PID" 2>/dev/null || true # wait "$MAIN_PID" 2>/dev/null || true @@ -27,9 +27,9 @@ trap cleanup TERM INT # Startup logging # --------------------------------------------------------------------------- -echo "Starting {{SERVICE_NAME}}..." +echo "Starting dicta-task..." echo " Host: ${APP_HOST:-[::]}" -echo " Port: ${APP_PORT:-{{PORT}}}" +echo " Port: ${APP_PORT:-8080}" echo " Data: ${APP_DATA_DIR:-/data}" echo " Log: ${APP_LOG_FORMAT:-json}" @@ -56,8 +56,8 @@ fi # # TODO: Replace the command below with your application binary. # Examples: -# exec /app/{{SERVICE_NAME}} -# exec /app/release/bin/{{SERVICE_NAME}} start -# exec /app/{{SERVICE_NAME}} serve --host "${APP_HOST}" --port "${APP_PORT}" +# exec /app/dicta-task +# exec /app/release/bin/dicta-task start +# exec /app/dicta-task serve --host "${APP_HOST}" --port "${APP_PORT}" exec "$@" diff --git a/container/.gatekeeper.yaml b/build/container/stapeln/.gatekeeper.yaml similarity index 96% rename from container/.gatekeeper.yaml rename to build/container/stapeln/.gatekeeper.yaml index f8e0ec2..dcd4bf3 100644 --- a/container/.gatekeeper.yaml +++ b/build/container/stapeln/.gatekeeper.yaml @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MPL-2.0 # -# Svalinn gatekeeper policy for Dictask +# Svalinn gatekeeper policy for Dicta-Task # # Controls which operations are permitted through the edge gateway. # This template provides moderate security defaults — not wide-open test @@ -65,9 +65,9 @@ rate_limits: trust: # Only accept .ctp bundles signed by these keys. trusted_signers: - - key_id: "dictask-release" + - key_id: "dicta-task-release" algorithm: "Ed25519" - public_key_file: "/etc/svalinn/keys/dictask-release.pub" + public_key_file: "/etc/svalinn/keys/dicta-task-release.pub" # Require these attestations on all .ctp bundles. required_attestations: diff --git a/container/compose.example.toml b/build/container/stapeln/compose.example.toml similarity index 99% rename from container/compose.example.toml rename to build/container/stapeln/compose.example.toml index d8d717c..1dd5794 100644 --- a/container/compose.example.toml +++ b/build/container/stapeln/compose.example.toml @@ -37,7 +37,7 @@ environment = { } # Bind-mount a named volume for persistent data. -# Format: "volume-name:/container/path" +# Format: "volume-name:/build/container/path" volumes = ["api-data:/data"] # Restart policy: "always" ensures the service comes back after crashes. diff --git a/container/compose.toml b/build/container/stapeln/compose.toml similarity index 58% rename from container/compose.toml rename to build/container/stapeln/compose.toml index c351ffd..e9a52f1 100644 --- a/container/compose.toml +++ b/build/container/stapeln/compose.toml @@ -1,6 +1,6 @@ # SPDX-License-Identifier: MPL-2.0 # -# dictask selur-compose configuration +# Dicta-Task selur-compose configuration # # Orchestrates the container stack as verified container bundles (.ctp). # Uses selur zero-copy IPC between services on the same host. @@ -10,11 +10,13 @@ # selur-compose up --detach # Start in background # selur-compose verify # Verify all .ctp signatures # selur-compose ps # Check status -# selur-compose logs -f dictask # Stream logs +# selur-compose logs -f dicta-task # Stream logs # selur-compose down # Stop all services # -# Fallback (when selur is not installed): -# podman compose --file compose.toml up --detach +# Fallback (when selur is not installed): this TOML is selur-native and is +# NOT parseable by `podman compose` / `docker compose`. For the portable +# (podman/nerdctl/docker) path use the compose-spec file instead: +# podman compose -f ../compose.yaml up -d # (or: just container-up) version = "1.0" @@ -23,8 +25,8 @@ version = "1.0" # ============================================================================ # Primary application service -[services.dictask] -image = "ghcr.io/hyperpolymath/dictask:latest.ctp" +[services.dicta-task] +image = "ghcr.io/hyperpolymath/dicta-task:latest.ctp" ports = ["8080:8080"] environment = { APP_HOST = "[::]", @@ -32,21 +34,40 @@ environment = { APP_LOG_FORMAT = "json", APP_DATA_DIR = "/data", } -volumes = ["dictask-data:/data"] +volumes = ["dicta-task-data:/data"] restart = "always" healthcheck = { test = "curl -sf http://localhost:8080/health", interval = "30s", timeout = "5s", retries = 3 } -# Svalinn edge gateway: validates requests, enforces policies, TLS termination +# Rokur secrets gate: refuses to start (and refuses to proxy) unless the +# declared secrets are present; emits a structured audit log of every access. +# Sits between svalinn and the application. Remove if you do not gate secrets. +[services.rokur] +image = "ghcr.io/hyperpolymath/rokur:latest.ctp" +ports = ["8081:8081"] +environment = { + ROKUR_BACKEND = "http://dicta-task:8080", + ROKUR_LISTEN = "[::]:8081", + ROKUR_REQUIRED_SECRETS = "", + ROKUR_AUDIT_LOG = "/var/log/rokur/audit.jsonl", + ROKUR_LOG_FORMAT = "json", +} +volumes = ["rokur-audit:/var/log/rokur"] +depends_on = ["dicta-task"] +restart = "always" +healthcheck = { test = "curl -sf http://localhost:8081/health", interval = "30s", timeout = "5s", retries = 3 } + +# Svalinn edge gateway: validates requests, enforces policies, TLS termination. +# Proxies to rokur, which in turn fronts the application. [services.svalinn] image = "ghcr.io/hyperpolymath/svalinn:latest.ctp" ports = ["443:443", "80:80"] environment = { - SVALINN_BACKEND = "http://dictask:8080", + SVALINN_BACKEND = "http://rokur:8081", SVALINN_POLICY_FILE = "/etc/svalinn/gatekeeper.yaml", SVALINN_TLS_AUTO = "true", } volumes = ["svalinn-config:/etc/svalinn:ro"] -depends_on = ["dictask"] +depends_on = ["rokur"] restart = "always" healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", timeout = "5s", retries = 3 } @@ -54,7 +75,10 @@ healthcheck = { test = "curl -sf http://localhost:80/health", interval = "30s", # Volumes # ============================================================================ -[volumes.dictask-data] +[volumes.dicta-task-data] +driver = "local" + +[volumes.rokur-audit] driver = "local" [volumes.svalinn-config] diff --git a/container/ct-build.sh b/build/container/stapeln/ct-build.sh similarity index 77% rename from container/ct-build.sh rename to build/container/stapeln/ct-build.sh index a54a541..9a127df 100755 --- a/container/ct-build.sh +++ b/build/container/stapeln/ct-build.sh @@ -1,14 +1,14 @@ #!/usr/bin/env bash # SPDX-License-Identifier: MPL-2.0 # -# {{PROJECT_NAME}} — Cerro Torre build, sign, and verify pipeline +# Dicta-Task — Cerro Torre build, sign, and verify pipeline # # Builds the container image, packages it as a verified .ctp bundle, # signs it with Ed25519, and verifies the result. Gracefully degrades # when cerro-torre tools are not installed. # # Prerequisites: -# - podman (container build — required) +# - podman / nerdctl / docker (container build — required; set CONTAINER_ENGINE) # - ct (cerro-torre CLI: pack, sign, verify — optional) # - cerro-sign (Ed25519 signing — optional, ct sign used as fallback) # @@ -18,8 +18,8 @@ # CT_KEY_ID=my-key ./ct-build.sh # Use specific signing key # # Environment variables: -# CT_KEY_ID — Signing key identifier (default: {{SERVICE_NAME}}-release) -# CT_REGISTRY — OCI registry to push to (default: {{REGISTRY}}) +# CT_KEY_ID — Signing key identifier (default: dicta-task-release) +# CT_REGISTRY — OCI registry to push to (default: ghcr.io/hyperpolymath) # CT_TAG — Image tag (default: latest) set -euo pipefail @@ -28,8 +28,14 @@ set -euo pipefail # Configuration # --------------------------------------------------------------------------- +# This script lives in build/container/stapeln/. The repo root is two levels up, +# and the Tier-A Containerfile is one level up in build/container/. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CONTAINER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" # build/container/stapeln -> repo root + +# Container engine — podman (recommended), nerdctl or docker. +ENGINE="${CONTAINER_ENGINE:-podman}" PUSH="" for arg in "$@"; do @@ -38,29 +44,29 @@ for arg in "$@"; do fi done -CT_KEY_ID="${CT_KEY_ID:-{{SERVICE_NAME}}-release}" -CT_REGISTRY="${CT_REGISTRY:-{{REGISTRY}}}" +CT_KEY_ID="${CT_KEY_ID:-dicta-task-release}" +CT_REGISTRY="${CT_REGISTRY:-ghcr.io/hyperpolymath}" CT_TAG="${CT_TAG:-latest}" -IMAGE_NAME="{{SERVICE_NAME}}" +IMAGE_NAME="dicta-task" FULL_IMAGE="${CT_REGISTRY}/${IMAGE_NAME}:${CT_TAG}" CTP_FILE="${SCRIPT_DIR}/${IMAGE_NAME}-${CT_TAG}.ctp" -echo "=== {{PROJECT_NAME}} Cerro Torre Build Pipeline ===" +echo "=== Dicta-Task Cerro Torre Build Pipeline ===" echo " Image: ${FULL_IMAGE}" echo " Key: ${CT_KEY_ID}" echo " Bundle: ${CTP_FILE}" echo "" # --------------------------------------------------------------------------- -# Step 1: Build container image with Podman +# Step 1: Build container image (CONTAINER_ENGINE, default podman) # --------------------------------------------------------------------------- -echo "--- Step 1: Building container image ---" +echo "--- Step 1: Building container image (${ENGINE}) ---" -podman build \ +"${ENGINE}" build \ -t "${FULL_IMAGE}" \ - -f "${SCRIPT_DIR}/Containerfile" \ + -f "${CONTAINER_DIR}/Containerfile" \ "${REPO_ROOT}" echo " Built: ${FULL_IMAGE}" @@ -82,7 +88,7 @@ else echo "" if [ "$PUSH" = "--push" ]; then echo "--- Pushing unsigned OCI image (no .ctp) ---" - podman push "${FULL_IMAGE}" + "${ENGINE}" push "${FULL_IMAGE}" echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" fi echo "" @@ -137,8 +143,8 @@ if [ "$PUSH" = "--push" ]; then echo " Pushed: ${FULL_IMAGE}" else # Fall back to podman push (unsigned OCI image) - echo " ct not available, falling back to podman push (unsigned)" - podman push "${FULL_IMAGE}" + echo " ct not available, falling back to '${ENGINE}' push (unsigned)" + "${ENGINE}" push "${FULL_IMAGE}" echo " Pushed: ${FULL_IMAGE} (unsigned OCI — not a .ctp bundle)" fi echo "" @@ -153,7 +159,7 @@ echo " Image: ${FULL_IMAGE}" echo " Bundle: ${CTP_FILE}" echo "" echo " To deploy with selur-compose:" -echo " cd container && selur-compose up" +echo " cd build/container/stapeln && selur-compose up" echo "" echo " To verify at any time:" echo " ct verify ${CTP_FILE}" diff --git a/container/deploy.k9.ncl b/build/container/stapeln/deploy.k9.ncl similarity index 79% rename from container/deploy.k9.ncl rename to build/container/stapeln/deploy.k9.ncl index 9447ba6..362ee1d 100644 --- a/container/deploy.k9.ncl +++ b/build/container/stapeln/deploy.k9.ncl @@ -1,6 +1,6 @@ K9! # SPDX-License-Identifier: MPL-2.0 -# deploy.k9.ncl — {{PROJECT_NAME}} deployment component (Hunt level) +# deploy.k9.ncl — Dicta-Task deployment component (Hunt level) # # k9-svc deployment specification with full pedigree (L1-L5). # Security Level: 'Hunt (requires cryptographic handshake for execution). @@ -9,9 +9,9 @@ K9! # It requires explicit authorisation via the Leash system. # # Usage: -# nickel typecheck container/deploy.k9.ncl -# k9-svc validate container/deploy.k9.ncl -# k9-svc deploy container/deploy.k9.ncl --env production +# nickel typecheck build/container/deploy.k9.ncl +# k9-svc validate build/container/deploy.k9.ncl +# k9-svc deploy build/container/deploy.k9.ncl --env production # The component's pedigree (self-description across five layers) let component_pedigree = { @@ -19,11 +19,11 @@ let component_pedigree = { # L1: The Snout — Identity # ───────────────────────────────────────────────────────────── metadata = { - name = "{{SERVICE_NAME}}-deploy", - version = "{{VERSION}}", + name = "dicta-task-deploy", + version = "0.1.0", breed = "application/vnd.k9+nickel", magic_number = "K9!", - description = "{{PROJECT_NAME}} deployment component (Hunt level)", + description = "Dicta-Task deployment component (Hunt level)", }, # ───────────────────────────────────────────────────────────── @@ -41,6 +41,9 @@ let component_pedigree = { # ───────────────────────────────────────────────────────────── security = { trust_level = 'Hunt, + # Named field the k9 validators grep for (leash/security_level); + # value mirrors trust_level — this component is documented Hunt-level. + security_level = 'Hunt, allow_network = true, allow_filesystem_write = true, allow_subprocess = true, @@ -94,8 +97,8 @@ let deployment = { # Container configuration container = { - image = "{{REGISTRY}}/{{SERVICE_NAME}}", - port = {{PORT}}, + image = "ghcr.io/hyperpolymath/dicta-task", + port = 8080, health_check = "/health", readiness_check = "/ready", }, @@ -114,8 +117,8 @@ let scripts = { pre_deploy = m%" #!/bin/sh set -eu -echo "K9: Pre-deployment validation for {{SERVICE_NAME}}..." -cd container && selur-compose verify || podman compose --file compose.toml config +echo "K9: Pre-deployment validation for dicta-task..." +cd build/container/stapeln && selur-compose verify || podman compose --file ../compose.yaml config echo "K9: Validation passed." "%, @@ -124,10 +127,10 @@ echo "K9: Validation passed." #!/bin/sh set -eu ENV="${1:-dev}" -echo "K9: Deploying {{SERVICE_NAME}} to $ENV environment..." -cd container +echo "K9: Deploying dicta-task to $ENV environment..." +cd build/container/stapeln ./ct-build.sh -selur-compose up --detach || podman compose --file compose.toml up --detach +selur-compose up --detach || podman compose --file ../compose.yaml up --detach echo "K9: Deployment to $ENV complete." "%, @@ -135,23 +138,16 @@ echo "K9: Deployment to $ENV complete." rollback = m%" #!/bin/sh set -eu -echo "K9: Rolling back {{SERVICE_NAME}} deployment..." -cd container -selur-compose down || podman compose --file compose.toml down +echo "K9: Rolling back dicta-task deployment..." +cd build/container/stapeln +selur-compose down || podman compose --file ../compose.yaml down echo "K9: Rollback complete." "%, } in # Export the component { - # Canonical top-level fields keep the pedigree directly discoverable while - # retaining the richer nested representation for existing consumers. - pedigree = component_pedigree & { - name = component_pedigree.metadata.name, - version = component_pedigree.metadata.version, - security_level = 'Hunt, - signature = component_pedigree.security.signature, - }, + pedigree = component_pedigree & { name = "dicta-task-deploy" }, deployment = deployment, scripts = scripts, @@ -169,6 +165,6 @@ Before running, ensure you have: 2. Verified the signature (when implemented) 3. Explicitly authorised Hunt-level execution -Run with: k9-svc authorize container/deploy.k9.ncl && k9-svc deploy container/deploy.k9.ncl +Run with: k9-svc authorize build/container/deploy.k9.ncl && k9-svc deploy build/container/deploy.k9.ncl "%, } diff --git a/container/manifest.toml b/build/container/stapeln/manifest.toml similarity index 71% rename from container/manifest.toml rename to build/container/stapeln/manifest.toml index d4d470c..2ee1e18 100644 --- a/container/manifest.toml +++ b/build/container/stapeln/manifest.toml @@ -1,27 +1,27 @@ # SPDX-License-Identifier: MPL-2.0 # -# Cerro Torre manifest for dictask .ctp bundle +# Cerro Torre manifest for Dicta-Task .ctp bundle # # This manifest describes the container image for verified # container packaging. Used by `ct pack` to create .ctp bundles. [metadata] -name = "dictask" +name = "dicta-task" version = "0.1.0" revision = 1 -summary = "dictask" +summary = "Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list" description = """ -dictask — containerised service packaged as a verified +Dicta-Task — containerised service packaged as a verified cerro-torre .ctp bundle with Ed25519 signing and full provenance tracking. """ license = "MPL-2.0" -homepage = "https://github.com/hyperpolymath/dictask" +homepage = "https://github.com/hyperpolymath/dicta-task" maintainer = "Jonathan D.A. Jewell " [provenance] -upstream = "https://github.com/hyperpolymath/dictask" -import_date = 2026-03-16T00:00:00Z +upstream = "https://github.com/hyperpolymath/dicta-task" +import_date = 2026-09-26T00:00:00Z [dependencies] runtime = ["ca-certificates", "curl"] @@ -35,7 +35,7 @@ APP_HOST = "[::]" APP_PORT = "8080" [outputs] -primary = "dictask" +primary = "dicta-task" split = [] [attestations] diff --git a/build/container/stapeln/rokur.toml b/build/container/stapeln/rokur.toml new file mode 100644 index 0000000..eb739e98 --- /dev/null +++ b/build/container/stapeln/rokur.toml @@ -0,0 +1,81 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# Rokur secrets-gate configuration for Dicta-Task +# +# Rokur (stapeln container stack) is a secrets-management gate: a small +# Deno service that sits in front of the application and refuses to pass +# traffic unless the declared secrets are present and valid. It rate-limits +# per client and writes a structured audit log of every secret access. +# +# Most settings can also be supplied as environment variables (the ROKUR_* +# names below); this file is the declarative equivalent consumed at startup +# and re-read on SIGHUP. Environment variables win over file values. +# +# Usage: +# rokur serve --config build/container/stapeln/rokur.toml +# # or, in the stack, via compose (see compose.toml / compose.yaml) + +[metadata] +name = "dicta-task-gate" +version = "0.1.0" + +# ============================================================================ +# Listener / routing (env: ROKUR_LISTEN, ROKUR_BACKEND) +# ============================================================================ + +[server] +# Address rokur listens on (inside the container). +listen = "[::]:8081" +# The upstream application rokur fronts once the gate is satisfied. +backend = "http://dicta-task:8080" +# Health/readiness endpoints rokur exposes for vordr / orchestrators. +health_path = "/health" +ready_path = "/ready" + +# ============================================================================ +# Required secrets (env: ROKUR_REQUIRED_SECRETS, comma-separated) +# ============================================================================ +# +# Names of secrets that MUST be present (as environment variables or in the +# mounted secret store) before rokur will start the gate. An empty list means +# the gate starts open — tighten this before production. + +[secrets] +required = [] +# Example: +# required = ["DATABASE_URL", "JWT_SIGNING_KEY", "TLS_PRIVATE_KEY"] + +# Where rokur looks for secret material, in order. "env" reads process +# environment; "file" reads a mounted directory (one file per secret). +sources = ["env"] +# file_dir = "/run/secrets" + +# ============================================================================ +# Rate limiting (rokur/rate_limit.js — per-client token bucket) +# ============================================================================ + +[rate_limit] +enabled = true +requests_per_second = 100 +burst = 200 + +# ============================================================================ +# Policy engine (rokur/policy/engine.js — pluggable evaluator) +# ============================================================================ + +[policy] +# "builtin" uses rokur's built-in evaluator; "external" delegates to a +# command/endpoint you supply. +engine = "builtin" +# external_command = "/usr/local/bin/my-policy" + +# ============================================================================ +# Audit log (env: ROKUR_AUDIT_LOG, ROKUR_LOG_FORMAT) +# ============================================================================ + +[audit] +log = "/var/log/rokur/audit.jsonl" +format = "json" +# Record a hash of secret values (never the values themselves) for tamper +# evidence in the audit trail. +hash_values = true diff --git a/container/vordr.toml b/build/container/stapeln/vordr.toml similarity index 78% rename from container/vordr.toml rename to build/container/stapeln/vordr.toml index a6cd3e4..960d4fc 100644 --- a/container/vordr.toml +++ b/build/container/stapeln/vordr.toml @@ -1,18 +1,18 @@ # SPDX-License-Identifier: MPL-2.0 # -# Vordr runtime monitoring configuration for dictask +# Vordr runtime monitoring configuration for Dicta-Task # # Vordr watches container health, detects crashes, tracks resource usage, # and emits structured logs. It runs alongside the application stack and # provides runtime observability without requiring in-process agents. # # Usage: -# vordr watch --config container/vordr.toml +# vordr watch --config build/container/vordr.toml # vordr status # vordr report [metadata] -name = "dictask" +name = "dicta-task" version = "0.1.0" # ============================================================================ @@ -34,15 +34,32 @@ failure_threshold = 3 readiness_endpoint = "http://localhost:8080/ready" readiness_timeout = "10s" -# Action on failure: "alert" (log + notify) or "restart" (alert + podman restart). +# Action on failure: "alert" (log + notify) or "restart" (alert + restart). on_failure = "alert" +# Additional deep probes for the sidecar stapeln services. Crash detection +# below already tracks every container's lifecycle via the engine; these add +# endpoint-level health checks for the secrets gate and the edge gateway. +[[health.extra]] +name = "rokur" +endpoint = "http://localhost:8081/health" +interval = "30s" +timeout = "5s" +failure_threshold = 3 + +[[health.extra]] +name = "svalinn" +endpoint = "http://localhost:80/health" +interval = "30s" +timeout = "5s" +failure_threshold = 3 + # ============================================================================ # Crash Detection # ============================================================================ # -# Monitors container state via Podman. Detects OOM kills, segfaults, -# and unexpected exits. +# Monitors container state via the engine (podman/nerdctl/docker). Detects +# OOM kills, segfaults, and unexpected exits across the whole stack. [crash_detection] enabled = true @@ -79,7 +96,7 @@ format = "json" level = "info" # Write vordr logs to stdout (captured by Podman) and optionally to file. output = "stdout" -# file = "/var/log/vordr/dictask.log" +# file = "/var/log/vordr/dicta-task.log" # ============================================================================ # Notifications (optional) @@ -95,6 +112,6 @@ output = "stdout" # [notifications.email] # to = "j.d.a.jewell@open.ac.uk" -# from = "vordr@dictask.local" +# from = "vordr@dicta-task.local" # smtp = "smtp://localhost:25" # on = ["failure", "resource_critical"] diff --git a/deploy/ansible/setup.yml b/build/deploy/ansible/setup.yml similarity index 59% rename from deploy/ansible/setup.yml rename to build/deploy/ansible/setup.yml index 1f6357b..3efd614 100644 --- a/deploy/ansible/setup.yml +++ b/build/deploy/ansible/setup.yml @@ -1,19 +1,19 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Ansible playbook for local dictask pipeline setup. +# Ansible playbook for local dicta-task pipeline setup. # Sets up: udev rules, systemd service, Rust/Haskell toolchains, SQLite. --- -- name: Setup dictask speech-to-do pipeline +- name: Setup dicta-task speech-to-do pipeline hosts: localhost connection: local become: false vars: - dictask_dir: "{{ ansible_env.HOME }}/Documents/hyperpolymath-repos/dictask" - dictask_data_dir: "{{ ansible_env.HOME }}/.local/share/dictask" - dictask_archive_dir: "{{ dictask_data_dir }}/audio-archive" - dictask_db_path: "{{ dictask_data_dir }}/tasks.db" + dicta_task_dir: "{{ ansible_env.HOME }}/Documents/hyperpolymath-repos/dicta-task" + dicta_task_data_dir: "{{ ansible_env.HOME }}/.local/share/dicta-task" + dicta_task_archive_dir: "{{ dicta_task_data_dir }}/audio-archive" + dicta_task_db_path: "{{ dicta_task_data_dir }}/tasks.db" # Portable recorder USB identifiers (customise for your device) recorder_vendor_id: "XXXX" recorder_product_id: "XXXX" @@ -22,30 +22,30 @@ # ======================================================================== # Directory setup # ======================================================================== - - name: Create dictask data directories + - name: Create dicta-task data directories file: path: "{{ item }}" state: directory mode: "0750" loop: - - "{{ dictask_data_dir }}" - - "{{ dictask_archive_dir }}" - - "{{ dictask_data_dir }}/transcripts" - - "{{ dictask_data_dir }}/candidates" - - "{{ dictask_data_dir }}/views" - - "{{ dictask_data_dir }}/quarantine" - - "{{ dictask_data_dir }}/logs" + - "{{ dicta_task_data_dir }}" + - "{{ dicta_task_archive_dir }}" + - "{{ dicta_task_data_dir }}/transcripts" + - "{{ dicta_task_data_dir }}/candidates" + - "{{ dicta_task_data_dir }}/views" + - "{{ dicta_task_data_dir }}/quarantine" + - "{{ dicta_task_data_dir }}/logs" # ======================================================================== # Initialise SQLite database # ======================================================================== - name: Check if SQLite database exists stat: - path: "{{ dictask_db_path }}" + path: "{{ dicta_task_db_path }}" register: db_stat - name: Initialise SQLite database from schema - command: sqlite3 "{{ dictask_db_path }}" ".read {{ dictask_dir }}/schemas/tasks.sql" + command: sqlite3 "{{ dicta_task_db_path }}" ".read {{ dicta_task_dir }}/schemas/tasks.sql" when: not db_stat.stat.exists # ======================================================================== @@ -54,45 +54,45 @@ - name: Install udev rule for portable recorder become: true copy: - dest: /etc/udev/rules.d/99-dictask-recorder.rules + dest: /etc/udev/rules.d/99-dicta-task-recorder.rules content: | - # dictask: trigger pipeline when portable voice recorder is connected + # dicta-task: trigger pipeline when portable voice recorder is connected # Customise VENDOR_ID and PRODUCT_ID for your recorder # Find yours with: lsusb - ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="{{ recorder_vendor_id }}", ATTR{idProduct}=="{{ recorder_product_id }}", TAG+="systemd", ENV{SYSTEMD_WANTS}="dictask-ingest.service" + ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="{{ recorder_vendor_id }}", ATTR{idProduct}=="{{ recorder_product_id }}", TAG+="systemd", ENV{SYSTEMD_WANTS}="dicta-task-ingest.service" mode: "0644" notify: Reload udev rules # ======================================================================== # systemd service for pipeline # ======================================================================== - - name: Install dictask systemd user service + - name: Install dicta-task systemd user service copy: - dest: "{{ ansible_env.HOME }}/.config/systemd/user/dictask-ingest.service" + dest: "{{ ansible_env.HOME }}/.config/systemd/user/dicta-task-ingest.service" content: | [Unit] - Description=dictask speech-to-do pipeline ingest + Description=dicta-task speech-to-do pipeline ingest After=local-fs.target [Service] Type=oneshot - ExecStart={{ dictask_dir }}/target/release/dictask-ingest - Environment=DICTASK_DATA_DIR={{ dictask_data_dir }} - Environment=DICTASK_DB_PATH={{ dictask_db_path }} - StandardOutput=append:{{ dictask_data_dir }}/logs/ingest.log - StandardError=append:{{ dictask_data_dir }}/logs/ingest-error.log + ExecStart={{ dicta_task_dir }}/target/release/dicta-task-ingest + Environment=DICTA_TASK_DATA_DIR={{ dicta_task_data_dir }} + Environment=DICTA_TASK_DB_PATH={{ dicta_task_db_path }} + StandardOutput=append:{{ dicta_task_data_dir }}/logs/ingest.log + StandardError=append:{{ dicta_task_data_dir }}/logs/ingest-error.log [Install] WantedBy=default.target mode: "0644" notify: Reload systemd user daemon - - name: Install dictask systemd timer (periodic batch check) + - name: Install dicta-task systemd timer (periodic batch check) copy: - dest: "{{ ansible_env.HOME }}/.config/systemd/user/dictask-batch.timer" + dest: "{{ ansible_env.HOME }}/.config/systemd/user/dicta-task-batch.timer" content: | [Unit] - Description=dictask periodic batch processing + Description=dicta-task periodic batch processing [Timer] OnCalendar=*:0/15 @@ -102,9 +102,9 @@ WantedBy=timers.target mode: "0644" - - name: Enable dictask batch timer + - name: Enable dicta-task batch timer systemd: - name: dictask-batch.timer + name: dicta-task-batch.timer scope: user enabled: true state: started diff --git a/deploy/terraform/main.tf b/build/deploy/terraform/main.tf similarity index 85% rename from deploy/terraform/main.tf rename to build/deploy/terraform/main.tf index 410298b..114fd30 100644 --- a/deploy/terraform/main.tf +++ b/build/deploy/terraform/main.tf @@ -1,7 +1,7 @@ # SPDX-License-Identifier: MPL-2.0 # Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) # -# Terraform configuration for dictask cloud backup. +# Terraform configuration for dicta-task cloud backup. # Provisions: encrypted storage bucket, IAM roles, lifecycle rules. # # This is designed for S3-compatible storage (GCS, Backblaze B2, MinIO). @@ -46,7 +46,7 @@ variable "retention_days" { variable "bucket_name" { description = "Name for the audio backup bucket" type = string - default = "dictask-audio-backup" + default = "dicta-task-audio-backup" } # ============================================================================ @@ -96,7 +96,7 @@ resource "google_storage_bucket" "audio_backup" { # Labels for cost tracking labels = { - project = "dictask" + project = "dicta-task" purpose = "audio-backup" environment = "production" } @@ -106,24 +106,24 @@ resource "google_storage_bucket" "audio_backup" { # Service Account — Minimal permissions for upload # ============================================================================ -resource "google_service_account" "dictask_uploader" { - account_id = "dictask-uploader" - display_name = "dictask Audio Uploader" - description = "Service account for dictask pipeline to upload encrypted audio backups" +resource "google_service_account" "dicta_task_uploader" { + account_id = "dicta-task-uploader" + display_name = "dicta-task Audio Uploader" + description = "Service account for dicta-task pipeline to upload encrypted audio backups" } # Only allow object creation (not read/delete) for upload resource "google_storage_bucket_iam_member" "uploader_create" { bucket = google_storage_bucket.audio_backup.name role = "roles/storage.objectCreator" - member = "serviceAccount:${google_service_account.dictask_uploader.email}" + member = "serviceAccount:${google_service_account.dicta_task_uploader.email}" } # Allow listing (for idempotent upload checks) resource "google_storage_bucket_iam_member" "uploader_viewer" { bucket = google_storage_bucket.audio_backup.name role = "roles/storage.objectViewer" - member = "serviceAccount:${google_service_account.dictask_uploader.email}" + member = "serviceAccount:${google_service_account.dicta_task_uploader.email}" } # ============================================================================ @@ -142,5 +142,5 @@ output "bucket_url" { output "service_account_email" { description = "The email of the upload service account" - value = google_service_account.dictask_uploader.email + value = google_service_account.dicta_task_uploader.email } diff --git a/build/guix.scm b/build/guix.scm new file mode 100755 index 0000000..80f06fa --- /dev/null +++ b/build/guix.scm @@ -0,0 +1,20 @@ +;; SPDX-License-Identifier: MPL-2.0 +;; Guix development environment template. +;; Usage: guix shell -D -f build/guix.scm + +(use-modules (guix packages) + (guix build-system gnu) + (guix licenses) + (gnu packages base) + (gnu packages bash)) + +(package + (name "dicta-task") + (version "0.1.0") + (source #f) + (build-system gnu-build-system) + (inputs (list coreutils bash)) + (synopsis "dicta-task") + (description "dicta-task — part of the hyperpolymath ecosystem.") + (home-page "https://github.com/hyperpolymath/dicta-task") + (license (@ (guix licenses) mpl2.0))) diff --git a/build/just/assess.just b/build/just/assess.just new file mode 100644 index 0000000..3c99031 --- /dev/null +++ b/build/just/assess.just @@ -0,0 +1,270 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# PROJECT SELF-ASSESSMENT + OPENSSF COMPLIANCE VERIFICATION +# +# Imported by ../../Justfile via `import? "build/just/assess.just"`. +# Recipes here advise on what to keep/remove (`self-assess`, read-only) and +# verify that OpenSSF Best Practices prerequisites are present (`verify`, +# called by `init` and CI). + +# 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 "build/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 .editorconfig .gitignore; do + if [ -f "$f" ]; then + echo " ✓ $f — KEEP (RSR required)" + else + echo " ✗ $f — MISSING (RSR violation!)" + fi + done + + # Community health files live at root OR .github/ (.github/ is the estate's + # canonical location — see .machine_readable/root-allow.txt). Checking root + # alone reported the template's own .github/ copies as "MISSING (RSR + # violation!)", which is a false negative. + for f in SECURITY CODE_OF_CONDUCT CONTRIBUTING; do + found="" + for cand in "$f.md" "$f.adoc" ".github/$f.md" ".github/$f.adoc"; do + [ -f "$cand" ] && { found="$cand"; break; } + done + if [ -n "$found" ]; then + echo " ✓ $found — KEEP (RSR required)" + else + echo " ✗ $f.md — MISSING at root and .github/ (RSR violation!)" + fi + done + + if [ -d ".machine_readable/descriptiles" ]; then + echo " ✓ .machine_readable/descriptiles/ — KEEP (SCM checkpoint files)" + else + echo " ✗ .machine_readable/descriptiles/ — 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/ — KEEP (Idris2 ABI definitions)" + elif ! $HAS_IDRIS && { [ -d "src/interface/abi" ] || [ -d "src/interface/Abi" ]; }; then + echo " ? src/interface/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 "build/container" ]; then + echo " ✓ build/container/ — KEEP (Containerfile + compose)" + elif ! $HAS_CONTAINER && [ -d "build/container" ]; then + echo " ? build/container/ — No Containerfile detected in use." + echo " → KEEP if you plan to containerise later." + echo " → SAFE TO REMOVE for libraries and CLI tools (run: just no-container)." + 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 "build/flake.nix" ] || [ -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 "build/guix.scm" ] && ! command -v guix >/dev/null 2>&1; then + echo " ○ build/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 "═══════════════════════════════════════════════════" + +# 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 + } + + # Community health files: accept root OR .github/, .md OR .adoc. + # + # .github/ is the canonical estate location for these — see + # .machine_readable/root-allow.txt, which says of CONTRIBUTING.md: + # "Accepted at root OR .github/ — CI (openssf/quality/rhodibot) now + # checks both; .github/ is the canonical estate location + # (org-inherited). Allow-listed if present at root." + # and of SECURITY.md: "security-policy contractile now accepts the + # .github/ copy." + # + # GitHub and the OpenSSF Best Practices criteria both treat .github/ as a + # valid home for them. Checking root only made this recipe a FALSE + # NEGATIVE: the files exist in .github/ and always have, so the template + # failed its own `just verify` ("repo cannot ship"), and so did every repo + # instantiated from it. This recipe was the last checker still looking at + # root alone. + check_community_file() { + local base="$1" + local cand + for cand in "$base.md" "$base.adoc" ".github/$base.md" ".github/$base.adoc"; do + if [ -f "$cand" ]; then + echo " OK: $cand" + return 0 + fi + done + echo " FAIL: $base.md (or $base.adoc) missing at root or .github/" + ERRORS=$((ERRORS + 1)) + } + + check_community_file "SECURITY" + check_file "LICENSE" + check_community_file "CONTRIBUTING" + check_either "README.adoc" "README.md" + check_file ".machine_readable/descriptiles/STATE.a2ml" + check_file ".machine_readable/descriptiles/META.a2ml" + check_file ".machine_readable/descriptiles/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." diff --git a/build/just/container.just b/build/just/container.just new file mode 100644 index 0000000..ec2e20f --- /dev/null +++ b/build/just/container.just @@ -0,0 +1,284 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# CONTAINERS — three-tier containerisation +# +# Imported by ../../Justfile via `import? "build/just/container.just"`. +# Variables (project, OWNER, ...) and `set shell` are inherited from the root. +# +# Tier A — OCI standard: build/container/Containerfile, build/container/.containerignore +# Tier B — portable engine: build/container/compose.yaml (podman | nerdctl | docker) +# Tier C — stapeln sovereign: build/container/stapeln/ (cerro-torre, svalinn, +# vordr, selur, rokur, k9) +# +# Engine is auto-selected: $CONTAINER_ENGINE, else the first of podman / +# nerdctl / docker found on PATH. Override with: CONTAINER_ENGINE=docker just … +# +# Narrow the template to the tiers you want (see the Narrowing section below): +# just container-keep oci portable # keep image + portable compose, drop the rest +# just no-stapeln | no-devcontainer | no-container # convenience aliases + +# ── Engine detection (shared snippet) ────────────────────────────────────── +# Resolves the OCI engine into $ENGINE. Sourced at the top of each recipe. +_engine := ''' + ENGINE="${CONTAINER_ENGINE:-}" + if [ -z "$ENGINE" ]; then + for e in podman nerdctl docker; do + if command -v "$e" >/dev/null 2>&1; then ENGINE="$e"; break; fi + done + fi + if [ -z "$ENGINE" ]; then + echo "No container engine found (looked for podman, nerdctl, docker)." >&2 + echo "Install one or set CONTAINER_ENGINE." >&2 + exit 1 + fi +''' + +# Initialise container templates — substitute placeholders with project values +container-init: + #!/usr/bin/env bash + set -euo pipefail + + if [ ! -d "build/container" ]; then + echo "Error: build/container/ directory not found." + echo "This repo may not have been created from rsr-template-repo," + echo "or containerisation was removed with 'just no-container'." + 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) [dicta_task]: " _SERVICE_NAME + SERVICE_NAME="${_SERVICE_NAME:-dicta_task}" + + read -rp "Primary port [8080]: " _PORT + PORT="${_PORT:-8080}" + + read -rp "Container registry [ghcr.io/${OWNER:-hyperpolymath}]: " _REGISTRY + REGISTRY="${_REGISTRY:-ghcr.io/${OWNER:-hyperpolymath}}" + + 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" + ) + + # Recurses into build/container/stapeln/ as well. + find build/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 build/container/Containerfile — add your build commands (Tier A)" + echo " 2. Edit build/container/entrypoint.sh — set your application binary" + echo " 3. Review build/container/compose.yaml — portable stack (Tier B)" + echo " 4. Review build/container/stapeln/ — sovereign stack (Tier C)" + echo " 5. Build: just container-build" + +# Build container image (cerro-torre pipeline if present, else plain OCI build) +container-build *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh {{args}} + elif [ -f "build/container/Containerfile" ]; then + "$ENGINE" build -t dicta_task:latest -f build/container/Containerfile . + elif [ -f "Containerfile" ]; then + "$ENGINE" build -t dicta_task:latest -f Containerfile . + else + echo "No Containerfile found in build/container/ or project root" + exit 1 + fi + +# Pick the compose path: selur-compose (sovereign) if installed, else portable +_compose action *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then + ( cd build/container/stapeln && selur-compose {{action}} {{args}} ) + elif [ -f "build/container/compose.yaml" ]; then + "$ENGINE" compose -f build/container/compose.yaml {{action}} {{args}} + else + echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" + exit 1 + fi + +# Verify compose configuration (selur-compose verify, else compose config) +container-verify: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if command -v selur-compose >/dev/null 2>&1 && [ -f "build/container/stapeln/compose.toml" ]; then + ( cd build/container/stapeln && selur-compose verify ) + elif [ -f "build/container/compose.yaml" ]; then + "$ENGINE" compose -f build/container/compose.yaml config + else + echo "No compose file found (build/container/stapeln/compose.toml or build/container/compose.yaml)" + exit 1 + fi + +# Start the container stack (selur-compose if available, else $ENGINE compose) +container-up *args: + @just _compose up {{args}} + +# Stop the container stack +container-down *args: + @just _compose down {{args}} + +# Sign and verify the container bundle (cerro-torre: build + pack + sign + verify) +container-sign: + #!/usr/bin/env bash + set -euo pipefail + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && ./ct-build.sh + else + echo "No build/container/stapeln/ct-build.sh found" + exit 1 + fi + +# Push the signed bundle (or plain image) to the registry +container-push: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + if [ -f "build/container/stapeln/ct-build.sh" ]; then + cd build/container/stapeln && CONTAINER_ENGINE="$ENGINE" ./ct-build.sh --push + else + echo "No build/container/stapeln/ct-build.sh found — falling back to $ENGINE push" + "$ENGINE" push dicta_task:latest + fi + +# Run the container interactively (for debugging) +container-run *args: + #!/usr/bin/env bash + set -euo pipefail + {{_engine}} + "$ENGINE" run --rm -it dicta_task:latest {{args}} + +# Container matrix: [build|run|push|shell|scan] x [registry] x [tag] +container-matrix action="build" registry="ghcr.io/hyperpolymath" tag="latest": + @echo "Container matrix: action={{action}} registry={{registry}} tag={{tag}}" + +# ── Narrowing / opt-out ────────────────────────────────────────────────────── +# +# `container-keep` is the workhorse: declare exactly which tiers to KEEP; the +# rest are removed. Tiers: oci (A) · portable (B) · stapeln (C) · devcontainer. +# Idempotent — safe to re-run. The aliases below are thin wrappers. +# +# just container-keep oci # bare OCI image only +# just container-keep oci portable # image + portable compose +# just container-keep oci portable stapeln # full prod stack, no dev container +# just container-keep all # no-op (keep everything) +# just container-keep none # remove everything (= no-container) +# DRY_RUN=1 just container-keep oci # show what would change, do nothing +# FORCE=1 just container-keep oci # skip the confirm prompt +# +# Accepts space- or comma-separated tokens, and aliases: a/A, b/B/compose, +# c/C/sovereign, dev. 'portable'/'stapeln' require 'oci' (they build the image). +container-keep +tiers: + #!/usr/bin/env bash + set -euo pipefail + + sel="$(echo "$@" | tr ',' ' ')" + keep_oci=false keep_port=false keep_stap=false keep_dev=false + for t in $sel; do + case "$t" in + all) keep_oci=true; keep_port=true; keep_stap=true; keep_dev=true ;; + none) : ;; + oci|a|A) keep_oci=true ;; + portable|compose|b|B) keep_port=true ;; + stapeln|sovereign|c|C) keep_stap=true ;; + devcontainer|dev) keep_dev=true ;; + *) echo "Unknown tier '$t'. Valid: oci portable stapeln devcontainer | all | none" >&2; exit 1 ;; + esac + done + + # Coherence: tiers B and C build the OCI image, so they need tier A. + if { $keep_port || $keep_stap; } && ! $keep_oci; then + echo "Incoherent selection: 'portable'/'stapeln' build the OCI image — add 'oci'." >&2 + exit 1 + fi + + # Build the removal plan. + plan=() + $keep_dev || plan+=(".devcontainer/ (dev container)") + if $keep_oci; then + $keep_stap || plan+=("build/container/stapeln/ (cerro-torre, svalinn, vordr, selur, rokur, k9)") + $keep_port || plan+=("build/container/compose.yaml + compose.example.yaml (portable stack)") + else + plan+=("build/container/ (entire image + compose + stapeln)") + plan+=("build/just/container.just (this module) + its Justfile import") + [ -f .github/workflows/container-build.yml ] && plan+=(".github/workflows/container-build.yml") + fi + + echo "Keeping: oci=$keep_oci portable=$keep_port stapeln=$keep_stap devcontainer=$keep_dev" + if [ ${#plan[@]} -eq 0 ]; then echo "Nothing to remove — selection already matches."; exit 0; fi + echo "Will remove:"; printf ' - %s\n' "${plan[@]}" + + if [ "${DRY_RUN:-}" = "1" ]; then echo "(DRY_RUN — no changes made)"; exit 0; fi + if [ "${FORCE:-}" != "1" ]; then + read -rp "Proceed? [y/N] " c; [[ "${c:-N}" =~ ^[Yy] ]] || { echo "Aborted."; exit 0; } + fi + + # Apply. + $keep_dev || rm -rf .devcontainer + if $keep_oci; then + $keep_stap || rm -rf build/container/stapeln + $keep_port || rm -f build/container/compose.yaml build/container/compose.example.yaml + else + rm -rf build/container + rm -f .machine_readable/configs/selur-compose.toml + rm -f .github/workflows/container-build.yml + rm -f build/just/container.just + [ -f Justfile ] && sed -i '/# >>> container-module/,/# <<< container-module/d' Justfile + for jf in Justfile .machine_readable/contractiles/Justfile; do + [ -f "$jf" ] && sed -i '\|import? "build/just/container.just"|d' "$jf" + done + fi + + echo "" + echo "Done. Review .github/workflows/ for image build/publish jobs you no longer need." + +# Remove ALL containerisation (image, compose, stapeln, dev container) +no-container: + @just container-keep none + +# Drop only the stapeln sovereign tier (keep OCI image + portable compose + dev container) +no-stapeln: + @just container-keep oci portable devcontainer + +# Drop only the dev container (keep the full image/compose/stapeln stack) +no-devcontainer: + @just container-keep oci portable stapeln diff --git a/build/just/groove.just b/build/just/groove.just new file mode 100644 index 0000000..94d028e --- /dev/null +++ b/build/just/groove.just @@ -0,0 +1,98 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# GROOVE PROTOCOL +# +# Imported by ../../Justfile via `import? "build/just/groove.just"`. +# Recipes here configure and validate the Groove protocol manifest at +# .machine_readable/integrations/groove.a2ml — port assignment, API surface +# flags (REST/gRPC/GraphQL/WebSocket/SSE), and template-placeholder hygiene. +# The manifest itself is consumed by the Groove bridge / zig-unified-api-adapter. + +# 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 repo-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 'dicta-task' Justfile 2>/dev/null; then + echo "⚠ Justfile still references 'dicta-task' — 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/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/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 repo-init' to replace placeholders, or edit files manually." + exit 1 + fi diff --git a/build/just/proofs.just b/build/just/proofs.just new file mode 100644 index 0000000..dfd6334 --- /dev/null +++ b/build/just/proofs.just @@ -0,0 +1,61 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# FORMAL VERIFICATION (PROOFS) +# +# Imported by ../../Justfile via `import? "build/just/proofs.just"`. +# Recipes here check formal proofs across Idris2, Lean4, Agda, and Coq, plus +# scan for dangerous/unsafe constructs and report status. Run via +# `just proof-check-all` for the full sweep. + +# 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 ===" + +# Each proof-check- delegates to scripts/check-proofs.sh, the single +# source of truth. A missing toolchain is FATAL (never a skip); every module is +# checked from its own source root; a proof file absent from the prover's +# manifest (verification/proofs//MANIFEST) is an error. See the script +# header for the four "checks that could not fail" this replaces. + +# Check Idris2 proofs (per MANIFEST; fatal if idris2 absent) +proof-check-idris2: + @bash scripts/check-proofs.sh idris2 + +# Check Lean4 proofs (per MANIFEST; fatal if lean absent) +proof-check-lean4: + @bash scripts/check-proofs.sh lean4 + +# Check Agda proofs (per MANIFEST; fatal if agda absent) +proof-check-agda: + @bash scripts/check-proofs.sh agda + +# Check Coq proofs (per MANIFEST; fatal if coqc absent) +proof-check-coq: + @bash scripts/check-proofs.sh coq + +# Scan for dangerous constructs USED in proof code (believe_me, sorry, Admitted, +# postulate, ...). Comments are ignored — a comment naming a banned construct +# must not trip the gate. See scripts/scan-dangerous.sh. +proof-scan-dangerous: + @bash scripts/scan-dangerous.sh + +# 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 "" + # PROOF-STATUS may live at root, docs/status/ (post-#20), .md or .adoc (post-#23) + for candidate in docs/status/PROOF-STATUS.adoc docs/status/PROOF-STATUS.md PROOF-STATUS.adoc PROOF-STATUS.md; do + if [ -f "$candidate" ]; then + grep -E "^\| \*\*Total\*\*|^\| \*Total\*" "$candidate" 2>/dev/null || echo "(No summary row in $candidate)" + exit 0 + fi + done + echo "(No PROOF-STATUS file found at root or docs/status/)" diff --git a/build/just/repo-init.just b/build/just/repo-init.just new file mode 100644 index 0000000..959fbe3 --- /dev/null +++ b/build/just/repo-init.just @@ -0,0 +1,857 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# INIT — Bootstrap a new project from this template +# +# Imported by ../../Justfile via `import? "build/just/repo-init.just"`. +# Variables (project, OWNER, REPO, version, tier) and the `set shell` directive +# are inherited from the root Justfile. + +# Interactive project bootstrap — replaces all {{PLACEHOLDER}} tokens +# Optional archetype argument (e.g. `just repo-init julia-library`) applies an +# archetype overlay: see archetypes/README.adoc and ADR-0003. +repo-init archetype="": + #!/usr/bin/env bash + set -euo pipefail + + # Non-interactive support: if RSR_NON_INTERACTIVE is set, dummy-out 'read'. + # This prevents blocking on input and seamlessly uses environment variables. + if [ -n "${RSR_NON_INTERACTIVE:-}" ]; then + read() { return 0; } + fi + + echo "═══════════════════════════════════════════════════" + echo " RSR Project Bootstrap" + echo "═══════════════════════════════════════════════════" + echo "" + + # --- Archetype overlay data (ADR-0003) --- + ARCHETYPE='{{archetype}}' + ARCH_FILE="" + ARCH_PRESET="" + if [ -n "$ARCHETYPE" ]; then + ARCH_FILE="archetypes/${ARCHETYPE}/ARCHETYPE.a2ml" + if [ ! -f "$ARCH_FILE" ]; then + echo "Error: unknown archetype '${ARCHETYPE}'. Available:" + ls -1 archetypes 2>/dev/null | grep -v 'README' || echo " (none)" + exit 1 + fi + ARCH_PRESET=$(sed -n 's/^preset = "\(.*\)"$/\1/p' "$ARCH_FILE" | head -1) + echo "Archetype: ${ARCHETYPE}${ARCH_PRESET:+ (capability preset: $ARCH_PRESET)}" + echo "" + fi + + # --- 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 + if [[ ! "$REPO" =~ ^[a-z0-9]+(-[a-z0-9]+)*$ ]]; then + echo "Error: repo slug must be lowercase alphanumeric words separated by single hyphens" + exit 1 + fi + + 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}}" + + read -rp "OpenSSF Best Practices project ID (blank if not yet registered) []: " OPENSSF_BP_ID + + # --- Container values (optional — only relevant if build/container/ exists) --- + if [ -d "build/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 the repo's uuid -------------------------------------------- + # THE UUID IS DERIVED, NOT ALLOCATED (gv-clade-index: + # docs/SPEC-clade-verisim-portal.adoc §Identity Model): + # uuid = UUIDv5(namespace = URL, name = "github.com//") + # We know OWNER and REPO, so this is computable here and must never be + # guessed or copied. The owner segment is part of the derived name, so it is + # part of the identity — a repo hosted elsewhere derives a different uuid. + if command -v uuidgen >/dev/null 2>&1; then + REPO_UUID=$(uuidgen --sha1 --namespace @url --name "${FORGE}/${OWNER}/${REPO}") + else + REPO_UUID="UNASSIGNED" + fi + + # 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..." + + # Apply archetype overlay files (if any) BEFORE substitution, so overlay + # files are rendered along with the rest of the tree. + if [ -n "$ARCHETYPE" ] && [ -d "archetypes/${ARCHETYPE}/overlay" ]; then + cp -a "archetypes/${ARCHETYPE}/overlay/." . + echo " applied overlay: archetypes/${ARCHETYPE}/overlay/" + fi + + # Seed docs/ from the documentation seed, BEFORE substitution so the seed's + # own placeholder tokens are rendered along with everything else. + # + # This is an OVERLAY, not a replacement. The seed's four perspective docs + # and its ADR template win on collision, because they are the + # downstream-facing shape; everything else the spine ships under docs/ + # survives -- notably docs/governance/ and docs/legal/, which + # build/just/validate.just hard-requires, so a wholesale replace would + # break the minted repo's own validate gate. + # + # The seed's own README.adoc is instructions-for-use ("copy this, replace + # the placeholders, delete this README"), not content, so it is not copied. + # tar is used rather than cp so that existing docs/ subdirectories MERGE + # instead of nesting (cp -a decisions docs/ would yield docs/decisions/decisions). + if [ -d "build/docs-seed" ]; then + mkdir -p docs + ( cd build/docs-seed && tar cf - --exclude=README.adoc . ) | ( cd docs && tar xf - ) + echo " seeded docs/ from build/docs-seed/ (its own README omitted)" + fi + + # 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}|MPL-2.0|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" + -e "s|${LB}UUID${RB}|${REPO_UUID}|g" + -e "s|${LB}DESCRIPTION${RB}|${PROJECT_DESCRIPTION}|g" + ) + [ -n "$AUTHOR_EMAIL_ALT" ] && SED_ARGS+=(-e "s|${LB}AUTHOR_EMAIL_ALT${RB}|${AUTHOR_EMAIL_ALT}|g") + [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_BP_ID${RB}|${OPENSSF_BP_ID}|g") + [ -n "$OPENSSF_BP_ID" ] && SED_ARGS+=(-e "s|${LB}OPENSSF_PROJECT_ID${RB}|${OPENSSF_BP_ID}|g") + + # Archetype-determined tokens (ADR-0003): the archetype contract fills + # what it can honestly determine. Added BEFORE the UNASSIGNED fallback + # below — sed applies expressions in order, so a token the archetype + # answers never reaches the fallback, and one it omits still fails loudly. + if [ -n "$ARCH_FILE" ]; then + while IFS= read -r arch_line; do + arch_tok="${arch_line%%=*}"; arch_tok="${arch_tok// /}" + arch_val=$(printf '%s' "$arch_line" | sed -n 's/^[^=]*= *"\(.*\)" *$/\1/p') + { [ -n "$arch_tok" ] && [ -n "$arch_val" ]; } || continue + case "$arch_val" in *'|'*) + echo "WARN: archetype token $arch_tok contains '|' (the sed delimiter) — skipped, stays UNASSIGNED" + continue ;; + esac + SED_ARGS+=(-e "s|${LB}${arch_tok}${RB}|${arch_val}|g") + done < <(awk '$0=="[tokens]"{f=1;next} /^\[/{f=0} f && !/^[[:space:]]*#/ && NF' "$ARCH_FILE") + fi + + # Tokens only the author can answer. init cannot know a project's unique + # strength or its language stack, and guessing would write fiction into + # ANCHOR.a2ml — the file the estate treats as semantic authority. They + # become UNASSIGNED, the same marker `just repo-init` already writes for an + # unchosen clade: honest, greppable, and obviously unfinished, whereas a + # leftover brace token just looks like the template broke. + # grep -rn UNASSIGNED . — to find what still needs you + # (Brace tokens are spelled via ${LB}/${RB} throughout this recipe: just + # interpolates literal double braces even inside comments.) + for tok in PROJECT_KIND PROJECT_DOMAIN PROJECT_UNIQUE_STRENGTH \ + TARGET_AUDIENCE LANG_STACK BUILD_CMD TEST_CMD MUST_INVARIANTS \ + PACKAGE_NAME DEPS BUILD_OUTPUT_PATH MAIN_FUNCTION; do + SED_ARGS+=(-e "s|${LB}${tok}${RB}|UNASSIGNED|g") + done + + # julia-library archetype: derive the package uuid at mint (owner ruling + # 2026-09-19: generate at mint). Same DERIVABLE class as the REPO uuid + # above - computed, never guessed. Namespace = the repo's own derived + # uuid, name = julia:: stable across re-mints of the same repo, + # never collides with the repo uuid. No uuidgen AND no python3 on the + # operator machine -> UNASSIGNED (the honest fallback at :437); Pkg + # then refuses the package loudly until the owner assigns one. + if [ "$ARCHETYPE" = "julia-library" ]; then + if command -v uuidgen >/dev/null 2>&1; then + PACKAGE_UUID=$(uuidgen --sha1 --namespace "${REPO_UUID}" --name "julia:${REPO}") + elif command -v python3 >/dev/null 2>&1; then + PACKAGE_UUID=$(python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('${REPO_UUID}'), 'julia:${REPO}'))") + else + PACKAGE_UUID=UNASSIGNED + fi + SED_ARGS+=(-e "s|${LB}PACKAGE_UUID${RB}|${PACKAGE_UUID}|g") + echo " package uuid: ${PACKAGE_UUID} (derived from repo uuid ${REPO_UUID})" + fi + + # Optional values with no answer: drop the line that depends on them rather + # than leaving the token in place. An unfilled token is not a neutral + # reminder — the OpenSSF line renders a broken badge image and a dead link + # in the new repo's README, and it trips the placeholder gate on every + # subsequent push, which trains people to ignore a red CI. No id yet means + # no badge yet; add it when you register at bestpractices.dev. + if [ -z "$OPENSSF_BP_ID" ]; then + sed -i "/bestpractices\.dev\/projects\/${LB}OPENSSF_BP_ID${RB}/d" README.adoc 2>/dev/null || true + sed -i "/${LB}OPENSSF_PROJECT_ID${RB}/d" docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc 2>/dev/null || true + fi + # A .mailmap exists to map an alternate address onto the canonical one. + # With no alternate address there is nothing to map, and the sole entry + # would keep an unfilled alt-email token in the angle brackets, which is a + # malformed mailmap rather than a harmless leftover. + # git reads .mailmap from the WORKTREE ROOT only (or via mailmap.file / + # mailmap.blob, which nothing here configures). The copy that used to live + # under .github/ was therefore inert and was never filled. + if [ -z "$AUTHOR_EMAIL_ALT" ]; then + printf '# No alternate author address to map.\n' > .mailmap + fi + + # Replace in all text files (skip .git, LICENSE text, and binaries) + find . -type f \ + -not -path './.git/*' \ + -not -name 'MPL-2.0.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 the repo deed + # (the family-7 allocation manifest folded into it — standards#837 pilot). + sed -i "s|\[YOUR-REPO-NAME\]|${PROJECT_NAME}|g" rsr-template-repo_chora.deed 2>/dev/null || true + sed -i "s|\[YOUR-NAME/ORG\]|${OWNER}|g" rsr-template-repo_chora.deed 2>/dev/null || true + + # --- Instruction-block pass -------------------------------------------- + # Delete the "TEMPLATE INSTRUCTIONS (delete this block before publishing)" + # comments. Nothing ever did, and 211 estate repos still carry one. The + # block is self-detonating: its own first line names a LITERAL doubled-brace + # token, so the sed pass above has nothing to match, yet every placeholder + # gate greps exactly that shape — a repo with every real token correctly + # substituted still trips its own gate. Rationale and measurements in + # scripts/strip-instruction-blocks.rs. + # + # Runs AFTER substitution, so real tokens are already filled; only the + # instructions go. Deliberately skipped for *-template-repo itself: the + # template's own blocks ARE the product, and its gate exempts it by remote + # name — the same protection the self-name pass below uses. + if [ "$REPO" != "${REPO%-template-repo}" ]; then + echo " instruction blocks: skipped (this IS a template repo)" + else + bash scripts/rust-tool.sh strip-instruction-blocks . + fi + + # --- Dependabot ecosystem pass ----------------------------------------- + # dependabot.yml ships every common ecosystem under "remove unused ones for + # your project". Nothing removed them: measured estate-wide, 206/206 repos + # declaring `pip` have no Python manifest, and each such entry fails on + # every scheduled run. Prune on the ARCHETYPE rather than on file presence — + # a freshly minted tree has no manifests yet, so presence would delete + # everything. Rationale in scripts/prune-dependabot-ecosystems.rs. + keep_eco="github-actions" + case "${ARCH_PRESET:-}" in *rust*|*cargo*) keep_eco="$keep_eco cargo" ;; esac + case "${ARCH_PRESET:-}" in *node*|*js*|*ts*|*deno*|*bun*|*web*) keep_eco="$keep_eco npm" ;; esac + case "${ARCH_PRESET:-}" in *elixir*|*mix*) keep_eco="$keep_eco mix" ;; esac + case "${ARCH_PRESET:-}" in *python*|*py*) keep_eco="$keep_eco pip" ;; esac + bash scripts/rust-tool.sh prune-dependabot-ecosystems .github/dependabot.yml $keep_eco + + # --- Self-name pass (ADR-0003) ----------------------------------------- + # The template's own name is written as a LITERAL, not as a placeholder + # token, so the substitution loop above never touched it. Measured on a + # scratch mint of 346ae56: a repo minted as `mint-check-lib` still carried + # Justfile: project := "rsr-template-repo" + # Justfile: REPO := "rsr-template-repo" + # sonar-project.properties sonar.projectKey=hyperpolymath_rsr-template-repo + # STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo" + # i.e. it declared that it WAS the template, and would have reported its + # code analysis into the TEMPLATE's SonarCloud project. This is the same + # failure the CLADE.a2ml block below fixes for the uuid/canonical-name; + # nothing generalised it to the rest of the tree. Confirmed in the wild: + # hyperpolymath/cargo-zigbuild still carries both Justfile lines. + # + # Provenance must SURVIVE this pass — a minted repo is supposed to record + # what it came from. Two protections: paths that are ABOUT the template + # (ADRs, its changelog, its own audit, and this recipe itself — which + # holds PARENT_SLUG) are skipped wholesale, and within every other file + # any line that names the parent AS a parent is left byte-for-byte alone. + if [ "$REPO" != "rsr-template-repo" ]; then + SELF_PROV='instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: ' + find . -type f \ + -not -path './.git/*' \ + -not -path './build/just/repo-init.just' \ + -not -path './docs/decisions/*' \ + -not -path './.machine_readable/descriptiles/CLADE.a2ml' \ + -not -path './.machine_readable/descriptiles/VARIANT.a2ml' \ + -not -name 'CHANGELOG.md' \ + -not -name 'TEMPLATE-STANDARDS-AUDIT.adoc' \ + -not -name 'TEMPLATE-LINEAGE-AUDIT.adoc' \ + -not -name '*.png' -not -name '*.jpg' -not -name '*.gif' \ + -not -name '*.woff' -not -name '*.woff2' \ + | while read -r file; do + grep -q 'rsr-template-repo' "$file" 2>/dev/null || continue + file --brief "$file" | grep -qi 'text\|ascii\|utf' || continue + # sed -i preserves the mode bit; rewriting via a temp file would not. + sed -i -E "/${SELF_PROV}/!{s|hyperpolymath/rsr-template-repo|${OWNER}/${REPO}|g;s|rsr-template-repo|${REPO}|g;}" "$file" + done + echo " self-name: rewrote template literal -> ${REPO} (provenance lines preserved)" + + # The repo deed's FILENAME carries the repo slug (deed dispatch: + # _chora.deed — deed.abnf v1.0.0). The self-name pass rewrote + # the slug inside the file; the file itself is renamed here. Plain mv: + # mint runs before the new repo's first commit. Renames (not tokens) + # are how filenames change — a dicta-task token in a FILENAME is the + # brace-collapse hazard class repo-init has already been burnt by. + if [ -f rsr-template-repo_chora.deed ]; then + mv rsr-template-repo_chora.deed "${REPO}_chora.deed" + echo " repo deed: renamed rsr-template-repo_chora.deed -> ${REPO}_chora.deed" + fi + + # ── self-OWNER pass ────────────────────────────────────────────────── + # + # Same class as the self-name leak above, and missed by it: the + # template's own OWNER is a plain literal, so nothing rewrote it. A repo + # minted under a different owner shipped: + # + # Justfile OWNER := "hyperpolymath" + # sonar-project.properties sonar.projectKey=hyperpolymath_ + # + # i.e. it declared the wrong owner and reported its code analysis into + # the wrong SonarCloud project. Measured on a metadatastician mint. + # + # This pass is deliberately NARROW — three exact, identity-bearing + # lines. A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: + # `hyperpolymath/standards`, `hyperpolymath/proven` and the other estate + # dependencies are real repos every child genuinely points at, and + # rewriting those would break the child's canon, CI and badges. + if [ "$OWNER" != "hyperpolymath" ]; then + sed -i -E 's|^(OWNER[[:space:]]*:=[[:space:]]*)"hyperpolymath"|\1"'"${OWNER}"'"|' Justfile + sed -i -E 's|^sonar\.organization=hyperpolymath$|sonar.organization='"${OWNER}"'|' sonar-project.properties + sed -i -E 's|^sonar\.projectKey=hyperpolymath_|sonar.projectKey='"${OWNER}"'_|' sonar-project.properties + echo " self-owner: rewrote owner literal -> ${OWNER} (estate dependency paths preserved)" + echo " NOTE: confirm the SonarCloud ORG is really '${OWNER}' before provisioning —" + echo " the GitHub owner and the SonarCloud org are not always the same." + fi + fi + + echo "" + echo "── Identity (CLADE.a2ml) ────────────────────────" + + # Install THIS repo's CLADE.a2ml, replacing rsr-template-repo's own. + # + # The CLADE.a2ml shipped in the template is the TEMPLATE's real identity — a + # real uuid for a real repo. Nothing in SED_ARGS ever touched it, so every + # repo created from this template inherited uuid a5ea1382-… and + # canonical-name "rsr-template-repo" verbatim, and told every arriving agent + # it WAS the template. Nothing caught it: each value was individually valid. + # chronicles-of-slavia and scaffoldia were corrected for exactly this on + # 2026-07-16; paint-type, cargo-zigbuild, email-octad-experiment, llm-grace + # and rsr-template-how-to still carry it. + # + # build/templates/CLADE.a2ml.in has already had its brace tokens filled by + # the substitution loop above (including the UUID one, derived earlier). + # NB: literal double-brace tokens cannot be written in this recipe body — + # just interpolates them, which is why LB/RB are built with printf above. + # The split is the whole point: + # DERIVABLE -> computed, never guessed (uuid, owner, canonical-name) + # JUDGEMENT -> left UNASSIGNED, fails loudly until a human chooses (clade) + CLADE_IN="build/templates/CLADE.a2ml.in" + CLADE_OUT=".machine_readable/descriptiles/CLADE.a2ml" + if [ -f "$CLADE_IN" ]; then + mkdir -p "$(dirname "$CLADE_OUT")" + cp "$CLADE_IN" "$CLADE_OUT" + rm -rf build/templates # template-only; not part of your repo + if [ "$REPO_UUID" = "UNASSIGNED" ]; then + echo " uuid: UNASSIGNED — uuidgen not found." + echo " Install util-linux (uuid-runtime), then run:" + echo " uuidgen --sha1 --namespace @url --name \"${FORGE}/${OWNER}/${REPO}\"" + echo " and put the result in $CLADE_OUT." + else + echo " uuid: $REPO_UUID" + echo " (uuid5 of ${FORGE}/${OWNER}/${REPO} — derived, recomputable, not invented)" + fi + echo " clade: UNASSIGNED — a human must choose one of the 12." + echo " CLADE-003/006 FAIL until you do. That is deliberate:" + echo " an unchosen clade must not look like a chosen one." + echo " The taxonomy is listed in $CLADE_OUT." + fi + + echo "" + echo "── Profile & provenance (ADR-0003) ──────────────" + + # The template's own rsr-profile.a2ml is deliberately MAXIMAL (it is the + # dogfood target). A minted repo must NOT inherit it: declaring + # capabilities the tree lacks makes the oracle score modules that are + # absent (na-honesty, RSR-SPEC-v2 §scoring). Rewrite it minimally. + PROFILE_OUT=".machine_readable/rsr-profile.a2ml" + + # ── Canon pin (bind the canon) ────────────────────────────────────────── + # Read the canon identity from the template's [canon] block BEFORE that + # block is overwritten below. A minted repo must record WHICH canon release + # it was cut from; without it "which repos are on canon 1.x?" is a campaign + # across the whole estate instead of a grep. + # + # Count rather than guessed: a value here that silently became empty is the + # same class of defect as an UNASSIGNED placeholder, and would let a repo + # claim conformance to nothing. + # Section reader: `canon_key ` prints the value of from + # the [canon] section, or nothing. + # + # Deliberately escape-free: the first attempt spelled the section-header + # test as /^\[/ , which reached awk as a DOUBLE backslash followed by an + # unterminated bracket expression. awk does not error on that - it parses a + # character class that swallows the rest of the program and then matches + # nothing, so the pin came out silently EMPTY rather than failing loudly. + # `substr($0,1,1)=="["` cannot be mis-escaped. This is the same class of + # defect as an UNASSIGNED placeholder: a value that looks computed but is + # empty would let a minted repo claim conformance to nothing. + canon_key() { + awk -v want="[canon]" -v key="$1" ' + substr($0,1,1)=="[" { f = ($0==want) ? 1 : 0; next } + f && $0 ~ "^[[:space:]]*" key "[[:space:]]*=" { + sub(/^[^=]*=[[:space:]]*/, "") + gsub(/^["[:space:]]+|["[:space:]]+$/, "") + print; exit + } + ' "$2" + } + + CANON_VERSION="" + CANON_CRITERIA_SHA="" + CANON_GATES_SHA="" + if [ -f "$PROFILE_OUT" ]; then + CANON_VERSION="$(canon_key version "$PROFILE_OUT")" + CANON_CRITERIA_SHA="$(canon_key criteria_sha256 "$PROFILE_OUT")" + CANON_GATES_SHA="$(canon_key gates_sha256 "$PROFILE_OUT")" + fi + CANON_VERSION="${CANON_VERSION:-UNASSIGNED}" + CANON_CRITERIA_SHA="${CANON_CRITERIA_SHA:-UNASSIGNED}" + CANON_GATES_SHA="${CANON_GATES_SHA:-UNASSIGNED}" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# rsr-profile.a2ml — this repo's declared RSR v2.0 capabilities." + echo "# Generated by just repo-init on ${CURRENT_DATE}. The template's maximal" + echo "# profile was removed at mint: declare only what this tree carries." + echo "" + echo "[rsr-profile]" + echo "version = \"1.0.0\"" + echo "spec = \"rsr-criteria-v2\"" + echo "declares-against = \"2.0.0-draft\"" + if [ -n "$ARCH_PRESET" ]; then + echo "preset = \"${ARCH_PRESET}\"" + else + echo "# UNASSIGNED: declare capabilities (or a preset) — see standards" + echo "# .machine_readable/template-capability-gates.toml" + echo "capabilities = []" + fi + } > "$PROFILE_OUT" + echo " profile: $PROFILE_OUT${ARCH_PRESET:+ (preset $ARCH_PRESET)}" + + # Provenance — the estate's answer-file (ADR-0002 contract shape, used + # per ADR-0003 with direction=generated-from-core). parent-pin resolves + # from the live template tip: correct for a fresh mint, UNASSIGNED when + # offline — honest, never guessed. + PARENT_SLUG="hyperpolymath/rsr-template-repo" + PARENT_PIN=$(git ls-remote "https://github.com/${PARENT_SLUG}.git" HEAD 2>/dev/null | cut -f1 || true) + PARENT_PIN="${PARENT_PIN:-UNASSIGNED}" + VARIANT_OUT=".machine_readable/descriptiles/VARIANT.a2ml" + mkdir -p "$(dirname "$VARIANT_OUT")" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# VARIANT.a2ml — provenance contract (ADR-0002 shape; ADR-0003 use)." + echo "# Records what this repo was minted from so the drift gate" + echo "# (scripts/check-variant-drift.sh ) and future" + echo "# re-templating tooling have a defined input." + echo "#" + echo "# NOTE: placeholder rendering means many files differ from the" + echo "# parent by design; folding rendered answers into [normalise] is" + echo "# future work (ADR-0003), so the gate is informational for minted" + echo "# repos until then." + echo "" + echo "[metadata]" + echo "project = \"${REPO}\"" + echo "schema_version = \"0.1.0\"" + echo "last-updated = \"${CURRENT_DATE}\"" + echo "" + echo "[variant]" + echo "parent = \"${PARENT_SLUG}\"" + echo "parent-pin = \"${PARENT_PIN}\" # template tip at mint" + echo "role = \"minted-repo\"" + echo "stack = \"${ARCHETYPE:-unspecified}\"" + echo "direction = \"generated-from-core\"" + echo "" + echo "[normalise]" + echo "rules = \"action-pins self-name\"" + echo "" + echo "[paths.added]" + echo "paths = []" + echo "" + echo "[paths.removed]" + echo "paths = []" + echo "" + echo "[paths.diverged]" + echo "# Files a minted repo owns from birth." + echo "paths = [" + echo " \".machine_readable/descriptiles/CLADE.a2ml\"," + echo " \".machine_readable/descriptiles/VARIANT.a2ml\"," + echo " \".machine_readable/rsr-profile.a2ml\"," + echo "]" + } > "$VARIANT_OUT" + if [ "$PARENT_PIN" = "UNASSIGNED" ]; then + echo " provenance: $VARIANT_OUT — parent-pin UNASSIGNED (offline?)" + echo " Set it to the template commit you minted from:" + echo " git ls-remote https://github.com/${PARENT_SLUG}.git HEAD" + else + echo " provenance: $VARIANT_OUT (parent-pin ${PARENT_PIN:0:12})" + fi + + # ── PROVENANCE.a2ml — the answer-file for the canon binding ───────────── + # ADR-0003 named this the single highest-leverage gap: "a minted repo does + # not know which template commit it came from, so template improvements + # cannot be propagated and drift cannot be detected." Copier/Cruft solved + # it with an answer-file + update + check; this estate hand-simulated it as + # recurring standardisation-PR campaigns. This is the answer-file. + # + # VARIANT.a2ml above records the TEMPLATE dimension (diffing against the + # parent). This records the CANON dimension (which law the repo was cut + # under). They are different questions and both are needed. + PROVENANCE_OUT=".machine_readable/PROVENANCE.a2ml" + { + echo "# SPDX-License-Identifier: MPL-2.0" + echo "#" + echo "# PROVENANCE.a2ml — what this repo was minted FROM." + echo "# Written once, at mint, by build/just/repo-init.just. Not a hand-edited" + echo "# file: tools that want \"what is this repo\" read STATE.a2ml; tools that" + echo "# want \"where did this repo come from\" read this." + echo "" + echo "[provenance]" + echo "minted_at = \"${CURRENT_DATE}\"" + echo "" + echo "template_repo = \"${PARENT_SLUG}\"" + echo "template_commit = \"${PARENT_PIN}\"" + echo "" + echo "canon_version = \"${CANON_VERSION}\"" + echo "criteria_sha256 = \"${CANON_CRITERIA_SHA}\"" + echo "gates_sha256 = \"${CANON_GATES_SHA}\"" + echo "" + echo "archetype = \"${ARCHETYPE:-}\"" + echo "minted_by = \"hand\"" + echo "" + echo "[substitutions]" + echo "# ADR-0003 records the old state honestly: \"renders 34 substitutions and" + echo "# derives identity, but leaves 12 tokens UNASSIGNED\". Listing them here" + echo "# converts that from a known defect into a QUERYABLE one: a non-empty" + echo "# list means the mint did not fully render, and the validation step" + echo "# below already fails on a leftover token." + echo "rendered = 34" + echo "unassigned = []" + } > "$PROVENANCE_OUT" + + if [ "$CANON_VERSION" = "UNASSIGNED" ]; then + echo " provenance: $PROVENANCE_OUT — canon pin UNASSIGNED" + echo " The template carried no [canon] block, so this repo" + echo " cannot state which canon it conforms to. Fix by minting" + echo " from a template commit that has one." + else + echo " provenance: $PROVENANCE_OUT (canon v${CANON_VERSION} ${CANON_CRITERIA_SHA:0:12})" + fi + + # archetypes/ is template-only overlay data, not part of your repo + # (same reason build/templates is removed above). + rm -rf archetypes + rm -rf build/docs-seed # template-only; its content now lives in docs/ + + # Overlay .in rule (julia-library archetype; the convention any future + # overlay can adopt): the overlay ships content templates as *.in so a + # substitution token never survives in a file with a final name. Rename + # the known set now that substitution has rendered them, before the + # placeholder gate below. + if [ "$ARCHETYPE" = "julia-library" ]; then + # the name is read from Project.toml.in until the loop below renames + # it - order matters. + TOML_FILE=Project.toml + [ -f "$TOML_FILE" ] || TOML_FILE=Project.toml.in + if [ -f src/PACKAGE.jl.in ] && [ -f "$TOML_FILE" ]; then + PKG_NAME=$(sed -n 's/^name = "\([^"]*\)".*/\1/p' "$TOML_FILE" | head -1) + if [ -n "$PKG_NAME" ] && [ "$PKG_NAME" != "UNASSIGNED" ]; then + mv "src/PACKAGE.jl.in" "src/${PKG_NAME}.jl" + echo " entry point: src/${PKG_NAME}.jl (from src/PACKAGE.jl.in)" + else + echo "WARN: Project.toml name is UNASSIGNED - src/PACKAGE.jl.in left in place" + echo " so the placeholder gate stays honest. Name the package, then run:" + echo " mv src/PACKAGE.jl.in src/.jl" + fi + fi + for f in Project.toml.in \ + .github/workflows/julia-ci.yml.in \ + .github/workflows/julia-docs.yml.in; do + [ -f "$f" ] && { mv "$f" "${f%.in}"; echo " overlay: ${f%.in}"; } + done + fi + + echo "" + echo "── Validation ───────────────────────────────────" + + # Check for remaining placeholders. + # + # This printed "WARNING" and then carried on to "Done! Mint complete." It + # detected the very defect that went on to reach 211 repos, said so every + # time, and stopped nobody. That is the fake-gate pattern this estate keeps + # re-learning: a check that cannot fail reads as coverage while changing no + # outcome. + # + # It now aborts the mint. A tree that still holds substitution tokens is not + # minted, it is half-minted, and shipping one is how the estate acquired 12 + # permanently-red OpenSSF builds and 12 settings.yml files declaring an + # unsubstituted repository name. That second one is not theoretical: the + # braces were once submitted to GitHub, collapsed to dashes, and renamed a + # repo to "-REPO-", which then read as deleted. + # + # RSR_ALLOW_PLACEHOLDERS=1 restores the old warning, for deliberately + # partial mints — not for getting past this message. + # + # META tokens are exempt, matching scripts/check-no-placeholders.sh. A file + # is only half-minted if it holds a token naming a real value; a file that + # says "replace all {PLACEHOLDER} values" is prose ABOUT tokens and is + # supposed to survive. Without this exemption the gate fires on README.adoc, + # EXPLAINME.adoc, both descriptiles and the checker itself on every single + # mint — measured — and a gate that always fires gets switched off. + # + # This exemption is also, exactly, why the instruction block went unnoticed + # in 211 repos: {PLACEHOLDER} is the ONLY doubled-brace text in it, so any + # gate sensibly exempting metasyntax must let the block through. The two + # checks here are therefore complementary, not redundant — the block can + # only be caught by name, which the check below does. + # The four allowlisted paths are copied verbatim from + # scripts/check-no-placeholders.sh so the two gates cannot drift apart. + # Each legitimately names tokens: the token vocabulary itself, prose + # explaining that tokens exist, the checker's own pattern, and the e2e + # test's answer list. + PATTERN="${LB}[A-Z_]*${RB}" + META='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + 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/' | while IFS= read -r f; do + case "${f#./}" in + .machine_readable/ai/PLACEHOLDERS.adoc|EXPLAINME.adoc|scripts/check-no-placeholders.sh|tests/e2e/template_instantiation_test.sh) continue ;; + esac + # Bracketed [{]{2} rather than a doubled brace written literally, for + # two independent reasons. First, `grep -E` is ugrep on some estate + # machines and rejects a bare doubled brace as an "empty + # (sub)expression", because a brace opens an interval quantifier — the + # same reason ci.yml uses this form estate-wide. Second, a doubled brace + # inside a just RECIPE BODY is just's own interpolation delimiter, so + # writing one here (even in a comment) is a parse error. That is why the + # recipe computes LB/RB with printf instead of spelling them out. + if grep -ohE '[{]{2}[A-Z][A-Z0-9_]*[}]{2}' "$f" | grep -qvE "^[{]{2}(${META})[}]{2}$"; then + echo "$f" + fi + done || true) + if [ -n "$REMAINING" ]; then + echo "Remaining placeholders in:" + echo "$REMAINING" | sed 's/^/ /' + echo "" + echo "Inspect with: grep -rn '$LB' . --include='*.md'" + if [ "${RSR_ALLOW_PLACEHOLDERS:-0}" = "1" ]; then + echo "WARNING: continuing anyway (RSR_ALLOW_PLACEHOLDERS=1)." + else + echo "ERROR: mint aborted — the tree above is half-minted." + echo " Fill those tokens, or re-run with RSR_ALLOW_PLACEHOLDERS=1" + echo " if a partial mint is genuinely what you want." + exit 1 + fi + else + echo "All placeholders replaced successfully!" + fi + + # An un-deleted instruction block is a different failure from an unfilled + # token, and once its literal metasyntax is the only doubled-brace text left + # the check above cannot distinguish the two. Fail on it by name. + # + # The strip pass, this recipe and the e2e test are excluded because they + # necessarily NAME the marker in order to describe or assert on it — the fix + # for the defect would otherwise be flagged by the check for the defect, and + # so would the test that guards the fix, and the cleanup test whose + # fixture prose must keep the phrase to prove prose is not collateral. + # All four are tooling, not + # community-health documents, so a mention in them cannot mislead a reader + # about what this project's Code of Conduct says. + LEFTOVER_BLOCKS=$(grep -rl 'TEMPLATE INSTRUCTIONS' . --exclude-dir=.git 2>/dev/null | grep -vE '^\./(scripts/strip-instruction-blocks\.rs|build/just/repo-init\.just|tests/e2e/template_instantiation_test\.sh|tests/workflows/mint_cleanup_test\.sh)$' || true) + if [ -n "$LEFTOVER_BLOCKS" ]; then + echo "" + echo "Un-deleted TEMPLATE INSTRUCTIONS block in:" + echo "$LEFTOVER_BLOCKS" | sed 's/^/ /' + echo "ERROR: the instruction-block pass should have removed these." + exit 1 + fi + + # ── www/ site-operations bundle (issue #53, stage 5) ────────────────── + # + # A repository minted from this template already carries www/.well-known/ + # as the canonical location and has no root .well-known/, so in the + # ordinary case both steps below do nothing at all. They exist for the + # case that is not ordinary: repo-init run over a tree that predates #53 + # and still has a root .well-known/. The migrator moves it — quarantining + # anything divergent rather than overwriting it — and the bundle's own + # suite then proves the result. + # + # #106 described this wiring as already present. It was not: nothing + # invoked the migrator or the suite at mint, which is why the estate + # still carries root .well-known/ directories years into the convention. + if [ -d .well-known ] && [ -f scripts/migrate-wellknown-to-www.sh ]; then + echo "" + echo "Migrating legacy root .well-known/ into www/.well-known/..." + bash scripts/migrate-wellknown-to-www.sh + fi + + # Fail-closed, like `just verify` below: the suite tolerates unminted + # double-brace placeholder tokens by design (it runs in the template + # itself), no archetype overlay touches www/, and the substituted Expires + # value is a parseable end-of-mint-year timestamp — so a failure here is + # real. (Spelled out in words because just interpolates double braces + # anywhere in a recipe, comments included, and an undefined variable here + # takes the whole Justfile down, not just this recipe.) + if [ -f www/tests/run-all.sh ]; then + echo "" + echo "Running www/ site-operations suite..." + bash www/tests/run-all.sh + 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! Mint complete. Remaining Forge stages (ADR-0003):" + echo " 1. Review changes: git diff" + echo " 2. Remove template cruft: rm .machine_readable/ai/PLACEHOLDERS.adoc" + echo " 3. Customize README.adoc; then: grep -rn UNASSIGNED . for what still needs you" + echo " 4. PROVISION — actually run your build and test commands and watch" + echo " them pass (echoed advice is not provisioning)." + echo " 5. Commit: git add -A && git commit -m 'feat: initialize from RSR template'" + echo " 6. Push: git remote add origin git@${FORGE}:${OWNER}/${REPO}.git && git push -u origin main" + echo " 7. CONFIGURE — out of band, once (this recipe runs no gh commands):" + echo " gh repo edit ${OWNER}/${REPO} --description '' --homepage '${WEBSITE}'" + echo " Visibility is fail-closed private; flip deliberately if wanted:" + echo " gh repo edit ${OWNER}/${REPO} --visibility public --accept-visibility-change-consequences" + echo " Register in gv-clade-index; required contexts must equal emitted job ids." + if [ -f ci/.gitlab-ci.yml ]; then + echo "" + echo " GitLab mirror only: this repo keeps its GitLab CI config at" + echo " ci/.gitlab-ci.yml, which GitLab does NOT look for by default." + echo " Settings > CI/CD > General pipelines > CI/CD configuration file" + echo " must be set to ci/.gitlab-ci.yml, or GitLab CI silently never" + echo " runs - no pipeline and no error. See ci/README.adoc." + fi + echo " 8. HARNESS — arm the drift gate against your recorded parent-pin:" + echo " scripts/check-variant-drift.sh " diff --git a/build/just/validate.just b/build/just/validate.just new file mode 100644 index 0000000..5718a77 --- /dev/null +++ b/build/just/validate.just @@ -0,0 +1,135 @@ +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# VALIDATION & COMPLIANCE +# +# Imported by ../../Justfile via `import? "build/just/validate.just"`. +# Recipes here check that this repo conforms to the RSR (Rhodium Standard +# Repository) skeleton: required files, METAdata, AI install guide +# completeness, etc. Run via `just validate`. + +# Validate RSR compliance +validate-rsr: + #!/usr/bin/env bash + cd "{{justfile_directory()}}" + echo "=== RSR Compliance Check ===" + MISSING="" + for f in .editorconfig .gitignore Justfile README.adoc LICENSE; do + [ -f "$f" ] || MISSING="$MISSING $f" + done + # The repo deed's filename carries the repo slug (_chora.deed), so + # this is a glob; the folded replacement of the old 0-AI-MANIFEST.a2ml. + ls *_chora.deed >/dev/null 2>&1 || MISSING="$MISSING repo-deed (*_chora.deed)" + for f in .machine_readable/descriptiles/STATE.a2ml .machine_readable/descriptiles/META.a2ml .machine_readable/descriptiles/ECOSYSTEM.a2ml .machine_readable/descriptiles/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 docs/legal/EXHIBIT-A-ETHICAL-USE.txt docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt LICENSES/MPL-2.0.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/descriptiles/META.a2ml" ]; then + grep -q 'axis-1 = "must > intend > like"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-1" + grep -q 'axis-2 = "corrective > adaptive > perfective"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-2" + grep -q 'axis-3 = "systems > compliance > effects"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:axis-3" + grep -q 'scoping-first = true' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:scoping-first" + grep -q 'idris-unsound-scan = "believe_me/assert_total"' .machine_readable/descriptiles/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/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:audit-focus" + grep -q 'compliance-focus = "seams/compromises/exception register, bounded exceptions, anti-drift checks"' .machine_readable/descriptiles/META.a2ml || MISSING="$MISSING META.a2ml:compliance-focus" + grep -q 'effects-evidence = "benchmark execution/results and maintainer status dialogue/review"' .machine_readable/descriptiles/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: + @cd "{{justfile_directory()}}" && if [ -f ".machine_readable/descriptiles/STATE.a2ml" ]; then \ + grep -q '^\[metadata\]' .machine_readable/descriptiles/STATE.a2ml && \ + grep -q 'project\s*=' .machine_readable/descriptiles/STATE.a2ml && \ + echo "STATE.a2ml: valid" || echo "STATE.a2ml: INVALID (missing required sections)"; \ + else \ + echo "No .machine_readable/descriptiles/STATE.a2ml found"; \ + fi + +# Validate AI installation guide completeness (finishbot pre-release check) +validate-ai-install: + #!/usr/bin/env bash + cd "{{justfile_directory()}}" + 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!" diff --git a/.gitlab-ci.yml b/ci/.gitlab-ci.yml similarity index 92% rename from .gitlab-ci.yml rename to ci/.gitlab-ci.yml index 7309fa9..2dcfe10 100644 --- a/.gitlab-ci.yml +++ b/ci/.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,18 +38,16 @@ cargo-audit: rules: - exists: - Cargo.toml - cargo-deny: stage: security image: rust:latest script: - cargo install cargo-deny - - cargo deny check + - cargo deny --manifest-path Cargo.toml check --config .machine_readable/compliance/rust/deny.toml rules: - 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/ci/.pre-commit-config.yaml b/ci/.pre-commit-config.yaml new file mode 100644 index 0000000..8069164 --- /dev/null +++ b/ci/.pre-commit-config.yaml @@ -0,0 +1,73 @@ +# SPDX-License-Identifier: MPL-2.0 +# Pre-commit hooks for hyperpolymath RSR repos. +# Install: pip install pre-commit && pre-commit install +# Run manually: pre-commit run --all-files + +repos: + # --- Standard hooks --- + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-merge-conflict + - id: detect-private-key + - id: check-added-large-files + args: ['--maxkb=1024'] + + # --- Manifest validation (DEED grammar) --- + # The former provider repo (hyperpolymath/a2ml-pre-commit) was DELETED + # upstream in the A2ML retirement (standards #836 owner ruling; R-H3), + # leaving this pin as broken plumbing — pre-commit could not clone it. + # Replaced with a local hook calling .github/hooks/validate-deed.sh, + # verified exit-0 against all 123 manifests in this repo. Dual-accept: + # scans both .a2ml (legacy extension; migration = standards #837, the + # DEED conversion campaign) and .deed. When the grammar-faithful .deed + # validator is wired per owner ruling R-H2, this hook is its natural home. + - repo: local + hooks: + - id: validate-deed + name: Validate DEED manifests (dual-accept .a2ml/.deed) + entry: .github/hooks/validate-deed.sh + language: system + files: '\.(a2ml|deed)$' + pass_filenames: false + + # --- K9 contract validation --- + - repo: https://github.com/hyperpolymath/k9-pre-commit + rev: 9b82e1f7a6b6c0f99df72c145d7dee8851803c30 # k9-pre-commit @ main 2026-06-23 + hooks: + - id: validate-k9 + name: Validate K9 contracts + + # --- Shell linting --- + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck + + # --- EditorConfig --- + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: 3.2.1 + hooks: + - id: editorconfig-checker + exclude: '(\.git|node_modules|target|_build|deps|\.|external_corpora|\.lake)/' + + # --- Secret detection --- + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.3 + hooks: + - id: gitleaks + + # --- Arrival pack drift: CLAUDE.md must stay in sync with a2ml --- + - repo: local + hooks: + - id: validate-claude-md + name: CLAUDE.md arrival pack in sync with a2ml + entry: bash .machine_readable/arrival-pack/verify.sh + language: system + pass_filenames: false + files: '^(\.machine_readable/descriptiles/.*|CLAUDE\.md)$' diff --git a/ci/README.adoc b/ci/README.adoc new file mode 100644 index 0000000..933aae6 --- /dev/null +++ b/ci/README.adoc @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += CI configuration + +Non-GitHub CI configuration lives here. GitHub Actions workflows stay in +`.github/workflows/` because GitHub reads that path and no other. + +[cols="1,3",options="header"] +|=== +| File | Consumer + +| `.gitlab-ci.yml` +| GitLab CI. **Not** found automatically at this path — see below. + +| `.pre-commit-config.yaml` +| The `pre-commit` framework, when invoked with `--config`. +|=== + +== Required out-of-band setting (GitLab) + +GitLab looks for `.gitlab-ci.yml` at the repository root by default. Because the +file now lives in `ci/`, the project setting must be changed or **GitLab CI +silently stops running** — no pipeline, no error: + +* Project → Settings → CI/CD → General pipelines → *CI/CD configuration file* +* Set it to `ci/.gitlab-ci.yml` + +== Using pre-commit + +The config is no longer at the root, so pass it explicitly: + +[source,bash] +---- +pre-commit install --config ci/.pre-commit-config.yaml +pre-commit run --all-files --config ci/.pre-commit-config.yaml +---- + +[NOTE] +==== +This repository's own push-time gate does not use the `pre-commit` framework. +It is `.github/hooks/pre-push`, activated by `bash .github/hooks/install.sh`, which sets +`core.hooksPath` to `.github/hooks`. +==== diff --git a/container/0.1-AI-MANIFEST.a2ml b/container/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index c63d9e6..0000000 --- a/container/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,143 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "container-templates" -version: "1.0.0" -context: - - "https://a2ml.org/ns/v2" - - "https://stapeln.dev/ns/v1" - ---- -### [AI_MANIFEST] -description: | - Container templates for the stapeln container ecosystem. This directory - provides Podman-Chainguard-stapeln templates that are customised via - `just container-init` or `just init` during project bootstrap. - - All files use dictask tokens that are substituted with project- - specific values during initialisation. - -purpose: | - Provide a complete, security-first container deployment story for any - RSR-compliant repository. The templates cover the full lifecycle: - build, sign, verify, deploy, monitor, and govern. - -canonical_locations: - compose: "container/compose.toml" - containerfile: "container/Containerfile" - manifest: "container/manifest.toml" - gatekeeper: "container/.gatekeeper.yaml" - build_pipeline: "container/ct-build.sh" - entrypoint: "container/entrypoint.sh" - monitoring: "container/vordr.toml" - deployment: "container/deploy.k9.ncl" - example: "container/compose.example.toml" - ---- -### [FILE_RELATIONSHIPS] -files: - - name: "compose.toml" - role: "Orchestration" - description: | - selur-compose stack definition. Declares services, volumes, networks, - and health checks. References the Containerfile for image builds and - .gatekeeper.yaml for svalinn policy. - depends_on: ["Containerfile", ".gatekeeper.yaml"] - - - name: "Containerfile" - role: "Image Build" - description: | - Multi-stage OCI container build. Stage 1 compiles the application on - wolfi-base; Stage 2 copies the binary into a minimal runtime image. - Copies entrypoint.sh, .gatekeeper.yaml, and manifest.toml into the - final image. - depends_on: ["entrypoint.sh", ".gatekeeper.yaml", "manifest.toml"] - - - name: "manifest.toml" - role: "Bundle Metadata" - description: | - Cerro-torre .ctp bundle manifest. Describes provenance, dependencies, - attestations, and runtime security profile. Used by `ct pack` and - `ct verify`. - depends_on: [] - - - name: ".gatekeeper.yaml" - role: "Gateway Policy" - description: | - Svalinn edge gateway policy. Controls authentication, rate limiting, - container trust, request validation, CORS, and audit logging. - depends_on: [] - - - name: "ct-build.sh" - role: "Build Pipeline" - description: | - Shell script implementing the 5-stage pipeline: build (Podman), - pack (cerro-torre .ctp), sign (Ed25519), verify, push (optional). - Degrades gracefully when cerro-torre tools are not installed. - depends_on: ["Containerfile", "manifest.toml"] - - - name: "entrypoint.sh" - role: "Container Entrypoint" - description: | - Startup script with signal handling (SIGTERM, SIGINT), logging, and - exec into the main application process. - depends_on: [] - - - name: "vordr.toml" - role: "Runtime Monitoring" - description: | - Vordr monitoring configuration. Health endpoint probing, crash - detection, resource thresholds, and structured log output. - depends_on: [] - - - name: "deploy.k9.ncl" - role: "Deployment Component" - description: | - k9-svc deployment specification at Hunt trust level. Full pedigree - (L1-L5), environment configs, container config, and rolling - deployment strategy. - depends_on: ["compose.toml", "ct-build.sh"] - - - name: "compose.example.toml" - role: "Example" - description: | - Fully-commented multi-service example (Rust API + Elixir worker + - svalinn gateway). Copy to compose.toml and customise. - depends_on: [] - ---- -### [STAPELN_ECOSYSTEM] -overview: | - The stapeln container ecosystem comprises six tools: - - selur — Container orchestration with zero-copy IPC. Reads compose.toml. - cerro-torre — Verified container packaging (.ctp bundles), Ed25519 signing. - svalinn — Policy-driven edge gateway (auth, rate limits, CORS, trust). - vordr — Runtime monitoring (health, crashes, resources, logs). - rokur — Secrets management (runtime injection, no baked secrets). - k9-svc — Nickel deployment components (Kennel/Yard/Hunt trust levels). - -invariants: - - "Base images MUST be cgr.dev/chainguard/wolfi-base or cgr.dev/chainguard/static" - - "Container runtime is Podman — never Docker" - - "Containerfile — never Dockerfile" - - "All images run as non-root (appuser or project-specific user)" - - ".ctp bundles are signed with Ed25519 via cerro-torre" - - "Health endpoints (/health, /ready) must always be public (no auth)" - ---- -### [USAGE] -initialisation: | - Run `just container-init` to substitute all dictask tokens with - project-specific values. This is also run as part of `just init`. - -development: | - 1. `just container-build` — Build the container image - 2. `just container-verify` — Verify compose configuration - 3. `just container-up` — Start the stack locally - 4. `just container-down` — Stop the stack - -production: | - 1. `just container-sign` — Build, sign, verify .ctp bundle - 2. `just container-push` — Push signed bundle to registry - 3. `selur-compose up` — Deploy on target host diff --git a/container/README.adoc b/container/README.adoc deleted file mode 100644 index dcf5c6b..0000000 --- a/container/README.adoc +++ /dev/null @@ -1,179 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -= Dictask Container Templates -:toc: left -:toclevels: 3 -:sectnums: - -== Overview - -This directory contains container templates for the -https://github.com/hyperpolymath/stapeln[stapeln] container ecosystem. -The stapeln stack provides verified container packaging, edge gateway -policies, runtime monitoring, and supply-chain signing for Podman-based -deployments using https://www.chainguard.dev/[Chainguard] Wolfi base images. - -All files use `dictask` tokens that are replaced by `just container-init` -(or by the top-level `just init` during project bootstrap). - -== File Reference - -[cols="1,3"] -|=== -| File | Purpose - -| `compose.toml` -| **selur-compose** stack definition. Declares services, volumes, networks, - and health checks. The primary orchestration file for local and production - deployment. Use `selur-compose up` or fall back to `podman compose`. - -| `compose.example.toml` -| Concrete multi-service example with detailed comments. Copy and customise - for your own stack. Not used directly by any tooling. - -| `Containerfile` -| Multi-stage OCI container build specification. Stage 1 builds the - application; Stage 2 produces a minimal runtime image on - `cgr.dev/chainguard/wolfi-base`. Uses Podman (never Docker). - -| `manifest.toml` -| **cerro-torre** bundle metadata. Describes the `.ctp` verified container - package: provenance, dependencies, attestations, and runtime security - profile. Used by `ct pack` and `ct verify`. - -| `.gatekeeper.yaml` -| **svalinn** edge gateway policy. Controls authentication, rate limiting, - container trust, request validation, CORS, and audit logging at the - network boundary. - -| `ct-build.sh` -| Build, sign, and verify pipeline script. Five stages: build (Podman), - pack (cerro-torre `.ctp`), sign (Ed25519), verify, and push (optional). - Gracefully degrades when cerro-torre tools are not installed. - -| `entrypoint.sh` -| Container entrypoint with signal handling (SIGTERM, SIGINT), startup - logging, and `exec` into the main application process. - -| `vordr.toml` -| **vordr** runtime monitoring configuration. Defines health endpoints, - crash detection, resource thresholds, and log output. - -| `deploy.k9.ncl` -| **k9-svc** deployment component at Hunt trust level. Full pedigree - (L1--L5), environment configs (dev/staging/prod), container - configuration, and rolling deployment strategy. - -| `0-AI-MANIFEST.a2ml` -| AI-readable manifest describing the container directory, file - interconnections, and the stapeln ecosystem. -|=== - -== The stapeln Ecosystem - -The stapeln container ecosystem comprises six interconnected tools: - -**selur** (compose):: - Container orchestration with zero-copy IPC for co-located services. - Reads `compose.toml` files. Falls back to standard Podman Compose - when the selur driver is unavailable. - -**cerro-torre** (bundles and signing):: - Verified container packaging. Produces `.ctp` bundles from OCI images, - signs them with Ed25519, and verifies the full chain. Tools: `ct pack`, - `ct sign`, `ct verify`, `ct push`, `ct explain`. - -**svalinn** (edge gateway):: - Policy-driven reverse proxy. Enforces authentication, rate limiting, - CORS, and container trust policies defined in `.gatekeeper.yaml`. - -**vordr** (monitoring):: - Runtime container monitoring. Watches health endpoints, detects crashes, - tracks resource usage, and emits structured logs. - -**rokur** (secrets):: - Secrets management for container deployments. Injects secrets at runtime - without baking them into images. Currently a stub/placeholder. - -**k9-svc** (deployment components):: - Nickel-based deployment specification. Components declare their pedigree - (identity, target, security, validation, recipes) and execute at one of - three trust levels: Kennel (data only), Yard (evaluation), Hunt (full - execution with cryptographic handshake). - -== How to Initialise - -[source,bash] ----- -# Option 1: During project bootstrap (includes all placeholders) -just init - -# Option 2: Container-specific initialisation -just container-init ----- - -The `container-init` recipe prompts for container-specific values -(service name, port, registry) and substitutes all `dictask` -tokens in the `container/` directory. - -== Development Workflow - -[source,bash] ----- -# 1. Build the container image -just container-build - -# 2. Verify the compose configuration -just container-verify - -# 3. Start the stack locally -just container-up --detach - -# 4. Check logs -podman compose --file container/compose.toml logs -f - -# 5. Stop the stack -just container-down ----- - -== Production Deployment - -[source,bash] ----- -# 1. Build, sign, and verify the .ctp bundle -just container-sign - -# 2. Push the signed bundle to the registry -just container-push - -# 3. Deploy on the target host -selur-compose up --detach ----- - -For k9-svc managed deployments: - -[source,bash] ----- -# Validate the deployment component -nickel typecheck container/deploy.k9.ncl - -# Deploy (requires Hunt-level authorisation) -k9-svc deploy container/deploy.k9.ncl --env production ----- - -== Base Images - -All Containerfiles use Chainguard Wolfi base images: - -* **Builder stage:** `cgr.dev/chainguard/wolfi-base:latest` -* **Runtime stage:** `cgr.dev/chainguard/wolfi-base:latest` (or - `cgr.dev/chainguard/static:latest` for statically-linked binaries) - -Chainguard images are minimal, CVE-free, and rebuilt daily. They use the -`apk` package manager (Alpine-compatible). - -== Container Runtime - -This project uses **Podman** (never Docker). All scripts, compose files, -and documentation reference Podman commands. The OCI Containerfile format -is compatible with Podman, Docker, and nerdctl. diff --git a/contractile.just b/contractile.just deleted file mode 100644 index 9a5827b..0000000 --- a/contractile.just +++ /dev/null @@ -1,75 +0,0 @@ -# Auto-generated by: contractile gen-just -# Source directory: contractiles -# Re-generate with: contractile gen-just --dir contractiles -# -# SPDX-License-Identifier: MPL-2.0 - -# === DUST (Recovery & Rollback) === -# Source: Dustfile.a2ml - -# List available dust recovery actions -dust-status: - @echo ' dust-source-rollback: Revert all source changes to last commit [rollback]' - -# Revert all source changes to last commit -dust-source-rollback: - @echo 'Executing rollback for source-rollback' - git checkout HEAD -- . - - -# === INTEND (Declared Future Intent) === -# Source: Intentfile.a2ml - -# Display declared future intents -intend-list: - @echo '=== Declared Intent ===' - @echo '' - @echo 'Features:' - @echo '' - @echo 'Quality:' - - -# === MUST (Physical State Checks) === -# Source: Mustfile.a2ml - -# Run all must checks -must-check: must-license-present must-readme-present must-spdx-headers must-no-banned-files - @echo 'All must checks passed' - -# LICENSE file must exist -must-license-present: - test -f LICENSE - -# README must exist -must-readme-present: - test -f README.adoc || test -f README.md - -# Source files should have SPDX license headers -must-spdx-headers: - find . -name '*.rs' -o -name '*.res' -o -name '*.gleam' | head -20 | xargs -r grep -L 'SPDX-License-Identifier' | wc -l | grep -q '^0$' - -# No Dockerfiles or Makefiles -must-no-banned-files: - test ! -f Dockerfile && test ! -f Makefile - - -# === TRUST (Integrity & Provenance Verification) === -# Source: Trustfile.a2ml - -# Run all trust verifications -trust-verify: trust-license-content trust-no-secrets-committed trust-container-images-pinned - @echo 'All trust verifications passed' - -# LICENSE contains expected SPDX identifier -trust-license-content: - grep -q 'SPDX\|License\|MIT\|Apache\|PMPL\|MPL' LICENSE - -# No .env or credential files in repo -trust-no-secrets-committed: - test ! -f .env && test ! -f credentials.json && test ! -f .env.local - -# Containerfile base images use pinned digests -trust-container-images-pinned: - test ! -f Containerfile || grep -q '@sha256:' Containerfile - - diff --git a/contractiles/intend/Intentfile.a2ml b/contractiles/intend/Intentfile.a2ml deleted file mode 100644 index 007086f..0000000 --- a/contractiles/intend/Intentfile.a2ml +++ /dev/null @@ -1,22 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Intentfile (A2ML Canonical) -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -@abstract: -Declared intent and purpose for Dictask. -@end - -## Purpose - -Dictask — // SPDX-License-Identifier: MPL-2.0 - -## Anti-Purpose - -This project is NOT: -- A fork or wrapper around another tool -- A monorepo (unless explicitly structured as one) - -## If In Doubt - -If you are unsure whether a change is in scope, ask. -Sensitive areas: ABI definitions, license headers, CI workflows. diff --git a/contractiles/must/Mustfile.a2ml b/contractiles/must/Mustfile.a2ml deleted file mode 100644 index b1acac0..0000000 --- a/contractiles/must/Mustfile.a2ml +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Mustfile (A2ML Canonical) -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -@abstract: -Physical State contract for Dictask. Baseline UX Manifesto invariants -that MUST hold at all times. -@end - -@requires: -- section: Core-Files -- section: Banned -@end - -## Core-Files - -### license-present -- description: LICENSE file must exist -- run: test -f LICENSE -- severity: critical - -### readme-present -- description: README must exist -- run: test -f README.adoc || test -f README.md -- severity: critical - -## Banned - -### no-hardcoded-paths -- description: No hardcoded developer paths -- run: "! grep -rn '$HOME\|$ECLIPSE_DIR' --include='*.rs' --include='*.res' --include='*.ex' --include='*.gleam' --include='*.zig' --include='*.sh' . 2>/dev/null | grep -v '.git/' | grep -v 'ux-rollout.jl' | head -1" -- severity: critical - -### no-dockerfiles -- description: No Dockerfiles (use Containerfile) -- run: test ! -f Dockerfile -- severity: warning - -### no-makefiles -- description: No Makefiles (use Justfile) -- run: test ! -f Makefile -- severity: warning diff --git a/contractiles/trust/Trustfile.a2ml b/contractiles/trust/Trustfile.a2ml deleted file mode 100644 index e0c2c1e..0000000 --- a/contractiles/trust/Trustfile.a2ml +++ /dev/null @@ -1,25 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Trustfile (A2ML Canonical) -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) - -@abstract: -Trust and provenance verification for Dictask. -Maximal trust by default — LLM may read, build, test, lint, format. -@end - -@trust-level: maximal -@trust-boundary: repo -@trust-actions: [read, build, test, lint, format] -@trust-deny: [delete-branch, force-push, modify-ci-secrets, publish] - -## Integrity - -### license-content -- description: LICENSE contains expected SPDX identifier -- run: grep -q 'SPDX\|License\|MIT\|Apache\|PMPL\|MPL' LICENSE -- severity: critical - -### no-secrets-committed -- description: No .env or credential files in repo -- run: test ! -f .env && test ! -f credentials.json && test ! -f .env.local -- severity: critical diff --git a/coordination.k9.ncl b/coordination.k9.ncl new file mode 100644 index 0000000..6f4f987 --- /dev/null +++ b/coordination.k9.ncl @@ -0,0 +1,49 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Thin coordination bindings for central session-management standards + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "session-coordination", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + session_management = { + source_of_truth = "standards/3-practice/session-management-standards", + canonical_commands = [ + "intake repo ", + "checkpoint change ", + "verify maintenance ", + "verify substantial ", + "verify release ", + "close planned ", + "close urgent ", + "recover repo ", + "handover full ", + "handover split ", + "handover model ", + "handover human ", + ], + }, + + signals = [ + { name = "session.intake", command = "intake repo " }, + { name = "session.checkpoint", command = "checkpoint change " }, + { name = "session.verify.maintenance", command = "verify maintenance " }, + { name = "session.verify.substantial", command = "verify substantial " }, + { name = "session.verify.release", command = "verify release " }, + { name = "session.close.planned", command = "close planned " }, + { name = "session.close.urgent", command = "close urgent " }, + { name = "session.recover", command = "recover repo " }, + { name = "session.handover.full", command = "handover full " }, + { name = "session.handover.split", command = "handover split " }, + { name = "session.handover.model", command = "handover model " }, + { name = "session.handover.human", command = "handover human " }, + ], +} diff --git a/dicta-task_chora.deed b/dicta-task_chora.deed new file mode 100644 index 0000000..40f8fcf --- /dev/null +++ b/dicta-task_chora.deed @@ -0,0 +1,152 @@ +;; SPDX-License-Identifier: MPL-2.0 +; +; The repo deed of dicta-task: one-deed-per-repo doctrine (standards#837, +; owner ruling 2026-09-19, Option A). Folds the former ply manifest tree +; (0.N-AI-MANIFEST.a2ml, 82 directories) and the family-7 AI +; allocation manifest (root 0-AI-MANIFEST.a2ml) into deed clauses. +; Generated by the #837 pilot emitter; every byte gated by deed_lint.py. +(repo-deed + :schema-version "1.0.0" + :canonical-name "dicta-task" + :beholding-chora #u5"estate/chora" + :repo-uuid #u5"github.com/hyperpolymath/dicta-task" + (manifest + :version "0.1.0" + :last-updated "2026-09-26" + :project "Dicta-Task" + :purpose "Speech-to-do pipeline (formerly dictask): portable voice recorder → offline transcription → Haskell NLP task extraction → prioritised, auditable SQLite task list." + (agent :name CLAUDE :role "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") + (agent :name CHATGPT :role "prose, papers, publication review, standards docs, outreach drafts, letters") + (agent :name GEMINI :role "estate audits, cross-repo sweeps, long-context triage, pattern detection") + (agent :name VIBE :role "UI/frontend, PanLL panels, components, theming, rapid prototyping") + (policy :rules ("Do not duplicate tasks across sections. If a task needs multiple LLMs, note the handoff." "Update THIS file during sessions. Do NOT recreate per-repo TODO files.")) + (work :agent CLAUDE :task "proofs, compilers, repo-local implementation, CI/CD, formal verification, Rust/Idris2/Zig") + (work :agent CHATGPT :task "prose, papers, publication review, standards docs, outreach drafts, letters") + (work :agent GEMINI :task "estate audits, cross-repo sweeps, long-context triage, pattern detection") + (work :agent VIBE :task "UI/frontend, PanLL panels, components, theming, rapid prototyping") + ) + (ply + (directory :ply 1 :path ".github") + (directory :id "machine-readable-pillar" :ply 1 :path ".machine_readable" :description "Registry for all machine-readable metadata, policies, and internal\nautomation scripts." + (canonical-locations :agentic "descriptiles/AGENTIC.a2ml" :ai_configs "ai/" :anchors "descriptiles/anchors/" :clade "descriptiles/CLADE.a2ml" :compliance "compliance/" :ecosystem "descriptiles/ECOSYSTEM.a2ml" :meta "descriptiles/META.a2ml" :neurosym "descriptiles/NEUROSYM.a2ml" :playbook "descriptiles/PLAYBOOK.a2ml" :policies "policies/" :scripts "scripts/" :state "descriptiles/STATE.a2ml") + :invariants ("Metadata files MUST follow a2ml format" "Internal automation MUST live in scripts/ subfolder") + (directory :id "ai-registry" :ply 2 :path ".machine_readable/ai" :description "Sub-registry for ai metadata.") + (directory :id "configs-registry" :ply 2 :path ".machine_readable/configs" :description "Sub-registry for configs metadata.") + (directory :ply 0 :path ".machine_readable/descriptiles" :description "This manifest declares the AI-assistant context for the descriptiles machine-readable metadata directory." + :invariants ("No duplicate files in root directory" "Single source of truth: this directory is authoritative" "No stale metadata") + (directory :ply 0 :path ".machine_readable/descriptiles/anchors" :description "This manifest declares the AI-assistant context for the anchor machine-readable metadata directory." + :invariants ("Multiple versions with different dates are permitted" "No other A2ML files in this directory" "Single source of truth for anchor documents"))) + (directory :id "policies-registry" :ply 2 :path ".machine_readable/policies" :description "Sub-registry for policies metadata.") + (directory :id "automation-scripts-unit" :ply 2 :path ".machine_readable/scripts" :description "Internal automation logic for the project lifecycle, forge sync,\nverification triggers, and maintenance." + (canonical-locations :forge "forge/" :lifecycle "lifecycle/" :maintenance "maintenance/" :verification "verification/") + (directory :id "automation-unit-forge" :ply 3 :path ".machine_readable/scripts/forge" :description "Internal automation logic for project forge.") + (directory :id "automation-unit-lifecycle" :ply 3 :path ".machine_readable/scripts/lifecycle" :description "Internal automation logic for project lifecycle.") + (directory :id "automation-unit-verification" :ply 3 :path ".machine_readable/scripts/verification" :description "Internal automation logic for project verification."))) + (directory :id "container-templates" :ply 1 :path "build/container" :version "1.0.0" :description "Container templates for the stapeln container ecosystem. This directory\nprovides Podman-Chainguard-stapeln templates that are customised via\n`just container-init` or `just repo-init` during project bootstrap.\n\nAll files use {{PLACEHOLDER}} tokens that are substituted with project-\nspecific values during initialisation." :purpose "Provide a complete, security-first container deployment story for any\nRSR-compliant repository. The templates cover the full lifecycle:\nbuild, sign, verify, deploy, monitor, and govern." :overview "The stapeln container ecosystem comprises six tools:\n\nselur — Container orchestration with zero-copy IPC. Reads compose.toml.\ncerro-torre — Verified container packaging (.ctp bundles), Ed25519 signing.\nsvalinn — Policy-driven edge gateway (auth, rate limits, CORS, trust).\nvordr — Runtime monitoring (health, crashes, resources, logs).\nrokur — Secrets management (runtime injection, no baked secrets).\nk9-svc — Nickel deployment components (Kennel/Yard/Hunt trust levels)." + :context ("https://a2ml.org/ns/v2" "https://stapeln.dev/ns/v1") + (canonical-locations :build_pipeline "build/container/stapeln/ct-build.sh" :compose "build/container/stapeln/compose.toml" :compose_example "build/container/stapeln/compose.example.toml" :compose_portable "build/container/compose.yaml" :compose_portable_example "build/container/compose.example.yaml" :containerfile "build/container/Containerfile" :containerignore "build/container/.containerignore" :deployment "build/container/stapeln/deploy.k9.ncl" :entrypoint "build/container/entrypoint.sh" :gatekeeper "build/container/stapeln/.gatekeeper.yaml" :just_module "build/just/container.just" :manifest "build/container/stapeln/manifest.toml" :monitoring "build/container/stapeln/vordr.toml" :secrets_gate "build/container/stapeln/rokur.toml") + :invariants ("Base images MUST be cgr.dev/chainguard/wolfi-base or cgr.dev/chainguard/static" "Container runtime is Podman — never Docker" "Containerfile — never Dockerfile" "All images run as non-root (appuser or project-specific user)" ".ctp bundles are signed with Ed25519 via cerro-torre" "Health endpoints (/health, /ready) must always be public (no auth)") + (file-relationships + (file :name "compose.toml" :role "Orchestration" :description "selur-compose stack definition. Declares services, volumes, networks,\nand health checks. References the Containerfile for image builds and\n.gatekeeper.yaml for svalinn policy." :depends-on ("Containerfile" ".gatekeeper.yaml")) + (file :name "Containerfile" :role "Image Build (Tier A)" :description "Multi-stage OCI container build. Stage 1 compiles the application on\nwolfi-base; Stage 2 copies the binary into a minimal runtime image and\ncopies entrypoint.sh. Engine-agnostic (podman/nerdctl/docker). The\nstapeln files (.gatekeeper.yaml, manifest.toml) are referenced only as\nOCI labels and copied in only if you uncomment those COPY lines, so the\nimage has no hard dependency on tier C." :depends-on ("entrypoint.sh")) + (file :name "manifest.toml" :role "Bundle Metadata" :description "Cerro-torre .ctp bundle manifest. Describes provenance, dependencies,\nattestations, and runtime security profile. Used by `ct pack` and\n`ct verify`." :depends-on ()) + (file :name ".gatekeeper.yaml" :role "Gateway Policy" :description "Svalinn edge gateway policy. Controls authentication, rate limiting,\ncontainer trust, request validation, CORS, and audit logging." :depends-on ()) + (file :name "ct-build.sh" :role "Build Pipeline" :description "Shell script implementing the 5-stage pipeline: build (Podman),\npack (cerro-torre .ctp), sign (Ed25519), verify, push (optional).\nDegrades gracefully when cerro-torre tools are not installed." :depends-on ("Containerfile" "manifest.toml")) + (file :name "entrypoint.sh" :role "Container Entrypoint" :description "Startup script with signal handling (SIGTERM, SIGINT), logging, and\nexec into the main application process." :depends-on ()) + (file :name "vordr.toml" :role "Runtime Monitoring" :description "Vordr monitoring configuration. Health endpoint probing, crash\ndetection, resource thresholds, and structured log output." :depends-on ()) + (file :name "deploy.k9.ncl" :role "Deployment Component" :description "k9-svc deployment specification at Hunt trust level. Full pedigree\n(L1-L5), environment configs, container config, and rolling\ndeployment strategy." :depends-on ("compose.toml" "ct-build.sh")) + (file :name "compose.example.toml" :role "Example" :description "Fully-commented multi-service example (Rust API + Elixir worker +\nsvalinn gateway). Copy to compose.toml and customise." :depends-on ()) + )) + (directory :id "docs-pillar" :ply 1 :path "docs" :description "Technical documentation hub. The root contains high-level orientation\n(README, Quickstart, State-Visualizer). Specialized tracks live in\nsubdirectories." + (canonical-locations :architecture "architecture/" :attribution "attribution/" :decisions "decisions/" :developer "developer/" :governance "governance/" :legal "legal/" :practice "practice/" :quickstart "QUICKSTART.adoc" :reports "reports/" :standards "standards/" :state_visualizer "STATE-VISUALIZER.adoc" :theory "theory/" :whitepapers "whitepapers/" :wikis "wikis/") + :invariants ("Primary documentation format MUST be AsciiDoc (.adoc)" "Root docs/ MUST only contain pillar entry points") + (directory :id "architecture-track" :ply 2 :path "docs/architecture" :description "Documentation track for system architecture and threat models." + (canonical-locations :threat_model "THREAT-MODEL.adoc") + :invariants ("Visual diagrams MUST include ASCII or Mermaid representations")) + (directory :id "attribution-unit" :ply 2 :path "docs/attribution" :description "Sub-unit of the docs pillar focusing on attribution.") + (directory :id "decisions-unit" :ply 2 :path "docs/decisions" :description "Sub-unit of the docs pillar focusing on decisions.") + (directory :id "developer-unit" :ply 2 :path "docs/developer" :description "Sub-unit of the docs pillar focusing on developer.") + (directory :id "governance-pillar" :ply 1 :path "docs/governance" :description "Primary governance pillar implementing the Triaxial Software Development\nMethodology (TSDM). Contains planning, maintenance, and audit tracks." + (canonical-locations :approach "SOFTWARE-DEVELOPMENT-APPROACH.adoc" :audit "audit/" :checklist "MAINTENANCE-CHECKLIST.adoc" :crg "CRG-CRITERIA.adoc" :maintenance "maintenance/" :planning "planning/" :tsdm_spec "TSDM.adoc") + (directory :id "governance-axis-audit" :ply 2 :path "docs/governance/audit" :description "TSDM Audit track." + (directory :id "governance-unit-compliance" :ply 3 :path "docs/governance/audit/compliance" :description "TSDM compliance unit within the Audit axis.") + (directory :id "governance-unit-effects" :ply 3 :path "docs/governance/audit/effects" :description "TSDM effects unit within the Audit axis.") + (directory :id "governance-unit-systems" :ply 3 :path "docs/governance/audit/systems" :description "TSDM systems unit within the Audit axis.")) + (directory :id "governance-axis-maintenance" :ply 2 :path "docs/governance/maintenance" :description "TSDM Maintenance track." + (directory :id "governance-unit-adaptive" :ply 3 :path "docs/governance/maintenance/adaptive" :description "TSDM adaptive unit within the Maintenance axis.") + (directory :id "governance-unit-corrective" :ply 3 :path "docs/governance/maintenance/corrective" :description "TSDM corrective unit within the Maintenance axis.") + (directory :id "governance-unit-perfective" :ply 3 :path "docs/governance/maintenance/perfective" :description "TSDM perfective unit within the Maintenance axis.")) + (directory :id "governance-axis-planning" :ply 2 :path "docs/governance/planning" :description "TSDM Planning track." + (directory :id "governance-unit-could" :ply 3 :path "docs/governance/planning/could" :description "TSDM could unit within the Planning axis.") + (directory :id "governance-unit-must" :ply 3 :path "docs/governance/planning/must" :description "TSDM must unit within the Planning axis.") + (directory :id "governance-unit-should" :ply 3 :path "docs/governance/planning/should" :description "TSDM should unit within the Planning axis."))) + (directory :id "legal-track" :ply 2 :path "docs/legal" :description "Sub-unit for legal and licensing documentation. Contains framework\nexhibits and archival license texts." + (canonical-locations :exhibits "exhibits/" :texts "texts/")) + (directory :id "practice-unit" :ply 2 :path "docs/practice" :description "Sub-unit of the docs pillar focusing on practice.") + (directory :id "reports-unit" :ply 2 :path "docs/reports" :description "Documentation unit for all automated and manual audit reports. Classified\nby domain." + (canonical-locations :compliance "compliance/" :maintenance "maintenance/" :performance "performance/" :quality "quality/" :security "security/") + (directory :id "report-unit-compliance" :ply 3 :path "docs/reports/compliance" :description "Specialised repository for compliance findings and evidence.") + (directory :id "report-unit-maintenance" :ply 3 :path "docs/reports/maintenance" :description "Maintenance reports.") + (directory :id "report-unit-performance" :ply 3 :path "docs/reports/performance" :description "Specialised repository for performance findings and evidence.") + (directory :id "report-unit-quality" :ply 3 :path "docs/reports/quality" :description "Specialised repository for quality findings and evidence.") + (directory :id "report-unit-security" :ply 3 :path "docs/reports/security" :description "Specialised repository for security findings and evidence.")) + (directory :id "standards-unit" :ply 2 :path "docs/standards" :description "Standards unit for high-rigor verification.") + (directory :id "theory-track" :ply 2 :path "docs/theory" :description "Documentation track for domain-specific theory and research foundations.\nCategorised by discipline." + (canonical-locations :computing "computing/" :formalisms "formalisms/" :mathematics "mathematics/" :ontologies "ontologies/" :other "other/" :socio_technical "socio-technical/") + :invariants ("Theoretical claims MUST reference established academic or technical formalisms") + (directory :id "theory-unit-computing" :ply 3 :path "docs/theory/computing" :description "Theoretical foundation for computing.") + (directory :id "theory-unit-formalisms" :ply 3 :path "docs/theory/formalisms" :description "Theoretical foundation for formalisms.") + (directory :id "theory-unit-mathematics" :ply 3 :path "docs/theory/mathematics" :description "Theoretical foundation for mathematics.") + (directory :id "theory-unit-ontologies" :ply 3 :path "docs/theory/ontologies" :description "Theoretical foundation for ontologies.") + (directory :id "theory-unit-other" :ply 3 :path "docs/theory/other" :description "Theoretical foundation for other.") + (directory :id "theory-unit-socio-technical" :ply 3 :path "docs/theory/socio-technical" :description "Theoretical foundation for socio technical.")) + (directory :id "whitepapers-track" :ply 2 :path "docs/whitepapers" :description "Unit for strategic publications and whitepapers. Categorised by target\naudience: Academic, Industry, and Outreach." + (canonical-locations :academic "academic/" :industry "industry/" :outreach "outreach/") + :invariants ("Each sub-track MUST have a clear audience definition in its README") + (directory :id "academic-unit" :ply 3 :path "docs/whitepapers/academic" :description "Academic logic at level 3.") + (directory :id "industry-unit" :ply 3 :path "docs/whitepapers/industry" :description "Industry logic at level 3.") + (directory :id "whitepapers-track-outreach" :ply 3 :path "docs/whitepapers/outreach" :description "Documentation track for outreach, education, and general-audience\nengagement. Focuses on accessibility and high-level conceptual clarity." + :invariants ("Language MUST be accessible to non-technical audiences" "Avoid deep technical jargon without providing clear definitions"))) + (directory :id "wikis-track" :ply 2 :path "docs/wikis" :description "Long-form collaborative documentation and project knowledge base.\nThis directory mirrors the content structure of the project wiki." + :invariants ("Primary wiki format MUST be Markdown (.md) — owner ruling 2026-09-19: wikis are the .md home (berrywiki); everything outside wikis stays .adoc"))) + (directory :ply 1 :path "examples") + (directory :id "features-pillar" :ply 1 :path "features" :description "Optional project features and ecosystem integrations. Provides bootstrap\nguides for high-rigor tools (Panic-Attacker, BoJ-Server, SSGs)." + (canonical-locations :boj_server "boj-server/" :panic_attacker "panic-attacker/" :ssg "ssg/") + (directory :id "feature-unit-boj-server" :ply 2 :path "features/boj-server" :description "Bootstrap and integration logic for the boj-server ecosystem component.") + (directory :id "feature-unit-panic-attacker" :ply 2 :path "features/panic-attacker" :description "Bootstrap and integration logic for the panic-attacker ecosystem component.") + (directory :id "feature-unit-ssg" :ply 2 :path "features/ssg" :description "Bootstrap and integration logic for the ssg ecosystem component.")) + (directory :id "source-pillar" :ply 1 :path "src" :description "Primary source code directory. Organized by role and architectural\naspect." + (canonical-locations :aspects "aspects/" :bridges "bridges/" :contracts "contracts/" :core "core/" :definitions "definitions/" :errors "errors/" :interface "interface/") + :invariants ("Core logic MUST reside in core/" "Verified seams MUST reside in interface/" "Safety constraints MUST reside in contracts/" "Failure dictionaries MUST reside in errors/") + (directory :id "source-unit-aspects" :ply 2 :path "src/aspects" :description "Cross-cutting concerns and domain-specific aspects (Security,\nObservability, Integrity)." + (canonical-locations :integrity "integrity/" :observability "observability/" :security "security/") + (directory :id "aspect-unit-integrity" :ply 3 :path "src/aspects/integrity" :description "Implementation logic for the integrity aspect.") + (directory :id "aspect-unit-observability" :ply 3 :path "src/aspects/observability" :description "Implementation logic for the observability aspect.") + (directory :id "aspect-unit-security" :ply 3 :path "src/aspects/security" :description "Implementation logic for the security aspect.")) + (directory :id "source-unit-bridges" :ply 2 :path "src/bridges" :description "Integration logic for external systems (API, Database, RPC, etc.).") + (directory :id "source-unit-contracts" :ply 2 :path "src/contracts" :description "Contracts unit for high-rigor source code.") + (directory :id "source-unit-core" :ply 2 :path "src/core" :description "Primary application logic and core domain models.") + (directory :id "source-unit-definitions" :ply 2 :path "src/definitions" :description "Definitions unit for high-rigor source code.") + (directory :id "source-unit-errors" :ply 2 :path "src/errors" :description "Errors unit for high-rigor source code.") + (directory :id "interface-seams-unit" :ply 2 :path "src/interface" :description "Consolidated \"Verified Interface Seams\" unit. This directory unifies the\nformal specification (ABI), the bridge implementation (FFI), and the\nresulting artifacts (Generated)." + (canonical-locations :abi "abi/" :ffi "ffi/" :generated "generated/") + :invariants ("ABI MUST be Idris2 (.idr)" "FFI MUST be Zig (.zig)" "Generated artifacts MUST be C-compatible" "The 'Truth' lives in abi/; the 'Implementation' lives in ffi/") + (directory :id "abi-logic" :ply 3 :path "src/interface/Abi" :description "Specialised Level 3 logic for abi.") + (directory :id "ffi-logic" :ply 3 :path "src/interface/ffi" :description "Specialised Level 3 logic for ffi." + (directory :id "src-unit" :ply 4 :path "src/interface/ffi/src" :description "Src logic at level 4.") + (directory :id "test-unit" :ply 4 :path "src/interface/ffi/test" :description "Test logic at level 4.")) + (directory :id "generated-logic" :ply 3 :path "src/interface/generated" :description "Specialised Level 3 logic for generated." + (directory :id "abi-unit" :ply 4 :path "src/interface/generated/abi" :description "Abi logic at level 4.")))) + (directory :id "verification-pillar" :ply 1 :path "verification" :description "Primary verification pillar. Contains evidence for correctness,\nperformance, formal proofs, randomized testing, and aerospace-grade\nhigh-assurance metrics (MC/DC coverage, traceability, safety cases)." + (canonical-locations :benchmarks "benchmarks/" :coverage "coverage/" :fuzzing "fuzzing/" :proofs "proofs/" :safety_case "safety_case/" :simulations "simulations/" :tests "tests/" :traceability "traceability/") + :invariants ("Evidence MUST be reproducible and documented" "High-assurance deployments MUST satisfy traceability and safety_case requirements") + (directory :id "benches-pillar" :ply 2 :path "verification/benchmarks" :description "Benches pillar.") + (directory :id "verification-unit-coverage" :ply 2 :path "verification/coverage" :description "High-assurance verification unit for coverage. \nCritical for safety-of-life and aerospace-grade deployment standards.") + (directory :id "fuzzing-unit" :ply 2 :path "verification/fuzzing" :description "Fuzzing unit for high-rigor verification.") + (directory :id "verification-unit-proofs" :ply 2 :path "verification/proofs" :description "Sub-unit focusing on proofs.") + (directory :id "verification-unit-safety_case" :ply 2 :path "verification/safety_case" :description "High-assurance verification unit for safety case. \nCritical for safety-of-life and aerospace-grade deployment standards.") + (directory :id "simulations-unit" :ply 2 :path "verification/simulations" :description "Simulations unit for high-rigor verification.") + (directory :ply 2 :path "verification/tests") + (directory :id "verification-unit-traceability" :ply 2 :path "verification/traceability" :description "High-assurance verification unit for traceability. \nCritical for safety-of-life and aerospace-grade deployment standards.")) + ) +) diff --git a/docs/0.1-AI-MANIFEST.a2ml b/docs/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index 7f79301..0000000 --- a/docs/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,33 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "docs-pillar" -level: 1 -parent: "../0-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Technical documentation hub. The root contains high-level orientation - (README, Quickstart, State-Visualizer). Specialized tracks live in - subdirectories. - -canonical_locations: - quickstart: "QUICKSTART.adoc" - state_visualizer: "STATE-VISUALIZER.adoc" - governance: "governance/" - architecture: "architecture/" - decisions: "decisions/" - theory: "theory/" - practice: "practice/" - developer: "developer/" - attribution: "attribution/" - reports: "reports/" - whitepapers: "whitepapers/" - standards: "standards/" - legal: "legal/" - wikis: "wikis/" - -invariants: - - "Primary documentation format MUST be AsciiDoc (.adoc)" - - "Root docs/ MUST only contain pillar entry points" diff --git a/docs/AFFIRMATION.adoc b/docs/AFFIRMATION.adoc new file mode 100644 index 0000000..0864cc1 --- /dev/null +++ b/docs/AFFIRMATION.adoc @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += AFFIRMATION — Dicta-Task, as of +:toc: macro +:toclevels: 2 +:std-docs: https://github.com/hyperpolymath/standards/blob/main/docs + +_the No-Bullshit file: what we affirm was true and checkable at this moment._ + +[NOTE] +==== +*This file is a template.* Replace the `{{…}}` placeholders and the `<…>` anchor +fields, then re-run your project's own checks in the same session before +signing. It conforms to *profile A (evidential)* of the +link:{std-docs}/AFFIRMATION-STANDARD.adoc[AFFIRMATION authoring standard]. A +policy-surface repo should use profile B (MUST / INTEND / WISH) instead — see +that standard for the profile B skeleton. + +An *affirmation* is a solemn declaration of the truth of a statement, made by +someone who _declines to swear an oath_ — our truth-as-best-believed at a +stamped instant, binding on our honesty, not a claim of infallibility. It is the +third of the README / EXPLAINME / AFFIRMATION trio: + +[cols="1,3,2",options="header"] +|=== +| File | Answers | Tense +| `README.adoc` | _Where is this going, and why?_ — steering, intent, vision | future / aspirational +| `EXPLAINME.adoc` | _How is it built, and what's the evidence?_ — engineering | descriptive / mechanism +| *`AFFIRMATION.adoc`* (this file) | _What can we honestly affirm was *true and checkable* at a stamped moment?_ | a frozen instant, falsifiable +|=== + +This file is *optional*. Delete it rather than carry an affirmation you have not +re-verified — a stale affirmation is worse than none. +==== + +toc::[] + +== What this is, and how it works + +*What it is.* A short, dated, signed snapshot of what hyperpolymath can honestly and +verifiably claim about *Dicta-Task* at one exact commit. Nothing here is +marketing and nothing is a promise about the future — those live in the README. +This file is the receipt. + +*What the project is.* Speech-to-do pipeline: portable recorder to transcription to NLP parsing to prioritised task list + +*How it stays trustworthy.* Three moving parts: + +. *Ground truth, not memory.* Every claim below must be produced by _running the + project's own checks_ in the session that writes this file (build, tests, + typecheck, `just audit`). Where a status doc, the `Justfile`, or memory says + otherwise, the live run wins and the contradiction is flagged here. +. *A frozen anchor.* The file names the exact commit SHA, branch, UTC timestamp, + working-tree delta and toolchain (see <>), so "true" always + means "true _at this point_". Move the SHA and this file is a draft until it + is re-run. +. *A real signature.* It is landed by a *signed git commit*; that signature over + this content at the anchored SHA is what makes the affirmation tamper-evident + and attributable — not the prose alone. + +*We are fallible.* This is our best honest belief, not a proof of its own +correctness. Treat it as a falsifiable claim, not gospel. + +== The epistemic contract (read this before you trust _or_ attack) + +This document records hyperpolymath's *best belief* at the timestamp below. It is +*not a guarantee of correctness.* The only guarantee is *no intentional +overclaim*: where something is proven we say "proven"; where it is a documented +trust boundary, an experiment, or an unwired module, we say so; where a claim is +the README's aspiration rather than a checked result, we say so. An honest claim +that later turns out false is an *error to be fixed* — not a lie. + +What you may conclude from this file: + +* Every claim below was produced by *running the tool* in the session that wrote + this file — not from memory, not copied forward from a previous affirmation, + and not read off a status document. +* Where a live run and a status document disagreed, the live run won and the + status document is named as stale in <>. + +What you may *not* conclude: + +* That anything is true *now*. This file describes the commit in + <> and nothing else. +* That unlisted things pass. *Silence is not a claim.* + +*Standing invitation to refute.* You are invited to bulldoze any claim in this +file. Bring a counter-example, a failing run, or a contradicting source. + +[#verifiable-anchor] +== Verifiable anchor + +[cols="1,3",options="header"] +|=== +| Field | Value + +| Project +| Dicta-Task + +| Repo +| `hyperpolymath/dicta-task` + +| Branch +| `main` + +| Commit (HEAD) +| `` + +| Permalink +| https://github.com/hyperpolymath/dicta-task/tree/ + +| Verified (UTC) +| `` + +| Working-tree delta at verification +| `clean`, or every modified and untracked file present when the checks ran, + with an explicit statement of whether it affects the results below. + +| Toolchain +| `` + +| Affirmed by +| Jonathan D.A. Jewell +|=== + +[IMPORTANT] +==== +*Never anchor to a tag.* Tags move, and a moved tag silently invalidates every +claim in this file. + +If you are reading this at a later commit, the claims may have drifted. Re-run +<> and write a fresh affirmation; do not trust a stale one. +==== + +== Companion documents and repo metadata (cross-check) + +The files a sceptic should read against this one — *including any that +contradict it*. A contradiction named here is honest; one the reader finds for +themselves is not. + +* `README.adoc` — the aspirational claims this file is measured against. +* `docs/EXPLAINME.adoc` — the mechanism. +* `docs/AUDIT.adoc` — the standing conformance audit. +* `*_chora.deed` (repo deed) — machine-readable repo metadata (AI allocation + ply tree). +* `` + +== The honest state (one breath) + +`` + +=== What is solid (and how we checked) + +[cols="2,1,3",options="header"] +|=== +| Claim | Status | Evidence (command, and what it printed) +| _e.g. The library builds clean_ | affirmed | `just build` at the anchor SHA — exit 0 +| _e.g. The ABI seam typechecks_ | affirmed | `idris2 --typecheck src/interface/abi.ipkg` — 0 errors +| _e.g. Feature X is complete_ | aspiration | README §… — *not checked*, do not read as affirmed +|=== + +=== The honest nuance you must not lose + +Where a true claim above is easily over-read. This section is what separates an +affirmation from marketing. + +* `` + +=== Known-incomplete but honestly fenced + +Gaps that fail *loudly* rather than silently. Name the guard that makes the +failure loud — a gap with no guard belongs in <> instead. + +* `` — fenced by `` + +[#outstanding] +=== Outstanding / weak / refuted (no spin) + +Known gaps, trust boundaries, postulates, unwired modules and stale docs. +*Silence is not affirmation* — name what you have not verified, and name +anything this session *refuted*. + +[IMPORTANT] +==== +Never delete a refuted claim. Deleting it is exactly the spin this genre exists +to prevent — mark it refuted and say what refuted it. +==== + +[#reproduce] +== Reproduce it yourself + +[source,bash] +---- +git clone https://github.com/hyperpolymath/dicta-task +cd dicta-task +git checkout +# +---- + +== One-line characterisation (quote this) + +> `` + +== Joint attestation + +We, the undersigned, assert that *to the best of our joint belief at the +timestamp above, every claim in this file is true and was checked as described* +— with no intentional overclaim, and with the open gaps stated rather than +hidden. + +* *Engineering party (AI):* `` — ran the checks recorded + here at `` and stands behind the wording above as a faithful + report of those runs. +* *Owner / maintainer:* Jonathan D.A. Jewell — _signs by committing + this file with `-S`; the git commit signature over this content, at the + commit SHA recorded above, is the cryptographic form of this affirmation._ + +[source,bash] +---- +git commit -S -s docs/AFFIRMATION.adoc -m "docs: affirm state at " +git log --show-signature -1 +---- + +[WARNING] +==== +Do not use `--no-verify`. The pre-commit hook enforces the SPDX header, and an +affirmation landed past its own repo's gates is self-refuting. +==== + +_Landed by a signed git commit. Verify with `git log --show-signature` that the +anchor SHA above matches the parent of the commit that introduced this +affirmation. If they do not match, this file is a *draft* and must be read as +one._ diff --git a/docs/AI-INSTALL-README-SECTION.adoc b/docs/AI-INSTALL-README-SECTION.adoc new file mode 100644 index 0000000..19ed463 --- /dev/null +++ b/docs/AI-INSTALL-README-SECTION.adoc @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += AI-Install README Section (canonical snippet) + +The block below is the canonical `== AI-Assisted Installation` section. Paste it +into a repository's `README.adoc` verbatim; `just validate-ai-install` greps the +README for the heading text, so keep the heading exactly as written. + +---- +== AI-Assisted Installation + +If you are an AI agent installing this project, read +link:docs/AI_INSTALLATION_GUIDE.adoc[the AI installation guide] first — it gives +the orientation order, the full prompt sequence, and the privacy notice. + +The one trap worth stating up front: **do not set `RSR_NON_INTERACTIVE=1`**. It +stubs the shell builtin `read`, which also disables the loops that perform token +substitution — the run never terminates and substitutes nothing. Pipe answers to +stdin instead. +---- diff --git a/docs/AI_INSTALLATION_GUIDE.adoc b/docs/AI_INSTALLATION_GUIDE.adoc new file mode 100644 index 0000000..abb8241 --- /dev/null +++ b/docs/AI_INSTALLATION_GUIDE.adoc @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += AI-Assisted Installation Guide +:toc: +:toclevels: 3 +:sectnums: + +This guide is for an **AI agent** instantiating this template into a new +repository. A human following it will not be misled, but the ordering and the +warnings are written for an agent working non-interactively. + +[[ai-implementation]] +== Implementation for an AI agent + +=== 1. Orient before acting + +Read, in this order, before running anything: + +. `*_chora.deed` (the repo deed) — the universal AI entry point at the repository root. +. `CLAUDE.md` — the generated arrival pack (estate doctrine + repo identity). +. `.machine_readable/descriptiles/STATE.a2ml` — current phase and completion. + +Do not infer the repository's purpose from its name. If a fact is not written +down, record it as `UNASSIGNED` rather than inventing it. + +=== 2. Obtain the template + +Always take the template from `origin/main`, never from a local working tree — +local checkouts across this estate are routinely stale or carry sweep debris. + +[source,bash] +---- +git clone https://github.com/hyperpolymath/dicta-task.git my-project +cd my-project +rm -rf .git && git init +---- + +Alternatively, use GitHub's *Use this template* button, which produces a repo +with no git ancestry; `just repo-init` recovers the template tip via +`git ls-remote` and records it as the `parent-pin`. + +=== 3. Run the instantiation + +[source,bash] +---- +just repo-init # or: just repo-init +---- + +`just repo-init` renders every `{{TOKEN}}` in the tree, derives the repository +identity, writes the provenance files, and removes the template-only +directories. + +[WARNING] +==== +**Do not set `RSR_NON_INTERACTIVE=1`.** It stubs the shell builtin `read` +globally, which also disables the two `while read -r file` loops that drive +substitution. The result is an infinite loop that performs zero substitution — +it does not fail, it never terminates. + +Drive it non-interactively by **piping answers to stdin** instead. Real `read` +stays intact, so the data loops work. +==== + +The prompts, in order, are: + +[cols="1,3",options="header"] +|=== +| # | Prompt + +| 1 | Project name +| 2 | Repository slug — validated against `^[a-z0-9]+(-[a-z0-9]+)*$`; no capitals, spaces or underscores (it must be a valid Guix package name) +| 3 | Owner +| 4 | Author full name +| 5 | Author email +| 6 | Author organisation +| 7 | Previous/alternate email +| 8 | Project description +| 9 | Forge domain +| 10 | Security email +| 11 | Conduct email +| 12 | Project type +| 13 | Website URL (blank = default) +| 14 | OpenSSF Best Practices ID (blank = none) +| 15 | Service name (blank = default) +| 16 | Primary port (blank = 8080) +| 17 | Container registry (blank = default) +| 18 | Proceed? — answer `y` +|=== + +[IMPORTANT] +==== +Prompts 15–17 are gated on the presence of the container directory, **not** on +the project type. The template always ships one, so they always fire. Supplying +only 15 answers makes `read` hit EOF, which returns 1 and kills the recipe under +`set -e`. +==== + +=== 4. Verify the result + +[source,bash] +---- +just --list # every recipe resolves (imports are silent on failure) +just check-root-shape # root matches the allowlist +bash scripts/check-no-placeholders.sh . +just verify +---- + +A successful instantiation leaves **no** `{{TOKEN}}` anywhere in the tree, and +removes the template-only directories from the minted repository. If +`scripts/validate-template.sh` or the end-to-end instantiation test are still +present, the repository was not cured — they are the clearest single marker of +an un-instantiated repo. + +== Privacy + +This template performs no telemetry and transmits nothing during installation. + +Note what instantiation *records*, all of it locally and in your own git history: + +* The identity you supply at prompts 3–7 (owner, author name, both email + addresses, organisation) is written into source headers, `CITATION.cff`, + `.mailmap`, `CODEOWNERS` and the security contact. Treat the alternate email + as published data. +* `just repo-init` runs `git ls-remote` against the template's forge to resolve + the parent pin. This is a single outbound request; when it is unavailable the + pin is recorded as `UNASSIGNED` rather than guessed. +* The clade UUID is *derived*, never allocated: + `uuidgen --sha1 --namespace @url --name "//"`. It is a + function of the repository's name, so renaming the repository changes it and + registries must be regenerated rather than string-swapped. + +If you are an agent acting on someone else's behalf, do not invent identity +values to satisfy a prompt. Leave them `UNASSIGNED` and report the gap. diff --git a/ARCHITECTURE.adoc b/docs/ARCHITECTURE.adoc similarity index 83% rename from ARCHITECTURE.adoc rename to docs/ARCHITECTURE.adoc index 1c0a7a6..44cce31 100644 --- a/ARCHITECTURE.adoc +++ b/docs/ARCHITECTURE.adoc @@ -1,13 +1,13 @@ -== Architecture += Architecture -=== Overview +== Overview -This repository follows a modular, maintainable architecture designed -for clarity, scalability, and long-term sustainability. +This repository follows a modular, maintainable architecture designed for clarity, scalability, and long-term sustainability. -=== Directory Structure +== Directory Structure -.... +[source] +---- . ├── src/ # Source code ├── tests/ # Test suites @@ -17,32 +17,32 @@ for clarity, scalability, and long-term sustainability. ├── LICENSE # License file ├── LICENSES/ # Full license texts └── README.adoc # Project documentation -.... +---- -=== Design Principles +== Design Principles * *Separation of Concerns*: Each module has a single responsibility * *Testability*: Code is written to be easily testable * *Documentation*: All public APIs are documented * *Configuration*: Environment-specific settings are externalized -=== Dependencies +== Dependencies * External dependencies are minimized and clearly declared * Version pinning is used for reproducibility -=== Security Considerations +== Security Considerations * Sensitive data is never committed to the repository * Secrets are managed through environment variables or secure vaults * Regular dependency audits are performed -=== Maintainability +== Maintainability * Code follows consistent style guidelines * Pull requests require review and CI checks * Issues and discussions are tracked transparently -''''' +--- _Last updated: 2026-07-18_ diff --git a/docs/AUDIT.adoc b/docs/AUDIT.adoc new file mode 100644 index 0000000..04f5d5e --- /dev/null +++ b/docs/AUDIT.adoc @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Audit Gate +Codex +v1.1, 2026-04-07 +:toc: +:toclevels: 2 +:sectnums: + +== Purpose + +This root document exists so humans and bots can see the hard audit posture +without having to discover the standards repository first. + +Canonical source documents live in the `standards` repository. This file is a +repo-local audit gate summary for template users and automated agents. + +== Hard Rules + +* Do not call anything `stable`, `v1.0.0`, or full release unless the stable + release gate has been passed end to end. +* Do not publish implementation-facing work below `B` in CRG unless the work is + genuinely abstract and makes no implementation-readiness claim. +* `D` requires RSR compliance or a documented equivalent repository discipline. +* `C` requires deep code and folder annotation, not just local confidence. +* `B` means `beta-stable`: external breadth and safe broad trial, not merely + public visibility. +* Papers, whitepapers, release notes, and READMEs must not outrun the proofs, + tests, or artefacts that support their claims. +* Release paths must not ship with placeholders, stubs, `FIXME`, `XXX`, + template residue, fake fuzz, fake benches, or partial proof debt hidden as + if it were complete. + +== Canonical Standards + +Read these as the authoritative source: + +* `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` +* `standards/3-practice/release-pre-flight/V1-GATE.adoc` +* `standards/3-practice/publication-pre-flight/PREFLIGHT.adoc` +* `standards/3-practice/publication-pre-flight/ESTATE-AUDIT-BASELINE-2026-03-30.adoc` +* `standards/3-practice/session-management-standards/README.adoc` + +== Bot Requirement + +Bots operating in repositories derived from this template should treat this +document as a key root audit document and should not make optimistic release or +publication claims that conflict with it. diff --git a/EXPLAINME.adoc b/docs/EXPLAINME.adoc similarity index 85% rename from EXPLAINME.adoc rename to docs/EXPLAINME.adoc index 0d978a2..f6586d7 100644 --- a/EXPLAINME.adoc +++ b/docs/EXPLAINME.adoc @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 -= dictask — Speech-to-Do Pipeline — Show Me The Receipts += dicta-task — Speech-to-Do Pipeline — Show Me The Receipts :toc: :icons: font diff --git a/docs/GOVERNANCE.adoc b/docs/GOVERNANCE.adoc new file mode 100644 index 0000000..ff82d8b --- /dev/null +++ b/docs/GOVERNANCE.adoc @@ -0,0 +1,65 @@ += Governance + +== Overview + +This project is governed by the following principles and structures to ensure transparent, inclusive, and effective decision-making. + +== Roles and Responsibilities + +=== Maintainers + +Maintainers are responsible for: + +* Reviewing and merging pull requests +* Managing releases and versioning +* Ensuring code quality and standards +* Triaging issues and bug reports +* Community engagement and support + +=== Contributors + +Contributors are expected to: + +* Follow the code of conduct +* Submit well-documented pull requests +* Write tests for new functionality +* Maintain existing tests +* Update documentation as needed + +== Decision Making + +=== Minor Changes + +* Can be made by any maintainer +* Include bug fixes, documentation updates, dependency updates + +=== Major Changes + +* Require discussion in issues or pull requests +* Include new features, architectural changes, API changes +* Need approval from at least 2 maintainers + +=== Breaking Changes + +* Require RFC (Request for Comments) process +* Need approval from majority of maintainers +* Must include migration guide + +== Code of Conduct + +All participants are expected to follow our Code of Conduct. Violations can be reported to the maintainers. + +== Communication + +* *Issues*: For bug reports and feature requests +* *Discussions*: For questions and general discussion +* *Pull Requests*: For code contributions + +== Licensing + +All contributions are made under the terms of the repository's LICENSE file. +By submitting a pull request, you agree to license your contributions accordingly. + +--- + +_Last updated: 2026-07-18_ diff --git a/docs/MAINTAINERS.adoc b/docs/MAINTAINERS.adoc new file mode 100644 index 0000000..3f1fad4 --- /dev/null +++ b/docs/MAINTAINERS.adoc @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell += Maintainers +:toc: preamble + +This is the single maintainer roster for this repository. It supersedes the +earlier duplicates at `.github/MAINTAINERS` and `docs/attribution/MAINTAINERS.adoc`. + +== Current maintainers + +[cols="2,3,2",options="header"] +|=== +| Name | Role | Contact + +| Jonathan D.A. Jewell +| Lead Maintainer +| https://github.com/hyperpolymath[@hyperpolymath] +|=== + +== Responsibilities + +Maintainers are responsible for: + +* 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 + +Contributions are welcome via: + +. **Issues** — report bugs, request features, ask questions +. **Pull requests** — submit improvements for review +. **Discussions** — engage with the wider project + +== Decision making + +* Routine decisions (bug fixes, minor improvements) may be made by any maintainer. +* Significant changes require discussion and consensus among maintainers. +* Breaking changes should be discussed in an issue, with a migration path, before + implementation. + +== Becoming a maintainer + +Contributors who demonstrate consistent, high-quality contributions, an +understanding of the project's goals and standards, constructive participation, +and commitment to its long-term health may be invited to become maintainers at +the discretion of the existing maintainers. + +== Contact + +For questions about project governance, open an issue in this repository. + +== See also + +* link:GOVERNANCE.adoc[Governance model] +* link:../.github/CODE_OF_CONDUCT.md[Code of Conduct] +* link:../.github/CONTRIBUTING.md[Contributing guide] +* link:attribution/CODEOWNERS.adoc[Code owners] diff --git a/docs/QUICKSTART.adoc b/docs/QUICKSTART.adoc index b20b3d0..b8f0088 100644 --- a/docs/QUICKSTART.adoc +++ b/docs/QUICKSTART.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Quickstart :toc: preamble @@ -13,10 +15,10 @@ Get up and running in 60 seconds. [source,bash] ---- -git clone https://github.com/hyperpolymath/rsr-template-repo my-project +git clone https://github.com/hyperpolymath/dicta-task my-project cd my-project rm -rf .git && git init -b main -just init # interactive placeholder replacement +just repo-init # interactive placeholder replacement ---- == Project Structure diff --git a/docs/README.adoc b/docs/README.adoc index df45be7..dc7274f 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -1,14 +1,53 @@ -= Documentation Layout +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Documentation -Primary tracks: +For the full directory map — every directory, what it is, and who reads it — +see link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc]. It is generated +and CI fails if it goes stale, so prefer it over any hand-written listing. -* `theory/` for formal and conceptual material -* `practice/` for operational and implementation material -* `maintenance/` for baseline checklists and release hard-pass runbooks -* `whitepapers/academic/` for research-facing whitepapers -* `whitepapers/industry/` for industry/outreach whitepapers +== Start here -Core docs: +* link:onboarding/QUICKSTART-USER.adoc[QUICKSTART-USER] — using a repo built from this template. +* link:onboarding/QUICKSTART-DEV.adoc[QUICKSTART-DEV] — developing in one. +* link:onboarding/QUICKSTART-MAINTAINER.adoc[QUICKSTART-MAINTAINER] — maintaining one. +* link:AI_INSTALLATION_GUIDE.adoc[AI_INSTALLATION_GUIDE] — for an AI agent instantiating the template. +* link:EXPLAINME.adoc[EXPLAINME] — how the pieces actually work. -* `maintenance/MAINTENANCE-CHECKLIST.md` -* `practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` +== The tracks + +[cols="1,3",options="header"] +|=== +| Directory | Holds + +| `architecture/` | Topology, threat model, and the generated repository map. +| `attribution/` | CODEOWNERS rationale and credit. +| `decisions/` | ADRs. `0000-template.adoc` is the template; number upwards from there. +| `developer/` | Developer-facing deep dives (ABI/FFI, tooling integrations). +| `governance/` | Governance model, maintenance checklist, development approach, audits, planning. +| `legal/` | Licence exhibits. +| `onboarding/` | Quickstarts and the LLM warm-up documents. +| `practice/` | Operational and implementation material, incl. AI conventions. +| `proposals/` | Proposals and RFCs, including the root-cleanup proposal this layout came from. +| `reports/` | Generated and periodic reports (audit, compliance, security, ...). +| `standards/` | Pointers to the estate canon in the `standards` repo. +| `status/` | READINESS, ROADMAP, TEST-NEEDS, PROOF-NEEDS, PROOF-STATUS. +| `theory/` | Formal and conceptual material. +| `whitepapers/` | Academic, industry and outreach whitepapers. +| `wikis/` | Long-form wiki material. +|=== + +== Core documents + +* link:governance/MAINTENANCE-CHECKLIST.adoc[governance/MAINTENANCE-CHECKLIST.adoc] +* link:governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc[governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc] +* link:MAINTAINERS.adoc[MAINTAINERS.adoc] — the single maintainer roster. +* link:GOVERNANCE.adoc[GOVERNANCE.adoc] — the governance model. + +[NOTE] +==== +`.md` files are not permitted under `docs/`; AsciiDoc is the estate standard and +`scripts/check-no-md-in-docs.sh` enforces it. The previous version of this file +pointed at `maintenance/MAINTENANCE-CHECKLIST.md` — a directory that does not +exist, holding a file with an extension this tree forbids. +==== diff --git a/docs/RSR-PHILOSOPHY.adoc b/docs/RSR-PHILOSOPHY.adoc new file mode 100644 index 0000000..ea85164 --- /dev/null +++ b/docs/RSR-PHILOSOPHY.adoc @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += RSR Philosophy — How Work Is Done Here +:toc: +:icons: font + +[.lead] +The RSR standard is not only a set of files to scaffold; it is a way of working. +This document states the operating principles an agent or maintainer is expected +to hold while doing work in any hyperpolymath repository. They are deliberately +few, deliberately blunt, and meant to be applied — not admired. + +These principles are the human-readable home of the *Doctrine* that the estate +arrival-pack projects into every repository's `CLAUDE.md`. The Doctrine list is +the terse machine-facing summary; this file is the reasoning behind it. Where the +two differ, the canon (`hyperpolymath/standards`) and the owner's `manifesto` +prevail. + +== The load-bearing four + +These four are named together because they describe the *order*, *manner*, *locus* +and *standard* of the work undertaken, and because each is a standing trap that +fluent, plausible work falls into. + +=== Holes before goals + +Fix soundness holes before you build features, optimise, or polish documentation. +A hole is anywhere the system can be wrong without saying so: an unproven seam, an +unchecked input, a `TODO` that load-bearing code depends on, a claim no tool +establishes. Goals are everything you would rather be doing. The discipline is to +let the holes set the agenda, not the goals — because a goal reached on top of a +hole is not reached. + +=== Always fail loudly + +No silent green. A check that cannot fail is not a check; a fallback that hides a +broken precondition is a forged result. When something is wrong, the system must +say so — visibly, early, and in a way that stops the line — rather than degrade +quietly into a plausible-looking success. Seams (ABI / FFI boundaries) are sealed +and proven, not assumed. Prefer a build that breaks to a build that lies. + +=== Solutions at source + +Fix the canonical, upstream origin of a problem — never patch the downstream +symptom. When a defect, a drift, or a wrong setting appears in many places, it is +almost never many problems; it is one problem at a source, expressed many times. +Remediating the copies without fixing the source guarantees the problem returns. + +Two obligations follow from this, and they are not optional: + +* *Find the source.* Before acting, trace the thing back to where it is actually + defined — the template, the generator, the canon, the single point that + everything else inherits from. The estate's structure is + `standards → dicta-task → (every repo)`; a fix that belongs at the + template does not belong in 380 leaves. +* *Be mindful of every up- and down-stream.* A change at a source propagates. + Before you make it, know what feeds into the thing you are changing (upstream) + and what depends on it (downstream), and make sure the change is safe across + all of them. A correct fix that breaks a downstream consumer is not yet a fix. + +When the source genuinely cannot be reached in this pass — an upstream you do not +own, a fix gated on owner ratification — remediate the downstream *and* record the +source fix as the real work still owed. Patching the symptom silently, as if it +were the cure, is itself a hole (see _always fail loudly_). + +=== Elegance by default + +Treat the most elegant and correct long-term solution as the default choice — and +say which option that is, every time you put a choice to the owner. This is not a +preference for tidiness; it is a refusal to let the judgment be made silently. + +A set of options offered as merely _different_ is not neutral. Whichever one is +listed first, or described most fluently, becomes the recommendation whether you +intended it or not — and the option that reads most fluently is usually the one you +found quickest to write. So an unlabelled list quietly substitutes your convenience +for the standard this estate is held to, which is the same error as patching a +symptom in place of a source: a choice backed by authority rather than justified by +the construction that produced it. + +Three obligations follow, and they are not optional: + +* *Label it.* Exactly one option is marked as the most elegant and correct in the + long run. Judge that on long-run grounds alone — correctness, no deferred + breakage, no special cases, a fix at the generator rather than at the instance — + and never on effort, speed, or convenience. If two options genuinely tie, say so; + silence is not a tie. +* *Justify any departure.* If your recommendation is not the elegant arm, name both + arms and state, in the offer itself, why you are departing on this occasion — an + irreversible step already taken, a live outage, a precondition still gated. An + unexplained departure is a defect in the question, not a matter of style. Never + merge the two labels to avoid having to write the explanation. +* *It binds unasked decisions too.* This is a methodology, not a formatting rule + for questions. Where you take the non-elegant arm without asking, report it + rather than absorb it. + +The default is a *starting point, not a prediction*. The owner may take the other +arm with full information, and often will; what may not happen is an expedient +choice made in ignorance that it was the expedient one. + +== The full Doctrine + +The four above are the principles most often abused, but they sit inside the +estate's full operating Doctrine. The canonical, always-current list is projected +into the top of every repository's `CLAUDE.md` from +`.machine_readable/arrival-pack/`. In summary it also holds: ground-truth by +running the tool, not trusting status docs; distrust the neural for exactness +(licences / invariants / equivalence belong to PLASMA, not an LLM); squabble, +don't bypass (reach green by satisfying the gate, never by admin-override); no +automated licence edits; no deletion by access-recency; wire first; always sign; +report faithfully (no overclaim); stop-first on costly or outward-facing actions; +boundaries are real; and equivalence as identity. + +== Status + +* *Licence:* CC-BY-SA-4.0 (documentation). +* *Canon:* `hyperpolymath/standards` is the source of truth for these principles; + `hyperpolymath/manifesto` states the doctrine in the owner's voice. This file + operationalises them for the RSR template and its descendants. diff --git a/docs/RSR_OUTLINE.adoc b/docs/RSR_OUTLINE.adoc new file mode 100644 index 0000000..9bbe401 --- /dev/null +++ b/docs/RSR_OUTLINE.adoc @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += RSR Template Repository + +image:https://img.shields.io/badge/Code-MPL--2.0-blue.svg[Code licence: MPL-2.0,link="https://opensource.org/licenses/MPL-2.0"] image:https://img.shields.io/badge/Docs-CC--BY--SA--4.0-blue.svg[Docs licence: CC-BY-SA-4.0,link="https://creativecommons.org/licenses/by-sa/4.0/"] +:toc: +:sectnums: + +// Badges +image:https://img.shields.io/badge/RSR-Infrastructure-cd7f32[RSR Infrastructure] +image:https://img.shields.io/badge/Phase-Maintenance-brightgreen[Phase] +image:https://img.shields.io/badge/Guix-Primary-purple?logo=gnu[Guix] + +[IMPORTANT] +==== +*Superseded as an overview.* The root `README.adoc` is the entry point, and the +authoritative directory map is generated at +link:architecture/REPOSITORY-MAP.adoc[architecture/REPOSITORY-MAP.adoc]. + +This document is kept for its longer-form rationale, not as a second README. +Its hand-written directory tree was removed in 2026-08 after being found stale +in six places at once (a2ml files listed outside `descriptiles/`, `Trustfile.hs` +for `Trustfile.a2ml`, `docs/CITATIONS.adoc` and `docs/TOPOLOGY-GUIDE.adoc` which +do not exist, and `src/interface/Abi/` which was renamed to lowercase). +==== + +== Overview + +**The canonical template for RSR (Rhodium Standard Repository) projects.** + +This repository provides the standardized structure, configuration, and tooling for all RSR-compliant repos. Use it to: + +* Bootstrap new projects with RSR compliance +* Reference the standard directory structure +* Copy configuration templates (Justfile, STATE.a2ml, etc.) + +== Quick Start + +[source,bash] +---- +# Clone the template +git clone https://github.com/hyperpolymath/RSR-template-repo my-project +cd my-project + +# Remove template git history +rm -rf .git +git init + +# Interactive bootstrap — replaces all placeholders +just repo-init + +# Enter development environment +guix shell -D -f build/guix.scm + +# Validate compliance +just validate-rsr +---- + +== What's Included + +[cols="1,3"] +|=== +|File/Directory |Purpose + +|`.editorconfig` +|Editor configuration (indent, charset) + +|`.gitignore` +|Standard ignore patterns + +|`.gitattributes` +|Line endings, diff drivers, binary detection + +|`.guix-channel` +|Guix channel definition + +|`www/` +|Site-operations bundle; RFC-compliant metadata at `www/.well-known/` (security.txt, ai.txt, humans.txt) + +|`.machine_readable/` +|All machine-readable content: state files (6 a2ml), `bot_directives/`, `contractiles/` + +|`docs/` +|Documentation directory + +|`build/guix.scm` +|Guix package definition (canon 1.2.1 guix-primary names `build/`) + +|`Justfile` +|Task runner with 40+ recipes + +|`Containerfile` +|Container build (Wolfi base, Podman) + +|`LICENSE` +|MPL-2.0 (code) / CC-BY-SA-4.0 (docs) + +|`EXHIBIT-A-ETHICAL-USE.txt` +|MPL-2.0 source-code-form license notice (LICENSE Exhibit A) + +|`EXHIBIT-B-QUANTUM-SAFE.txt` +|Quantum-safe provenance spec (LICENSE Exhibit B) + +|`README.adoc` +|Project overview + +|`TOPOLOGY.md` +|Architecture diagram and completion dashboard + +|`PLACEHOLDERS.md` +|Template variable reference and replacement guide + +|`*_chora.deed` (repo deed) +|Universal AI agent entry point + +|`AI.a2ml` +|Claude-specific instructions + +|`src/interface/Abi/` +|Idris2 ABI definitions (Types, Layout, Foreign) + +|`ffi/zig/` +|Zig FFI implementation + +|`generated/abi/` +|Auto-generated C headers from Idris2 ABI +|=== + +== Justfile Features + +The template Justfile provides: + +* **Combinatoric matrix recipes** for build, test, container, CI +* **Cookbook generation**: `just cookbook` -> `docs/just-cookbook.adoc` +* **Man page generation**: `just man` -> `docs/man/project.1` +* **RSR validation**: `just validate-rsr` +* **STATE.a2ml management**: `just state-touch`, `just state-phase` +* **Container support**: `just container-build`, `just container-push` +* **CI matrix**: `just ci-matrix [stage] [depth]` + +=== Key Recipes + +[source,bash] +---- +just # Show all recipes +just help # Detailed help +just info # Project info +just combinations # Show matrix options + +just build # Build (debug) +just test # Run tests +just quality # Format + lint + test +just ci # Full CI pipeline + +just validate # RSR + STATE validation +just docs # Generate all docs +just cookbook # Generate Justfile docs + +just guix-shell # Guix dev environment +just container-build # Build container +---- + +== Directory Structure + +[source] +See link:architecture/REPOSITORY-MAP.adoc[REPOSITORY-MAP.adoc] for the +directory map. It is generated from the tree and diffed in CI, so unlike the +hand-written tree that used to sit here it cannot go stale. + + +== RSR Compliance + +=== Language Tiers + +* **Tier 1** (Gold): Rust, Elixir, Zig, Ada, Haskell, , Gleam +* **Tier 2** (Silver): Nickel, Guile Scheme, Idris2, OCaml +* **Infrastructure**: Guix channels, derivations, Julia batch scripts + +=== Required Files + +* `.editorconfig` +* `.gitignore` +* `Justfile` +* `README.adoc` +* `LICENSE` (MPL-2.0) +* `.machine_readable/descriptiles/STATE.a2ml` +* `www/.well-known/security.txt` +* `www/.well-known/ai.txt` +* `www/.well-known/humans.txt` +* `build/guix.scm` + +=== Prohibited + +* Python outside `salt/` directory +* /JavaScript (use ) +* CUE (use Guile/Nickel) +* `Dockerfile` (use `Containerfile`) +* npm, Bun, pnpm, yarn (use ) +* Go (use Rust) + +== STATE.a2ml + +The STATE.a2ml file tracks project state: + +[source] +---- +# STATE — Project State Checkpoint +# Format: a2ml (AI-readable markup) + +project: v-graphql +version: 0.1.0 +last-updated: 2026-02-14 +status: active + +phase: implementation +maturity: beta + +ecosystem: + part-of: RSR Framework + depends-on: [] + +milestones: + - name: Initial setup + completion: 100 + - name: Core implementation + completion: 0 +---- + +== Badge Schema + +Generate badges from STATE.a2ml: + +[source,bash] +---- +just badges standard +---- + +See `docs/BADGE_SCHEMA.adoc` for the full badge taxonomy. + +== Ecosystem Integration + +This template is part of: + +* **STATE.a2ml Ecosystem**: Conversation checkpoints +* **RSR Framework**: Repository standards +* **Consent-Aware-HTTP**: .well-known compliance +* **Hypatia**: Neurosymbolic security scanning +* **gitbot-fleet**: Bot orchestration + +== License + +SPDX-License-Identifier: CC-BY-SA-4.0 + +== Links + +* https://github.com/hyperpolymath/elegant-STATE[elegant-STATE] - STATE tooling +* https://github.com/hyperpolymath/conative-gating[conative-gating] - Policy enforcement +* https://rhodium.sh[Rhodium Standard] - RSR documentation diff --git a/docs/STATE-VISUALIZER.adoc b/docs/STATE-VISUALIZER.adoc index f60a9d9..2e23ca3 100644 --- a/docs/STATE-VISUALIZER.adoc +++ b/docs/STATE-VISUALIZER.adoc @@ -1,4 +1,7 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Project State Visualizer + [source] ---- @@ -46,7 +49,7 @@ ┌─────────────────────────────────────────┐ │ PLATFORM INTEGRATION │ │ ┌───────────┐ ┌───────────┐ ┌───────┐│ - │ │ GitHub │ │ GitLab │ │ Guix / ││ + │ │ GitHub │ │ GitLab │ │ Nix / ││ │ │ Workflows │ │ CI/CD │ │ Guix ││ │ └───────────┘ └───────────┘ └───────┘│ └─────────────────────────────────────────┘ @@ -54,7 +57,7 @@ ┌─────────────────────────────────────────┐ │ REPO INFRASTRUCTURE │ │ Justfile / Mustfile .machine_readable/ │ - │ Codeowners / Reuse 0-AI-MANIFEST.a2ml │ + │ Codeowners / Reuse *_chora.deed (root) │ └─────────────────────────────────────────┘ ``` @@ -65,7 +68,7 @@ COMPONENT STATUS NOTES ───────────────────────────────── ────────────────── ───────────────────────────────── CORE STANDARDS ABI/FFI Standard (Idris2/Zig) ██████████ 100% Universal interface stable - AI Gatekeeper (0-AI-MANIFEST) ██████████ 100% Universal entry point active + AI Gatekeeper (repo deed) ██████████ 100% Universal entry point active TOPOLOGY.md Standard ██████████ 100% Visual summary guide active 6SCM Metadata Structure ██████████ 100% Machine-readable state stable @@ -87,8 +90,8 @@ CONTAINER ECOSYSTEM (Phase 2) REPO INFRASTRUCTURE .machine_readable/ ██████████ 100% STATE/META/ECOSYSTEM active - Governance & License ██████████ 100% PMPL & Ethical use verified - Development Shells (Guix/Guix) ██████████ 100% Reproducible env stable + Governance & License ██████████ 100% MPL-2.0 & Ethical use verified + Development Shells (Guix) ██████████ 100% Reproducible env stable ───────────────────────────────────────────────────────────────────────────── OVERALL: ██████████ 100% RSR Template Stable & Certified @@ -100,7 +103,7 @@ OVERALL: ██████████ 100% RSR T Philosophy ──────► RSR Standard ──────► Template Scaffolding ──► New Repo │ │ │ │ ▼ ▼ ▼ ▼ -CCCP Policy ─────► 0-AI-MANIFEST ────────► Justfile ──────────► Compliance +CCCP Policy ─────► repo deed ────────────► Justfile ──────────► Compliance │ ▼ Container Ecosystem diff --git a/docs/accessibility/README.adoc b/docs/accessibility/README.adoc deleted file mode 100644 index 7415d98..0000000 --- a/docs/accessibility/README.adoc +++ /dev/null @@ -1,257 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -= Burble Accessibility -:toc: auto -:icons: font - -== Overview - -Burble is committed to providing an accessible voice-first communication platform that works for everyone, regardless of ability. This document outlines our accessibility features, compliance targets, and roadmap. - -== Current Accessibility Features - -=== Keyboard Accessibility - -[cols="1,3,1", options="header"] -|=== -|Feature |Description |Status - -|Custom Keybindings -|Remappable keyboard shortcuts for all major actions -|✅ Implemented - -|Push-To-Talk -|Space key (configurable) for voice activation -|✅ Implemented - -|Mute/Deafen -|M/D keys for quick audio control -|✅ Implemented - -|Volume Control -|Arrow keys for volume adjustment -|✅ Implemented - -|Room Navigation -|Tab/Shift+Tab for room switching -|✅ Implemented -|=== - -=== Screen Reader Support - -[cols="1,3,1", options="header"] -|=== -|Feature |Description |Status - -|Presence Announcements -|"User joined/left room" audio cues -|✅ Implemented - -|Room Change Announcements -|"Switched to room X" notifications -|✅ Implemented - -|Moderation Announcements -|"User was action by moderator" alerts -|✅ Implemented - -|ARIA Attributes -|Semantic HTML for screen readers -|⚠️ Partial - -|Live Regions -|Dynamic content updates for screen readers -|⚠️ Planned -|=== - -== Planned Accessibility Features - -=== Visual Accessibility (Q2 2026) - -[cols="1,3,1", options="header"] -|=== -|Feature |Description |Target - -|High Contrast Mode -|WCAG 4.5:1 contrast ratios -|Q2 2026 - -|Colorblind Support -|Deuteranopia, Protanopia, Tritanopia palettes -|Q2 2026 - -|Font Size Adjustment -|4 levels (14-20px) with persistence -|Q2 2026 - -|Theme Switching -|Dark/Light/System with OS preference detection -|Q2 2026 - -|Reduced Motion -|Respects `prefers-reduced-motion` -|Q2 2026 -|=== - -=== Auditory Accessibility (Q3 2026) - -[cols="1,3,1", options="header"] -|=== -|Feature |Description |Target - -|Closed Captions -|Real-time voice-to-text (Web Speech API) -|Q3 2026 - -|Visual Notifications -|On-screen indicators for audio events -|Q3 2026 - -|Volume Normalization -|EBU R128 (-23 LUFS) compliance -|Q3 2026 - -|Voice Commands -|Voice-controlled interface -|Q3 2026 -|=== - -=== Cognitive Accessibility (Q4 2026) - -[cols="1,3,1", options="header"] -|=== -|Feature |Description |Target - -|Clear Language Mode -|Simplified terminology with tooltips -|Q4 2026 - -|Predictable Navigation -|Consistent layout across all pages -|Q4 2026 - -|Error Prevention -|Confirmations for all destructive actions -|Q4 2026 - -|Contextual Help -|Persistent help system with guidance -|Q4 2026 -|=== - -== Compliance Targets - -=== WCAG 2.1 Compliance - -[cols="1,2,1", options="header"] -|=== -|Level |Requirements |Target - -|WCAG 2.1 A -|Minimum accessibility -|Q2 2026 - -|WCAG 2.1 AA -|Recommended accessibility -|Q4 2026 - -|WCAG 2.1 AAA -|Enhanced accessibility -|2027 -|=== - -=== Legal Compliance - -[cols="1,3,1", options="header"] -|=== -|Standard |Description |Target - -|Section 508 -|U.S. federal accessibility requirements -|Q4 2026 - -|EN 301 549 -|EU public sector procurement -|Q4 2026 - -|VPAT 2.4 -|Voluntary Product Accessibility Template -|Q4 2026 -|=== - -== Implementation Roadmap - -=== Q2 2026: Foundation - -* Port PanLL's accessibility engine -* Implement theme switching -* Add font size adjustment -* Integrate K9 accessibility validator -* Create basic compliance report - -=== Q3 2026: Enhancement - -* Add closed captions -* Implement visual notifications -* Add colorblind support -* Complete screen reader support -* Add accessibility testing to CI - -=== Q4 2026: Compliance - -* Achieve WCAG 2.1 AA compliance -* Complete legal compliance documentation -* Publish VPAT -* Final accessibility audit - -== Testing & Validation - -=== Automated Testing - -* **pa11y**: Automated WCAG testing -* **axe-core**: Accessibility engine -* **K9-SVC**: Contractile validation -* **CI Integration**: GitHub Actions workflow - -=== Manual Testing - -* Keyboard-only navigation -* Screen reader testing (NVDA, VoiceOver) -* High contrast mode verification -* Colorblind simulation testing - -== Reporting Accessibility Issues - -Found an accessibility issue? Please report it: - -* **GitHub Issues**: https://github.com/hyperpolymath/burble/issues -* **Email**: accessibility@hyperpolymath.dev -* **Matrix**: #hyperpolymath-accessibility:matrix.org - -Include: -- Description of the issue -- Steps to reproduce -- Browser/OS/Assistive technology used -- Suggested fix (if any) - -== Accessibility Statement - -Burble is committed to: - -* Following WCAG 2.1 guidelines -* Providing equivalent alternatives for audio content -* Ensuring keyboard accessibility -* Supporting screen readers and other assistive technologies -* Continuous improvement based on user feedback - -We welcome contributions to improve accessibility. See our link:../CONTRIBUTING.adoc[Contributing Guide] for details. - -== Related Documents - -* link:../ROADMAP.adoc[Project Roadmap] — Accessibility milestones -* link:../../.machine_readable/contractiles/adjust/Adjustfile.a2ml[Adjustfile] — Accessibility contract -* link:https://www.w3.org/WAI/standards-guidelines/wcag/[WCAG Guidelines] — Official standards -* link:https://www.section508.gov/[Section 508] — U.S. federal requirements - -== License - -This document is licensed under MPL-2.0. See link:../../LICENSE[LICENSE] for details. diff --git a/docs/architecture.adoc b/docs/architecture.adoc new file mode 100644 index 0000000..073805c --- /dev/null +++ b/docs/architecture.adoc @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Architecture — Dicta-Task +:revdate: 2026-MM-DD + +== System overview + +One paragraph: what this project does and what it does not do. State the +*invariant* — the property that, if violated, would make the whole project +pointless. Future maintainers will read this paragraph first. + +== Component diagram + +Replace this section with an ASCII or Mermaid diagram. Keep it under 20 +lines — anything bigger belongs in `architecture/`. + +[source] +---- ++------------------+ +------------------+ +| Component A | ---> | Component B | ++------------------+ +------------------+ + | + v ++------------------+ +| Component C | ++------------------+ +---- + +== Data flow + +For each external input, describe: + +* **Source**: where it comes from. +* **Validation**: what guarantees we enforce on entry. +* **Transformation**: high-level processing stages. +* **Sink**: where the result goes. + +== Key invariants + +Enumerate the load-bearing invariants of the system. Each should have: + +. A one-line statement. +. The code location(s) that enforce it. +. The failure mode if the invariant is violated. + +Example: + +[cols="1,2,2,2", options="header"] +|=== +| # | Invariant | Enforced at | Failure mode + +| 1 +| All HTTP requests carry a valid `X-Request-ID`. +| `src/request_id.rs` +| Logs become unjoinable; correlation breaks. + +| 2 +| The output buffer is always flushed before exit. +| `src/main.rs:88-92` (Drop impl) +| Last ~16KB of log lost on crash. +|=== + +== Dependencies + +* **Internal**: list other hyperpolymath repos this depends on. +* **External**: SHA-pinned (see `Cargo.lock` / `.lock` / etc.). +* **Build-time**: tools required to build (just, , cargo, …). + +== Out of scope + +Explicit non-goals. Things we deliberately do *not* do, with a one-line +reason for each. This section saves more time than the rest combined. + +== See also + +* link:./usage.adoc[Usage] — consumer perspective. +* link:./contributing.adoc[Contributing] — developer setup. +* link:./decisions/[ADRs] — historical record of why this shape. diff --git a/docs/architecture/0.2-AI-MANIFEST.a2ml b/docs/architecture/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 028b503..0000000 --- a/docs/architecture/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "architecture-track" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Documentation track for system architecture and threat models. - -canonical_locations: - threat_model: "THREAT-MODEL.adoc" - -invariants: - - "Visual diagrams MUST include ASCII or Mermaid representations" diff --git a/docs/architecture/REPOSITORY-MAP.adoc b/docs/architecture/REPOSITORY-MAP.adoc new file mode 100644 index 0000000..57dd566 --- /dev/null +++ b/docs/architecture/REPOSITORY-MAP.adoc @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell +// +// GENERATED by scripts/gen-repo-map.sh - do not hand-edit. +// Regenerate with `just repo-map`. CI fails if this file is stale. += Repository map +:toc: + +The single authoritative map of this repository. It is generated from the +tree and from the annotations in `.machine_readable/root-allow.txt`, and CI +fails if it drifts, so it cannot rot the way its five hand-written +predecessors did. + +== Root + +[cols="2,1,4",options="header"] +|=== +| Path | Required | What it is, and who reads it + +| `.cicd-hygiene-allow` +| yes +| code-hygiene gate allowlist; template scaffolds are seams, not debt + +| `.clinerules` +| yes +| AI editor rules. Cline/Cursor/Windsurf read these from the project + +| `.cursorrules` +| yes +| ROOT, so the copies formerly under .machine_readable/ai/ were inert. + +| `.devcontainer/` +| optional +| VS Code dev container spec; present only where the container capability is declared + +| `.editorconfig` +| yes +| - + +| `.envrc` +| yes +| - + +| `.gitattributes` +| yes +| - + +| `.github/` +| yes +| community health + workflows (GitHub reads this path and no other) + versioned git hooks (.github/hooks/, wired via core.hooksPath) + +| `.gitignore` +| yes +| - + +| `.gitleaksignore` +| yes +| exact fingerprints for reviewed historical false positives + +| `.gitmessage` +| yes +| git commit template; wired by .github/hooks/install.sh (git config commit.template) + +| `.hypatia-ignore` +| yes +| the Hypatia scanner reads it from the repo root + +| `.machine_readable/` +| yes +| manifests, contractiles, policies. Renamed back from machine-readable/ 2026-09-17 by owner ruling, for one canonical spelling estate-wide (census at the 2026-08 divergence: 48 dotted vs 9 hyphenated — the majority was already dotted). See docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc + +| `.mailmap` +| yes +| git reads .mailmap from the worktree root only + +| `.tool-versions` +| yes +| - + +| `.windsurfrules` +| yes +| - + +| `CHANGELOG.adoc` +| yes +| AsciiDoc is the estate-standard documentation format + +| `CITATION.cff` +| yes +| citation metadata; GitHub reads it from the root of the default branch only + +| `CLAUDE.md` +| yes +| generated arrival pack; the generator, pre-commit and Claude Code all read it from the ROOT (do not hand-edit; edit the a2ml source) + +| `CONTRIBUTING.adoc` +| optional +| estate docs gate (standards scripts/check-docs-presence.sh) requires CONTRIBUTING at the ROOT; this is the copy it verifies + +| `GEMINI.md` +| optional +| pointer to CLAUDE.md for repos without AGENTS.md yet + +| `Justfile` +| yes +| thin; delegates phases to build/just/*.just + +| `LICENSE` +| yes +| - + +| `LICENSES/` +| yes +| REUSE licence texts, dual-licence model (code MPL-2.0 / docs CC-BY-SA-4.0) + +| `README.adoc` +| yes +| - + +| `archetypes/` +| optional +| template-only variation seam (ADR-0003); `just repo-init` removes it from minted repos + +| `benches/` +| optional +| Cargo-conventional at package root + +| `build/` +| yes +| build orchestration: just/ phase modules, container/, docs-seed/, templates/, guix.scm (canon 1.2.1 guix-primary template_ref = build/) + +| `ci/` +| yes +| .gitlab-ci.yml + .pre-commit-config.yaml; ci/README.adoc records the out-of-band GitLab project setting these require + +| `coordination.k9.ncl` +| yes +| repo-local session binding (template-mandated) + +| `docs/` +| yes +| human documentation + +| `examples/` +| optional +| Cargo-conventional at package root + +| `features/` +| optional +| optional feature packs + +| `mise.toml` +| yes +| pinned toolchains + +| `dicta-task_chora.deed` +| yes +| the repo deed: universal AI entry point; family-7 allocation manifest + ply tree folded in (standards#837 pilot). Filename carries the repo slug (deed dispatch _chora.deed), so repo-init renames it at mint + +| `scripts/` +| yes +| repo helper scripts + +| `session/` +| yes +| dispatch.sh, custom-checks.k9.ncl, local-hooks.sh + +| `sonar-project.properties` +| optional +| only where the repo is analysed by SonarCloud + +| `src/` +| yes +| - + +| `tests/` +| yes +| - + +| `verification/` +| optional +| proofs; only where formal-proofs is declared + +| `www/` +| yes +| site-operations bundle; canonical .well-known/ lives at www/.well-known/ (issue #53) + +|=== + +== Declared structure not yet filled + +These directories currently hold only a stub `README.adoc` and a level +manifest. That is deliberate. They declare the shape a repository minted +from this template is expected to grow into; they are *intended +structure, not abandoned work*. Add content, or delete the directory in +your own repo - but do not read their emptiness as neglect here. + +[cols="1"] +|=== +| `.machine_readable/scripts/verification/` +| `docs/governance/audit/` +| `docs/governance/audit/compliance/` +| `docs/governance/audit/effects/` +| `docs/governance/audit/systems/` +| `docs/governance/maintenance/` +| `docs/governance/maintenance/adaptive/` +| `docs/governance/maintenance/corrective/` +| `docs/governance/maintenance/perfective/` +| `docs/governance/planning/` +| `docs/governance/planning/could/` +| `docs/governance/planning/must/` +| `docs/governance/planning/should/` +| `docs/reports/compliance/` +| `docs/reports/maintenance/` +| `docs/reports/performance/` +| `docs/reports/quality/` +| `docs/reports/security/` +| `docs/standards/` +| `docs/theory/` +| `docs/theory/computing/` +| `docs/theory/formalisms/` +| `docs/theory/mathematics/` +| `docs/theory/ontologies/` +| `docs/theory/other/` +| `docs/theory/socio-technical/` +| `docs/whitepapers/` +| `docs/whitepapers/academic/` +| `docs/whitepapers/industry/` +| `docs/whitepapers/outreach/` +| `examples/` +| `features/boj-server/` +| `features/panic-attacker/` +| `src/aspects/` +| `src/aspects/integrity/` +| `src/aspects/observability/` +| `src/aspects/security/` +| `src/bridges/` +| `src/contracts/` +| `src/core/` +| `src/definitions/` +| `src/errors/` +| `src/interface/generated/` +| `src/interface/generated/abi/` +| `tests/fuzz/` +| `verification/benchmarks/` +| `verification/coverage/` +| `verification/fuzzing/` +| `verification/safety_case/` +| `verification/simulations/` +| `verification/tests/` +| `verification/traceability/` +|=== + +== Where things are enforced + +* Root shape - `scripts/check-root-shape.sh` against + `.machine_readable/root-allow.txt`, run by `.github/workflows/estate-rules.yml`. + The check is bidirectional: unlisted entries fail, and required entries + that are absent also fail. +* This map - `just repo-map` must produce no diff. +* Community health - GitHub reads `.github/` and no other path. +* Variant divergence - `scripts/check-variant-drift.sh` compares a child + to its parent BY PATH, so any move here invalidates every child's + declared path lists until they are re-anchored. diff --git a/docs/architecture/THREAT-MODEL.adoc b/docs/architecture/THREAT-MODEL.adoc index e4b4016..a1aac32 100644 --- a/docs/architecture/THREAT-MODEL.adoc +++ b/docs/architecture/THREAT-MODEL.adoc @@ -2,13 +2,13 @@ -# Threat Model: Dictask +# Threat Model: DictaTask ## Document Info | Field | Value | |---------------|--------------------------------| -| Project | Dictask | +| Project | DictaTask | | Version | 1.0 | | Last Reviewed | 2026-03-16 | | Author | Jonathan D.A. Jewell | @@ -34,7 +34,7 @@ ## System Overview -Brief description of Dictask and its architecture. +Brief description of DictaTask and its architecture. > See [STATE-VISUALIZER.adoc](../STATE-VISUALIZER.adoc) for the full architecture diagram and completion dashboard. diff --git a/TOPOLOGY.adoc b/docs/architecture/TOPOLOGY.adoc similarity index 99% rename from TOPOLOGY.adoc rename to docs/architecture/TOPOLOGY.adoc index ca59465..de7b1a3 100644 --- a/TOPOLOGY.adoc +++ b/docs/architecture/TOPOLOGY.adoc @@ -1,6 +1,6 @@ == SPDX-License-Identifier: CC-BY-SA-4.0 -== dictask — Architecture Topology +== dicta-task — Architecture Topology === System Architecture diff --git a/docs/attribution/0.2-AI-MANIFEST.a2ml b/docs/attribution/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 52beaea..0000000 --- a/docs/attribution/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "attribution-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-unit of the docs pillar focusing on attribution. diff --git a/docs/attribution/CITATION.cff b/docs/attribution/CITATION.cff deleted file mode 100644 index 9b66f9b..0000000 --- a/docs/attribution/CITATION.cff +++ /dev/null @@ -1,17 +0,0 @@ -cff-version: 1.2.0 -message: "If you use this software, please cite it as below." -authors: -- family-names: "{{AUTHOR_LAST}}" - given-names: "{{AUTHOR_FIRST}}" - orcid: "https://orcid.org/0000-0000-0000-0000" # Placeholder -title: "{{PROJECT_NAME}}" -version: 0.1.0 -date-released: {{CURRENT_DATE}} -url: "https://github.com/hyperpolymath/dictask" -repository-code: "https://github.com/hyperpolymath/dictask" -license: MPL-2.0 -keywords: - - "rsr" - - "formal-verification" - - "neurosymbolic" - - "provenance" diff --git a/docs/attribution/CITATIONS.adoc b/docs/attribution/CITATIONS.adoc index 24df3f3..1f285ca 100644 --- a/docs/attribution/CITATIONS.adoc +++ b/docs/attribution/CITATIONS.adoc @@ -1,34 +1,36 @@ -= Dictask - Citation Guide +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Dicta-Task - Citation Guide :toc: == BibTeX [source,bibtex] ---- -@software{Dictask_2026, - author = {Jewell, Jonathan}, - title = {Dictask}, +@software{Dicta-Task_2026, + author = {Jewell, Jonathan D.A.}, + title = {Dicta-Task}, year = {2026}, - url = {https://github.com/hyperpolymath/Dictask}, + url = {https://github.com/hyperpolymath/Dicta-Task}, license = {MPL-2.0} } ---- == Harvard Style -Jewell, J.D.A. (2026) _Dictask_ [Computer software]. Available at: https://github.com/hyperpolymath/Dictask +Jewell, J. (2026) _Dicta-Task_ [Computer software]. Available at: https://github.com/hyperpolymath/Dicta-Task == OSCOLA -Jonathan D.A. Jewell, 'Dictask' (2026) +Jonathan D.A. Jewell, 'Dicta-Task' (2026) == MLA -Jewell, Jonathan "Dictask." 2026, github.com/hyperpolymath/Dictask. +Jewell, Jonathan D.A. "Dicta-Task." 2026, github.com/hyperpolymath/Dicta-Task. == APA 7 -Jewell, J.D.A. (2026). _Dictask_ [Computer software]. GitHub. https://github.com/hyperpolymath/Dictask +Jewell, J. (2026). _Dicta-Task_ [Computer software]. GitHub. https://github.com/hyperpolymath/Dicta-Task == See Also diff --git a/docs/attribution/CODEOWNERS.adoc b/docs/attribution/CODEOWNERS.adoc index 3714055..ffd88f1 100644 --- a/docs/attribution/CODEOWNERS.adoc +++ b/docs/attribution/CODEOWNERS.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Code Ownership :icons: font @@ -16,4 +18,4 @@ The technical rules for automatic review assignments are maintained in the machi == Component Owners -A full list of maintainers and their contact information can be found in link:MAINTAINERS.adoc[MAINTAINERS.adoc]. +A full list of maintainers and their contact information can be found in link:../MAINTAINERS.adoc[MAINTAINERS.adoc]. diff --git a/docs/attribution/MAINTAINERS.adoc b/docs/attribution/MAINTAINERS.adoc deleted file mode 100644 index 48d9781..0000000 --- a/docs/attribution/MAINTAINERS.adoc +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: CC-BY-SA-4.0 -= Maintainers -:toc: preamble - -This document lists the maintainers of this project and their responsibilities. - -== Current Maintainers - -[cols="2,3,2",options="header"] -|=== -| Name | Role | Contact - -| Jonathan D.A. Jewell -| Lead Maintainer -| https://github.com/hyperpolymath[@hyperpolymath] -|=== - -== Responsibilities - -Maintainers are responsible for: - -* 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 - -== Becoming a Maintainer - -Contributors who demonstrate: - -* Consistent, high-quality contributions -* Understanding of the project's goals and standards -* Constructive participation in discussions -* Commitment to the project's long-term health - -May be invited to become maintainers at the discretion of existing maintainers. - -== Decision Making - -* Routine decisions (bug fixes, minor improvements) can be made by any maintainer -* Significant changes require discussion and consensus among maintainers -* Breaking changes or major features should be discussed in issues before implementation - -== Contact - -For questions about project governance, open an issue or contact the maintainers listed above. diff --git a/docs/attribution/README.adoc b/docs/attribution/README.adoc index b095612..c6c3b5e 100644 --- a/docs/attribution/README.adoc +++ b/docs/attribution/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = attribution Unit diff --git a/docs/compliance/ACCESSIBILITY.adoc b/docs/compliance/ACCESSIBILITY.adoc deleted file mode 100644 index 4f94233..0000000 --- a/docs/compliance/ACCESSIBILITY.adoc +++ /dev/null @@ -1,3 +0,0 @@ -= Accessibility Compliance -Status: Partial -Target: WCAG 2.1 AA diff --git a/docs/contributing.adoc b/docs/contributing.adoc new file mode 100644 index 0000000..2750a06 --- /dev/null +++ b/docs/contributing.adoc @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Contributing — Dicta-Task +:revdate: 2026-MM-DD + +== Audience + +Developers working *on* `Dicta-Task`. For consumers (people calling +or depending on it) see link:./usage.adoc[usage.adoc]. + +== Local-dev setup + +Prerequisites — the minimum versions and where to get them: + +* `` v`` — ``. +* `` v`` — ``. +* GPG signing key configured (estate policy — all commits must be + signed). See + link:https://github.com/hyperpolymath/standards/blob/main/docs/secure-coding-training.md[standards/docs/secure-coding-training.md]. + +One-shot setup: + +[source,bash] +---- +git clone git@github.com:hyperpolymath/Dicta-Task.git +cd Dicta-Task +just setup # installs deps, sets up hooks +just test # runs the full test suite +---- + +== Running tests + +* **Unit**: `just test-unit` — fast, no I/O. +* **Integration**: `just test-int` — uses real services (database, + HTTP, etc.). Estate policy: prefer real over mocked + (see `feedback_integration_tests_real_db` in maintainer's memory). +* **Property**: `just test-prop` — randomised, slower; budget + documented in `docs/proof-debt.md` if applicable. +* **Full**: `just test` — runs all of the above. + +== Code style + +We enforce style via CI (governance-reusable.yml from hyperpolymath/standards). +Locally: + +[source,bash] +---- +just fmt # auto-format +just lint # static checks +---- + +* All commits must be **GPG-signed** (CI enforces; see + link:https://github.com/hyperpolymath/standards[standards]). +* All source files must carry an **SPDX-License-Identifier** header + (CI enforces). +* Conventional commits — `feat`, `fix`, `chore`, `refactor`, `docs`, + `test`, `ci`, `revert` (CHANGELOG is auto-generated from these + via link:https://github.com/hyperpolymath/standards/blob/main/.github/workflows/changelog-reusable.yml[`changelog-reusable.yml`]). + +== Branching & PR workflow + +. Branch off `main` as `claude/` (for AI agents) or + `/` (for humans). +. Make focused, narrow commits — one logical change per commit. +. Open a PR against `main`. +. **Enable auto-merge immediately** on every PR you open + (`gh pr merge --auto --squash`) — estate standing policy + (see standards#196 audit and policies). +. CI must be green. The PR auto-merges when checks pass + reviews land. + +== Adding a new dependency + +. State the *why* in the PR body — what does this dependency unlock? +. Check provenance (maintained, audited, no malicious history). +. Pin to a SHA, not a tag. +. Update `docs/architecture.adoc#Dependencies`. + +== Adding an ADR + +When you make a non-obvious design decision, write it down: + +. Copy `docs/decisions/0001-template.adoc` → `0002-.adoc`. +. Fill in: Context, Decision, Consequences, Alternatives. +. Link the ADR from the README or relevant code as a comment. + +== Reporting issues + +* Bugs in `Dicta-Task`: file at `hyperpolymath/Dicta-Task/issues`. +* Estate-wide concerns (policy, conventions, CI): file at + `hyperpolymath/standards/issues`. diff --git a/docs/decisions/0.2-AI-MANIFEST.a2ml b/docs/decisions/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index ac26298..0000000 --- a/docs/decisions/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "decisions-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-unit of the docs pillar focusing on decisions. diff --git a/docs/decisions/0000-template.adoc b/docs/decisions/0000-template.adoc index 6710304..0025b64 100644 --- a/docs/decisions/0000-template.adoc +++ b/docs/decisions/0000-template.adoc @@ -1,35 +1,37 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Architecture Decision Record: 0000-template - - +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# [NUMBER]. [TITLE] +== [NUMBER]. [TITLE] Date: YYYY-MM-DD -## Status +=== Status -[Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN](NNNN-title.md) | Rejected] +link:NNNN-title.md[Proposed | Accepted | Deprecated | Superseded by [ADR-NNNN] | Rejected] -## Context +=== Context What is the issue that we're seeing that is motivating this decision or change? -## Decision +=== Decision What is the change that we're proposing and/or doing? -## Consequences +=== Consequences What becomes easier or more difficult to do because of this change? -### Positive +==== Positive - ... -### Negative +==== Negative - ... -### Neutral +==== Neutral - ... diff --git a/docs/decisions/0001-adopt-rsr-standard.adoc b/docs/decisions/0001-adopt-rsr-standard.adoc index b5f8a1e..1d7c260 100644 --- a/docs/decisions/0001-adopt-rsr-standard.adoc +++ b/docs/decisions/0001-adopt-rsr-standard.adoc @@ -1,16 +1,18 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Architecture Decision Record: 0001-adopt-rsr-standard - - +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# 1. Adopt Rhodium Standard Repository (RSR) Template +== 1. Adopt Rhodium Standard Repository (RSR) Template Date: 2026-02-14 -## Status +=== Status Accepted -## Context +=== Context Managing multiple repositories with an ad-hoc approach led to significant inconsistencies across the ecosystem. Common problems included: @@ -27,7 +29,7 @@ inconsistencies across the ecosystem. Common problems included: Without a single source of truth for repository structure, each new repo required manual setup and inevitably drifted from best practices over time. -## Decision +=== Decision Adopt the Rhodium Standard Repository (RSR) template (`rsr-template-repo`) as the canonical starting point for all new repositories. Existing repositories @@ -40,7 +42,7 @@ The RSR template provides: - **AI manifest** (`0-AI-MANIFEST.a2ml`) as a universal entry point for all AI agents - **Bot directives** in `.machine_readable/bot_directives/` for bot orchestration integration -- **Contractiles** in `.machine_readable/contractiles/` (k9, dust, lust, must, trust) for +- **Contractiles** in `.machine_readable/contractiles/` (k9, dust, intend, must, trust) for policy enforcement - **Standardized workflows** (16+ GitHub Actions workflows, all SHA-pinned) - **Justfile automation** with standard recipes for common tasks @@ -50,15 +52,16 @@ The RSR template provides: New repositories are created by cloning the template: -```bash +[source,bash] +---- git clone https://github.com/hyperpolymath/rsr-template-repo new-repo-name cd new-repo-name rm -rf .git && git init -``` +---- -## Consequences +=== Consequences -### Positive +==== Positive - Consistency across all repositories, enforced from creation - Automated compliance checking via `rsr-antipattern.yml` workflow @@ -69,7 +72,7 @@ rm -rf .git && git init - Reduced maintenance burden: fix once in template, propagate to all repos - Machine-readable state enables tooling and automation pipelines -### Negative +==== Negative - Migration effort for existing repos requires time and attention - Learning curve for contributors unfamiliar with RSR conventions @@ -77,7 +80,7 @@ rm -rf .git && git init - Some repos may have unique needs that do not fit the standard template without customization -### Neutral +==== Neutral - Existing CI/CD pipelines continue to work; RSR workflows are additive - Third-party dependencies retain their original licenses regardless of diff --git a/docs/decisions/0001-template.adoc b/docs/decisions/0001-template.adoc new file mode 100644 index 0000000..b4070a8 --- /dev/null +++ b/docs/decisions/0001-template.adoc @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += ADR-0001 — Use Architecture Decision Records +:revdate: 2026-MM-DD +:status: ACCEPTED + +== Context + +We need a lightweight way to record significant architectural decisions +and the reasoning behind them, so future maintainers (and AI agents) +can understand *why* the project is shaped as it is. + +== Decision + +Adopt link:https://adr.github.io/[Architecture Decision Records (ADRs)] +in `docs/decisions/`, numbered sequentially (0001, 0002, ...). + +* Each ADR is a single `.adoc` file. +* The first ADR (this one) records the decision to use ADRs. +* Status values: PROPOSED, ACCEPTED, DEPRECATED, SUPERSEDED. +* When a decision is overturned, the new ADR records the supersession + and updates the old one's status to `SUPERSEDED by 00NN`. + +== Consequences + +. **Positive**: future readers see *why* without git-archaeology. +. **Positive**: design alternatives are documented, not just the + winning choice. +. **Positive**: ADRs are reviewable in PRs — the design discussion + happens alongside the code that implements it. +. **Negative**: maintenance burden — every non-obvious decision now + warrants an ADR. (We mitigate by keeping ADRs short; "non-obvious" + is a judgment call.) +. **Negative**: ADRs can rot. We accept this; the SUPERSEDED chain + is the recovery mechanism. + +== Alternatives considered + +. **Comments in code** — too local; doesn't capture cross-cutting + decisions. +. **Wiki / external doc** — drifts from code; not in PR review. +. **Commit messages** — too transient and not discoverable. +. **No record** — discarded; this is how every project ends up with + "I don't know why we do it this way" debt. + +== Companion ADRs + +* (None yet — this is the first ADR.) + +== References + +* MADR template — `https://adr.github.io/madr/` +* Estate convention — `hyperpolymath/standards/docs/RSR_OUTLINE.adoc` diff --git a/docs/decisions/0002-variant-contract.adoc b/docs/decisions/0002-variant-contract.adoc new file mode 100644 index 0000000..dfed0ec --- /dev/null +++ b/docs/decisions/0002-variant-contract.adoc @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0002-variant-contract + +== 2. Variant templates declare a machine-readable contract with their parent + +Date: 2026-07-07 + +=== Status + +Accepted (ratified by owner 2026-07-07; first instance live in +rsr-julia-library-template-repo) + +=== Context + +The RSR template family grows variants where the spine genuinely diverges — +build system, test/docs toolchain, ABI story (first: the Julia library +template; candidates: Rust+SPARK paired-lib). Hand-maintained variant repos +silently drift from the canonical template: the parent gains fixes the +variant never receives, the variant fixes defects the parent cannot see +(especially while the parent's own CI is red), and nothing records which +differences are intentional. + +Anti-proliferation doctrine requires that variation be *declared, bounded and +enforceable*, and the long-term plan (Scaffoldia as composer; template repos +becoming generated artifacts with a maintenance sync loop until cut-off) +needs variant deltas to exist as data, not tribal knowledge. + +=== Decision + +Every variant template carries a **variant contract** at +`.machine_readable/descriptiles/VARIANT.a2ml` declaring: + +- **parent** (owner/repo) and **parent-pin** — the last reconciled parent + commit. Bumping the pin after reconciling is the template-maintenance sync + operation. +- **normalisation rules** separating per-repo operational state from spine + content: 40-hex action/reusable pin SHAs (dependabot cadence differs per + repo) and self-name folding (both repo names → `SELF` on both sides). +- **path lists**, verified against the parent at the pin: + `added`, `removed`, `diverged` (permanently variant-owned), + `diverged-pending-upstream` (fixes made variant-first; doubles as the + upstream-promotion worklist), and `operational-state` (excluded from + comparison, e.g. coaptation receipts). + +A **drift gate** (`scripts/check-variant-drift.sh` + the Variant Drift Gate +workflow: push / PR / weekly cron / manual, no `paths:` filter) enforces the +contract: any tracked file not declared variant-owned must be identical to +the parent at the pin modulo normalisation, and declared additions/removals +are asserted in both directions. + +CLADE stays identity-only: `[lineage]` records *who* the parent is and gains +a one-line `variant-contract` pointer; the operational mechanics (pin, path +lists) live in VARIANT.a2ml so pin bumps never churn the identity file. + +The contract is **direction-agnostic**: today the parent is the source and +the variant a hand-maintained specialisation (`direction = +"template-is-source"`); after the Scaffoldia inversion the same lists +describe how to regenerate the variant from core + variant pack +(`direction = "generated-from-core"`). + +=== Consequences + +- Variant drift becomes a red check instead of an archaeology project. +- The `diverged-pending-upstream` list is a standing, exact worklist for + upstream promotion (first use: rsr-template-repo PR #137). +- Scaffoldia can consume variant contracts as data when composing or + regenerating repos; GitHub's "Use this template" path and the composer + path cannot silently diverge because both are checked against the same + contract. +- New variants must justify spine divergence in the contract's `[variant]` + block; anything expressible as a feature or profile should not become a + variant repo (anti-proliferation). +- Reference implementation: rsr-julia-library-template-repo (VARIANT.a2ml, + scripts/check-variant-drift.sh, .github/workflows/variant-drift-gate.yml). diff --git a/docs/decisions/0003-forge-and-sustain-lifecycle.adoc b/docs/decisions/0003-forge-and-sustain-lifecycle.adoc new file mode 100644 index 0000000..9726e04 --- /dev/null +++ b/docs/decisions/0003-forge-and-sustain-lifecycle.adoc @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Architecture Decision Record: 0003-forge-and-sustain-lifecycle + +== 3. One lifecycle, one contract: Forge (mint → provision → configure → harness) and Sustain (corrective / adaptive / perfective / preventive) + +Date: 2026-07-21 + +=== Status + +Accepted (drafted 2026-07-21; ratified by owner ruling 2026-09-19). The normative +statement lives in `hyperpolymath/standards` +`0-canon/rsr/SCAFFOLD-LIFECYCLE.adoc` (relocated from `rhodium-standard-repositories/spec/` in the September 2026 reorg); this ADR records +the decision and its mechanics in the spine. + +=== Context + +The scaffolding machinery exists but is disconnected, and each disconnection +has produced a measured incident: + +- `just repo-init` renders 34 substitutions and derives identity, but leaves 12 + tokens `UNASSIGNED`, performs no platform configuration, and — decisive — + records **no provenance**. A minted repo does not know which template + commit it came from, so template improvements cannot be propagated and + drift cannot be detected. The external audit + (rsr-template-repo-experiment, 05-ZIGZAG §2.2) named this the single + highest-leverage gap: Copier/Cruft solved it with an answer-file + + `update` + `check`; the estate hand-simulates it as recurring + standardisation-PR campaigns. +- `rsr-profile.a2ml` — the file that makes hypatia's implemented + `rsr-conformance` oracle able to score a repo at all — exists in ~2 of + ~300 repos, because nothing mints it. +- Identity in the probot-managed `settings.yml` renamed this very repo to + `-REPO-` on every push (the 2026-07-20 incident). Identity must be set + out of band, but no stage of the lifecycle owned that step, and this + file's own header previously claimed `just repo-init` did it via `gh` — it + never has. +- ADR-0002's variant contract + drift gate work (reference: + rsr-julia-library-template-repo) but apply only to variant *templates*, + not to the ~200 minted repos that need the same mechanism. + +=== Decision + +**One lifecycle.** A repository is *forged* in four ordered stages — exit +criteria in SCAFFOLD-LIFECYCLE.adoc: + +1. **Mint** — tree exists, placeholders rendered, identity derived, + archetype overlay applied, `rsr-profile.a2ml` and provenance written, + and **no template identity survives in the child**: the spine's own + name is a literal rather than a token, so rendering placeholders is not + sufficient — a self-name pass must rewrite it everywhere it denotes + *this* repo, while leaving every line that names the parent *as* a + parent untouched. A minted repo that still declares + `sonar.projectKey=…_rsr-template-repo` has not been minted; it has been + copied. +2. **Provision** — the recorded `BUILD_CMD`/`TEST_CMD` actually run + (echoed advice is not provisioning). +3. **Configure** — identity, visibility, branch protection set out of band + via `gh`/UI, once per repo; required contexts equal emitted job ids; + repo registered in `gv-clade-index`. +4. **Harness** — CI green or accounted for, drift gate armed against the + recorded pin, oracle able to score. + +It is then *sustained* in four modes: **corrective** (fleet `fix-*` on +broken invariants), **adaptive** (parent-pin bumps + fan-out campaigns), +**perfective** (tier climbing via `mix hypatia.rsr_score`; promotion of +`diverged-pending-upstream` fixes), **preventive** (canary-tested gates, +placeholder guard, drift-gate cron). + +**One contract.** The ADR-0002 `VARIANT.a2ml` shape is extended in *use*, +not in format, to minted repos: `just repo-init` writes +`.machine_readable/descriptiles/VARIANT.a2ml` with `direction = +"generated-from-core"`, `parent = hyperpolymath/rsr-template-repo`, +`parent-pin` = the template tip at mint (resolved via `git ls-remote`; +`UNASSIGNED` when offline — honest, never guessed), and empty path lists +that grow as the repo legitimately diverges. A variant template, a minted +repo, and a retrofitted repo are the same object — a child of the spine at +a pin with declared divergences — so `scripts/check-variant-drift.sh` +(promoted into the spine by this ADR, verbatim from the Julia variant — it +was already contract-driven) is the estate's re-templating check for all +three populations. + +**Archetypes are data.** `archetypes//ARCHETYPE.a2ml` carries a +capability `preset` (defined in standards `template-capability-gates.toml`) +plus a `[tokens]` table filling the previously-`UNASSIGNED` init tokens +that are archetype-determined (`PROJECT_KIND`, `LANG_STACK`, `BUILD_CMD`, +`TEST_CMD`, …). Tokens that are genuine per-repo judgement +(`PROJECT_UNIQUE_STRENGTH`, `MUST_INVARIANTS`, …) stay `UNASSIGNED` — an +archetype must not write fiction. `just repo-init ` applies the +overlay; the `archetypes/` directory is template-only and removed from the +minted tree (like `build/templates/`). First archetype: `julia-library`, +harvested from the Julia variant's contract. Declared next (content to +follow, not stubbed empty): `wordpress-plugin`, `zotero-plugin`, +`userscript` — presets already reserved in the canon. + +**Scaffoldia consumes, never redefines.** The composer's pack axis is the +archetype/variant contract; its profile axis is the capability preset. Its +GitLab-lineage implementation's registry is harvested as input data; the +engine stays dormant until the composer ADR (scaffoldia Phase 1) chooses +its implementation. + +=== Consequences + +- Every newly minted repo is born sustainable: scorable by the oracle + (profile), re-templatable (provenance), and drift-checkable (gate) — the + three artefacts whose absence created the manual-campaign treadmill. +- The 12 `UNASSIGNED` tokens split honestly: archetype-determined ones get + real values; judgement ones stay loud. +- The `settings.yml` header's false claim about `gh` automation is + corrected: Configure is an operator stage today, printed as exact + commands by `just repo-init`; automating it is future work and must not be + described as existing. +- Retrofit of already-minted repos = writing the provenance contract into + them (megasweep can detect which repos lack one; mutation stays + owner-gated, audit-first). +- The dogfood loop closes: the template ships the contract format, the + canon defines its lifecycle meaning, the oracle scores its presence, the + fleet propagates its updates. diff --git a/docs/decisions/0004-storage-backend.adoc b/docs/decisions/0004-storage-backend.adoc new file mode 100644 index 0000000..2b019e0 --- /dev/null +++ b/docs/decisions/0004-storage-backend.adoc @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) += ADR-0004: Keep SQLite as the canonical store; put LMDB behind a trait later + +Date: 2026-09-26 + +== Status + +Accepted + +== Context + +The canonical task store (`src/store`, schema in +`src/definitions/schemas/tasks.sql`) is SQLite in WAL mode. The question came +up of switching to LMDB now, so the project can be extended more easily later. + +What the store has to do today: + +* *Relational integrity.* `transcripts → audio_files`, `candidate_intents → + transcripts`, `tasks → candidate_intents`, plus `supersedes_task_id` and + `duplicate_of_task_id` self-references. Foreign keys and `CHECK` + constraints (status, review state, confidence in [0,1]) enforce these + inside the database. +* *Ad-hoc queries and views.* The Markdown/JSON/CSV views are + `WHERE status NOT IN (...) ORDER BY priority_score DESC`. Review queues, + dedup candidate lookup (by project/title) and audit history are more of the + same. +* *Auditability.* The data has to be inspectable with standard tools + (`sqlite3`, Datasette, backups as a single file). +* *Low write volume.* A few recordings a day produce tens of rows. There is one + writer (the pipeline) and a few readers (views, review UI). + +LMDB is a memory-mapped, ordered key-value B+tree. It is extremely fast at +reads, supports multiple readers with a single writer, and gives crash-safe +copy-on-write. But it has no schema, no query language, no constraints, no +secondary indexes and no joins. Every one of those would have to be +hand-written in Rust (e.g. with `heed`), including index maintenance and +migrations. You also have to choose the map size ahead of time. + +== Decision + +1. *SQLite stays the canonical source of truth.* We are not switching to LMDB. +2. *Make the backend swappable instead.* Extract a `TaskStore` trait in + `src/store` (insert/update/query tasks, record intents, append audit + events, iterate for views) and make the current rusqlite code its first + implementation. Nothing outside `src/store` may issue SQL directly. +3. *Where LMDB fits later.* If one of these workloads turns up, add an + LMDB-backed implementation or *side store* behind the same trait, not a + replacement: + * a high-rate embedding/semantic-dedup cache (vector → task id), + * a content-addressed audio/transcript blob index keyed by SHA-256, + * an embedded, read-heavy replica for a UI or FFI consumer. +4. Federation/replication is already sketched through VeriSimDB + (`src/interface/ffi/src/verisimdb.zig`) as a *secondary* write target. It + should also go through the trait. + +== Consequences + +=== Positive + +* The integrity rules stay declarative and are enforced by the database. +* Views and review queries remain one-line SQL. +* The trait gives the extensibility that was the reason for considering LMDB, + without paying LMDB's costs now. +* SQLite files are portable, easy to back up and easy to inspect for audit. + +=== Negative + +* SQLite allows one writer at a time. That is fine for this pipeline but would + need revisiting for multi-device concurrent writes. +* The trait layer is a small amount of extra code. + +=== Revisit when + +* the store has more than one concurrent writer process, or +* semantic dedup needs sub-millisecond lookups over large embedding sets, or +* profiling shows SQLite as the pipeline bottleneck (target: under 5 minutes + end-to-end per recording). diff --git a/docs/decisions/README.adoc b/docs/decisions/README.adoc index 153a5e7..cb9c6d8 100644 --- a/docs/decisions/README.adoc +++ b/docs/decisions/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = decisions Unit diff --git a/docs/developer/0.2-AI-MANIFEST.a2ml b/docs/developer/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index c16fcc7..0000000 --- a/docs/developer/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "developer-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-unit of the docs pillar focusing on developer. diff --git a/docs/developer/ABI-FFI-README.adoc b/docs/developer/ABI-FFI-README.adoc index c896025..48db036 100644 --- a/docs/developer/ABI-FFI-README.adoc +++ b/docs/developer/ABI-FFI-README.adoc @@ -1,9 +1,11 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = ABI/FFI Standards {{~ Aditionally delete this line and fill out the template below ~}} -# dictask ABI/FFI Documentation +== DICTA_TASK ABI/FFI Documentation -## Overview +=== Overview This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: @@ -12,12 +14,12 @@ This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: - **Generated C headers** bridge Idris2 ABI to Zig FFI - **Any language** can call through standard C ABI -## Architecture +=== Architecture -``` +---- ┌─────────────────────────────────────────────┐ │ ABI Definitions (Idris2) │ -│ src/abi/ │ +│ src/interface/Abi/ │ │ - Types.idr (Type definitions) │ │ - Layout.idr (Memory layout proofs) │ │ - Foreign.idr (FFI declarations) │ @@ -27,7 +29,7 @@ This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: ▼ ┌─────────────────────────────────────────────┐ │ C Headers (auto-generated) │ -│ generated/abi/{{project}}.h │ +│ generated/abi/dicta_task.h │ └─────────────────┬───────────────────────────┘ │ │ imported by @@ -40,18 +42,18 @@ This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: │ - Memory-safe by default │ └─────────────────┬───────────────────────────┘ │ - │ compiled to lib{{project}}.so/.a + │ compiled to libdicta_task.so/.a ▼ ┌─────────────────────────────────────────────┐ │ Any Language via C ABI │ -│ - Rust, AffineScript, Julia, Python, etc. │ +│ - Rust, , Julia, Python, etc. │ └─────────────────────────────────────────────┘ -``` +---- -## Directory Structure +=== Directory Structure -``` -{{project}}/ +---- +dicta_task/ ├── src/ │ ├── abi/ # ABI definitions (Idris2) │ │ ├── Types.idr # Core type definitions with proofs @@ -68,25 +70,26 @@ This library follows the **Hyperpolymath RSR Standard** for ABI and FFI design: │ ├── test/ │ │ └── integration_test.zig │ └── include/ -│ └── {{project}}.h # C header (optional, can be generated) +│ └── dicta_task.h # C header (optional, can be generated) │ ├── generated/ # Auto-generated files │ └── abi/ -│ └── {{project}}.h # Generated from Idris2 ABI +│ └── dicta_task.h # Generated from Idris2 ABI │ └── bindings/ # Language-specific wrappers (optional) ├── rust/ - ├── affinescript/ + ├── / └── julia/ -``` +---- -## Why Idris2 for ABI? +=== Why Idris2 for ABI? -### 1. **Formal Verification** +==== 1. **Formal Verification** Idris2's dependent types allow proving properties about the ABI at compile-time: -```idris +[source,idris] +---- -- Prove struct size is correct public export exampleStructSize : HasSize ExampleStruct 16 @@ -98,13 +101,14 @@ fieldAligned : Divides 8 (offsetOf ExampleStruct.field) -- Prove ABI is platform-compatible public export abiCompatible : Compatible (ABI 1) (ABI 2) -``` +---- -### 2. **Type Safety** +==== 2. **Type Safety** Encode invariants that C/Zig cannot express: -```idris +[source,idris] +---- -- Non-null pointer guaranteed at type level data Handle : Type where MkHandle : (ptr : Bits64) -> {auto 0 nonNull : So (ptr /= 0)} -> Handle @@ -112,13 +116,14 @@ data Handle : Type where -- Array with length proof data Buffer : (n : Nat) -> Type where MkBuffer : Vect n Byte -> Buffer n -``` +---- -### 3. **Platform Abstraction** +==== 3. **Platform Abstraction** Platform-specific types with compile-time selection: -```idris +[source,idris] +---- CInt : Platform -> Type CInt Linux = Bits32 CInt Windows = Bits32 @@ -126,13 +131,14 @@ CInt Windows = Bits32 CSize : Platform -> Type CSize Linux = Bits64 CSize Windows = Bits64 -``` +---- -### 4. **Safe Evolution** +==== 4. **Safe Evolution** Prove that new ABI versions are backward-compatible: -```idris +[source,idris] +---- -- Compiler enforces compatibility abiUpgrade : ABI 1 -> ABI 2 abiUpgrade old = MkABI2 { @@ -141,71 +147,78 @@ abiUpgrade old = MkABI2 { -- Can add new fields new_features = defaults } -``` +---- -## Why Zig for FFI? +=== Why Zig for FFI? -### 1. **C ABI Compatibility** +==== 1. **C ABI Compatibility** Zig exports C-compatible functions naturally: -```zig +[source,zig] +---- export fn library_function(param: i32) i32 { return param * 2; } -``` +---- -### 2. **Memory Safety** +==== 2. **Memory Safety** Compile-time safety without runtime overhead: -```zig +[source,zig] +---- // Null check enforced at compile time const handle = init() orelse return error.InitFailed; defer free(handle); -``` +---- -### 3. **Cross-Compilation** +==== 3. **Cross-Compilation** Built-in cross-compilation to any platform: -```bash +[source,bash] +---- zig build -Dtarget=x86_64-linux zig build -Dtarget=aarch64-macos zig build -Dtarget=x86_64-windows -``` +---- -### 4. **Zero Dependencies** +==== 4. **Zero Dependencies** No runtime, no libc required (unless explicitly needed): -```zig +[source,zig] +---- // Minimal binary size pub const lib = @import("std"); // Only includes what you use -``` +---- -## Building +=== Building -### Build FFI Library +==== Build FFI Library -```bash +[source,bash] +---- cd ffi/zig zig build # Build debug zig build -Doptimize=ReleaseFast # Build optimized zig build test # Run tests -``` +---- -### Generate C Header from Idris2 ABI +==== Generate C Header from Idris2 ABI -```bash +[source,bash] +---- cd src/abi -idris2 --cg c-header Types.idr -o ../../generated/abi/{{project}}.h -``` +idris2 --cg c-header Types.idr -o ../../generated/abi/dicta_task.h +---- -### Cross-Compile +==== Cross-Compile -```bash +[source,bash] +---- cd ffi/zig # Linux x86_64 @@ -216,39 +229,42 @@ zig build -Dtarget=aarch64-macos # Windows x86_64 zig build -Dtarget=x86_64-windows -``` +---- -## Usage +=== Usage -### From C +==== From C -```c -#include "{{project}}.h" +[source,c] +---- +#include "dicta_task.h" int main() { - void* handle = {{project}}_init(); + void* handle = dicta_task_init(); if (!handle) return 1; - int result = {{project}}_process(handle, 42); + int result = dicta_task_process(handle, 42); if (result != 0) { - const char* err = {{project}}_last_error(); + const char* err = dicta_task_last_error(); fprintf(stderr, "Error: %s\n", err); } - {{project}}_free(handle); + dicta_task_free(handle); return 0; } -``` +---- Compile with: -```bash -gcc -o example example.c -l{{project}} -L./zig-out/lib -``` +[source,bash] +---- +gcc -o example example.c -ldicta_task -L./zig-out/lib +---- -### From Idris2 +==== From Idris2 -```idris -import dictask.ABI.Foreign +[source,idris] +---- +import DICTA_TASK.ABI.Foreign main : IO () main = do @@ -260,49 +276,51 @@ main = do free handle putStrLn "Success" -``` +---- -### From Rust +==== From Rust -```rust -#[link(name = "{{project}}")] +[source,rust] +---- +#[link(name = "dicta_task")] extern "C" { - fn {{project}}_init() -> *mut std::ffi::c_void; - fn {{project}}_free(handle: *mut std::ffi::c_void); - fn {{project}}_process(handle: *mut std::ffi::c_void, input: u32) -> i32; + fn dicta_task_init() -> *mut std::ffi::c_void; + fn dicta_task_free(handle: *mut std::ffi::c_void); + fn dicta_task_process(handle: *mut std::ffi::c_void, input: u32) -> i32; } fn main() { unsafe { - let handle = {{project}}_init(); + let handle = dicta_task_init(); assert!(!handle.is_null()); - let result = {{project}}_process(handle, 42); + let result = dicta_task_process(handle, 42); assert_eq!(result, 0); - {{project}}_free(handle); + dicta_task_free(handle); } } -``` +---- -### From Julia +==== From Julia -```julia -const lib{{project}} = "lib{{project}}" +[source,julia] +---- +const libdicta_task = "libdicta_task" function init() - handle = ccall((:{{project}}_init, lib{{project}}), Ptr{Cvoid}, ()) + handle = ccall((:dicta_task_init, libdicta_task), Ptr{Cvoid}, ()) handle == C_NULL && error("Failed to initialize") handle end function process(handle, input) - result = ccall((:{{project}}_process, lib{{project}}), Cint, (Ptr{Cvoid}, UInt32), handle, input) + result = ccall((:dicta_task_process, libdicta_task), Cint, (Ptr{Cvoid}, UInt32), handle, input) result end function cleanup(handle) - ccall((:{{project}}_free, lib{{project}}), Cvoid, (Ptr{Cvoid},), handle) + ccall((:dicta_task_free, libdicta_task), Cvoid, (Ptr{Cvoid},), handle) end # Usage @@ -313,50 +331,53 @@ try finally cleanup(handle) end -``` +---- -## Testing +=== Testing -### Unit Tests (Zig) +==== Unit Tests (Zig) -```bash +[source,bash] +---- cd ffi/zig zig build test -``` +---- -### Integration Tests +==== Integration Tests -```bash +[source,bash] +---- cd ffi/zig zig build test-integration -``` +---- -### ABI Verification (Idris2) +==== ABI Verification (Idris2) -```idris +[source,idris] +---- -- Compile-time verification %runElab verifyABI -- Runtime checks main : IO () main = do - verifyLayoutsCorrect - verifyAlignmentsCorrect + verifyLayouorrect + verifyAlignmenorrect putStrLn "ABI verification passed" -``` +---- -## Contributing +=== Contributing When modifying the ABI/FFI: -1. **Update ABI first** (`src/abi/*.idr`) +1. **Update ABI first** (`src/interface/Abi/*.idr`) - Modify type definitions - Update proofs - Ensure backward compatibility 2. **Generate C header** ```bash - idris2 --cg c-header src/abi/Types.idr -o generated/abi/{{project}}.h + idris2 --cg c-header src/interface/Abi/Types.idr -o generated/abi/dicta_task.h ``` 3. **Update FFI implementation** (`ffi/zig/src/main.zig`) @@ -373,12 +394,12 @@ When modifying the ABI/FFI: - Usage examples - Migration guide (if breaking changes) -## License +=== License MPL-2.0 -## See Also +=== See Also -- [Idris2 Documentation](https://idris2.readthedocs.io) -- [Zig Documentation](https://ziglang.org/documentation/master/) -- [Rhodium Standard Repositories](https://github.com/hyperpolymath/rhodium-standard-repositories) +- https://idris2.readthedocs.io[Idris2 Documentation] +- https://ziglang.org/documentation/master/[Zig Documentation] +- https://github.com/hyperpolymath/rhodium-standard-repositories[Rhodium Standard Repositories] diff --git a/docs/developer/README.adoc b/docs/developer/README.adoc index 1d00529..8d0a283 100644 --- a/docs/developer/README.adoc +++ b/docs/developer/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = developer Unit diff --git a/docs/developer/invariant-path.adoc b/docs/developer/invariant-path.adoc new file mode 100644 index 0000000..040d7a2 --- /dev/null +++ b/docs/developer/invariant-path.adoc @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Invariant Path Integration (RSR Template) + +Run Invariant Path from this repository root: + +[source,bash] +---- +./scripts/invariant-path.sh scan --file ./README.adoc --artifact-uri repo://README.adoc --write +---- + +Or through Just: + +[source,bash] +---- +just invariant-path scan --file ./README.adoc --artifact-uri repo://README.adoc --write +---- + +This wrapper points to the shared workspace at `/var/mnt/eclipse/repos/invariant-path` +and defaults to the `generic` profile. diff --git a/docs/governance/0.1-AI-MANIFEST.a2ml b/docs/governance/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index 6e373bd..0000000 --- a/docs/governance/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-pillar" -level: 1 -parent: "../0-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Primary governance pillar implementing the Triaxial Software Development - Methodology (TSDM). Contains planning, maintenance, and audit tracks. - -canonical_locations: - tsdm_spec: "TSDM.adoc" - planning: "planning/" - maintenance: "maintenance/" - audit: "audit/" - crg: "CRG-CRITERIA.adoc" - checklist: "MAINTENANCE-CHECKLIST.adoc" - approach: "SOFTWARE-DEVELOPMENT-APPROACH.adoc" diff --git a/docs/governance/CRG-AUDIT-TEMPLATE.adoc b/docs/governance/CRG-AUDIT-TEMPLATE.adoc new file mode 100644 index 0000000..d919a3e --- /dev/null +++ b/docs/governance/CRG-AUDIT-TEMPLATE.adoc @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Dicta-Task — CRG Audit (2026-09-26) +Jonathan D.A. Jewell +2026-09-26 +:toc: +:sectnums: + +// USAGE +// ----- +// Copy this file to `docs/governance/CRG-AUDIT-YYYY-MM-DD.adoc` in the target +// repo, replace the {{PLACEHOLDER}} tokens, and fill every section with +// *verified-today* evidence. Do not leave "TODO" or "tbd" in a finished audit. +// Worked example of a completed audit: boj-server/docs/governance/CRG-AUDIT-2026-04-18.adoc +// +// The audit must grade the repo *as-is today*, not aspirationally. Per CRG v2.0: +// "honest D > dishonest B". + +== Scope and Standard + +Evaluates the `dicta-task` repository against +*Component Readiness Grades v2.0 (STRICT)*, 2026-03-30 revision. + +- Standard: `standards/component-readiness-grades/COMPONENT-READINESS-GRADES.md` +- Template (criteria boilerplate): `dicta-task/docs/governance/CRG-CRITERIA.adoc` +- Self-declared grade (prior art): + * `.machine_readable/descriptiles/STATE.a2ml` → `grade = ""` + * `docs/READINESS.md` (if present) → per-component grades + * Third-party badges (Glama, OpenSSF, etc.) if any — note date and scope +- Audit date: 2026-09-26 +- Auditor: Jonathan D.A. Jewell + +The audit grades the repo *as-is today*, not aspirationally. + +== v2.0 Strictness Recap + +[cols="1,3,4"] +|=== +| Grade | Release Stage | Hard Requirement (v2.0) + +| X | None | Untested. +| F | Reject | Harmful, wasteful, or superseded. +| E | Pre-alpha | >=1 test, failures documented. +| D | Alpha | Test matrix + scope documented + *RSR compliance mandatory*. +| C | Alpha-stable | Dogfooded, CI green, *deep per-file + per-directory annotation*. +| B | Beta | 6+ *diverse* external targets, issues fed back. +| A | Stable | Real-world external feedback confirms value; no harm. +|=== + +Publication requires B+. Long alpha is discipline, not shame. + +== Evidence Inventory (verified 2026-09-26) + +=== Structural compliance (Grade D floor) + +List every RSR-mandated artefact with PRESENT/ABSENT and the concrete source. +Do *not* mark PRESENT without citing the path. + +[cols="2,1,3"] +|=== +| Item | State | Source + +| RSR mandatory workflows (17+) +| PRESENT / PARTIAL (count) / ABSENT +| `.github/workflows/` + +| `.machine_readable/descriptiles/` canonical A2ML +| PRESENT (STATE, META, ECOSYSTEM, AGENTIC, NEUROSYM, PLAYBOOK) / ABSENT +| Layout matches CLAUDE.md invariant + +| `*_chora.deed` (repo deed) +| PRESENT / ABSENT +| Root + +| `docs/EXPLAINME.adoc`, `docs/READINESS.md`, `docs/RSR_OUTLINE.adoc` +| PRESENT / PARTIAL / ABSENT +| `docs/` + +| `build/guix.scm` +| PRESENT (primary + fallback) / PARTIAL / ABSENT +| build/ + +| `www/.well-known/` (security.txt, ai.txt, humans.txt) +| PRESENT / ABSENT +| Per RSR_OUTLINE.adoc; a root `.well-known/` is the legacy location (migration window, issue #53) + +| SPDX headers on source +| PRESENT on sampled files (N/N) / PARTIAL / ABSENT +| `LICENSE`, `LICENSE-MPL-2.0` fallback text + +| `Containerfile` (not `Dockerfile`) +| PRESENT / N/A (no container) / VIOLATION (Dockerfile found) +| Container policy + +| Contractile trident +| PRESENT (`INTENT.contractile`, `TRUST.contractile`, `MUST.contractile`, `ADJUST.contractile`) / PARTIAL / ABSENT +| `.machine_readable/` + +| `Justfile` + `Mustfile` (no `Makefile`) +| PRESENT / VIOLATION (Makefile found) +| Build-system policy (RSR R-020) + +| Remote +| `git@github.com:hyperpolymath/dicta-task.git` (origin only) / drift +| `git remote -v` +|=== + +RSR compliance verdict: *met* / *partial* / *not met* — Grade D floor satisfied? Yes/No. + +=== Code and proofs + +[cols="2,1,3"] +|=== +| Item | Count | Notes + +| Idris2 ABI modules (`src/interface/Abi/**/*.idr`) +| N files, N LOC +| Note any proof modules that typecheck green. + +| Zig FFI modules (`ffi/zig/src/*.zig`) +| N files, N LOC +| Call out largest modules. + +| <> (e.g. cartridges, panels, plugins) +| N directories/files +| Link to manifest. + +| Axiomatic `believe_me` sites +| N irreducible +| Each must have a file-header note justifying it as a documented primitive. + +| Non-axiomatic `believe_me` +| N (should be 0 for C) +| Reference the sweep commit and date. + +| Dangerous-pattern scan (`assert_total`, `Admitted`, `sorry`, `unsafeCoerce`, `Obj.magic`) +| N +| Grepped 2026-09-26. +|=== + +=== Test matrix + +Cite *from authoritative source* (STATE.a2ml or CI log). Do not re-count by +filesystem grep — grade the evidence that already exists. + +- `total-tests = N` (from `.machine_readable/descriptiles/STATE.a2ml` or CI). +- Breakdown by suite: list each named suite and its count. +- CI: `.github/workflows/.yml` runs on push / tag / schedule. +- Last green run: date + commit SHA. + +=== Annotation depth + +v2.0 C requires **per-file and per-directory orientation**. Evidence checklist: + +- Per-directory orientation docs: which `docs/` subtrees exist + (`docs/architecture/`, `docs/decisions/`, `docs/integration/`, + `docs/specification/`, `docs/wiki/`) and their file counts. +- Per-<> README coverage: *M of N* (percentage). M of N that lack + a README is the single biggest tell for C-readiness. Anything below + ~90% coverage fails the "deep per-file annotation" clause. +- Per-module inline docs: sample-audit the largest/most-important modules + and note whether purpose comments, invariants, and boundary conditions + are documented. +- Per-subtree README: which non-trivial source subtrees lack an orienting + README (e.g. `/`, `.machine_readable/`, `ffi/zig/src/`). + +=== Dogfooding / home-stable evidence + +Cite STATE.a2ml `[dogfooding-status]`. For each line item: +- WHAT was dogfooded (capability), +- HOW (concrete use-path), +- WHEN (date completed), +- WHERE (link to commit / artefact / deployment). + +- <> — <>. DONE / IN-PROGRESS / PLANNED. +- <> — … +- <> — … +- LIVE deployment (if any): URL, health signal, last verified. + +=== External validation (Grade B / A) + +Distinguish carefully — v2.0 B requires **real external users with feedback**, +not catalogue listings. Populate the canonical STATE.a2ml sections named +below; internal-capability items belong under `[grade-b-status]` (legacy) or +a roadmap section, *not* under `[external-targets]`. + +- STATE.a2ml `[external-targets]` — at least 6 diverse entries for B. + Target identity + date + outcome for each. If absent, grade is capped at C. +- STATE.a2ml `[issues-fed-back]` — closed-issue trail with external-reporter + label for B. +- STATE.a2ml `[field-signal]` — multi-source external confirmation for A. +- Awesome-list PRs merged (N). *Catalogue listings, not dogfooders.* +- Directory/registry listings (MCP directory, Glama, etc.) with automated + assessment badges — *third-party automated assessment, not external usage*. +- Seeded/reference nodes: configured but not yet run by third parties. + +NOTE: Legacy `[grade-b-status]` sections (pre-2026-04-18) often mix internal +capabilities with external targets. During audit, re-classify each entry +and either move it to `[external-targets]` or drop it. See the boj-server +audit for a worked example (six items all re-classified as internal). + +== Grade x Evidence Matrix + +[cols="1,1,3,3"] +|=== +| Grade | Met? | Evidence supporting | Evidence against / gaps + +| X +| n/a +| Tests exist -> not X +| — + +| F +| n/a +| Not superseded; no harm signal +| — + +| E +| ✓/✗ +| ... +| ... + +| D +| ✓/✗ +| All RSR structural requirements met; N tests; scope documented in STATE.a2ml, ROADMAP.adoc +| ... + +| C +| ✓/✗ +| Home-stable; CI green; core dogfood demonstrated across N capability lines; deep annotation present for core and architecture docs. +| Per-<> README coverage M/N. Per-directory orientation gaps. READINESS.md schema version. Dogfood-sweep log. + +| B +| ✓/✗ +| — +| N external targets (need 6+). External users. Issues-fed-back pipeline. + +| A +| ✓/✗ +| — +| Field signal of external confirmation. +|=== + +== Verdict + +*Current CRG grade: **<> (<>)**.* + +State how this matches / diverges from the self-declared grade. v2.0 +strictness clause: does stricter evidence standard change the outcome? + +The grade is earned, not conservative-by-default — list the three strongest +*positive* signals: + +1. <> +2. <> +3. <> + +What blocks *immediate* promotion to <>: + +1. <> +2. <> +3. <> + +What blocks promotion to the grade after that: + +1. <> +2. <> + +== Notes on Non-Negotiables Respected by this Audit + +List irreducible / intentional exceptions so future audits don't retread +them. Examples: + +- Documented axiomatic `believe_me` sites at `` are backend primitives, + not proof debt. +- Proof closures declared "done" (e.g. credential-isolation modules) are not + re-audited here. +- Standing rules (e.g. januskey, private-repo exceptions) are respected. + +== Related Files + +- `docs/READINESS.md` — per-component table. Note v1.0-vs-v2.0 schema alignment. +- `.machine_readable/descriptiles/STATE.a2ml` — authoritative self-declared state. +- `docs/governance/CRG-LIFT-PLAN-2026-09-26.adoc` — companion plan for + D→C (and medium-term C→B) lifts. +- `docs/governance/CRG-CRITERIA.adoc` — boilerplate criteria doc. + +_End of audit._ diff --git a/docs/governance/CRG-CRITERIA.adoc b/docs/governance/CRG-CRITERIA.adoc index f8264e6..e37dce1 100644 --- a/docs/governance/CRG-CRITERIA.adoc +++ b/docs/governance/CRG-CRITERIA.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Component Readiness Grades (CRG) Criteria :toc: preamble :icons: font diff --git a/docs/governance/MAINTENANCE-CHECKLIST.a2ml b/docs/governance/MAINTENANCE-CHECKLIST.a2ml deleted file mode 100644 index b41a32e..0000000 --- a/docs/governance/MAINTENANCE-CHECKLIST.a2ml +++ /dev/null @@ -1,160 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Cross-repo maintenance baseline (machine-readable canonical) - -[metadata] -name = "maintenance-checklist" -version = "1.1.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/maintenance/MAINTENANCE-CHECKLIST.adoc" -companion-human = "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc" -companion-machine = ".machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml" - -[policy] -single-source = true -notes = "Use this file as canonical machine policy and keep markdown synchronized." - -[maintenance-axes] -scoping-first = true -execution-order = ["scoping", "axis-1", "axis-2", "axis-3"] -axis-1 = "must > intend > like" -axis-2 = "corrective > adaptive > perfective" -axis-3 = "systems > compliance > effects" - -[scoping] -inputs_required = [ - "README", - "roadmap", - "status-docs", - "maintenance-checklist", - "ci-and-security-docs", -] - -marker_scan_required = [ - "TODO", - "FIXME", - "XXX", - "HACK", - "STUB", - "PARTIAL", -] - -idris_unsound_scan_required = [ - "believe_me", - "assert_total", -] - -scope_assembly_buckets = ["must", "intend", "like"] - -[axis-2-maintenance-rules] -corrective-first = true -adaptive-second = true -adaptive_examples = [ - "scope-change reconciliation", - "stale-reference removal", - "obsolete-work culling", -] -perfective-third = true -perfective_source = "axis-1 honest state after corrective/adaptive updates" - -[axis-3-audit-rules] -systems-check = true -documentation-honesty-check = true -safety-security-accounted-check = true -effects-review-check = true -benchmark-evidence-required = true -maintainer-dialogue-review-required = true -compliance-seams-check = true -exception-register-required = true -exception-bounded-scope-required = true -policy-drift-contamination-check = true -example-drift-risk = "single TypeScript exception causing broad AffineScript->TypeScript migration" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[generic-cleanup-finish-off] -root-cleanup-required = true -stale-work-cull-required = true -docs-parity-required = true -machine-human-sync-required = true -compliance-finish-off-required = true -effects-finish-off-required = true -release-prep-summary-required = true -next-actions-required = ["corrective", "adaptive", "perfective"] - -[must] -root_control_files = [ - ".gitignore", - ".gitattributes", - ".editorconfig", - ".tool-versions", - "Containerfile", - "Justfile", -] - -root_hosting_files = [ - "CNAME", - ".nojekyll", -] - -ownership_files = [ - "MAINTAINER", - ".github/CODEOWNERS", -] - -machine_readable_required = [ - ".machine_readable/anchors/ANCHOR.a2ml", - ".machine_readable/contractiles/", - ".machine_readable/ai/", - ".machine_readable/bot_directives/", -] - -contractiles_required = [ - "Mustfile", - "Trustfile", - "Intentfile", -] - -security_required = [ - ".well-known/security.txt", - "ci-security-scan", -] - -quality_gate_required = [ - "format", - "lint", - "unit-tests", - "integration-tests", - "p2p-tests", - "e2e-tests", - "bench-smoke", - "docs-check", - "security-scan", -] - -abi_ffi_policy = [ - "ABI Idris2 in src/interface/abi/*.idr", - "FFI Zig in ffi/**/*.zig", -] - -[should] -docs_primary_format = "adoc" -docs_structure = [ - "docs/theory", - "docs/practice", - "docs/whitepapers/academic", - "docs/whitepapers/industry", - "docs/proofs", - "docs/reports", -] - -root_minimization = true -well_known_metadata = true -roadmap_honesty_with_dates = true -ci_doc_format_policy = true - -[could] -generate_human_from_machine = true -mode_aware_bots = ["corrective", "adaptive", "perfective", "audit"] -topology_dashboard = true -exception_registry = true diff --git a/docs/governance/MAINTENANCE-CHECKLIST.adoc b/docs/governance/MAINTENANCE-CHECKLIST.adoc index bcb3af5..48149d8 100644 --- a/docs/governance/MAINTENANCE-CHECKLIST.adoc +++ b/docs/governance/MAINTENANCE-CHECKLIST.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Maintenance Checklist # Maintenance Checklist (Cross-Repo) @@ -50,7 +52,7 @@ Before Axis 2/3 execution, assemble a scoped worklist from evidence: - [ ] Audit compliance seams/compromises explicitly: - policy exceptions are recorded with rationale, scope, and expiry/review - exception does not silently broaden into general policy drift - - language-policy contamination checks run (example: a single TS exception must not trigger broad TypeScript conversion) + - language-policy contamination checks run (example: a single TS exception must not trigger broad conversion) - run `panic-attack` as the compliance-audit scanner - run ecological checking under effects (using sustainabot guidance as current baseline) @@ -91,20 +93,20 @@ Run this pass at the end of a corrective/adaptive/perfective cycle: - [ ] Keep machine-readable canonical structure under `.machine_readable/`: - state/meta/ecosystem files (`*.a2ml` or repo standard) - `anchors/ANCHOR.a2ml` - - `contractiles/` (`must`, `trust`, `lust`, and related) + - `contractiles/` (`must`, `trust`, `intend`, and related) - `ai/` for AI guidance files - `bot_directives/` for bot control files - [ ] Keep contractiles/invariants present and wired: - root `Mustfile` (or equivalent) with enforceable checks - `Trustfile` and `Intentfile` present - [ ] Keep security metadata present: - - `.well-known/security.txt` and relevant policy metadata + - `www/.well-known/security.txt` and relevant policy metadata - CI security scanning configured and runnable - [ ] Keep docs and navigation coherent: - single navigation entry point in root (`NAVIGATION.adoc` or equivalent) - no duplicate conflicting docs for same purpose (for example both `.md` and `.adoc` in root unless intentionally required) - [ ] Enforce ABI/FFI purity where the policy applies: - - ABI definitions in Idris2 (`src/abi/*.idr`) + - ABI definitions in Idris2 (`src/interface/Abi/*.idr`) - FFI implementations in Zig (`ffi/**/*.zig`) - [ ] Ensure quality gate includes: formatting, lint, unit/integration tests, p2p/e2e checks, benchmark smoke, docs checks, security scan. @@ -115,7 +117,7 @@ Run this pass at the end of a corrective/adaptive/perfective cycle: - `docs/` (theory/practice/whitepapers/proofs/reports) - `tests/` (fixtures/outputs) - `docs/legal/` (while retaining root `LICENSE` when forge detection needs it) -- [ ] Maintain `.well-known/` for public metadata where applicable (`security.txt`, `humans.txt`, `ads.txt` mirrors if used). +- [ ] Maintain `www/.well-known/` for public metadata where applicable (`security.txt`, `humans.txt`, `ads.txt` mirrors if used); a root `.well-known/` is legacy (issue #53). - [ ] Keep CI policy checks for doc-format conventions and canonical file placement. - [ ] Keep roadmap/status docs honest with dated evidence. @@ -140,7 +142,7 @@ Do **not** move the following out of root if you want default tool behavior: Use the helper script first, then use the checklist for deeper/manual follow-up. Script locations: -- `/var$REPOS_DIR/run-maintenance.sh` +- `${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/run-maintenance.sh` - `~/Desktop/run-maintenance.sh` ```bash @@ -259,7 +261,7 @@ find . -type f \( -name '*.idr' -o -name '*.idris2' -o -name '*.zig' \) - [ ] Re-run until acceptable. ```bash -PANIC_BIN="/var$REPOS_DIR/panic-attacker/target/release/panic-attack" +PANIC_BIN="${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/panic-attacker/target/release/panic-attack" "$PANIC_BIN" assail "$REPO" --output /tmp/assail.json --output-format json --quiet jq -r '.weak_points | length' /tmp/assail.json jq -r '.weak_points[] | "\(.severity)|\(.location)|\(.description)"' /tmp/assail.json @@ -410,7 +412,7 @@ Add these checks for language/compiler repositories with formal ABI/FFI constrai - [x] README structure restored (index/TOC, audience paths, quickstart sanity). - [x] Wiki split by audience (laypeople/users/developers) and linked from docs index. - [x] Root-level clutter reduced (archive, analysis, reports relegated to `docs/` subtrees). -- [x] Machine-readable docs synchronized (`STATE.scm`, `META.scm`, `ECOSYSTEM.scm`, contractiles). +- [x] Machine-readable docs synchronized (`STATE.a2ml`, `META.a2ml`, `ECOSYSTEM.a2ml`, contractiles). - [x] Human-readable docs synchronized (`README`, `QUICK_STATUS`, roadmap, wiki home). - [x] `Mustfile` invariants present and enforceable in CI. - [x] `Trustfile` and `Intentfile` present and complete. @@ -550,20 +552,20 @@ Fail closed: if evidence is missing for any checklist item, treat that item as N For centralized coverage across existing and new repos: ```bash -cd /var$REPOS_DIR/gitbot-fleet +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet just enroll-repos ``` Optional directive write-back to repos that already have `.machine_readable/`: ```bash -cd /var$REPOS_DIR/gitbot-fleet +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet just enroll-repos /var$REPOS_DIR true ``` Release hard gate from fleet: ```bash -cd /var$REPOS_DIR/gitbot-fleet +cd ${REPOS_ROOT:-~/Documents/hyperpolymath-repos}/gitbot-fleet just maintenance-hard-pass /absolute/path/to/repo ``` diff --git a/docs/governance/README.adoc b/docs/governance/README.adoc index 114ee94..3031484 100644 --- a/docs/governance/README.adoc +++ b/docs/governance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Governance Pillar (TSDM) diff --git a/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.a2ml b/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.a2ml deleted file mode 100644 index 093573a..0000000 --- a/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.a2ml +++ /dev/null @@ -1,53 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# General software development approach (machine-readable) - -[metadata] -version = "1.0.0" -last-updated = "2026-02-24" -scope = "cross-repo" -source-human = "docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc" - -[execution] -order = ["axis-1", "axis-2", "axis-3"] - -[axis-1] -name = "scope" -priority = "must > intend > like" -inputs = [ - "README", - "roadmap", - "status-docs", - "ci-and-security-docs", -] -marker-scan = ["TODO", "FIXME", "XXX", "HACK", "STUB", "PARTIAL"] -idris-unsound-scan = ["believe_me", "assert_total"] -output = "scoped-work-assembly" - -[axis-2] -name = "maintenance" -priority = "corrective > adaptive > perfective" -corrective = "defect/regression/safety/security fixes" -adaptive = "scope reconciliation, stale-reference removal, obsolete-work culling" -perfective = "quality improvements derived from axis-1 honest state" - -[axis-3] -name = "audit" -priority = "systems > compliance > effects" -systems = "required systems present and operating" -compliance = "exceptions explicit, bounded, and drift-resistant" -effects = "benchmark/operational impact evidence captured and reviewed" -compliance-tooling = "panic-attack" -effects-tooling = "ecological checking with sustainabot guidance" - -[cleanup-finish-off] -root-cleanup = true -stale-work-cull = true -docs-sync-human-machine = true -compliance-audit = true -effects-audit = true -release-summary = ["must", "should", "could"] -next-actions = ["corrective", "adaptive", "perfective"] - -[collaboration] -maintainer-dialogue-required = true -dialogue-topics = ["what changed", "why", "remaining risks"] diff --git a/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc b/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc index e8805c6..7c43d56 100644 --- a/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc +++ b/docs/governance/SOFTWARE-DEVELOPMENT-APPROACH.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Software Development Approach (General) :toc: left :toclevels: 2 diff --git a/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc b/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc new file mode 100644 index 0000000..163a07e --- /dev/null +++ b/docs/governance/TEMPLATE-LINEAGE-AUDIT.adoc @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell += Template Lineage Audit — self-identity drift in minted repositories +:toc: macro +:toclevels: 3 +:sectnums: + +*Measured 2026-09-15 against the live estate.* This document records what the +template's self-identity repair passes do, what they provably did *not* do to +repositories minted before they existed, and — just as importantly — which of +the measurements behind those claims are reliable and which are not. + +This file is deliberately placed in `docs/governance/` alongside +`TEMPLATE-STANDARDS-AUDIT.adoc`, and is deliberately added to the self-name +pass's skip list in `build/just/repo-init.just`. A document *about* the +literal `rsr-template-repo` must not have that literal rewritten inside it +when a child is minted, or every minted copy becomes nonsense. + +toc::[] + +== Summary + +Two mint-time repair passes in `build/just/repo-init.just` exist to stop a +freshly minted repository from declaring that it *is* the template. Both work. +Neither has ever been applied retroactively, so repositories minted before the +passes landed still carry the template's identity. + +The live, directly verified population is *small* — eight repositories, in two +overlapping fault classes — and is a *floor*, not a total. An earlier estimate +of seventeen affected repositories was produced by trusting a code-search +result and is *wrong*; see <>. + +== Root cause: a literal that cannot be a token + +The template's own comment states the mechanism, and it is worth quoting +because it is more precise than any paraphrase: + +[quote, 'build/just/repo-init.just, Self-name pass (ADR-0003)'] +____ +The template's own name is written as a LITERAL, not as a placeholder token, +so the substitution loop above never touched it. Measured on a scratch mint of +346ae56: a repo minted as `mint-check-lib` still carried +`project := "rsr-template-repo"`, `REPO := "rsr-template-repo"`, +`sonar.projectKey=hyperpolymath_rsr-template-repo`, +`STATE.a2ml / ECOSYSTEM.a2ml project = "rsr-template-repo"` … i.e. it declared +that it WAS the template, and would have reported its code analysis into the +TEMPLATE's SonarCloud project. +____ + +The ordinary mint path rewrites `hyperpolymath`, `dicta-task` and friends. Those +tokens *are* consumed widely — 56 files in this repository, including the +README badge block, `CITATION.cff`, `.github/CODEOWNERS`, +`.github/settings.yml` and the three AI-assistant rule files. The problem is +confined to files that cannot carry a `{{...}}` token, which is why it looks +arbitrary from the outside. + +== The two repair passes, and the exact limit of each + +Both live in `build/just/repo-init.just` and both run *only at mint time*. + +=== Self-name pass + +Guarded by `if [ "$REPO" != "rsr-template-repo" ]`. Rewrites the template +literal to the child's name across the tree, with two protections: + +* Paths that are *about* the template are skipped wholesale — `.git`, + `build/just/repo-init.just` itself, `docs/decisions/*`, + `.machine_readable/descriptiles/{CLADE,VARIANT}.a2ml`, `CHANGELOG.md`, + `TEMPLATE-STANDARDS-AUDIT.adoc`, and binary extensions. +* Within every other file, any line naming the parent *as a parent* survives + byte-for-byte, matched by + `instantiated-from|parent|upstream|chain =|lineage|minted from|created from|Template: `. + +It uses `sed -i` deliberately — the comment records why: ``sed -i` preserves +the mode bit; rewriting via a temp file would not.` + +=== Self-OWNER pass + +Guarded by `if [ "$OWNER" != "hyperpolymath" ]`, and *deliberately narrow* — +exactly three lines: the `Justfile` `OWNER :=` declaration, +`sonar.organization` and `sonar.projectKey`. The reasoning is sound and should +not be "improved" into a blanket rewrite: + +[quote, 'build/just/repo-init.just'] +____ +A blanket `hyperpolymath` -> ${OWNER} rewrite would be WRONG: +`hyperpolymath/standards`, `hyperpolymath/proven` and the other estate +dependencies are real repos every child genuinely points at. +____ + +== Measured: what is actually wrong in the estate today + +All rows below were read *directly* from each repository's +`sonar-project.properties` via the contents API, not inferred from search. + +=== Class 1 — self-name pass never applied (4 repositories) + +These declare the template's project key, so their analyses are configured to +land in the template's own SonarCloud project. + +[cols="2,3", options="header"] +|=== +| Repository | `sonar.projectKey` +| `hyperpolymath/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` +| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` +|=== + +NOTE: "configured to report into the template's project" is what has been +measured. No analysis has been *observed* landing there; the SonarCloud side +was not inspected. + +=== Class 2 — self-OWNER pass never applied (7 repositories) + +`metadatastician`-owned repositories carrying `sonar.organization=hyperpolymath`. + +[cols="2,3", options="header"] +|=== +| Repository | `sonar.projectKey` +| `metadatastician/cadastra` | `hyperpolymath_cadastra` +| `metadatastician/chronicles-of-slavia` | `hyperpolymath_chronicles-of-slavia` +| `metadatastician/harvard-dehallucinator`| `hyperpolymath_harvard-dehallucinator` +| `metadatastician/IDApTIK` | `hyperpolymath_IDApTIK` +| `metadatastician/first-post` | `hyperpolymath_rsr-template-repo` +| `metadatastician/_pathroot` | `hyperpolymath_rsr-template-repo` +| `metadatastician/sim-public-relations` | `hyperpolymath_rsr-template-repo` +|=== + +The last three are also Class 1. The union is *eight distinct repositories*: +one Class-1-only, four Class-2-only, three in both. + +*Negative control:* `metadatastician/pong-ping` reads +`sonar.organization=metadatastician` / `sonar.projectKey=metadatastician_pong-ping` +— correct on both axes, and correctly absent from both tables. + +[[method]] +== Method, and three ways the measurement lied + +This section exists because the first version of this audit was wrong, and the +way it was wrong is reusable. + +=== Failure 1 — a false zero on punctuation + +`gh search code --owner hyperpolymath --owner metadatastician 'REPO := "rsr-template-repo"' --filename Justfile` +returns *zero repositories*. That string had already been read directly at +`rsr-julia-library-template-repo/Justfile:24`. GitHub code search does not do +exact substring matching across `:=`, spaces and quotes. + +⇒ *The `Justfile`-level population is unmeasured, not zero.* No count of it +appears in this document. + +=== Failure 2 — false positives from the recipe's own comment + +Searching for `sonar.projectKey=hyperpolymath_rsr-template-repo` returned 18 +repositories. Most matched in `build/just/repo-init.just` — the mint recipe, +which quotes that exact key *inside the comment documenting this defect*, and +which every minted child carries. Only five matched in an actual +`sonar-project.properties`, and one of those is the template itself. + +Two repositories the search listed (`metadatastician/pong-ping`, +`metadatastician/enaction-engine`) have entirely correct keys today. + +⇒ *A search hit is a hit on a byte sequence, not on a fact.* Every row in the +tables above was re-read from the file that would actually be consumed. + +=== Failure 3 — counts that are not defect counts + +A case-insensitive search for banned runtimes (`rescript`, `deno`, +`typescript`) in `rsr-julia-library-template-repo` returns 39 files; with word +boundaries, 38. *This is not a defect count.* At least 17 of those files +contain ban/forbid language — they name the runtime precisely in order to +prohibit it, `.github/workflows/runtime-policy.yml` most obviously. Files that +look like genuine *use* rather than prohibition, and so need individual +judgement, are `examples/web-project-deno.json`, `mise.toml`, +`container/compose.yaml` and `.github/workflows/release.yml`. + +⇒ Recorded here as a pointer for a human, deliberately without a headline +number. + +== Related findings + +=== `rsr-julia-library-template-repo` misreports its own name + +Both `Justfile:24` and `.machine_readable/contractiles/Justfile:24` declare +`REPO := "rsr-template-repo"`, while +`.machine_readable/descriptiles/CLADE.a2ml:9` correctly says +`canonical-name = "rsr-julia-library-template-repo"`. `OWNER :=` is correct in +both files. + +*Severity is lower than it appears.* Across every `Justfile` and `*.just` in +that repository there are *zero* `hyperpolymath` or `dicta-task` interpolations, so +neither variable is consumed by any recipe. This is a wrong self-description +with no runtime effect — worth fixing because a template propagates its own +mistakes, not because anything currently misbehaves. + +=== Layout migration lag, not divergence + +This template used `machine-readable/`; `rsr-julia-library-template-repo` and +much of the estate use `.machine_readable/`. Commit `a983c00` ("chore(shape): +remake the repository layout for human legibility", #43, 2026-08-26, 219 files) +renamed `.machine_readable/` to `machine-readable/` here, on a legibility +argument. That rename was **reversed on 2026-09-17 by owner ruling**: dotted is +the standard everywhere. The direction of "lag" therefore inverted — the +repositories still on the hyphenated spelling are now the ones that have not +followed, and `scripts/check-root-shape.sh` continues to resolve both spellings +so those repositories keep working rather than exiting 2. + +Estate floors at the time of the 2026-08 rename, from code search and therefore +subject to <>: 48 repositories on `.machine_readable/`, 9 on +`machine-readable/`. The majority was already dotted when this template +diverged from it. + +== What this document deliberately does not do + +* *It changes no other repository.* Rewriting `sonar-project.properties` + across eight repositories in two organizations is a fleet action, and fleet + campaigns are parked by standing decision. The tables above are the work + order if and when that is unparked. +* *It does not touch `.a2ml` manifests, `.deed` grammar or `k9` contracts.* +* *It does not widen the self-OWNER pass*, for the reason the recipe already + gives. +* *It does not claim the passes are buggy.* They are correct; they are simply + mint-time-only, and nothing has ever backfilled. + +== Provenance + +Measured 2026-09-15 against the live GitHub estate by direct contents-API +reads, with a stated negative control (`metadatastician/pong-ping`) and three +recorded search failures. Template state as of `cc76f4e`. diff --git a/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc b/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc new file mode 100644 index 0000000..fcfba12 --- /dev/null +++ b/docs/governance/TEMPLATE-STANDARDS-AUDIT.adoc @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Template Standards Audit +Codex +v1.0, 2026-04-07 +:toc: +:toclevels: 3 +:sectnums: + +== Scope And Inventory + +Audit scope: + +* Repository: `rsr-template-repo` +* Focus: root authority docs/manifests, Justfile integration, session bindings +* Recursive inventory method: `rg --files -g '!.git' | sort` + +Inventory snapshot at audit time: + +* `rsr-template-repo` total tracked files discovered: `240` +* Session-local binding files discovered: `4` under `session/` plus `coordination.k9` + +== Claim Vs Actual + +[cols="2,2,3,1,3,3",options="header"] +|=== +| Claimed item | Claimed by | Expected path | Actual status | Notes | Recommended action + +| Placeholder badge tokens in README +| `README.adoc` +| placeholder +| Template placeholders are intentional pre-bootstrap content. +| Keep; document clearly as template tokens. + +| Docs links used `.adoc` files not present +| legacy `README.adoc` (pre-migration) +| `CONTRIBUTING.adoc`, `GOVERNANCE.adoc`, `SECURITY.adoc` +| outdated +| Actual files are `CONTRIBUTING.md`, no root `GOVERNANCE.adoc`, `SECURITY.md`. +| Fixed README links to existing files. + +| ABI path lower-case only +| legacy `README.adoc` + legacy checks +| `src/interface/abi/*.idr` +| outdated +| Tree previously shipped `src/interface/Abi/*.idr` (uppercase A); now renamed. +| Renamed to canonical lowercase `src/interface/abi/*.idr` (matches `validate-template.sh` + gossamer). Checks remain tolerant of either case. + +| Root manifest structure with non-existent files +| legacy `0-AI-MANIFEST.a2ml` (pre-migration) +| `GOVERNANCE.adoc`, several strict root assumptions +| outdated +| Manifest claims did not match actual template contents. +| Replaced with accurate authority split + startup checklist. + +| Source-human references maintenance/practice docs +| legacy Justfile + policy A2ML (pre-migration) +| `docs/maintenance/...`, `docs/practice/SOFTWARE-DEVELOPMENT-APPROACH.adoc` +| outdated +| Those paths did not exist in template. +| Updated to `docs/governance/...` paths. + +| Session-management local binding files +| target architecture +| `session/README.md`, `session/custom-checks.k9`, `session/local-hooks.sh`, `coordination.k9` +| exists +| Added as thin integration layer without protocol duplication. +| Keep. + +| Canonical command mapping in local automation +| target architecture +| `Justfile` session aliases + `session/dispatch.sh` +| exists +| All canonical commands map to dispatcher. +| Keep. + +| Central protocols are authoritative +| `README.adoc`, `0-AI-MANIFEST.a2ml`, `AUDIT.adoc` +| `standards/3-practice/session-management-standards/` +| exists +| Local docs now explicitly defer protocol authority to standards repo. +| Keep. + +| Runtime session artifacts stay per-repo +| `session/README.md`, `session/dispatch.sh` +| `.session/` in target repo path +| exists +| Dispatcher records canonical commands into runtime `.session/` files. +| Keep runtime artifacts out of authoritative standards docs. + +| Local policy hooks remain local +| `session/local-hooks.sh`, `session/custom-checks.k9` +| local session binding layer +| exists +| Policy/hook logic separated from central protocol definitions. +| Keep local-only. +|=== + +== Classification + +=== Authoritative Shared Standard + +* External to this repo: `standards/3-practice/session-management-standards/*` + +=== Repo-Local Binding/Integration + +* `Justfile` canonical session aliases +* `session/dispatch.sh` +* `session/custom-checks.k9` +* `session/local-hooks.sh` +* `session/README.md` +* `coordination.k9` +* `AUDIT.adoc` (local gate summary) + +=== Generated Runtime Artifact + +* `.session/*` outputs created by local dispatcher per repository path + +=== Obsolete/Duplicate/Drifted + +* Legacy path assumptions (`docs/maintenance/*`, `docs/practice/*` for governance policy references) +* Legacy root-doc claims to files not present in this template +* Legacy lower-case-only ABI path references + +== Move/Stay/Delete Guidance + +=== Move Into `standards` + +* Any future full protocol text drafts should move to + `standards/3-practice/session-management-standards/`. + +=== Stay In `rsr-template-repo` + +* Local aliases, hooks, coordination wiring, and repo-local checks. + +=== Delete/Archive + +* Archive or remove legacy references to non-existent docs/paths if reintroduced. +* Avoid reintroducing full duplicated protocol definitions. + +== Missing-But-Expected Session Files (Template Repo) + +Template repo expected only thin local integration files. + +Current status: + +* No mandatory thin-binding files are missing. +* Full protocol directories/files are intentionally absent here by design. + +== Proposed Final Directory Map + +[source,text] +---- +rsr-template-repo/ + README.adoc + AUDIT.adoc + 0-AI-MANIFEST.a2ml + EXPLAINME.adoc + Justfile + coordination.k9 + session/ + README.md + dispatch.sh + custom-checks.k9 + local-hooks.sh + docs/ + ... (repo-local human docs) + .machine_readable/ + ... (repo-local machine-readable policy/state) +---- + +== Maintenance Model Note + +* Protocols central: maintained in `standards/3-practice/session-management-standards/`. +* Policy local: maintained in template/downstream repos (`session/*.k9`, hooks, + coordination bindings). +* State per-repo: generated during execution (`.session/*`) in the active + working repository. diff --git a/docs/governance/TSDM.adoc b/docs/governance/TSDM.adoc index cbd582c..42899ec 100644 --- a/docs/governance/TSDM.adoc +++ b/docs/governance/TSDM.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Triaxial Software Development Methodology (TSDM) :toc: preamble :icons: font diff --git a/docs/governance/audit/0.2-AI-MANIFEST.a2ml b/docs/governance/audit/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 4722486..0000000 --- a/docs/governance/audit/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-axis-audit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM Audit track. diff --git a/docs/governance/audit/README.adoc b/docs/governance/audit/README.adoc index fac3740..a0b0b25 100644 --- a/docs/governance/audit/README.adoc +++ b/docs/governance/audit/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Audit Axis diff --git a/docs/governance/audit/compliance/0.3-AI-MANIFEST.a2ml b/docs/governance/audit/compliance/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index b13ec69..0000000 --- a/docs/governance/audit/compliance/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-compliance" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM compliance unit within the Audit axis. diff --git a/docs/governance/audit/compliance/README.adoc b/docs/governance/audit/compliance/README.adoc index 876954f..aa01a9a 100644 --- a/docs/governance/audit/compliance/README.adoc +++ b/docs/governance/audit/compliance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Compliance Unit diff --git a/docs/governance/audit/effects/0.3-AI-MANIFEST.a2ml b/docs/governance/audit/effects/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 0bccae0..0000000 --- a/docs/governance/audit/effects/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-effects" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM effects unit within the Audit axis. diff --git a/docs/governance/audit/effects/README.adoc b/docs/governance/audit/effects/README.adoc index 3634799..f34e583 100644 --- a/docs/governance/audit/effects/README.adoc +++ b/docs/governance/audit/effects/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Effects Unit diff --git a/docs/governance/audit/systems/0.3-AI-MANIFEST.a2ml b/docs/governance/audit/systems/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index f97bc9c..0000000 --- a/docs/governance/audit/systems/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-systems" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM systems unit within the Audit axis. diff --git a/docs/governance/audit/systems/README.adoc b/docs/governance/audit/systems/README.adoc index 8d179b4..70ef18c 100644 --- a/docs/governance/audit/systems/README.adoc +++ b/docs/governance/audit/systems/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Systems Unit diff --git a/docs/governance/maintenance/0.2-AI-MANIFEST.a2ml b/docs/governance/maintenance/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 8e0dff5..0000000 --- a/docs/governance/maintenance/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-axis-maintenance" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM Maintenance track. diff --git a/docs/governance/maintenance/README.adoc b/docs/governance/maintenance/README.adoc index 0ed2f1b..2cb875f 100644 --- a/docs/governance/maintenance/README.adoc +++ b/docs/governance/maintenance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Maintenance Axis diff --git a/docs/governance/maintenance/adaptive/0.3-AI-MANIFEST.a2ml b/docs/governance/maintenance/adaptive/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 63d1a99..0000000 --- a/docs/governance/maintenance/adaptive/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-adaptive" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM adaptive unit within the Maintenance axis. diff --git a/docs/governance/maintenance/adaptive/README.adoc b/docs/governance/maintenance/adaptive/README.adoc index 7b60992..ea4269e 100644 --- a/docs/governance/maintenance/adaptive/README.adoc +++ b/docs/governance/maintenance/adaptive/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Adaptive Unit diff --git a/docs/governance/maintenance/corrective/0.3-AI-MANIFEST.a2ml b/docs/governance/maintenance/corrective/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 05cb89d..0000000 --- a/docs/governance/maintenance/corrective/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-corrective" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM corrective unit within the Maintenance axis. diff --git a/docs/governance/maintenance/corrective/README.adoc b/docs/governance/maintenance/corrective/README.adoc index ed904a8..7a04598 100644 --- a/docs/governance/maintenance/corrective/README.adoc +++ b/docs/governance/maintenance/corrective/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Corrective Unit diff --git a/docs/governance/maintenance/perfective/0.3-AI-MANIFEST.a2ml b/docs/governance/maintenance/perfective/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 832762f..0000000 --- a/docs/governance/maintenance/perfective/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-perfective" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM perfective unit within the Maintenance axis. diff --git a/docs/governance/maintenance/perfective/README.adoc b/docs/governance/maintenance/perfective/README.adoc index 8759d74..1bf8f42 100644 --- a/docs/governance/maintenance/perfective/README.adoc +++ b/docs/governance/maintenance/perfective/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Perfective Unit diff --git a/docs/governance/planning/0.2-AI-MANIFEST.a2ml b/docs/governance/planning/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 80339e7..0000000 --- a/docs/governance/planning/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-axis-planning" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM Planning track. diff --git a/docs/governance/planning/README.adoc b/docs/governance/planning/README.adoc index 62aa375..6764604 100644 --- a/docs/governance/planning/README.adoc +++ b/docs/governance/planning/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Planning Axis diff --git a/docs/governance/planning/could/0.3-AI-MANIFEST.a2ml b/docs/governance/planning/could/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index fc17a27..0000000 --- a/docs/governance/planning/could/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-could" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM could unit within the Planning axis. diff --git a/docs/governance/planning/could/README.adoc b/docs/governance/planning/could/README.adoc index ad5a6b8..06863c8 100644 --- a/docs/governance/planning/could/README.adoc +++ b/docs/governance/planning/could/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Could Unit diff --git a/docs/governance/planning/must/0.3-AI-MANIFEST.a2ml b/docs/governance/planning/must/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 0987dae..0000000 --- a/docs/governance/planning/must/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-must" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM must unit within the Planning axis. diff --git a/docs/governance/planning/must/README.adoc b/docs/governance/planning/must/README.adoc index 47eb46d..990c563 100644 --- a/docs/governance/planning/must/README.adoc +++ b/docs/governance/planning/must/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Must Unit diff --git a/docs/governance/planning/should/0.3-AI-MANIFEST.a2ml b/docs/governance/planning/should/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index f492289..0000000 --- a/docs/governance/planning/should/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "governance-unit-should" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - TSDM should unit within the Planning axis. diff --git a/docs/governance/planning/should/README.adoc b/docs/governance/planning/should/README.adoc index 605489c..8c9ea24 100644 --- a/docs/governance/planning/should/README.adoc +++ b/docs/governance/planning/should/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Should Unit diff --git a/docs/legal/0.2-AI-MANIFEST.a2ml b/docs/legal/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index e547798..0000000 --- a/docs/legal/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "legal-track" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-unit for legal and licensing documentation. Contains framework - exhibits and archival license texts. - -canonical_locations: - exhibits: "exhibits/" - texts: "texts/" diff --git a/docs/legal/EXHIBIT-A-ETHICAL-USE.txt b/docs/legal/EXHIBIT-A-ETHICAL-USE.txt new file mode 100644 index 0000000..b873155 --- /dev/null +++ b/docs/legal/EXHIBIT-A-ETHICAL-USE.txt @@ -0,0 +1,20 @@ +SPDX-License-Identifier: MPL-2.0 + +================================================================================ +EXHIBIT A — SOURCE CODE FORM LICENSE NOTICE +Mozilla Public License Version 2.0 +================================================================================ + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file) where +a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +================================================================================ +END OF EXHIBIT A +================================================================================ diff --git a/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt b/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt new file mode 100644 index 0000000..81f9288 --- /dev/null +++ b/docs/legal/EXHIBIT-B-QUANTUM-SAFE.txt @@ -0,0 +1,104 @@ +SPDX-License-Identifier: MPL-2.0 + +================================================================================ +QUANTUM-SAFE PROVENANCE SPECIFICATION +Rhodium Standard Repository (RSR) — Exhibit B +================================================================================ + +1. PURPOSE + + This exhibit specifies the cryptographic algorithms and procedures for + quantum-safe provenance of contributions in an RSR-compliant repository. + It is a standalone, licence-agnostic specification: it imposes no licence + terms of its own and may be referenced by software under any licence + (this repository is MPL-2.0 for code, CC-BY-SA-4.0 for prose). + +2. APPROVED ALGORITHMS + + The following post-quantum cryptographic algorithms are approved for + signing Provenance Metadata: + + 2.1. Digital Signatures + - ML-DSA (FIPS 204, formerly CRYSTALS-Dilithium) + Recommended: ML-DSA-65 (security level 3) or ML-DSA-87 (level 5) + - SLH-DSA (FIPS 205, formerly SPHINCS+) + Recommended: SLH-DSA-SHA2-256f or SLH-DSA-SHAKE-256f + - FALCON (NIST Round 3 finalist) + Recommended: FALCON-1024 + + 2.2. Key Encapsulation (for encrypted provenance) + - ML-KEM (FIPS 203, formerly CRYSTALS-Kyber) + Recommended: ML-KEM-1024 + + 2.3. Hash Functions + - SHA-3 (FIPS 202) + Recommended: SHA3-256 or SHA3-512 + - SHAKE (FIPS 202 extendable output) + Recommended: SHAKE-256 + + 2.4. Key Derivation + - Argon2id (RFC 9106) + Parameters: t=3, m=65536, p=4 (minimum) + +3. PROVENANCE METADATA FORMAT + + Provenance Metadata should include: + + 3.1. Required Fields + - author-identity: Contributor name and contact + - timestamp: ISO 8601 with timezone + - content-hash: SHA3-256 hash of the contribution + - signature: Quantum-safe signature over all fields + + 3.2. Optional Fields + - parent-hash: Hash of the previous contribution in the chain + - context-notes: Narrative context markers + - platform: Build/development environment + - witnesses: Third-party attestation signatures + +4. SIGNATURE PROCEDURE + + 4.1. Signing + a. Compute SHA3-256 hash of the contribution content + b. Construct metadata record with all required fields + c. Serialize metadata in canonical JSON form + d. Sign with ML-DSA-65 (or approved alternative) + e. Attach signature to distribution + + 4.2. Verification + a. Extract metadata and signature from distribution + b. Verify signature against contributor's public key + c. Verify content hash matches actual content + d. Verify timestamp is within acceptable range + e. Verify parent-hash chain if present + +5. KEY MANAGEMENT + + 5.1. Contributors should publish quantum-safe public keys via: + - OpenPGP keyservers (with PQ algorithm support) + - Repository www/.well-known/keys/ directory (served at /.well-known/keys/) + - Contributor's personal website + + 5.2. Key rotation should occur: + - At least annually + - When algorithm recommendations change + - When key compromise is suspected + +6. TRANSITION PERIOD + + During the transition to quantum-safe cryptography: + + 6.1. Classical signatures (Ed25519, RSA) remain valid + 6.2. Hybrid signatures (classical + PQ) are encouraged + 6.3. Pure PQ signatures are preferred for new contributions + 6.4. Classical-only signatures will be deprecated in a future version + +7. COMPLIANCE + + Quantum-safe provenance is OPTIONAL. When present, it must follow this + specification, and quantum-safe signatures should not be stripped from + distributions that carry them. + +================================================================================ +END OF EXHIBIT B +================================================================================ diff --git a/QUICKSTART-DEV.adoc b/docs/onboarding/QUICKSTART-DEV.adoc similarity index 84% rename from QUICKSTART-DEV.adoc rename to docs/onboarding/QUICKSTART-DEV.adoc index a525ba1..1f6b393 100644 --- a/QUICKSTART-DEV.adoc +++ b/docs/onboarding/QUICKSTART-DEV.adoc @@ -1,4 +1,4 @@ -= Dictask — Developer Quickstart += DictaTask — Developer Quickstart :toc: preamble Clone, build, test, contribute. @@ -13,8 +13,8 @@ Clone, build, test, contribute. [source,bash] ---- -git clone https://github.com/hyperpolymath/dictask -cd dictask +git clone https://github.com/hyperpolymath/dicta-task +cd dicta-task just doctor # verify toolchain just heal # auto-install missing tools ---- diff --git a/QUICKSTART-MAINTAINER.adoc b/docs/onboarding/QUICKSTART-MAINTAINER.adoc similarity index 89% rename from QUICKSTART-MAINTAINER.adoc rename to docs/onboarding/QUICKSTART-MAINTAINER.adoc index b212cca..037bdd0 100644 --- a/QUICKSTART-MAINTAINER.adoc +++ b/docs/onboarding/QUICKSTART-MAINTAINER.adoc @@ -1,4 +1,4 @@ -= Dictask — Maintainer Quickstart += DictaTask — Maintainer Quickstart :toc: preamble Packaging, deployment, and release management. @@ -31,7 +31,7 @@ Key workflows: [source,bash] ---- -podman build -f Containerfile -t dictask:latest . +podman build -f Containerfile -t dicta-task:latest . ---- == Mirrors diff --git a/QUICKSTART-USER.adoc b/docs/onboarding/QUICKSTART-USER.adoc similarity index 72% rename from QUICKSTART-USER.adoc rename to docs/onboarding/QUICKSTART-USER.adoc index 6c50643..c35a422 100644 --- a/QUICKSTART-USER.adoc +++ b/docs/onboarding/QUICKSTART-USER.adoc @@ -1,4 +1,4 @@ -= Dictask — User Quickstart += DictaTask — User Quickstart :toc: preamble Get up and running in 60 seconds. @@ -12,8 +12,8 @@ Get up and running in 60 seconds. [source,bash] ---- -git clone https://github.com/hyperpolymath/dictask -cd dictask +git clone https://github.com/hyperpolymath/dicta-task +cd dicta-task just doctor # check toolchain just heal # auto-install missing tools ---- @@ -30,4 +30,4 @@ just help-me # see common workflows * `just help-me` — common workflows * `just doctor` — diagnose toolchain issues -* https://github.com/hyperpolymath/dictask/issues — report bugs +* https://github.com/hyperpolymath/dicta-task/issues — report bugs diff --git a/llm-warmup-dev.adoc b/docs/onboarding/llm-warmup-dev.adoc similarity index 84% rename from llm-warmup-dev.adoc rename to docs/onboarding/llm-warmup-dev.adoc index 9aabfcc..344136f 100644 --- a/llm-warmup-dev.adoc +++ b/docs/onboarding/llm-warmup-dev.adoc @@ -1,6 +1,6 @@ -== LLM Warmup — dictask (Developer) +== LLM Warmup — dicta-task (Developer) -=== What is dictask? +=== What is dicta-task? See README.adoc for overview. diff --git a/llm-warmup-user.adoc b/docs/onboarding/llm-warmup-user.adoc similarity index 85% rename from llm-warmup-user.adoc rename to docs/onboarding/llm-warmup-user.adoc index 819f19e..a5498a1 100644 --- a/llm-warmup-user.adoc +++ b/docs/onboarding/llm-warmup-user.adoc @@ -1,6 +1,6 @@ -== LLM Warmup — dictask (User) +== LLM Warmup — dicta-task (User) -=== What is dictask? +=== What is dicta-task? See README.adoc for overview. diff --git a/docs/practice/0.2-AI-MANIFEST.a2ml b/docs/practice/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 257f3a4..0000000 --- a/docs/practice/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "practice-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Sub-unit of the docs pillar focusing on practice. diff --git a/docs/practice/AI-CONVENTIONS.adoc b/docs/practice/AI-CONVENTIONS.adoc index 6a3636b..467e0ab 100644 --- a/docs/practice/AI-CONVENTIONS.adoc +++ b/docs/practice/AI-CONVENTIONS.adoc @@ -1,85 +1,102 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = AI Conventions - - +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# AI Conventions (Authoritative Source) +== AI Conventions (Authoritative Source) All AI coding agents working in this repository MUST follow these rules. Per-tool config files (.cursorrules, .clinerules, etc.) reference this document. -## Session Startup +=== Session Startup -1. Read `0-AI-MANIFEST.a2ml` FIRST (mandatory gatekeeper). -2. Read `.machine_readable/STATE.a2ml` for current status and blockers. -3. Read `.machine_readable/anchors/ANCHOR.a2ml` for canonical authority boundaries. +1. Read the repo deed (`*_chora.deed` at root) FIRST (mandatory gatekeeper). +2. Read `.machine_readable/descriptiles/STATE.a2ml` for current status and blockers. +3. Read `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` for canonical authority boundaries. 4. Read `.machine_readable/policies/MAINTENANCE-AXES.a2ml` for maintenance/audit sequencing. 5. Read `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` for baseline controls. 6. Read `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` for execution order. -7. Read `.machine_readable/AGENTIC.a2ml` for agent constraints. +7. Read `.machine_readable/descriptiles/AGENTIC.a2ml` for agent constraints. -## License +=== License -- All original code: **MPL-2.0** -- Fallback (platform-required only): MPL-2.0 with comment explaining why. -- NEVER use AGPL-3.0. -- Preserve third-party licenses verbatim. -- Every source file needs `# SPDX-License-Identifier: CC-BY-SA-4.0`. +Per estate policy (hyperpolymath/standards `LICENCE-POLICY.adoc` Rule 1 + Addendum A8): -## Author Attribution +- Code / config / scripts: `SPDX-License-Identifier: MPL-2.0`. +- Prose docs (`*.adoc`, `*.md` narrative): `SPDX-License-Identifier: CC-BY-SA-4.0`. +- GitHub shows only one repo licence: the root `LICENSE` is MPL-2.0 (so the + sidebar reads MPL-2.0); **both** canonical texts live in `LICENSES/` + (`MPL-2.0.txt` + `CC-BY-SA-4.0.txt`) and prose is CC-BY-SA-4.0 by its + per-file header, not a second root licence file. +- Fallback (platform-required only): MPL-2.0 with a comment explaining why. +- NEVER use AGPL-3.0 (son-shared repos are the only exception, per Rule 3 — not this template). +- Preserve third-party licenses verbatim; never relicense. +- Licence-header remediation on existing files is manual, owner-only — no + automated/bulk SPDX edits (Addendum A2). New files may carry the correct + SPDX from birth. + +=== Author Attribution - Name: **Jonathan D.A. Jewell** - Email: **j.d.a.jewell@open.ac.uk** - Copyright: `Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) ` -## State Files +=== State Files State/metadata files, anchors, and policies (.a2ml) belong in `.machine_readable/` ONLY. NEVER create STATE.a2ml, META.a2ml, ECOSYSTEM.a2ml, AGENTIC.a2ml, NEUROSYM.a2ml, PLAYBOOK.a2ml, ANCHOR.a2ml, MAINTENANCE-AXES.a2ml, MAINTENANCE-CHECKLIST.a2ml, or SOFTWARE-DEVELOPMENT-APPROACH.a2ml in the repository root. -## Banned Patterns +=== Banned Patterns + +|=== +| Language | Banned | Reason + +| Idris2 | `believe_me`, `assert_total` | Unsound escape hatches +| Haskell | `unsafeCoerce`, `unsafePerformIO` | Breaks type safety +| OCaml | `Obj.magic`, `Obj.repr`, `Obj.obj` | Unsafe casting +| Coq | `Admitted` | Unproven assumption +| Lean | `sorry` | Unproven assumption +| Rust | `transmute` (unless FFI + SAFETY:) | Unsound reinterpret +|=== + + +=== Banned Languages -| Language | Banned | Reason | -|----------|-------------------------------------|---------------------------| -| Idris2 | `believe_me`, `assert_total` | Unsound escape hatches | -| Haskell | `unsafeCoerce`, `unsafePerformIO` | Breaks type safety | -| OCaml | `Obj.magic`, `Obj.repr`, `Obj.obj` | Unsafe casting | -| Coq | `Admitted` | Unproven assumption | -| Lean | `sorry` | Unproven assumption | -| Rust | `transmute` (unless FFI + SAFETY:) | Unsound reinterpret | +|=== +| Banned | Use Instead -## Banned Languages +| | +| Node.js / npm / bun | +| Go | Rust +| Python | Julia / Rust +|=== -| Banned | Use Instead | -|---------------------|--------------------| -| TypeScript | AffineScript | -| Node.js / npm / bun | Deno | -| Go | Rust | -| Python | Julia / Rust | -## Container Standard +=== Container Standard - Runtime: **Podman** (never Docker). - File: **Containerfile** (never Dockerfile). - Base images: `cgr.dev/chainguard/wolfi-base:latest` or `cgr.dev/chainguard/static:latest`. -## ABI/FFI Standard +=== ABI/FFI Standard -- ABI definitions: **Idris2** with dependent types (`src/abi/`). +- ABI definitions: **Idris2** with dependent types (`src/interface/Abi/`). - FFI implementation: **Zig** with C ABI compatibility (`ffi/zig/`). - Generated C headers: `generated/abi/`. -## Build System +=== Build System -Use `just` (justfile) for all build, test, lint, and format tasks. +Use `just` (Justfile) for all build, test, lint, and format tasks. -## References +=== References -- `0-AI-MANIFEST.a2ml` -- universal AI entry point -- `.machine_readable/AGENTIC.a2ml` -- agent permissions and constraints -- `.machine_readable/STATE.a2ml` -- current project state -- `.machine_readable/anchors/ANCHOR.a2ml` -- canonical authority and policy boundary +- `*_chora.deed` (repo deed) -- universal AI entry point +- `.machine_readable/descriptiles/AGENTIC.a2ml` -- agent permissions and constraints +- `.machine_readable/descriptiles/STATE.a2ml` -- current project state +- `.machine_readable/descriptiles/anchors/ANCHOR.a2ml` -- canonical authority and policy boundary - `.machine_readable/policies/MAINTENANCE-AXES.a2ml` -- canonical axis sequencing and audit requirements - `.machine_readable/policies/MAINTENANCE-CHECKLIST.a2ml` -- baseline maintenance checklist policy - `.machine_readable/policies/SOFTWARE-DEVELOPMENT-APPROACH.a2ml` -- axis execution approach policy diff --git a/docs/practice/README.adoc b/docs/practice/README.adoc index ae3326b..117fa89 100644 --- a/docs/practice/README.adoc +++ b/docs/practice/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = practice Unit diff --git a/docs/practice/STATE-VISUALIZER-GUIDE.adoc b/docs/practice/STATE-VISUALIZER-GUIDE.adoc index 835db9c..3ac0829 100644 --- a/docs/practice/STATE-VISUALIZER-GUIDE.adoc +++ b/docs/practice/STATE-VISUALIZER-GUIDE.adoc @@ -1,4 +1,5 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = TOPOLOGY.md — Generation Guide Jonathan D.A. Jewell (hyperpolymath) :toc: @@ -61,7 +62,7 @@ The file must contain exactly three sections: Add a header comment with SPDX-License-Identifier and Last updated date. End with an "Update Protocol" section explaining how to maintain the file. -Use the template at TOPOLOGY.md in rsr-template-repo as a structural reference, +Use the template at TOPOLOGY.md in dicta-task as a structural reference, but make the content completely specific to THIS project. ---- @@ -69,7 +70,7 @@ but make the content completely specific to THIS project. [source,bash] ---- -cp /path/to/rsr-template-repo/TOPOLOGY.md ./TOPOLOGY.md +cp /path/to/dicta-task/TOPOLOGY.md ./TOPOLOGY.md # Then edit: replace placeholders, draw the real architecture, fill the dashboard ---- @@ -145,7 +146,7 @@ TOPOLOGY.md complements but does not replace: - **STATE.a2ml** — machine-readable state (tasks, blockers, next actions) - **ECOSYSTEM.a2ml** — position in the wider project ecosystem - **META.a2ml** — architecture decisions and design rationale -- **0-AI-MANIFEST.a2ml** — AI agent entry point and invariants +- ***_chora.deed (repo deed)** — AI agent entry point: allocation policy and directory invariants TOPOLOGY.md is the _visual summary_ for humans; the a2ml files are the _structured data_ for tooling. Both should agree. diff --git a/docs/practice/ci-cost-reduction.adoc b/docs/practice/ci-cost-reduction.adoc new file mode 100644 index 0000000..2cff5a0 --- /dev/null +++ b/docs/practice/ci-cost-reduction.adoc @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += CI Cost Reduction — RSR Estate Spec +:toc: left +:toclevels: 3 + +Estate-wide playbook for reducing GitHub Actions minutes consumption +without sacrificing CI coverage. Born out of an incident where the +`hyperpolymath` Actions billing tripped and every runner-based workflow +in the estate went dark. The blocker was external, but the exposure — +how much we spend — was self-inflicted. This document captures the +knobs that exist, which ones are worth pulling, and in what order. + +Priority sort applied: *dependability > security > interop > usability +> performance > versatility > functional extension*. Cost reduction is +a dependability/performance concern; nothing here trades security for +savings. + +== Why this is worth doing + +A representative RSR repo (007) runs ~26 active workflows. On a single +push: + +* ~10-15 workflows fire in parallel. +* Several (scorecard, codeql, hypatia-scan) also fire on a schedule. +* `oracle-fuzz.yml` runs a 10-minute differential job every hour. +* Multiple workflows do `fetch-depth: 0` full-history clones. +* There are meaningful overlaps (trufflehog + gitleaks, multiple + security-gate jobs, codeql + hypatia-scan). + +Conservative saving potential from the five highest-leverage patterns +below: *60–80%* of current Actions-minutes, with no coverage loss. + +== Priority-ordered patterns + +=== 1. Concurrency kills (one-line, estate-wide) + +Every push-triggered workflow should cancel its superseded runs when +a rapid-fire commit lands: + +[source,yaml] +---- +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +---- + +Add this at the workflow top level. Exceptions: release workflows, +deploy workflows, scheduled workflows — those need to finish. + +*Impact:* cancels obsolete runs immediately. On active branches with +many commits, easily 20–40% of current minutes. + +=== 2. Path filters on heavy workflows + +Rust CI, CodeQL, E2E, and language-specific test suites should not run +on docs-only or comment-only changes. + +[source,yaml] +---- +on: + push: + paths: + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' + - '.github/workflows/rust-ci.yml' + pull_request: + paths: + - 'src/**' + - 'crates/**' + - 'Cargo.toml' + - 'Cargo.lock' +---- + +For workflows whose scope is "the whole repo" (scorecard, codeql, +hypatia-scan): leave unfiltered. For language-specific jobs: filter. + +*Impact:* single largest saving on repos that get a lot of README or +issue-template churn. + +=== 3. Reduce schedule frequency + +Scheduled workflows should run as often as the decision they inform +actually changes — not more. + +[cols="2,1,1,2",options="header"] +|=== +| Workflow | Current | Recommended | Rationale + +| `oracle-fuzz.yml` (differential fuzz) +| Every 10 min +| Every 6h or nightly +| TRG §5.7.4 90-day differential-fuzzing clock measures *total time*, + not *frequency*. A nightly 10-min slice gives the same statistical + coverage at 1/144 the cost. + +| `scorecard.yml` +| Daily +| Weekly +| OSSF's own recommendation is weekly for stable repos. + +| `codeql.yml` (schedule branch) +| Daily +| Weekly, or remove schedule (relies on push trigger) +| CodeQL on every push already covers PR and main commits. A weekly + sweep catches newly-disclosed CVEs that affect existing code. + +| `hypatia-scan.yml` +| Weekly +| Keep weekly +| Correct cadence. Don't change. + +| `parser-fuzz.yml` (if present) +| Nightly, 5 min per target × 3 targets +| Keep +| Already minimal. +|=== + +=== 4. Overlap consolidation + +Several workflows cover overlapping ground. Consolidate where the +substitution is near-free: + +[cols="2,2,2",options="header"] +|=== +| Overlap | Resolution | Notes + +| `trufflehog` + `gitleaks` in `secret-scanner.yml` +| Keep one (gitleaks preferred for CVE coverage; trufflehog for + verified-only mode) +| Running both is belt-and-braces with ~90% coverage overlap. One is + enough for prevention; both only for quarterly history-sweeps. + +| `rsr-antipattern.yml` + `scorecard-enforcer.yml` + + `static-analysis-gate.yml` +| Merge into a single composite `rsr-gate.yml` with three steps +| They already run in the same CI slot; merging deduplicates + setup/checkout/clone. + +| `codeql.yml` + `hypatia-scan.yml` +| Keep both, but drop Hypatia from on-push; let it run only on + schedule +| Hypatia is the slower of the two, and its neurosymbolic analysis + doesn't need sub-minute latency on every commit. + +| `codeql.yml` (on-push) + `codeql.yml` (scheduled) +| Keep on-push, drop scheduled +| Redundant unless the repo rarely gets commits. +|=== + +=== 5. Shallow clones where full-history isn't needed + +Full-history clones (`fetch-depth: 0`) are expensive on large repos. +Required for: + +* Scorecard (history-based metrics). +* Gitleaks history-sweep mode. +* TruffleHog history-sweep mode. + +Not required for: + +* PR-event secret scanning (limit to the PR delta). +* Rust CI, language-specific tests. +* CodeQL (shallow is fine). +* Hypatia-scan (shallow is fine for pattern detection). + +Pattern for secret-scanner: + +[source,yaml] +---- +on: + pull_request: # shallow clone, just the diff + push: + branches: [main] + schedule: + - cron: '0 4 * * 1' # weekly history sweep + +jobs: + pr-scan: + if: github.event_name == 'pull_request' + # shallow — don't need history for delta scanning + steps: + - uses: actions/checkout@... + full-scan: + if: github.event_name != 'pull_request' + steps: + - uses: actions/checkout@... + with: + fetch-depth: 0 # only for push-to-main + scheduled +---- + +=== 6. Job-level timeouts + +Cap every job so a hung runner doesn't burn the budget: + +[source,yaml] +---- +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 # fail-fast instead of 360 default +---- + +Recommendations: + +* Setup / lint / format jobs: 5 min. +* Build jobs: 15–20 min. +* Test jobs: 20–30 min. +* E2E / integration: 45 min max. +* Fuzz: exact target time + 2 min tolerance. + +=== 7. Reusable workflow (longer-term) + +Ship the above patterns in a single reusable workflow under +`dicta-task/.github/workflows/rsr-ci-defaults.yml`, and adopt +it estate-wide via `uses: hyperpolymath/dicta-task/.github/workflows/rsr-ci-defaults.yml@main` +in downstream repos. Single PR propagates improvements. + +=== 8. Self-hosted runner for heavy work (long-term) + +Oracle-fuzz, E2E+Conformance+Bench, Hypatia-scan: these are the +expensive jobs. Moving them to a self-hosted runner on the Eclipse +host gets the cost to zero ongoing. Setup: ~1 day. Security: run in +a rootless Podman container with the `ubuntu-22.04` runner image. +Not urgent; track as future work. + +=== 9. Dependabot noise + +`.github/dependabot.yml` in this template uses +`open-pull-requests-limit: 0` on cargo to suppress routine patch PRs +while keeping security PRs flowing. That's the correct pattern — do +NOT revert to the previous `ignore: "*" patch` rule, which also +silences security PRs (see +007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md). + +Paired with `.github/workflows/dependabot-automerge.yml`, security +PRs for low/moderate patches+minors auto-merge after CI, leaving +humans to review only HIGH+CRITICAL security updates and all +non-security bumps. + +== Rollout plan + +. *Week 1:* Apply patterns 1–2 (concurrency + path filters) to every + active RSR repo. Reusable template update; downstream propagation + is a find-and-replace pass. +. *Week 2:* Apply patterns 3–4 (schedule frequency + overlap + consolidation). Audit one repo at a time; check ~30 days of runs + before concluding an overlap is safe to drop. +. *Week 3:* Apply patterns 5–6 (shallow clone + timeouts). Low-risk. +. *Week 4+:* Pattern 7 (reusable workflow) — single PR, big payoff. +. *When scope allows:* Pattern 8 (self-hosted runner). + +== Measurement + +Before/after: `gh api /users//settings/billing/actions` shows +current minutes usage. Diff after each rollout wave. Target a 60% +reduction by end of week 4 on the three highest-consumption repos +(boj-server, hypatia, 007 — based on workflow count × schedule +frequency × job count). + +== Out of scope + +* Reducing CI coverage (not on the table). +* Disabling security workflows to save minutes. +* Skipping CI on "trivial" commits via commit-message tags + (gameable, easy to abuse). +* Switching to a paid Actions tier before exhausting these patterns. + +== Cross-references + +* `007-lang/audits/audit-rsr-workflows-2026-04-17.md` — billing + incident that motivated this spec. +* `007-lang/audits/audit-dependabot-automation-gap-2026-04-17.md` — + separate defect stack also addressed in the same session. +* `dicta-task/.github/workflows/dependabot-automerge.yml` — + canonical pattern for auto-merge pattern referenced above. diff --git a/docs/proposals/root-cleanup.adoc b/docs/proposals/root-cleanup.adoc new file mode 100644 index 0000000..84548e3 --- /dev/null +++ b/docs/proposals/root-cleanup.adoc @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Root Cleanup Proposal — Reconcile Template With Its Own Audit +:toc: +:toclevels: 3 +:sectnums: + +== Motivation + +`TEMPLATE-STANDARDS-AUDIT.adoc` ships a "Proposed Final Directory Map" for the +template root. The tracked root violates that map by roughly 5x: + +[cols="1,1,1",options="header"] +|=== +| Bucket | Audit-mandated count | Actual count + +| Root files (visible) +| 6 +| 36 + +| Root directories (visible) +| 3 +| 17 +|=== + +Downstream repositories (e.g. `the-nash-equilibrium`) inherit the violation +verbatim. This proposal describes the relocations that bring the template back +into compliance with its own map, and adds an automated guard +(`scripts/check-root-shape.sh` against `.machine_readable/root-allow.txt`) so +the violation cannot silently return. + +NOTE: This is a template-side change. Downstream repos pick it up via a +one-shot relocation PR per project once the template lands. + +== Allowlist (canonical root) + +The full enumeration lives in `.machine_readable/root-allow.txt`. Summary: + +* *Authority files (root):* `README.adoc`, `AUDIT.adoc`, `EXPLAINME.adoc`, + `0-AI-MANIFEST.a2ml` (thin pointer), `LICENSE`, `CHANGELOG.md`. +* *Build entry points (root):* `Justfile`, `coordination.k9`. +* *Tool-required dotfiles (root):* `.editorconfig`, `.envrc`, `.gitattributes`, + `.gitignore`, `.tool-versions`. +* *Directories (root):* `.git/`, `.github/`, `.machine_readable/`, + `.well-known/`, `build/`, `ci/`, `docs/`, `session/`, plus the conventional + source/test trees (`src/`, `tests/`, `benches/`, `examples/`, `features/`, + `scripts/`, `verification/`, `build/container/`). + +== Relocation plan + +Each line is a `git mv` (or delete) plus the references that need updating. +Run from the template repo root. + +=== Onboarding prose → `docs/onboarding/` + +[source,bash] +---- +mkdir -p docs/onboarding +git mv QUICKSTART-DEV.adoc docs/onboarding/ +git mv QUICKSTART-USER.adoc docs/onboarding/ +git mv QUICKSTART-MAINTAINER.adoc docs/onboarding/ +git mv llm-warmup-dev.md docs/onboarding/ +git mv llm-warmup-user.md docs/onboarding/ +---- + +References to update: + +* `README.adoc` — any `link:QUICKSTART-*.adoc[…]`. +* `Justfile` — any `cat QUICKSTART-*` echoes in `init`/`help`. + +=== Status/roadmap docs → `docs/status/` + +[source,bash] +---- +mkdir -p docs/status docs/architecture +git mv READINESS.md docs/status/ +git mv ROADMAP.adoc docs/status/ +git mv TEST-NEEDS.md docs/status/ +git mv PROOF-NEEDS.md docs/status/ +git mv PROOF-STATUS.md docs/status/ +git mv TOPOLOGY.md docs/architecture/ # validate-template.sh already accepts this path +---- + +References to update: + +* `README.adoc` — `link:ROADMAP.adoc[…]` and similar. +* `Justfile` — `readiness:` recipe (currently reads `READINESS.md`). + +=== Health files → `.github/` + +GitHub auto-discovers these under `.github/`, so the move is transparent to +contributors and tools. + +[source,bash] +---- +git mv CONTRIBUTING.md .github/ +git mv CODE_OF_CONDUCT.md .github/ +git mv SECURITY.md .github/ +---- + +=== Build orchestration → `build/` + +`Justfile` stays at root (just convention) but becomes thin: it imports +phase-specific just files from `build/`. + +[source,bash] +---- +mkdir -p build +git mv contractile.just build/ +git mv setup.sh build/ +git mv guix.scm build/ +git mv .guix-channel build/ +# Containerfile may already live in build/container/ — keep whichever the project uses. +[ -f Containerfile ] && git mv Containerfile build/ +---- + +References to update in `Justfile`: + +[source,diff] +---- +- import? "contractile.just" ++ import? "build/contractile.just" +---- + +The current Justfile already supports `build/container/Containerfile` *or* root +`Containerfile` — extend that to also accept `build/Containerfile`. + +The 62 KB monolithic `Justfile` is a separate smell. A follow-up should split +it under `build/just/{init,verify,test,docs,container,security}.just` with +`import?` lines from the thin root file. + +=== CI configs → `ci/` + +Most CI tools accept a custom config path; where they don't, a one-line root +shim file is acceptable. + +[source,bash] +---- +mkdir -p ci +git mv .gitlab-ci.yml ci/ +git mv .pre-commit-config.yaml ci/ +---- + +Updates required: + +* GitLab CI: project setting "CI/CD configuration file" → `ci/.gitlab-ci.yml`. +* pre-commit: invoke as `pre-commit run --config ci/.pre-commit-config.yaml`, + or leave a one-line root shim. + +=== Custom-format configs → `.machine_readable/configs/` + +[source,bash] +---- +git mv eclexiaiser.toml .machine_readable/configs/ +git mv selur-compose.toml .machine_readable/configs/ +git mv stapeln.toml .machine_readable/configs/ +---- + +References to update wherever any tool reads them (grep for the filenames +across `Justfile`, `scripts/`, `.machine_readable/`). + +=== AI manifest deduplication + +`.machine_readable/0.1-AI-MANIFEST.a2ml` is the canonical AI manifest in both +the template and downstream repos (e.g. `the-nash-equilibrium` README line +244). The root `0-AI-MANIFEST.a2ml` becomes a thin pointer: + +[source,a2ml] +---- +# 0-AI-MANIFEST.a2ml — pointer file +authority = ".machine_readable/0.1-AI-MANIFEST.a2ml" +note = "AI agents MUST read the canonical manifest at the path above." +---- + +Once the canonical version is stable, decide whether to keep this pointer at +root or delete it entirely. + +=== Template-only relocations + +A dry-run of `check-root-shape.sh` against the unmodified template flagged +three template-only extras that aren't drift in downstream repos but should +move regardless: + +[source,bash] +---- +# The audit doc itself is drift — it doesn't apply at root. +git mv TEMPLATE-STANDARDS-AUDIT.adoc docs/governance/ + +# `tools/` and `scripts/` overlap; pick one (proposal: keep `scripts/`). +# Inspect tools/ contents and either merge into scripts/ or rename and document +# the split (e.g. `scripts/` = repo-local helpers, `tools/` = bundled binaries). +---- + +=== Hygiene: case collisions in `affinescript/stdlib/` + +Cloning the template on a case-insensitive filesystem (Windows, default macOS) +silently drops files because of pairs like `Math.affine` vs `math.affine`. + +Pick one canonical case per name and `git mv` the duplicates away. Keeping +both is not portable. + +== Justfile recipe + +[source,just] +---- +# Verify root layout against the canonical allowlist. +check-root-shape: + ./scripts/check-root-shape.sh + +# Add to the existing aggregate `verify` target. +verify: ... check-root-shape ... +---- + +A pre-commit hook (in `ci/.pre-commit-config.yaml`) and a CI job +(`ci/.gitlab-ci.yml`) should both call `just check-root-shape`. + +== Downstream rollout + +For each downstream repo (start with `the-nash-equilibrium`): + +1. Apply the same `git mv` set (skip moves whose source doesn't exist locally). +2. Copy the new `.machine_readable/root-allow.txt` and adapt comments/extras. +3. Update internal links (README, docs, Justfile recipes). +4. Run `just check-root-shape` and `scripts/validate-template.sh` to confirm + shape parity with the template. + +== Out of scope (mentioned for follow-up) + +* Splitting the 62 KB monolithic `Justfile`. +* Migrating from GitLab CI to GitHub Actions parity (or vice versa). +* Reworking the `0.1-` / `0.2-` manifest versioning convention. diff --git a/docs/reports/0.2-AI-MANIFEST.a2ml b/docs/reports/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 5eb265d..0000000 --- a/docs/reports/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "reports-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Documentation unit for all automated and manual audit reports. Classified - by domain. - -canonical_locations: - maintenance: "maintenance/" - security: "security/" - performance: "performance/" - compliance: "compliance/" - quality: "quality/" diff --git a/docs/reports/README.adoc b/docs/reports/README.adoc index 0c06c31..d3dfdf9 100644 --- a/docs/reports/README.adoc +++ b/docs/reports/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = reports Unit diff --git a/docs/reports/audit/pillar-audit-2026-04-15.adoc b/docs/reports/audit/pillar-audit-2026-04-15.adoc new file mode 100644 index 0000000..7d484e8 --- /dev/null +++ b/docs/reports/audit/pillar-audit-2026-04-15.adoc @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Gemini Audit Report (M2: Pillar Repo Audits) +:date: 2026-04-15 + +Date: 2026-04-15 + +Repository: `/var/mnt/eclipse/repos/dicta-task` + +== Audit Criteria + +* *Dangerous Patterns*: *CLEAN*. +* *Standards Check*: +** `.machine_readable/*.a2ml`: `0-AI-MANIFEST.a2ml` present. +** `Justfile`: *PRESENT*. +** `K9.k9` / `coordination.k9`: *PRESENT* (`coordination.k9`). +* *CI/CD Status*: `.github/workflows` and `.gitlab-ci.yml` *PRESENT*. +* *Documentation Parity*: Template repo claims. +* *Template Residue*: *HIGH* (Expected as it is the template itself). + +== Verdict + +* *CRG Grade*: A (Template) +* *Publishable?*: YES diff --git a/docs/reports/compliance/0.3-AI-MANIFEST.a2ml b/docs/reports/compliance/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 6b39752..0000000 --- a/docs/reports/compliance/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "report-unit-compliance" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised repository for compliance findings and evidence. diff --git a/docs/reports/compliance/README.adoc b/docs/reports/compliance/README.adoc index c38c66a..e3d88e4 100644 --- a/docs/reports/compliance/README.adoc +++ b/docs/reports/compliance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Compliance Reports diff --git a/docs/reports/maintenance/0.3-AI-MANIFEST.a2ml b/docs/reports/maintenance/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 43eefe2..0000000 --- a/docs/reports/maintenance/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "report-unit-maintenance" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Maintenance reports. diff --git a/docs/reports/maintenance/README.adoc b/docs/reports/maintenance/README.adoc index f13abf7..948e597 100644 --- a/docs/reports/maintenance/README.adoc +++ b/docs/reports/maintenance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Maintenance Reports diff --git a/docs/reports/performance/0.3-AI-MANIFEST.a2ml b/docs/reports/performance/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 40c0954..0000000 --- a/docs/reports/performance/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "report-unit-performance" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised repository for performance findings and evidence. diff --git a/docs/reports/performance/README.adoc b/docs/reports/performance/README.adoc index 037767d..36e0e09 100644 --- a/docs/reports/performance/README.adoc +++ b/docs/reports/performance/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Performance Reports diff --git a/docs/reports/quality/0.3-AI-MANIFEST.a2ml b/docs/reports/quality/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index d460edc..0000000 --- a/docs/reports/quality/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "report-unit-quality" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised repository for quality findings and evidence. diff --git a/docs/reports/quality/README.adoc b/docs/reports/quality/README.adoc index d1be848..660e105 100644 --- a/docs/reports/quality/README.adoc +++ b/docs/reports/quality/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Quality Reports diff --git a/docs/reports/security/0.3-AI-MANIFEST.a2ml b/docs/reports/security/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 696ab59..0000000 --- a/docs/reports/security/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "report-unit-security" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised repository for security findings and evidence. diff --git a/docs/reports/security/README.adoc b/docs/reports/security/README.adoc index 9a78a8b..7e1ab5f 100644 --- a/docs/reports/security/README.adoc +++ b/docs/reports/security/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Security Reports diff --git a/docs/standards/0.2-AI-MANIFEST.a2ml b/docs/standards/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index c147c6f..0000000 --- a/docs/standards/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "standards-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Standards unit for high-rigor verification. diff --git a/docs/standards/README.adoc b/docs/standards/README.adoc index 34a94c4..b31c112 100644 --- a/docs/standards/README.adoc +++ b/docs/standards/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Standards Unit diff --git a/PROOF-NEEDS.adoc b/docs/status/PROOF-NEEDS.adoc similarity index 100% rename from PROOF-NEEDS.adoc rename to docs/status/PROOF-NEEDS.adoc diff --git a/docs/status/PROOF-STATUS.adoc b/docs/status/PROOF-STATUS.adoc new file mode 100644 index 0000000..699272c --- /dev/null +++ b/docs/status/PROOF-STATUS.adoc @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// Template: rsr-template-repo/docs/status/PROOF-STATUS.adoc +// Tracks proof completion. Requirements defined in PROOF-NEEDS.adoc += Proof Status — DICTA_TASK + +== Summary + +[cols="2,1,1,1,1,1", options="header"] +|=== +| Category | Total | Done | In Progress | Blocked | Remaining + +| ABI/FFI (ABI) | 5 | 0 | 0 | 0 | 5 +| Typing (TP) | 2 | 0 | 0 | 0 | 2 +| Invariant (INV) | 0 | 0 | 0 | 0 | 0 +| Security (SEC) | 0 | 0 | 0 | 0 | 0 +| Concurrency (CONC) | 0 | 0 | 0 | 0 | 0 +| Algorithm (ALG) | 0 | 0 | 0 | 0 | 0 +| Domain (DOM) | 0 | 0 | 0 | 0 | 0 +| *Total* | *7* | *0* | *0* | *0* | *7* +|=== + +*Overall*: 0% proven + +== Proofs Done + +// Format: +// | ID | Proof | Prover | File | Date | Verified By | +// | ABI-1 | Non-null pointer proofs | Idris2 | verification/proofs/idris2/ABI/Pointers.idr | 2026-XX-XX | idris2 --check | + +[cols="1,3,1,3,1,2", options="header"] +|=== +| ID | Proof | Prover | File | Date | Verified By + +| — | No proofs completed yet | — | — | — | — +|=== + +== Proofs In Progress + +[cols="1,3,1,2,1,2", options="header"] +|=== +| ID | Proof | Prover | Assignee | Started | Blocker + +| — | — | — | — | — | — +|=== + +== Proofs Blocked + +[cols="1,3,2,3", options="header"] +|=== +| ID | Proof | Blocked By | Notes + +| — | — | — | — +|=== + +== Proofs Remaining + +[cols="1,3,1,1,1,1", options="header"] +|=== +| ID | Proof | Category | Prover | Priority | Est. Effort + +| ABI-1 | Non-null pointer proofs | ABI | Idris2 | P1 | 2h +| ABI-2 | Memory layout correctness | ABI | Idris2 | P1 | 4h +| ABI-3 | Platform type size proofs | ABI | Idris2 | P1 | 2h +| ABI-4 | FFI function return type proofs | ABI | Idris2 | P1 | 2h +| ABI-5 | C ABI compliance | ABI | Idris2 | P1 | 4h +| TP-1 | Core data type well-formedness | TP | Idris2 | P1 | 4h +| TP-2 | Public API type safety | TP | Lean4 | P2 | 4h +|=== + +== Verification Commands + +[source,bash] +---- +# Check all Idris2 proofs +just proof-check-idris2 + +# Check all Lean4 proofs +just proof-check-lean4 + +# Check all Agda proofs +just proof-check-agda + +# Check all Coq proofs +just proof-check-coq + +# Run all proof checks +just proof-check-all + +# Scan for dangerous patterns +panic-attack assail --proofs-only +---- + +== Changelog + +[cols="1,3,1", options="header"] +|=== +| Date | Change | By + +| 2026-04-04 | Initial proof status tracking | Template +|=== diff --git a/docs/status/READINESS.adoc b/docs/status/READINESS.adoc new file mode 100644 index 0000000..50f13a3 --- /dev/null +++ b/docs/status/READINESS.adoc @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += Dicta-Task Component Readiness Assessment + +*Standard:* link:https://github.com/hyperpolymath/standards/tree/main/component-readiness-grades[Component Readiness Grades (CRG) v2.0 STRICT] + +*Assessed:* 2026-09-26 + +*Assessor:* Jonathan D.A. Jewell + +*Previous assessment:* __ + +*Current Grade:* X + +This line is parsed by `just crg-grade` / `just crg-badge`. The grade above is +the worst-graded in-scope component — *the project's weakest link sets its +grade*. See the per-component table in §3. + +[NOTE] +==== +*Honest grading.* Per CRG v2.0 Principle 4: grade as-is today, not +aspirationally. Long alpha is discipline, not shame. Demote immediately if +evidence doesn't support the claim. +==== + +''' + +== 1. CRG v2.0 Grade Reference + +[cols="1,2,2,3,2", options="header"] +|=== +| Grade | Name | Release Stage | Stability Posture | Shorthand + +| X | Untested | — | — | — +| F | Harmful / Wasteful | — | — | reject/delegate +| E | Minimal / Salvageable | Pre-alpha | Unstable | `pre-alpha` +| D | Partial / Inconsistent| Alpha | Unstable | `alpha-unstable` +| C | Self-Validated | Alpha | Stable in home context | `alpha-stable` +| B | Broadly Validated | Beta | Stable for broad trial | `beta-stable` +| A | Field-Proven | Stable | Stable | `stable` +|=== + +*Evidence gates (v2.0 is stricter than v1.0):* + +* *D*: RSR-compliant + per-capability tests + documented scope. Test matrix + must cite counts and live in `.machine_readable/descriptiles/STATE.a2ml` or CI. +* *C*: All of D, plus active dogfooding in home context with *no known + home-context failures over an evidence window*, plus *deep per-file and + per-directory annotation* (purpose, boundaries, invariants, + execution/test/proof surfaces, per-directory orientation READMEs). + STATE.a2ml `[dogfooding-status]` populated with concrete "done — " + entries. +* *B*: All of C, plus *six genuinely diverse external targets* with + feedback fed back. STATE.a2ml `[external-targets]` holds target identities + + dates + outcomes; `[issues-fed-back]` holds the closed-issue trail. + Internal-capability items do *not* count. +* *A*: All of B, plus multi-source real-world external feedback confirming + value, no harm in the wild. STATE.a2ml `[field-signal]` populated. + +*Publication gate:* non-abstract implementation claims require *B+*. +Below B, any publication must be explicitly abstract or provisional. + +''' + +== 2. Headline Evidence (as of 2026-09-26) + +[cols="2,3,3", options="header"] +|=== +| Metric | Value | Source + +| Test count | __ | `.machine_readable/descriptiles/STATE.a2ml` or CI +| Formal-verification posture | __ | grep of proof dirs +| Dangerous patterns (`assert_total`, `unsafeCoerce`, `Obj.magic`) | __ | grep 2026-09-26 +| Per-unit README coverage | __ | filesystem scan of unit dirs +| CI status | __ | `.github/workflows/` +| RSR mandatory workflows | __ | `.github/workflows/` +| Third-party badges (if any) | __ | external +| LIVE deployment (if any) | __ | production +|=== + +''' + +== 3. Component Assessment + +All components graded *as-is today*, per CRG v2.0 Principle 4. Stability +posture reflects the component's state *within its home context only* +unless noted. + +[cols="2,1,1,3,3,2", options="header"] +|=== +| Component | Grade | Posture | Evidence Summary | Promotion blocker | Last Assessed + +| __ | X/F/E/D/C/B/A | __ | __ | __ | 2026-09-26 +| __ | … | … | … | … | 2026-09-26 +| __ | … | … | … | … | 2026-09-26 +|=== + +*Rules of thumb for populating this table:* + +* One row per independently-gradable unit. If a subsystem can ship or break + independently, it gets its own row. +* Evidence Summary must cite *numbers* (test counts, LOC, file counts) or + *paths* (e.g. `src/interface/Abi/Module.idr`), never vague claims. +* Promotion blocker must be *actionable* — "add external consumer in home + context, then annotate" beats "needs more validation". +* Re-assess every release cycle; date-stamp each row. + +''' + +== 4. What's Needed for D → C + +CRG v2.0 requires two new pieces of evidence on top of D: + +. *Active dogfooding in home context with no known home-context failures.* +** Start date: __. +** Home context: __. +** "No known failures" is a moving claim — must hold continuously across + the evidence window (recommended: 4 weeks, daily use). +** Populate `STATE.a2ml [dogfooding-status]` with one entry per capability: + `capability = "done — "`. + +. *Deep code and folder annotation.* +** Per-directory orientation READMEs in every non-trivial source subtree. +** Per-file header comments: purpose, boundaries, invariants, + execution/test/proof surface. +** Per-unit (cartridge/panel/plugin/module) README covering: purpose, + tools, architecture-at-a-glance, build steps. Overview-level alone is + not sufficient — depth is required where a reviewer would otherwise + have to read source to orient. + +*Minimum first-ring targets for C promotion:* list the trunk components +here. If these aren't C, nothing else can be. + +''' + +== 5. What's Needed for C → B + +Six *genuinely diverse* external targets with feedback fed back into the +component. Candidate diversity axes: + +* Different language runtime (not just variants of the same one). +* Different OS family (at least one must not be Linux). +* Different hardware class (cloud / server / desktop / embedded / mobile). +* Different auth posture (unauthenticated, API-key, vault-brokered, mTLS). +* Different topology (star, mesh, peer-to-peer). +* Different trust model (same-org, federated, adversarial). + +Populate `STATE.a2ml`: + +* `[external-targets]` — target-id → `" — — "`. +* `[issues-fed-back]` — issue-id → `" — — "`. + +Six variations of the same use case do *not* count. Re-classify any items +currently under `[grade-b-status]` (legacy) and move external-eligible ones +into `[external-targets]`. + +''' + +== 6. What's Needed for B → A + +Real-world external feedback confirming value. Populate +`STATE.a2ml [field-signal]` with multi-source entries: + +* External users beyond the six B-targets. +* Third-party writeups, talks, papers, or testimonials. +* No unresolved safety / correctness incidents in the last 90 days. + +''' + +== 7. Summary (2026-09-26) + +* Project grade: __. Why: _<1-2 line justification>_. +* Delta since last assessment: __. +* Next milestone: __. +* Machine-readable grade line present (§ header) for `just crg-grade` / `just crg-badge`. + +''' + +== 8. Companion Artefacts + +* `docs/governance/CRG-CRITERIA.adoc` — grade definitions (boilerplate). +* `docs/governance/CRG-AUDIT-.adoc` — formal audit (populate from + `dicta-task/docs/governance/CRG-AUDIT-TEMPLATE.adoc`). +* `docs/practice/DOGFOOD-LOG.adoc` — dated dogfood evidence (required for C). +* `.machine_readable/descriptiles/STATE.a2ml` — authoritative state (canonical keys + `[dogfooding-status]`, `[external-targets]`, `[issues-fed-back]`, + `[field-signal]`). + +_Run `just crg-badge` to generate the shields.io badge for your README._ diff --git a/ROADMAP.adoc b/docs/status/ROADMAP.adoc similarity index 98% rename from ROADMAP.adoc rename to docs/status/ROADMAP.adoc index b0c2ddb..e032bc2 100644 --- a/ROADMAP.adoc +++ b/docs/status/ROADMAP.adoc @@ -1,5 +1,5 @@ // SPDX-License-Identifier: CC-BY-SA-4.0 -= dictask Roadmap += dicta-task Roadmap :toc: == Current Status diff --git a/TEST-NEEDS.adoc b/docs/status/TEST-NEEDS.adoc similarity index 95% rename from TEST-NEEDS.adoc rename to docs/status/TEST-NEEDS.adoc index 0e3f942..633fb20 100644 --- a/TEST-NEEDS.adoc +++ b/docs/status/TEST-NEEDS.adoc @@ -1,4 +1,4 @@ -== TEST-NEEDS.md — dictask +== TEST-NEEDS.md — dicta-task === CRG Grade: C — ACHIEVED 2026-04-04 diff --git a/docs/theory/0.2-AI-MANIFEST.a2ml b/docs/theory/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 93df187..0000000 --- a/docs/theory/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-track" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Documentation track for domain-specific theory and research foundations. - Categorised by discipline. - -canonical_locations: - ontologies: "ontologies/" - mathematics: "mathematics/" - computing: "computing/" - socio_technical: "socio-technical/" - formalisms: "formalisms/" - other: "other/" - -invariants: - - "Theoretical claims MUST reference established academic or technical formalisms" diff --git a/docs/theory/README.adoc b/docs/theory/README.adoc index c0ddf28..20ef5b6 100644 --- a/docs/theory/README.adoc +++ b/docs/theory/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = theory Unit diff --git a/docs/theory/computing/0.3-AI-MANIFEST.a2ml b/docs/theory/computing/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index f387d08..0000000 --- a/docs/theory/computing/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-computing" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for computing. diff --git a/docs/theory/computing/README.adoc b/docs/theory/computing/README.adoc index 4d0db25..be73e73 100644 --- a/docs/theory/computing/README.adoc +++ b/docs/theory/computing/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Computing Theory diff --git a/docs/theory/formalisms/0.3-AI-MANIFEST.a2ml b/docs/theory/formalisms/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index cdc2baa..0000000 --- a/docs/theory/formalisms/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-formalisms" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for formalisms. diff --git a/docs/theory/formalisms/README.adoc b/docs/theory/formalisms/README.adoc index 5d064c3..fcbc818 100644 --- a/docs/theory/formalisms/README.adoc +++ b/docs/theory/formalisms/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Formalisms Theory diff --git a/docs/theory/mathematics/0.3-AI-MANIFEST.a2ml b/docs/theory/mathematics/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 677a4da..0000000 --- a/docs/theory/mathematics/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-mathematics" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for mathematics. diff --git a/docs/theory/mathematics/README.adoc b/docs/theory/mathematics/README.adoc index 356236f..f4653d1 100644 --- a/docs/theory/mathematics/README.adoc +++ b/docs/theory/mathematics/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Mathematics Theory diff --git a/docs/theory/ontologies/0.3-AI-MANIFEST.a2ml b/docs/theory/ontologies/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index d888cee..0000000 --- a/docs/theory/ontologies/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-ontologies" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for ontologies. diff --git a/docs/theory/ontologies/README.adoc b/docs/theory/ontologies/README.adoc index 6d16ecf..2008f2a 100644 --- a/docs/theory/ontologies/README.adoc +++ b/docs/theory/ontologies/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Ontologies Theory diff --git a/docs/theory/other/0.3-AI-MANIFEST.a2ml b/docs/theory/other/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 166ed9e..0000000 --- a/docs/theory/other/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-other" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for other. diff --git a/docs/theory/other/README.adoc b/docs/theory/other/README.adoc index 1861d6d..16ed2b1 100644 --- a/docs/theory/other/README.adoc +++ b/docs/theory/other/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Other Theory diff --git a/docs/theory/socio-technical/0.3-AI-MANIFEST.a2ml b/docs/theory/socio-technical/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 8919522..0000000 --- a/docs/theory/socio-technical/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "theory-unit-socio-technical" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Theoretical foundation for socio technical. diff --git a/docs/theory/socio-technical/README.adoc b/docs/theory/socio-technical/README.adoc index 9ab4ee0..91fa670 100644 --- a/docs/theory/socio-technical/README.adoc +++ b/docs/theory/socio-technical/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Socio technical Theory diff --git a/docs/troubleshooting.adoc b/docs/troubleshooting.adoc new file mode 100644 index 0000000..bb94368 --- /dev/null +++ b/docs/troubleshooting.adoc @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Troubleshooting — Dicta-Task +:revdate: 2026-MM-DD + +This file is a *living FAQ* — known failure modes and recovery paths. +Add a new entry every time you debug something that took more than 15 +minutes; future-you (and other maintainers) will thank you. + +== Known failure modes + +=== `` + +**Symptom**: short reproduction. What does the user see? + +**Cause**: root cause (one or two sentences). + +**Fix**: +[source,bash] +---- + +---- + +**Avoidance**: how to not hit this again (config setting, doc link, etc.). + +--- + +=== `` + +(Replace this and the above example with real entries as you encounter +them.) + +== Diagnostic toolkit + +When something is wrong, run these first: + +[source,bash] +---- +just doctor # runs all available self-checks +just version # prints the version & build hash +just log-tail # last N lines of logs +---- + +== When to escalate + +. Filed an issue with: version, OS, exact command, full error output, + and the symptom in plain English. +. Linked from the issue: the relevant ADR(s) and any related issues. +. For *security*-relevant findings, see + link:./../SECURITY.md[SECURITY.md] — do **not** file public issues. + +== Where to look for more help + +* `docs/architecture.adoc` — internals. +* `docs/decisions/` — historical record of why things are this shape. +* `https://github.com/hyperpolymath/Dicta-Task/issues?q=is%3Aissue+` — has anyone hit this before? +* `https://github.com/hyperpolymath/standards/issues` — for estate-wide problems. diff --git a/docs/usage.adoc b/docs/usage.adoc new file mode 100644 index 0000000..e338aac --- /dev/null +++ b/docs/usage.adoc @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += Usage — Dicta-Task +:revdate: 2026-MM-DD + +== Audience + +This document is for *consumers* of `Dicta-Task` — people who depend +on it, call it, or include it. For developers working *on* it, see +link:./contributing.adoc[contributing.adoc]. + +== Quickstart + +The shortest path from zero to a working call: + +[source,bash] +---- +# 1. Install +just install # or: cargo install --path . / task install + +# 2. Configure +cp examples/config.example.toml ./config.toml +# Edit minimal required fields. + +# 3. Run +just run # or the equivalent for your language +---- + +Expected output: + +[source] +---- +Dicta-Task v0.0.0 +Listening on 127.0.0.1:8080 +---- + +== Common use cases + +For each canonical use case, give: + +. A one-line statement of the goal. +. The minimal invocation. +. Expected output / side effect. + +=== Use case 1: + +(Replace with real content.) + +=== Use case 2: + +== Configuration reference + +Document every configurable field. Keep this section authoritative — if +the code grows a new option, this table must grow too (CI can be wired +to enforce this). + +[cols="1,1,1,3", options="header"] +|=== +| Field | Type | Default | Meaning + +| `` | `` | `` | +|=== + +== Stability guarantees + +Be explicit: + +* **Stable**: API surfaces that follow SemVer (breaking change = major bump). +* **Unstable**: behind a flag / pre-1.0 / explicitly marked. +* **Internal**: documented for reference but no compatibility promise. + +== Limits & known constraints + +* Maximum supported ``: ``. +* `` is not yet implemented; track at issue `#`. +* On ``, `` behaves differently because ``. + +== See also + +* link:./architecture.adoc[Architecture] — how it works internally. +* link:./troubleshooting.adoc[Troubleshooting] — when things go wrong. diff --git a/docs/whitepapers/0.2-AI-MANIFEST.a2ml b/docs/whitepapers/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index c936101..0000000 --- a/docs/whitepapers/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "whitepapers-track" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Unit for strategic publications and whitepapers. Categorised by target - audience: Academic, Industry, and Outreach. - -canonical_locations: - academic: "academic/" - industry: "industry/" - outreach: "outreach/" - -invariants: - - "Each sub-track MUST have a clear audience definition in its README" diff --git a/docs/whitepapers/README.adoc b/docs/whitepapers/README.adoc index 88e83c5..36d9f90 100644 --- a/docs/whitepapers/README.adoc +++ b/docs/whitepapers/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = whitepapers Unit diff --git a/docs/whitepapers/academic/.gitkeep b/docs/whitepapers/academic/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/whitepapers/academic/0.3-AI-MANIFEST.a2ml b/docs/whitepapers/academic/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index ceb8a1e..0000000 --- a/docs/whitepapers/academic/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "academic-unit" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Academic logic at level 3. diff --git a/docs/whitepapers/academic/README.adoc b/docs/whitepapers/academic/README.adoc index 16c3f45..a050acd 100644 --- a/docs/whitepapers/academic/README.adoc +++ b/docs/whitepapers/academic/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Academic Logic diff --git a/docs/whitepapers/industry/.gitkeep b/docs/whitepapers/industry/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/whitepapers/industry/0.3-AI-MANIFEST.a2ml b/docs/whitepapers/industry/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 20156dd..0000000 --- a/docs/whitepapers/industry/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "industry-unit" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Industry logic at level 3. diff --git a/docs/whitepapers/industry/README.adoc b/docs/whitepapers/industry/README.adoc index 7bc7fcd..ca743a1 100644 --- a/docs/whitepapers/industry/README.adoc +++ b/docs/whitepapers/industry/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Industry Logic diff --git a/docs/whitepapers/outreach/0.3-AI-MANIFEST.a2ml b/docs/whitepapers/outreach/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index ed7e152..0000000 --- a/docs/whitepapers/outreach/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,16 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "whitepapers-track-outreach" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Documentation track for outreach, education, and general-audience - engagement. Focuses on accessibility and high-level conceptual clarity. - -invariants: - - "Language MUST be accessible to non-technical audiences" - - "Avoid deep technical jargon without providing clear definitions" diff --git a/docs/whitepapers/outreach/README.adoc b/docs/whitepapers/outreach/README.adoc index 8141463..6ccc80b 100644 --- a/docs/whitepapers/outreach/README.adoc +++ b/docs/whitepapers/outreach/README.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Outreach & Education :toc: preamble :icons: font diff --git a/docs/wikis/0.2-AI-MANIFEST.a2ml b/docs/wikis/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index f071ca8..0000000 --- a/docs/wikis/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,15 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "wikis-track" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Long-form collaborative documentation and project knowledge base. - This directory mirrors the content structure of the project wiki. - -invariants: - - "Primary wiki format MUST be AsciiDoc (.adoc)" diff --git a/docs/wikis/README.adoc b/docs/wikis/README.adoc deleted file mode 100644 index 71b60d1..0000000 --- a/docs/wikis/README.adoc +++ /dev/null @@ -1,15 +0,0 @@ -= Project Wikis -:toc: preamble -:icons: font - -This directory contains the source files for the project wiki. It is intended for long-form documentation, deep-dives, and community-maintained knowledge. - -== Structure - -* **Core Concepts:** Fundamental architectural ideas. -* **Workflows:** Step-by-step guides for contributors. -* **Glossary:** Definitions of project-specific terminology. - -== Wiki Synchronization - -Changes made here should be synchronised with the forge-hosted wiki (GitHub/GitLab) using the project's sync scripts. diff --git a/docs/wikis/README.md b/docs/wikis/README.md new file mode 100644 index 0000000..f15c7cf --- /dev/null +++ b/docs/wikis/README.md @@ -0,0 +1,17 @@ + +# Project Wikis + +This directory contains the source files for the project wiki. It is intended for long-form documentation, deep-dives, and community-maintained knowledge. + +## Structure + +- **Core Concepts:** Fundamental architectural ideas. +- **Workflows:** Step-by-step guides for contributors. +- **Glossary:** Definitions of project-specific terminology. + +## Wiki Synchronization + +Changes made here should be synchronised with the forge-hosted wiki (GitHub/GitLab) using the project's sync scripts. diff --git a/eclexiaiser.toml b/eclexiaiser.toml deleted file mode 100644 index f6d9805..0000000 --- a/eclexiaiser.toml +++ /dev/null @@ -1,19 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# eclexiaiser manifest for dictask - -[project] -name = "dictask" - -[[functions]] -name = "main" -source = "src/main.rs" -energy-budget-mj = 15.0 - -[carbon] -provider = "static" -region = "GB" -static-intensity = 200.0 - -[report] -format = "text" -include-recommendations = true diff --git a/examples/0.1-AI-MANIFEST.a2ml b/examples/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index 0d69c90..0000000 --- a/examples/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1 +0,0 @@ -# AI Manifest - Level 1: examples diff --git a/examples/README.adoc b/examples/README.adoc index b9cdb48..f0bf52e 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = examples Pillar diff --git a/examples/web-project-deno.json b/examples/web-project-deno.json deleted file mode 100644 index ee775a4..0000000 --- a/examples/web-project-deno.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "// NOTE": "Example deno.json for AffineScript web projects", - "tasks": { - "build": "deno run -A npm:affinescript", - "clean": "deno run -A npm:affinescript clean", - "watch": "deno run -A npm:affinescript -w", - "serve": "deno run -A jsr:@std/http/file-server .", - "test": "deno test --allow-all" - }, - "imports": { - "affinescript": "^12.0.0", - "@affinescript/core": "npm:@affinescript/core@^1.6.0", - "safe-dom/": "https://raw.githubusercontent.com/hyperpolymath/affinescript-dom-mounter/main/src/", - "proven/": "../proven/bindings/affinescript/src/" - }, - "compilerOptions": { - "allowJs": true, - "checkJs": false - } -} diff --git a/features/0.1-AI-MANIFEST.a2ml b/features/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index dc3e4ee..0000000 --- a/features/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "features-pillar" -level: 1 -parent: "../0-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Optional project features and ecosystem integrations. Provides bootstrap - guides for high-rigor tools (Panic-Attacker, BoJ-Server, SSGs). - -canonical_locations: - panic_attacker: "panic-attacker/" - boj_server: "boj-server/" - ssg: "ssg/" diff --git a/features/README.adoc b/features/README.adoc index 3899280..5c24f38 100644 --- a/features/README.adoc +++ b/features/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Project Features diff --git a/features/boj-server/0.2-AI-MANIFEST.a2ml b/features/boj-server/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index c77798c..0000000 --- a/features/boj-server/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "feature-unit-boj-server" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Bootstrap and integration logic for the boj-server ecosystem component. diff --git a/features/boj-server/README.adoc b/features/boj-server/README.adoc index 0039c37..25f08a1 100644 --- a/features/boj-server/README.adoc +++ b/features/boj-server/README.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = BoJ Server Integration :icons: font diff --git a/features/panic-attacker/0.2-AI-MANIFEST.a2ml b/features/panic-attacker/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index e61ad24..0000000 --- a/features/panic-attacker/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "feature-unit-panic-attacker" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Bootstrap and integration logic for the panic-attacker ecosystem component. diff --git a/features/panic-attacker/README.adoc b/features/panic-attacker/README.adoc index 72d56a4..deff9f1 100644 --- a/features/panic-attacker/README.adoc +++ b/features/panic-attacker/README.adoc @@ -1,3 +1,5 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Panic Attacker Feature :icons: font diff --git a/features/ssg/0.2-AI-MANIFEST.a2ml b/features/ssg/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 121c5ae..0000000 --- a/features/ssg/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "feature-unit-ssg" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Bootstrap and integration logic for the ssg ecosystem component. diff --git a/features/ssg/README.adoc b/features/ssg/README.adoc index e15687b..a6063e1 100644 --- a/features/ssg/README.adoc +++ b/features/ssg/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Ssg Feature diff --git a/features/ssg/ssg-bootstrap.sh b/features/ssg/ssg-bootstrap.sh index 89c6fa5..25d23cb 100755 --- a/features/ssg/ssg-bootstrap.sh +++ b/features/ssg/ssg-bootstrap.sh @@ -3,40 +3,49 @@ # # ssg-bootstrap.sh — Universal SSG Initialisation Helper # -# Provides a starting hand for creating a documentation site or blog -# using hyperpolymath-approved formal or pretty-formal SSGs. +# Provides a starting hand for creating a documentation site or blog. +# Options 1-2 are hyperpolymath-maintained SSGs; options 3-5 are popular +# third-party choices. Use whichever fits your project. set -euo pipefail +DEST="${1:-docs/site}" + echo "═══════════════════════════════════════════════════" echo " SSG BOOTSTRAP HELPER" +echo " Target directory: $DEST" echo "═══════════════════════════════════════════════════" echo "" echo "Select an SSG to initialize in this project:" -echo " [1] Casket-SSG (Haskell) — Pretty-formal, high-rigor default" -echo " [2] Ddraig-SSG (Idris2) — Super-formal, dependent-type proofed" -echo " [3] Serum-SSG (Elixir) — Concurrent, robust, BEAM-based" -echo " [4] Zola (Rust) — Fast, standalone, standard" +echo " [1] Casket-SSG (Haskell) — hyperpolymath, pretty-formal" +echo " [2] Ddraig-SSG (Idris2) — hyperpolymath, dependent-type proofed" +echo " [3] Serum (Elixir) — BEAM-based, concurrent" +echo " [4] Zola (Rust) — Fast, standalone, standard" +echo " [5] Custom Git URL — Any SSG from a git repository" echo "" -read -rp "Enter choice [1-4]: " choice +read -rp "Enter choice [1-5]: " choice case "$choice" in 1) echo "Selected: Casket-SSG" - echo "Integration: git clone https://github.com/hyperpolymath/casket-ssg docs/site" + echo "Run: git clone https://github.com/hyperpolymath/casket-ssg $DEST" ;; 2) echo "Selected: Ddraig-SSG" - echo "Integration: git clone https://github.com/hyperpolymath/ddraig-ssg docs/site" + echo "Run: git clone https://github.com/hyperpolymath/ddraig-ssg $DEST" ;; 3) - echo "Selected: Serum-SSG" - echo "Integration: mix serum.new docs/site" + echo "Selected: Serum" + echo "Run: mix serum.new $DEST" ;; 4) echo "Selected: Zola" - echo "Integration: zola init docs/site" + echo "Run: zola init $DEST" + ;; + 5) + read -rp "Git URL: " custom_url + echo "Run: git clone $custom_url $DEST" ;; *) echo "Invalid selection. Aborting." @@ -44,5 +53,38 @@ case "$choice" in ;; esac -echo "" -echo "Note: For more advanced polystack options, visit: https://github.com/hyperpolymath/polystack" +echo "═══════════════════════════════════════════════════" +echo " SCAFFOLDING SECURITY & RESOURCE DIRECTORIES" +echo "═══════════════════════════════════════════════════" +REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +WWW="$REPO_ROOT/www" + +mkdir -p "$DEST/security_headers" +mkdir -p "$DEST/.well-known" +mkdir -p "$DEST/resource_records" + +# Canonical sources live in www/ (issue #53). Copy from there when present; +# the inline defaults below remain for repositories without the bundle. +if [ -d "$WWW/.well-known" ]; then + cp -r "$WWW/.well-known/." "$DEST/.well-known/" + echo " .well-known: copied from canonical www/.well-known/" +fi +if [ -d "$WWW/dns/records" ]; then + cp -r "$WWW/dns/records/." "$DEST/resource_records/" + echo " resource records: copied from www/dns/records/" +fi +if [ -f "$WWW/security_headers/csp.conf" ]; then + cp "$WWW/security_headers/csp.conf" "$DEST/security_headers/csp.conf" + echo " security headers: copied from www/security_headers/csp.conf" +else +cat << 'EOF' > "$DEST/security_headers/csp.conf" +Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests; +X-Content-Type-Options: nosniff +X-Frame-Options: DENY +X-XSS-Protection: 1; mode=block +Referrer-Policy: strict-origin-when-cross-origin +Strict-Transport-Security: max-age=31536000; includeSubDomains; preload +EOF +fi + +echo "Scaffolding complete. Please ensure these directories are copied to your site's output root." diff --git a/k9iser.toml b/k9iser.toml deleted file mode 100644 index 2902735..0000000 --- a/k9iser.toml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# k9iser manifest for dictask -# Generates K9 contracts from repo configuration files. -# dictask is a dictation-aware task manager with Rust crates in src/ - -[project] -name = "dictask" -safety_tier = "hunt" - -[[source]] -path = "Justfile" -type = "justfile" -output = "generated/k9iser/justfile-recipes.k9" - -[[source]] -path = "Containerfile" -type = "containerfile" -output = "generated/k9iser/container-build.k9" - -[[source]] -path = "selur-compose.toml" -type = "nickel" -output = "generated/k9iser/selur-service.k9" - -[[source]] -path = ".github/workflows/hypatia-scan.yml" -type = "workflow" -output = "generated/k9iser/ci-security.k9" - -[[source]] -path = ".github/workflows/codeql.yml" -type = "workflow" -output = "generated/k9iser/ci-codeql.k9" - -[[constraint]] -rule = "build.dependencies has no banned_packages" -severity = "error" - -[[constraint]] -rule = "container.base_image uses chainguard or distroless" -severity = "warn" - -[[constraint]] -rule = "workflows includes hypatia-scan" -severity = "error" - -[[constraint]] -rule = "services.app.healthcheck is defined" -severity = "warn" diff --git a/mise.toml b/mise.toml index 6dd983f..02ec7f2 100644 --- a/mise.toml +++ b/mise.toml @@ -1,3 +1,12 @@ +# Every tool name below was checked against `mise registry` AND resolved with +# `mise ls-remote` before being written. The previous version of this file named +# 13 tools that do not exist in the registry, so mise emitted ~25 lines of +# warnings on every shell entry while installing none of them. +# +# This file is a TEMPLATE. Repos minted from it inherit it verbatim, so add +# your project's actual language toolchain here after minting — pinned to the +# same versions your CI uses, so local and CI agree. + [tools] # Language runtimes node = "latest" @@ -7,20 +16,16 @@ go = "latest" zig = "latest" java = "latest" bun = "latest" -denojs = "latest" +deno = "latest" # was `denojs` — not a registry name -# Package managers +# Package managers / task runner npm = "latest" yarn = "latest" pnpm = "latest" -pip = "latest" -cargo = "latest" -go-task = "latest" +just = "latest" # the estate's task runner (Justfile) -# Formatting & Linting -gofmt = "latest" +# Formatting & linting black = "latest" -isort = "latest" ruff = "latest" prettier = "latest" shfmt = "latest" @@ -31,27 +36,53 @@ cmake = "latest" make = "latest" ninja = "latest" -# Shell tools -git = "latest" -gnu-sed = "latest" -gnu-tar = "latest" -gnu-grep = "latest" - -# Testing -vitest = "latest" -pytest = "latest" -jest = "latest" +# --------------------------------------------------------------------------- +# REMOVED — none of these resolve in the mise registry, so they installed +# nothing and only produced warnings. Grouped by why they were wrong: +# +# Already provided by a tool that IS listed above: +# cargo -> ships with `rust` +# gofmt -> ships with `go` +# pip -> ships with `python` +# +# Project-level dependencies, not system tools. These belong in +# package.json / pyproject.toml / mix.exs, not in a toolchain manager: +# vitest, jest, pytest, isort +# +# Base system binaries that mise should not be shadowing on Linux: +# git, gnu-sed, gnu-grep, gnu-tar +# (If a GNU-vs-BSD coreutils difference ever actually bites on macOS, add +# `coreutils`, which does exist in the registry, rather than these names.) +# +# Task runner that never installed: +# go-task -> not a registry name. NB the bare name `task` does not work +# either: `mise registry` LISTS it as `aqua:go-task/task`, but resolving +# `task@latest` still fails. Appearing in `mise registry` output is NOT +# sufficient to conclude a name resolves — check with `mise ls-remote`. +# `just` is listed above instead. +# --------------------------------------------------------------------------- [env] -# Common environment variables NODE_ENV = "development" PYTHONDONTWRITEBYTECODE = "1" PYTHONUNBUFFERED = "1" -# Task runner alias -[alias] -task = "go-task" -build = "cargo build --release || npm run build || go build" -test = "cargo test || npm test || go test ./..." -lint = "ruff check . || prettier --check . || black --check ." -fmt = "ruff format . || prettier --write . || black ." +# --------------------------------------------------------------------------- +# REMOVED — an `[alias]` section holding what were clearly meant to be tasks: +# +# [alias] +# task = "go-task" +# build = "cargo build --release || npm run build || go build" +# test = "cargo test || npm test || go test ./..." +# lint = "ruff check . || prettier --check . || black --check ." +# fmt = "ruff format . || prettier --write . || black ." +# +# mise's `[alias]` maps an alias to a TOOL PLUGIN — it does not define tasks, +# so none of these ever ran. mise also warns that `[alias]` is deprecated in +# favour of `[tool_alias]`. +# +# They are not re-added as `[tasks]`: the estate drives builds through the +# Justfile, and a second task runner sharing the verbs `build`/`test`/`lint`/ +# `fmt` — one of which silently falls through `||` chains into a different +# language's build — is worse than one. Use `just `. +# --------------------------------------------------------------------------- diff --git a/scripts/campaigns/www-bundle.campaign b/scripts/campaigns/www-bundle.campaign new file mode 100644 index 0000000..5abc73a --- /dev/null +++ b/scripts/campaigns/www-bundle.campaign @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: MPL-2.0 +# +# www-bundle.campaign — propagate the canonical www/ test suite and runbooks. +# +# THE GAP THIS CLOSES +# +# Issue #119 stage 5 moved every estate repository's root .well-known/ to +# www/.well-known/. What it could not do was bring the rest of the bundle: +# +# "no www/tests bundle" — all 262 sweep results +# 0/269 estate repositories carry any www/ tree +# +# so repositories ended up with a www/.well-known/ directory and nothing that +# knows how to check it. This campaign carries the checks and the runbooks that +# operate them. +# +# WHY IT IS SCOPED TO TWO DIRECTORIES AND NOT ALL OF www/ +# +# Measured 2026-09-18 by applying the suite to a real post-migration checkout +# (Axiom.jl at chore/well-known-to-www): +# +# www/tests/ alone -> 3 of 6 checks FAIL +# www/tests/ + guards -> 0 failures, 3 explicit SKIPs +# +# The failures were checks whose INPUTS the bundle does not supply: +# check-publication-boundary.sh wants www/schemas/ + www/webservers/, +# check-aibdp.sh wants www/.well-known/aibdp.json, check-migration.sh wants the +# template-side migrator. Those guards now exist, so the suite states that it +# does not apply instead of failing where it does not apply. +# +# The rest of www/ is deliberately NOT propagated: +# +# www/public/ per-repo placeholders (Dicta-Task and friends) +# www/policies/ per-repo placeholders +# www/dns/ a DNS scaffold most repositories do not operate +# www/tls/ as above +# www/webservers/ as above +# www/schemas/ only meaningful alongside the servers above +# www/profiles/ as above +# www/.well-known/ per-repo CONTENT, already migrated by stage 5 — the +# spine's copy holds placeholders and would overwrite +# every repository's real security.txt contact +# +# Carrying those would re-plant template identity in 262 repositories at once. +# They are a separate campaign with per-repo substitution, and they need their +# own decision and their own review. +# +# The two directories here contain no non-meta {{TOKEN}} at all, so every file +# travels verbatim. rsr-campaign.sh still checks, so this manifest cannot +# silently become untrue. + +[meta] +name = chore/rsr-www-bundle +description = canonical www/ test suite + operational runbooks (issue #119 follow-on) + +[paths] +www/tests/ +www/runbooks/ diff --git a/scripts/check-action-pinning.js b/scripts/check-action-pinning.js new file mode 100644 index 0000000..80533f6 --- /dev/null +++ b/scripts/check-action-pinning.js @@ -0,0 +1,105 @@ +#!/usr/bin/env bun +// SPDX-License-Identifier: MPL-2.0 +// +// Assert every workflow action ref is pinned — inline SHA, or via actions.lock. +// +// What counts as "pinned" changed when GitHub introduced workflow lockfiles. A +// ref is pinned if EITHER it is an inline 40-hex SHA, OR +// .github/workflows/actions.lock resolves it to one. The previous inline-only +// rule rejected all 40 of this repo's own refs, so `Workflow Security Linter` +// was red on main permanently — and every repo minted from this template +// inherited both the tag-form workflows and the gate that rejects them. +// +// Runtime: Bun — the estate's first-choice runtime per LANGUAGE-POLICY.adoc §1 +// (Bun > Deno > pnpm > npm). Plain JavaScript, not TypeScript: TypeScript is +// banned estate-wide (owner ruling; CLAUDE.md banned-languages table). Note +// LANGUAGE-POLICY.adoc §1.2 currently claims TS is "permitted under Bun" — that +// line is wrong and should be corrected; the ban stands. +// +// Python was the first draft of this script and is banned with no exceptions. +// +// Three details are load-bearing, each of them a defect found by testing: +// +// `-?` in the pattern. `- uses:` is the list-item form and by far the most +// common; a `\s+uses:` pattern silently misses every one of them. Estate +// memory records this exact failure — "green linter != full SHA-pinning". A +// gate that cannot see the common case reads as coverage and enforces nothing. +// +// Case-insensitive comparison. Workflows write `SonarSource/...`; the lockfile +// records `sonarsource/...`. Action refs are case-insensitive in practice, so +// a case-sensitive match reports a false positive on a correctly-pinned action. +// +// Only *.yml / *.yaml, depth 1. A bare recursive grep also reads actions.lock +// (whose `uses:` keys are lockfile entries, not refs) and *.yml.template +// (whose tag ref is deliberate and resolved at mint time). Both were reported +// as unpinned, which made the gate unsatisfiable the moment a lockfile existed. +// +// Exit 0 if every ref is pinned, 1 otherwise, listing file:line: ref. + +import { readdirSync, readFileSync, existsSync, statSync } from "node:fs"; +import { join } from "node:path"; + +const WF = ".github/workflows"; +const LOCK = join(WF, "actions.lock"); +// `-?` matches the list-item form; see the header. +const USES = /^\s*-?\s*uses:\s*([^\s#]+)/; +const SHA = /@[a-f0-9]{40}$/; +const EXEMPT_PREFIX = ["./", "$", "docker://"]; + +function workflowFiles() { + if (!existsSync(WF) || !statSync(WF).isDirectory()) return []; + return readdirSync(WF) + .filter((f) => f.endsWith(".yml") || f.endsWith(".yaml")) + .sort() + .map((f) => join(WF, f)); +} + +function main() { + if (!existsSync(WF)) { + console.log("no .github/workflows — nothing to check"); + return 0; + } + + let known = new Set(); + if (existsSync(LOCK)) { + const lock = readFileSync(LOCK, "utf8"); + known = new Set( + [...lock.matchAll(/'([^']+@[^']+)'/g)].map((m) => m[1].toLowerCase()), + ); + console.log(`lockfile present: ${known.size} ref(s) resolvable through it`); + } else { + console.log("no lockfile — every ref must be an inline 40-character SHA"); + } + + const bad = []; + for (const wf of workflowFiles()) { + const lines = readFileSync(wf, "utf8").split("\n"); + lines.forEach((line, i) => { + const m = USES.exec(line); + if (!m) return; + const ref = m[1]; + if (EXEMPT_PREFIX.some((p) => ref.startsWith(p))) return; + if (ref.includes("actions/github-script")) return; + if (SHA.test(ref)) return; + const at = ref.lastIndexOf("@"); + const name = at === -1 ? ref : ref.slice(0, at); + const tag = at === -1 ? "" : ref.slice(at + 1); + // subpath actions key by repo root: github/codeql-action/init -> github/codeql-action + const root = name.split("/").slice(0, 2).join("/"); + if (known.has(ref.toLowerCase()) || known.has(`${root}@${tag}`.toLowerCase())) return; + bad.push(`${wf}:${i + 1}: ${ref}`); + }); + } + + if (bad.length) { + console.log("\nERROR: these action refs are neither SHA-pinned nor covered by the lockfile:"); + for (const b of bad) console.log(` ${b}`); + console.log("\nEither pin to a full commit SHA, or run `gh actions-lock` so the"); + console.log("lockfile resolves the ref."); + return 1; + } + console.log("all action refs are pinned"); + return 0; +} + +process.exit(main()); diff --git a/scripts/check-adoc-renders.sh b/scripts/check-adoc-renders.sh new file mode 100755 index 0000000..4f8fe4f --- /dev/null +++ b/scripts/check-adoc-renders.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Every tracked .adoc file must parse. Asciidoctor's --failure-level DEFAULTS TO +# FATAL, so a document that emits WARNING or ERROR to stderr still exits 0 -- +# which is why every gate in this estate that shelled out to asciidoctor was +# unfailable. The cure is the FLAG, not a wrapper, plus a three-way verdict: +# +# exit 0 + empty stderr -> the document rendered clean +# exit 0 + stderr output -> a finding (this is the case bare asciidoctor hides) +# non-zero -> a finding, or the run did not complete +# +# This is the same contract empty-linter ships. The two are complementary halves +# of document integrity: empty-linter's remit is invisible bytes, this gate's +# remit is whether the document parses at all. +# +# Exit codes: +# 0 all tracked .adoc rendered clean (or there are none) +# 1 at least one file emitted a diagnostic or failed to render +# 2 the check could not run (bad path, asciidoctor not installed) + +set -euo pipefail + +REPO_ROOT="${1:-.}" +if [ ! -d "$REPO_ROOT" ]; then + echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 + exit 2 +fi + +if ! command -v asciidoctor >/dev/null 2>&1; then + echo "ERROR: asciidoctor is not installed; cannot verify .adoc rendering." >&2 + echo " Install it with: gem install asciidoctor -v 2.0.26 --no-document" >&2 + echo " Failing closed: 'did not complete' is not 'clean'." >&2 + exit 2 +fi + +# Subject list read NUL-delimited so a path containing a space cannot split. +FILES=() +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + while IFS= read -r -d '' f; do + FILES+=("$REPO_ROOT/$f") + done < <(git -C "$REPO_ROOT" ls-files -z -- '*.adoc' 2>/dev/null || true) +else + while IFS= read -r -d '' f; do + FILES+=("$f") + done < <(find "$REPO_ROOT" -type f -name '*.adoc' \ + -not -path '*/.git/*' -not -path '*/node_modules/*' -print0 2>/dev/null || true) +fi + +# An empty subject list is a PASS, not a failure. This is a TEMPLATE: a stripped +# mint may legitimately carry no .adoc yet, and the house idiom for an absent +# subject is check-no-md-in-docs.sh's "no docs/ directory (nothing to check)". +if [ "${#FILES[@]}" -eq 0 ]; then + echo "PASS: no tracked .adoc (nothing to check)" + exit 0 +fi + +FAILED=0 +FINDINGS="" +for f in "${FILES[@]}"; do + set +e + err="$(asciidoctor --failure-level=WARN --backend=html5 -o /dev/null "$f" 2>&1 >/dev/null)" + rc=$? + set -e + if [ "$rc" -ne 0 ] || [ -n "$err" ]; then + FAILED=$((FAILED + 1)) + first="$(printf '%s\n' "$err" | sed -n '1p')" + [ -n "$first" ] || first="(no diagnostic; exit $rc)" + FINDINGS="${FINDINGS} ${f#"$REPO_ROOT"/}: ${first}"$'\n' + fi +done + +if [ "$FAILED" -eq 0 ]; then + echo "PASS: all ${#FILES[@]} tracked .adoc file(s) render clean" + exit 0 +fi + +echo "FAIL: $FAILED of ${#FILES[@]} tracked .adoc file(s) did not render clean:" >&2 +printf '%s' "$FINDINGS" >&2 +echo >&2 +echo "Repair the document so asciidoctor emits nothing on stderr. Common causes:" >&2 +echo " * a Markdown body inside a .adoc: '# H' parses as a level-0 section" >&2 +echo " * an attribute line flush against '= Title', swallowed into the header" >&2 +echo " * a repeated [[anchor]] in prose, processed even inside backticks" >&2 +echo " * a stray '|' inside a table, shifting the cell count" >&2 +echo "Reproduce one file with:" >&2 +echo " asciidoctor --failure-level=WARN --backend=html5 -o /dev/null " >&2 +exit 1 diff --git a/scripts/check-invisible-characters.sh b/scripts/check-invisible-characters.sh new file mode 100755 index 0000000..8cc49fc --- /dev/null +++ b/scripts/check-invisible-characters.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Byte-safe scanner for invisible Unicode encodings and forbidden C0 controls. +set -u + +scan_root="${1:-}" +results_file="${2:-}" +blocking_results_file="${3:-}" +grep_bin="${INVISIBLE_GREP_BIN:-grep}" +find_bin="${INVISIBLE_FIND_BIN:-find}" + +if [[ -z "$scan_root" || ! -d "$scan_root" || -z "$results_file" ]]; then + echo "usage: $0 SCAN_ROOT RESULTS_FILE" >&2 + exit 2 +fi + +# Scan bytes under the C locale. This detects UTF-8 encodings even when another +# byte in the file is invalid UTF-8, while excluding permitted TAB/LF/CR bytes. +pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]|\xC2(?:\xA0|\xAD)|\xE2\x80[\x8B-\x8F\xAA-\xAF]|\xE2\x81(?:\xA0|[\xA6-\xA9])|\xEF\xBB\xBF' +blocking_pattern='[\x00-\x08\x0B\x0C\x0E-\x1F]' +: > "$results_file" || exit 2 +if [[ -n "$blocking_results_file" ]]; then + : > "$blocking_results_file" || exit 2 +fi +scan_error=0 +enumeration_file="$(mktemp)" || exit 2 # TMPDIR-respecting; Hypatia hardcoded_tmp (alert #122) +# Invoked indirectly by the EXIT trap. +# shellcheck disable=SC2329 +cleanup() { + rm -f -- "$enumeration_file" +} +trap cleanup EXIT + +if ! "$find_bin" "$scan_root" \ + -not -path '*/.git/*' -not -path '*/node_modules/*' \ + -not -path '*/.deno/*' -not -path '*/target/*' \ + -not -path '*/_build/*' -not -path '*/deps/*' \ + -not -path '*/external_corpora/*' -not -path '*/.lake/*' \ + -type f \( -name '*.rs' -o -name '*.ex' -o -name '*.exs' -o -name '*.res' \ + -o -name '*.js' -o -name '*.ts' -o -name '*.json' -o -name '*.toml' \ + -o -name '*.yml' -o -name '*.yaml' -o -name '*.md' -o -name '*.adoc' \ + -o -name '*.idr' -o -name '*.zig' -o -name '*.v' -o -name '*.jl' \ + -o -name '*.gleam' -o -name '*.hs' -o -name '*.ml' -o -name '*.sh' \ + -o -name '*.a2ml' -o -name '*.txt' -o -name '*.just' \ + -o -name 'Justfile' -o -name 'Mustfile' -o -name 'Trustfile' -o -name 'Bustfile' \) \ + -print0 > "$enumeration_file"; then + echo "file enumeration failed: $scan_root" >&2 + exit 1 +fi + +while IFS= read -r -d '' filepath; do + LC_ALL=C "$grep_bin" -aPq "$pattern" "$filepath" + status=$? + case "$status" in + 0) + printf '%s\0' "$filepath" >> "$results_file" || scan_error=1 + if [[ -n "$blocking_results_file" ]]; then + LC_ALL=C "$grep_bin" -aPq "$blocking_pattern" "$filepath" + blocking_status=$? + case "$blocking_status" in + 0) printf '%s\0' "$filepath" >> "$blocking_results_file" || scan_error=1 ;; + 1) ;; + *) echo "blocking-classifier error ($blocking_status): $filepath" >&2; scan_error=1 ;; + esac + fi + ;; + 1) ;; + *) echo "scanner error ($status): $filepath" >&2; scan_error=1 ;; + esac +done < "$enumeration_file" + +exit "$scan_error" diff --git a/scripts/check-lock-sync.sh b/scripts/check-lock-sync.sh new file mode 100755 index 0000000..3088bbd --- /dev/null +++ b/scripts/check-lock-sync.sh @@ -0,0 +1,297 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-lock-sync.sh — verify .github/workflows/actions.lock is in sync with the +# workflow YAML, in BOTH directions (including job-level reusable-workflow refs), +# AND that the lockfile is TRANSITIVELY CLOSED. +# +# Three clauses, each of which alone is insufficient: +# +# 1. every `uses:` in a workflow is locked under THAT workflow's own path; +# 2. every lockfile entry is still referenced by its workflow (no orphans); +# 3. every ref NAMED anywhere in the lockfile resolves to a top-level +# `dependencies:` record — the lockfile has no dangling edges. +# +# Clause 3 is not decoration. It is the clause that catches the failure mode that +# clauses 1 and 2 are structurally blind to, and it was added only after that +# blindness was measured. On hyperpolymath/cicd-squabbler, 2026-09-22: +# +# commit dangling-edge class result +# fe22bbc workflows: -> dependencies: (ref listed, no record) 4 workflows startup_failure, jobs=0 +# cfadcf9 dependencies: -> dependencies: (record added, its +# own nested uses: unrecorded) the same 4 still startup_failure +# 5286aa5 none - transitively closed 0 startup_failure, all 17 runs create jobs +# +# At fe22bbc AND cfadcf9 this script exited 0, `gh actions-lock --verify-local` +# exited 0, and the Lock Sync Gate reported green - while GitHub was refusing to +# start four workflows. Every local gate was green on a fatal commit. That is the +# guard/consumer trap: the gate asked "is every uses: locked?" and GitHub asks +# "is every locked ref RESOLVABLE?". +# +# The asymmetry that makes clause 3 mandatory, and counter-intuitive: +# * a job-level ref ABSENT from the lockfile entirely is HARMLESS; +# * a ref PRESENT in the lockfile but unresolvable is FATAL. +# So adding entries without closing them is strictly worse than adding nothing. +# Clause 1 demands entries be added; only clause 3 makes that demand safe. Shipping +# clause 1 without clause 3 actively steers a developer into the fatal state: +# Dependabot bumps a job-level ref -> clause 1 reds -> `gh actions-lock` is blind to +# job-level refs and will not backfill -> the developer hand-adds the workflows: +# entry to get green -> no dependencies: record -> CI dies silently, gate green. +# +# Exit 0 only when all three clauses hold. Any violation exits 1. There is no +# warn-only mode: a desync means GitHub refuses to start the run, so it must fail +# the job. A `::warning::` cannot fail a job and would be a vacuous gate. + +set -euo pipefail + +WF_DIR="${1:-.github/workflows}" +LOCK="$WF_DIR/actions.lock" + +# gawk is required: the parser uses 3-argument match(), a GNU extension. mawk +# (the Debian/Ubuntu default `awk`) does not support it, and a silent parse +# failure here would read as a clean pass - the exact failure mode this script +# exists to prevent. Probe it rather than trusting the name. +AWK="" +for cand in gawk awk; do + if command -v "$cand" >/dev/null 2>&1 \ + && echo x | "$cand" '{ if (match($0, /(x)/, m) && m[1] == "x") exit 0; exit 1 }' 2>/dev/null; then + AWK="$cand"; break + fi +done +if [ -z "$AWK" ]; then + echo "check-lock-sync: FATAL: no awk supporting 3-argument match() (need gawk)" >&2 + echo "check-lock-sync: install it with: sudo apt-get install -y gawk" >&2 + exit 1 +fi + +if [ ! -f "$LOCK" ]; then + echo "check-lock-sync: FATAL: no lockfile at $LOCK" >&2 + exit 1 +fi + +shopt -s nullglob +mapfile -t WORKFLOWS < <(printf '%s\n' "$WF_DIR"/*.yml "$WF_DIR"/*.yaml | sort -u) +if [ "${#WORKFLOWS[@]}" -eq 0 ]; then + echo "check-lock-sync: FATAL: no workflow files under $WF_DIR" >&2 + exit 1 +fi + +read -r -d '' PROG <<'AWK' || true +# owner/repo[/subpath...]@ref -> owner/repo@ref ("" if not an external ref) +function norm(r, at, path, ref, n, parts) { + at = 0 + for (n = length(r); n > 0; n--) { if (substr(r, n, 1) == "@") { at = n; break } } + if (at == 0) return "" + path = substr(r, 1, at - 1); ref = substr(r, at + 1) + if (path == "" || ref == "") return "" + if (substr(path, 1, 2) == "./" || substr(path, 1, 2) == "$/") return "" # local action + if (split(path, parts, "/") < 2) return "" + return parts[1] "/" parts[2] "@" ref +} + +# Fold case on the OWNER/REPO segment only, for comparison keys. GitHub resolves +# owner and repository names case-insensitively, and this is measured, not assumed: +# metadatastician/pong-ping's lockfile records sonarsource/sonarqube-scan-action@v8.2.1 +# while sonarqube.yml says SonarSource/..., and at commit cd5f90f that workflow ran +# SUCCESS while codeql.yml at the SAME commit was startup_failure. A same-commit +# control, so the case difference is provably not what kills a run. +# The REF is NOT folded: git tags and branch names are case-sensitive. +function ck(r, at, s) { + at = 0 + for (s = length(r); s > 0; s--) { if (substr(r, s, 1) == "@") { at = s; break } } + if (at == 0) return tolower(r) + return tolower(substr(r, 1, at - 1)) substr(r, at) +} + +# ---------- pass 1: the lockfile ---------- +FILENAME == lockfile { + if ($0 ~ /^workflows:[[:space:]]*$/) { inwf = 1; indep = 0; next } + if ($0 ~ /^dependencies:[[:space:]]*$/) { inwf = 0; indep = 1; next } + if ($0 ~ /^[a-z_]+:/) { inwf = 0; indep = 0; next } + + # --- the dependencies: section, for clause 3 --- + if (indep) { + # " 'owner/repo@ref':" -- a top-level dependency record + if (match($0, /^ '([^']+)':/, m)) { + depkey = m[1] + haverec[ck(depkey)] = 1; disp[ck(depkey)] = depkey + next + } + # " - 'owner/repo@ref'" -- a nested uses: of that record + if (match($0, /^ - '([^']+)'/, m) && depkey != "") { + r = ck(m[1]); disp[r] = m[1] + want[r] = 1 + wantsrc[r] = wantsrc[r] " dependencies:" depkey + next + } + next + } + + if (!inwf) next + + # " '.github/workflows/x.yml':" or "... : []" + if (match($0, /^ '([^']+)':/, m)) { + cur = m[1] + seen_path[cur] = 1 + next + } + if (match($0, /^ - '([^']+)'[[:space:]]*$/, m) && cur != "") { + lr = ck(m[1]); disp[lr] = m[1]; lock[cur, lr] = 1 + lockcount[cur]++ + want[lr] = 1 + wantsrc[lr] = wantsrc[lr] " " cur + next + } + next +} + +# ---------- pass 2: the workflow YAML ---------- +FNR == 1 { wf = FILENAME } +{ + line = $0 + sub(/[[:space:]]+#.*$/, "", line) # strip trailing comment + if (match(line, /^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*(.+)$/, m)) { + raw = m[1] + gsub(/^["']|["']$/, "", raw) + gsub(/[[:space:]]+$/, "", raw) + if (raw ~ /^\$\//) { dollar[wf] = dollar[wf] " " raw; next } # known corruption + n = norm(raw) + if (n != "") { uses[wf, ck(n)] = 1; useslist[wf] = useslist[wf] " " n } + } +} + +END { + bad = 0 + for (i = 1; i < ARGC; i++) { + wf = ARGV[i] + if (wf == lockfile) continue + key = wf + sub(/.*\//, "", key) + key = ".github/workflows/" key # the lockfile always uses this canonical path + + if (dollar[wf] != "") { + printf "FAIL %s\n invalid local-action rewrite (uses: $/...):%s\n", key, dollar[wf] + bad = 1 + } + + # --- clause 1: every uses: must be locked under THIS path --- + nu = split(useslist[wf], u, " ") + delete uniq; missing = "" + for (j = 1; j <= nu; j++) { + if (u[j] == "" || (u[j] in uniq)) continue + uniq[u[j]] = 1 + if (!((key SUBSEP ck(u[j])) in lock)) missing = missing " " u[j] + } + if (missing != "") { + if (!(key in seen_path)) + printf "FAIL %s\n not onboarded: no lockfile entry for this path\n unlocked refs:%s\n", key, missing + else + printf "FAIL %s\n refs missing from the lockfile:%s\n", key, missing + bad = 1 + } + + # --- clause 2: every lock entry must be referenced by this workflow --- + orphan = "" + for (k in lock) { + split(k, kp, SUBSEP) + if (kp[1] != key) continue + if (!((wf SUBSEP kp[2]) in uses)) orphan = orphan " " (kp[2] in disp ? disp[kp[2]] : kp[2]) + } + if (orphan != "") { + printf "FAIL %s\n stale lockfile entries, no uses: references them:%s\n", key, orphan + bad = 1 + } + } + + # --- lockfile entries for workflow files that no longer exist --- + for (p in seen_path) { + found = 0 + for (i = 1; i < ARGC; i++) { + q = ARGV[i]; if (q == lockfile) continue + sub(/.*\//, "", q); q = ".github/workflows/" q + if (q == p) { found = 1; break } + } + if (!found) { printf "FAIL %s\n lockfile entry for a workflow file that does not exist\n", p; bad = 1 } + } + + # --- clause 4: COVERAGE. Every workflow FILE must have a key in the lockfile, + # including one with no uses: at all - the value is then an empty list. + # MEASURED 2026-09-22, single-variable flip on two independent repos: + # hyperpolymath/verisimdb's lock-sync-gate.yml was startup_failure 7 times + # running with ZERO uses: refs, and adding + # '.github/workflows/lock-sync-gate.yml': [] + # flipped it to success; reproduced on hyperpolymath/blocky-writer, 2 of 2. + # `gh actions-lock` already emits this empty-list form for other zero-uses: + # workflows (labels.yml), so it is the generator's own convention, not ours. + # Clauses 1-3 CANNOT catch this: they ask "is every uses: locked?", and a + # workflow with no uses: satisfies them vacuously while GitHub still refuses + # to start it. 13 repos passed clauses 1-3 with exactly this gap. + nunlisted = 0; unlisted = "" + for (i = 1; i < ARGC; i++) { + q = ARGV[i]; if (q == lockfile) continue + sub(/.*\//, "", q); q = ".github/workflows/" q + if (q in seen_path) continue + nunlisted++; unlisted = unlisted "\n " q + } + if (nunlisted > 0) { + printf "FAIL actions.lock: UNLISTED WORKFLOWS\n" + printf " %d workflow file(s) have no key in the lockfile. GitHub refuses such a\n", nunlisted + printf " run at startup (jobs=0) even when the workflow has no uses: at all.\n" + printf " The entry for a zero-uses: workflow is an empty list:%s\n", unlisted + bad = 1 + } + + # --- clause 3: TRANSITIVE CLOSURE. Every ref named anywhere in the lockfile + # must resolve to a top-level dependencies: record. A dangling edge makes + # GitHub refuse the run at startup with jobs=0. --- + ndang = 0; dang = "" + for (r in want) { + if (r !~ /^[^\/]+\/[^\/@]+@/) continue # not an OWNER/REPO@REF pin; not ours to resolve + if (r in haverec) continue + ndang++ + dang = dang sprintf("\n %s\n named by:%s", (r in disp ? disp[r] : r), wantsrc[r]) + } + if (ndang > 0) { + printf "FAIL actions.lock: DANGLING EDGES\n" + printf " %d ref(s) are named in the lockfile but have no top-level dependencies: record.%s\n", ndang, dang + bad = 1 + } + + # --- a dependencies: record nothing names is dead weight, not fatal: report only --- + nunref = 0 + for (d in haverec) if (!(d in want)) nunref++ + + if (bad) { + print "" + print "actions.lock is OUT OF SYNC with the workflow YAML, or is not transitively closed." + print "GitHub refuses such a run at startup: zero jobs are created and the run" + print "reports \"This run likely failed because of a workflow file issue.\"" + print "" + print "Fix, in this order:" + print " 1. `gh actions-lock --no-migrate-local-actions`, then review the diff. It does" + print " NOT handle job-level reusable-workflow refs and it can de-pin bare SHAs to" + print " floating tags - both must be corrected by hand." + print " 2. For any DANGLING EDGES above, add a top-level `dependencies:` record for each" + print " ref. A leaf record may legally omit the nested `uses:` key entirely, so adding" + print " leaves introduces no new dangling edges and closure terminates in one pass." + print " Keys are sorted with LC_ALL=C collation (ASCII '-' 0x2d sorts before '@' 0x40)." + print " 3. Nested `uses:` entries must be bare OWNER/REPO@REF. A subpath pin such as" + print " github/codeql-action/upload-sarif@ is REJECTED by the schema; collapse it" + print " to github/codeql-action@." + print " 4. For any UNLISTED WORKFLOWS above, add the path as a lockfile key. A workflow" + print " with no uses: takes an empty list: \x27.github/workflows/x.yml\x27: []" + print " `gh actions-lock` has been observed to OMIT such a workflow entirely; that" + print " omission is itself the defect, so re-running the tool may not add it." + exit 1 + } + printf "actions.lock is in sync and transitively closed:\n" + printf " * every uses: is locked under its own workflow path (job-level reusable refs included)\n" + printf " * every lockfile entry is still referenced\n" + printf " * every ref named in the lockfile resolves to a dependencies: record (0 dangling edges)\n" + printf " * every workflow file has a lockfile key (zero-uses: workflows included)\n" + if (nunref > 0) + printf " note: %d dependencies: record(s) are unreferenced - harmless, but prunable.\n", nunref +} +AWK + +"$AWK" -v lockfile="$LOCK" "$PROG" "$LOCK" "${WORKFLOWS[@]}" diff --git a/scripts/check-no-md-in-docs.sh b/scripts/check-no-md-in-docs.sh new file mode 100644 index 0000000..1658f93 --- /dev/null +++ b/scripts/check-no-md-in-docs.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-no-md-in-docs.sh — enforce "AsciiDoc by default for general docs". +# +# Estate rule: .adoc for general docs (TOPOLOGY, READINESS, ROADMAP, etc.); +# .md only for files GitHub's community-health rules special-case by name +# (CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CHANGELOG, etc.) — those live at +# root or in .github/, never under docs/. +# +# Fails if any .md files exist under docs/. Add justified entries to the +# ALLOWED list below if a docs/-rooted .md is genuinely needed (rare). +# +# Exit codes: +# 0 — no .md files under docs/ (or all matches are allow-listed) +# 1 — disallowed .md files found +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" +DOCS_DIR="$REPO_ROOT/docs" + +# Justified exceptions, relative to repo root. Empty by default. +ALLOWED=() +# docs/berrywiki/ and docs/wikis/ are wiki-SYNC source trees: their content is +# mirrored to/from forge-hosted wikis (GitHub/GitLab), which are inherently +# Markdown. Converting them to AsciiDoc would break the sync contract, so the +# directories are allow-listed rather than the files. +ALLOWED_DIRS=("docs/berrywiki/" "docs/wikis/") + +if [ ! -d "$DOCS_DIR" ]; then + echo "PASS: no docs/ directory (nothing to check)" + exit 0 +fi + +mapfile -t HITS < <(find "$DOCS_DIR" -name '*.md' -type f 2>/dev/null | sort) + +EXTRAS=() +for hit in "${HITS[@]}"; do + rel="${hit#"$REPO_ROOT/"}" + skip=0 + for allowed in "${ALLOWED[@]}"; do + if [ "$rel" = "$allowed" ]; then skip=1; break; fi + done + for allowed_dir in "${ALLOWED_DIRS[@]}"; do + if [[ "$rel" == "$allowed_dir"* ]]; then skip=1; break; fi + done + if [ $skip -eq 0 ]; then EXTRAS+=("$rel"); fi +done + +if [ ${#EXTRAS[@]} -eq 0 ]; then + echo "PASS: no .md files under docs/ (${#HITS[@]} total found, ${#ALLOWED[@]} allow-listed)" + exit 0 +fi + +echo "FAIL: ${#EXTRAS[@]} .md files found under docs/ (estate rule: AsciiDoc by default):" >&2 +for e in "${EXTRAS[@]}"; do + echo " - $e" >&2 +done +echo "" >&2 +echo "Convert these to .adoc, or add a justified entry to the ALLOWED list" >&2 +echo "in scripts/check-no-md-in-docs.sh." >&2 +exit 1 diff --git a/scripts/check-no-placeholders.sh b/scripts/check-no-placeholders.sh new file mode 100755 index 0000000..27c41be --- /dev/null +++ b/scripts/check-no-placeholders.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-no-placeholders.sh — no repo may ship an unfilled {{PLACEHOLDER}}. +# +# Estate rule (methodology.a2ml: reject-if-contains): a token that `just repo-init` +# did not fill is debt, and in .github/settings.yml or SECURITY.md it is a +# defect with consequences — probot/settings applies settings.yml on every push, +# and a security policy that cites a key nobody holds is worse than one that +# says "email us". +# +# This is the single implementation of that rule, called from two places: +# * .github/workflows/openssf-compliance.yml — on the repo as committed +# * tests/e2e/template_instantiation_test.sh — on a freshly init'd repo, +# which is where a leak is still cheap to fix +# It exists as a script rather than inline shell in each caller because the +# previous split — a workflow that checked a hand-listed set of files, and an +# e2e test that re-implemented substitution with its own token list — let the +# two drift until the test passed while real instantiation leaked. +# +# Scans every text file and allow-lists the few legitimate carriers, rather +# than checking a list of files someone must remember to extend. The old +# required-files list omitted .github/settings.yml and ANCHOR.a2ml, which is +# precisely where the leaks were. +# +# Matches upper-snake brace tokens only. Justfiles are skipped entirely: an +# ARGS token there is just's own interpolation syntax, not a template token. +# GitHub Actions expressions are ${{ dotted.lower }} and do not match. +# +# Exit codes: +# 0 — no unfilled tokens (or this is a template repo, where tokens are the product) +# 1 — unfilled tokens found +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" + +if [ ! -d "$REPO_ROOT" ]; then + echo "usage: $0 [repo-root]" >&2 + exit 2 +fi + +# ─── settings.yml identity guard — runs EVERYWHERE, template repos included ──── +# +# This check deliberately precedes the template exemption below. That exemption +# is why the original incident went unseen: the gate skipped `*-template-repo` +# entirely, so nobody noticed that .github/settings.yml shipped `name: "dicta-task"` +# — and .github/settings.yml is not inert content in a template. probot/settings +# applies it on every push to the default branch, in the template as much as in +# an instantiation. The template submitted the literal `dicta-task` as its own +# name; GitHub collapsed the illegal braces to dashes and renamed the repository +# to `-REPO-`, which then read as a deleted repo. +# +# So: in this one file, a placeholder is never "the product". Neither is an +# identity key with a real value — `name`/`private` cannot be inherited by a +# child repo without being wrong (see the header of .github/settings.yml). +SETTINGS="$REPO_ROOT/.github/settings.yml" +if [ -f "$SETTINGS" ]; then + settings_fail=0 + + # Comment-aware: the file's own header documents the incident and has to be + # able to quote the offending token. Prose about a token is not a token — + # the same distinction META_TOKENS draws below. Line numbers are preserved + # by filtering `grep -n` output rather than the file. + settings_tokens="$(grep -nE '\{\{' "$SETTINGS" | grep -vE '^[0-9]+:[[:space:]]*#' || true)" + if [ -n "$settings_tokens" ]; then + echo "FAIL: .github/settings.yml contains an unrendered {{ token." >&2 + printf '%s\n' "$settings_tokens" | sed 's/^/ /' >&2 + settings_fail=1 + fi + + # Keys of the `repository:` map sit at exactly two spaces of indent. Label + # and branch entries are list items (" - name:") and are not matched. + if grep -qE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS"; then + echo "FAIL: .github/settings.yml declares repository identity." >&2 + grep -nE '^[[:space:]]{2}(name|description|homepage|private):' "$SETTINGS" \ + | sed 's/^/ /' >&2 + settings_fail=1 + fi + + if [ "$settings_fail" -ne 0 ]; then + echo "" >&2 + echo "probot/settings applies this file on every push to the default branch," >&2 + echo "so these keys are enforced, not described. Repository identity and" >&2 + echo "visibility are set out of band at creation time — by \`just repo-init\` via" >&2 + echo "\`gh\` for minted repos, and deliberately by the owner for the template." >&2 + exit 1 + fi +fi + +# A template repo's placeholders ARE its product — they are what `just repo-init` +# consumes. Any other repo is an instantiation and is checked in full. +# +# Identity comes from the git remote, not the directory name. +# +# This used to be `basename "$(pwd)"`, which is right in CI — GITHUB_REPOSITORY +# is set to `owner/dicta-task` and matches — but wrong anywhere the +# checkout is not literally named `*-template-repo`. A git worktree is the common +# case: `git worktree add .claude/worktrees/defects` gives basename `defects`, +# the exemption misses, and the gate reports every one of the template's ~85 +# deliberate placeholder files as a failure. A clone into `dicta-task-2`, +# or any renamed directory, does the same. +# +# The remote URL is the repo's actual identity and survives all of that. The +# basename remains as the last fallback for a checkout with no remote. +REPO_NAME="${GITHUB_REPOSITORY:-}" +if [ -z "$REPO_NAME" ]; then + REPO_NAME="$(git -C "$REPO_ROOT" config --get remote.origin.url 2>/dev/null \ + | sed -E 's#(\.git)?/?$##; s#^.*[:/]([^/]+/[^/]+)$#\1#')" +fi +[ -z "$REPO_NAME" ] && REPO_NAME="$(cd "$REPO_ROOT" && basename "$(pwd)")" +case "$REPO_NAME" in + *-template-repo) + echo "PASS: $REPO_NAME is a template repo — unfilled tokens are intentional" + echo " (.github/settings.yml identity guard above still applied)" + exit 0 + ;; +esac + +# Files that legitimately contain tokens after instantiation. +ALLOWED=( + ".machine_readable/ai/PLACEHOLDERS.adoc" # the token vocabulary itself + "EXPLAINME.adoc" # prose explaining that tokens exist + "scripts/check-no-placeholders.sh" # this file (the pattern above) + "tests/e2e/template_instantiation_test.sh" # names tokens in its answer list +) + +is_allowed() { + local rel="$1" + for a in "${ALLOWED[@]}"; do + [ "$rel" = "$a" ] && return 0 + done + # just owns brace tokens inside justfiles — an ARGS token there is + # interpolation, not a placeholder. Justfiles are not only at the root: + # the contractiles ship one too. + case "$rel" in + Justfile|justfile|*/Justfile|*/justfile|*.just) return 0 ;; + esac + return 1 +} + +# Metasyntactic tokens: prose *about* tokens, not tokens. "Replace all +# {{PLACEHOLDER}} values" names the concept — there is no PLACEHOLDER variable +# for init to fill, so these can never be a leak, and flagging them would only +# teach people that this gate cries wolf. Real tokens name a real init variable. +META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + +LEAKS=() +while IFS= read -r hit; do + rel="${hit#"$REPO_ROOT"/}" + is_allowed "$rel" && continue + # Re-check the file for at least one non-metasyntactic token. + if grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$hit" \ + | grep -qvE "^\{\{($META_TOKENS)\}\}$"; then + LEAKS+=("$rel") + fi +done < <(grep -rlE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT" \ + --exclude-dir=.git --binary-files=without-match 2>/dev/null | sort) + +if [ ${#LEAKS[@]} -eq 0 ]; then + echo "PASS: no unfilled {{PLACEHOLDER}} tokens" + exit 0 +fi + +echo "FAIL: ${#LEAKS[@]} file(s) contain unfilled {{PLACEHOLDER}} tokens:" >&2 +for leak in "${LEAKS[@]}"; do + tokens=$(grep -ohE '\{\{[A-Z][A-Z0-9_]*\}\}' "$REPO_ROOT/$leak" \ + | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ') + echo " - $leak: $tokens" >&2 +done +echo "" >&2 +echo "Each token must either be filled by build/just/init.just's SED_ARGS, or" >&2 +echo "removed from the shipped file. A token with no possible value (a PGP key" >&2 +echo "the estate does not hold) makes this gate unsatisfiable — delete the" >&2 +echo "section instead of leaving the gate permanently red." >&2 +exit 1 diff --git a/scripts/check-no-vlang.sh b/scripts/check-no-vlang.sh new file mode 100755 index 0000000..aeb62fc --- /dev/null +++ b/scripts/check-no-vlang.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Enforce the estate ban on the V programming language. Zig is the supported +# systems/FFI language and must never be matched by this check. + +set -euo pipefail + +REPO_ROOT="${1:-.}" +if [ ! -d "$REPO_ROOT" ]; then + echo "ERROR: repository path does not exist: $REPO_ROOT" >&2 + exit 2 +fi + +PATTERN='gen-v-connector|V-TRIPLE|v-triple|vlang|connectors/v-|import[[:space:]]+vweb' +HITS="" +V_MODS="" + +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + # Search tracked content only. The exclusions are the policy and its + # enforcement/tests, which necessarily name the forbidden patterns. + HITS=$(git -C "$REPO_ROOT" grep -n -i -E "$PATTERN" -- \ + . \ + ':(exclude)affinescript/**' \ + ':(exclude)scripts/check-no-vlang.sh' \ + ':(exclude)tests/workflows/check_no_vlang_test.sh' \ + ':(exclude).github/workflows/estate-rules.yml' \ + ':(exclude).machine_readable/descriptiles/PLAYBOOK.a2ml' \ + 2>/dev/null || true) + V_MODS=$(git -C "$REPO_ROOT" ls-files -- 'v.mod' '**/v.mod' 2>/dev/null || true) +else + HITS=$(grep -rni -E "$PATTERN" "$REPO_ROOT" \ + --exclude-dir=.git \ + --exclude-dir=affinescript \ + --exclude-dir=node_modules \ + --exclude=check-no-vlang.sh \ + --exclude=check_no_vlang_test.sh \ + --exclude=estate-rules.yml \ + --exclude=PLAYBOOK.a2ml \ + 2>/dev/null || true) + V_MODS=$(find "$REPO_ROOT" -type f -name v.mod \ + -not -path '*/.git/*' -not -path '*/affinescript/*' \ + -printf '%P\n' 2>/dev/null || true) +fi + +# Drop self-references. A line whose only match is this checker's own file name +# is an INVOCATION, not a V-language artefact. The :(exclude) list above can +# only name call sites that already exist, so without this filter the gate +# false-positives the moment a repo invokes it from a new place -- a Justfile, a +# pre-push hook, a different workflow. Proven 2026-09-02: a Justfile line +# `bash scripts/check-no-vlang.sh .` was reported as a V-language reference. +SELF_REF='check[-_]no[-_]vlang(_test)?[.]sh' +if [ -n "$HITS" ]; then + HITS=$(printf '%s\n' "$HITS" | awk -v self="$SELF_REF" -v pat="$PATTERN" ' + { + line = tolower($0) + gsub(self, "", line) + if (line ~ tolower(pat)) { print } + }') +fi + +if [ -z "$HITS" ] && [ -z "$V_MODS" ]; then + echo "PASS: no V-language references in the inspected repository" + exit 0 +fi + +COUNT=0 +if [ -n "$HITS" ]; then + CONTENT_COUNT=$(printf '%s\n' "$HITS" | awk 'NF { count++ } END { print count + 0 }') + COUNT=$((COUNT + CONTENT_COUNT)) +fi +if [ -n "$V_MODS" ]; then + FILE_COUNT=$(printf '%s\n' "$V_MODS" | awk 'NF { count++ } END { print count + 0 }') + COUNT=$((COUNT + FILE_COUNT)) +fi + +echo "FAIL: $COUNT V-language reference(s) found (estate policy forbids V):" >&2 +if [ -n "$HITS" ]; then + printf '%s\n' "$HITS" | sed 's/^/ /' >&2 +fi +if [ -n "$V_MODS" ]; then + printf '%s\n' "$V_MODS" | sed 's/^/ tracked module file: /' >&2 +fi +echo >&2 +echo "Remove the V-language remnants; use the supported Zig adapter where an FFI/API bridge is needed." >&2 +exit 1 diff --git a/scripts/check-proofs.sh b/scripts/check-proofs.sh new file mode 100755 index 0000000..0135dfe --- /dev/null +++ b/scripts/check-proofs.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-proofs.sh prover in: idris2 | lean4 | agda | coq +# +# The single source of truth for "do the proofs in this repo compile?". +# build/just/proofs.just calls this (`just proof-check-`), and CI should +# too, so a green local run and a green CI run mean the same thing. +# +# It replaces four separate "checks that could not fail" that let non-compiling +# proofs sit in estate repos for months while every status file said "proved": +# +# 1. `command -v || { echo SKIP; exit 0; }` — a MISSING TOOLCHAIN +# reported success. A gate that cannot run must never report OK: it +# manufactures false confidence, which is worse than having no gate. +# 2. ` --check ` — Idris2 (and friends) derive the +# expected module name from the path they are handed, so a module checked +# from the wrong directory fails on a name mismatch rather than its real +# errors, and verdicts invert. Every module here is checked from its own +# SOURCE ROOT (declared in the MANIFEST). +# 3. Path-filtered CI that only looked at one directory — nothing checked the +# rest. Here, a proof file present on disk but absent from the MANIFEST is +# an ERROR, so new proofs are gated by default, not by remembering. +# 4. `idris2 --check X && ok` — `idris2 --check` EXITS 0 ON A MISSING IMPORT +# (verified against 0.7.0) while printing `Error: ...`. Testing the exit +# code alone is unsound; for idris2 we require exit 0 AND no `Error:` line. +# +# They share one shape: a null check that emits reassuring text. If you extend +# this script, the test to apply is not "does it pass?" but "have I watched it +# fail?". +# +# CONVENTION: proofs live under verification/proofs// ; the MANIFEST for +# a prover is verification/proofs//MANIFEST, one entry per line: +# +# ||gated|quarantine| +# +# source-root : directory the prover is invoked from, chosen so the module's +# declared name matches its path (getting this wrong is hole #2). +# gated : MUST compile. A failure fails this script and CI. +# quarantine : known-broken, tracked in STATE.a2ml. Must CONTINUE to fail; +# if one starts compiling the script fails and tells you to +# promote it, so the list cannot rot into a permanent excuse. +# +# Blank lines and lines starting with # are ignored. +# +# Exit: 0 = every gated module compiles AND every quarantined module still fails +# AND every proof file on disk is listed; 1 = otherwise; 2 = misuse. + +set -euo pipefail + +PROVER="${1:-}" +case "$PROVER" in + idris2|lean4|agda|coq) ;; + *) echo "usage: $(basename "$0") " >&2; exit 2 ;; +esac + +# This script lives in /scripts/ ; run everything from the repo root. +cd "$(dirname "${BASH_SOURCE[0]}")/.." +ROOT="$PWD" +PROOF_DIR="verification/proofs/$PROVER" +MANIFEST_FILE="$PROOF_DIR/MANIFEST" + +# --- per-prover configuration ------------------------------------------------- +# CMD : executable that must be on PATH (absent => FAIL, never skip). +# EXT : file extension, for the "unlisted proof" coverage scan. +# ERROR_RE : if non-empty, output must not match it even when the exit code is 0. +# Only idris2 needs this (its --check exits 0 on a missing import); +# for lean4 it is a harmless belt-and-braces guard. +case "$PROVER" in + idris2) CMD=idris2; EXT=idr; ERROR_RE='^Error:' ;; + lean4) CMD=lean; EXT=lean; ERROR_RE='error:' ;; + agda) CMD=agda; EXT=agda; ERROR_RE='' ;; + coq) CMD=coqc; EXT=v; ERROR_RE='' ;; +esac + +check_one() { + # $1 source-root (rel to ROOT), $2 module (rel to source-root). + # Sets LAST_OUT to the tool output on failure; returns 0 iff the module compiles. + local root="$1" rel="$2" out rc + set +e + case "$PROVER" in + idris2) out="$(cd "$ROOT/$root" && idris2 --check "$rel" 2>&1)"; rc=$? ;; + lean4) out="$(cd "$ROOT/$root" && lean "$rel" 2>&1)"; rc=$? ;; + agda) out="$(cd "$ROOT/$root" && agda --safe "$rel" 2>&1)"; rc=$? ;; + coq) out="$(cd "$ROOT/$root" && coqc "$rel" 2>&1)"; rc=$? ;; + esac + set -e + if [ "$rc" -eq 0 ] && { [ -z "$ERROR_RE" ] || ! grep -qE "$ERROR_RE" <<<"$out"; }; then + LAST_OUT=""; return 0 + fi + LAST_OUT="$out"; return 1 +} + +echo "=== $PROVER proof check ===" + +# --- toolchain: absent means FAIL, never skip --------------------------------- +if ! command -v "$CMD" >/dev/null 2>&1; then + { + echo "FAIL: '$CMD' not found on PATH." + echo + echo "This is deliberately fatal. The previous recipe did 'exit 0' here with" + echo "\"SKIP: $CMD not installed\", so every $PROVER proof reported green on any" + echo "machine that could not check it. Install the $PROVER toolchain, or run" + echo "this in CI where the workflow installs it." + } >&2 + exit 1 +fi +"$CMD" --version 2>/dev/null | head -1 || true +echo + +# --- a repo with proofs but no MANIFEST is itself a failure ------------------- +if [ ! -f "$MANIFEST_FILE" ]; then + if [ -d "$PROOF_DIR" ] && [ -n "$(find "$PROOF_DIR" -name "*.$EXT" 2>/dev/null)" ]; then + echo "FAIL: $PROOF_DIR contains .$EXT proofs but has no MANIFEST." >&2 + echo " Create $MANIFEST_FILE listing each as 'gated' or 'quarantine'." >&2 + exit 1 + fi + echo "no $PROOF_DIR/*.$EXT proofs and no MANIFEST — nothing to check." + exit 0 +fi + +fails=0 +unexpected_pass=0 +listed_tmp="$(mktemp)" +trap 'rm -f "$listed_tmp"' EXIT + +while IFS='|' read -r root rel status note; do + # skip blank lines and comments + [ -z "${root// }" ] && continue + case "${root#"${root%%[![:space:]]*}"}" in \#*) continue ;; esac + printf ' %-30s %-24s ' "$root" "$rel" + echo "$root/$rel" >>"$listed_tmp" + if check_one "$root" "$rel"; then + if [ "$status" = gated ]; then + echo "PASS" + else + echo "PASS -- UNEXPECTED (quarantined module now compiles)" + echo " Promote '$rel' to 'gated' in $MANIFEST_FILE and update STATE.a2ml." + unexpected_pass=$((unexpected_pass + 1)) + fi + else + if [ "$status" = gated ]; then + echo "FAIL" + printf '%s\n' "${LAST_OUT//$'\n'/$'\n '}" | sed '1s/^/ /' + fails=$((fails + 1)) + else + echo "fail (quarantined, expected)" + [ -n "${note// }" ] && echo " reason:$note" + fi + fi +done < "$MANIFEST_FILE" + +# --- coverage: every proof on disk must be listed ----------------------------- +# The anti-recurrence rule: proofs went unchecked for months because nothing +# forced them onto anyone's list. A file absent from the MANIFEST is an error. +echo +echo "=== manifest coverage ($PROOF_DIR) ===" +listed="$(sort -u "$listed_tmp")" +found="$(cd "$ROOT" && find "$PROOF_DIR" -name "*.$EXT" -not -path '*/build/*' 2>/dev/null | sort)" +unlisted="$(comm -13 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" +missing="$(comm -23 <(printf '%s\n' "$listed") <(printf '%s\n' "$found") || true)" + +if [ -n "${unlisted//[[:space:]]/}" ]; then + echo "FAIL: .$EXT proofs on disk but absent from $MANIFEST_FILE:" + printf ' %s\n' $unlisted + echo " List each as 'gated' or 'quarantine'; new proofs are gated by default." + fails=$((fails + 1)) +fi +if [ -n "${missing//[[:space:]]/}" ]; then + echo "FAIL: MANIFEST lists modules that do not exist (stale entries):" + printf ' %s\n' $missing + fails=$((fails + 1)) +fi +[ -z "${unlisted//[[:space:]]/}${missing//[[:space:]]/}" ] && \ + echo " all $(printf '%s\n' "$found" | grep -c .) .$EXT file(s) accounted for" + +echo +if [ "$fails" -gt 0 ] || [ "$unexpected_pass" -gt 0 ]; then + echo "RESULT: FAIL ($fails failure(s), $unexpected_pass unexpected pass(es))" + exit 1 +fi +echo "RESULT: PASS -- gated modules compile; quarantined modules still fail as recorded" diff --git a/scripts/check-root-shape.sh b/scripts/check-root-shape.sh new file mode 100755 index 0000000..6ffdb7d --- /dev/null +++ b/scripts/check-root-shape.sh @@ -0,0 +1,156 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# check-root-shape.sh — enforce the canonical root shape, in BOTH directions, +# against the repository root allowlist, under EITHER canonical spelling: +# .machine_readable/root-allow.txt (dotted, CANONICAL ESTATE-WIDE) +# machine-readable/root-allow.txt (hyphenated, accepted legacy) +# +# The dotted form is the standard (owner ruling 2026-09-17; estate census 48 +# repos dotted vs 9 hyphenated). The hyphenated form is still RESOLVED, not +# rejected, because the checker is shared with repos that have not migrated. +# Removing this branch would turn 9 working gates into exit-2 setup errors. +# +# * an entry at root that is not listed -> drift (extra) +# * a listed entry WITHOUT '?' that is missing -> drift (missing) +# +# The second direction was absent until 2026-08, and its absence is why the +# allowlist rotted: it accumulated 19 permissions for files the April root +# cleanup had already moved into docs/, and nothing could ever notice. A +# one-directional allowlist only ratchets open, so over time it licenses +# exactly the drift it was written to prevent. +# +# '?' marks an entry that is legitimately absent in some conforming repo — +# template-only material removed at mint, or a capability-gated module. +# +# Companion to scripts/validate-template.sh: that script enforces required +# files; this one enforces the shape as a whole. +# +# Exit codes: +# 0 — root matches allowlist +# 1 — drift (extras at root, or required entries missing) +# 2 — usage / setup error + +set -euo pipefail + +REPO_ROOT="${1:-.}" +REPO_ROOT_DOTTED="${REPO_ROOT}/.machine_readable/root-allow.txt" +REPO_ROOT_HYPHEN="${REPO_ROOT}/machine-readable/root-allow.txt" + +# Both spellings are estate contract. Resolve whichever exists; if BOTH exist +# that is itself drift (two sources of truth) and is refused. +if [ -f "$REPO_ROOT_DOTTED" ] && [ -f "$REPO_ROOT_HYPHEN" ]; then + echo "ERROR: both .machine_readable/ and machine-readable/ carry a root-allow.txt;" >&2 + echo " pick one spelling — two allowlists cannot both be canonical." >&2 + exit 2 +elif [ -f "$REPO_ROOT_DOTTED" ]; then + ALLOW_FILE="$REPO_ROOT_DOTTED" +elif [ -f "$REPO_ROOT_HYPHEN" ]; then + ALLOW_FILE="$REPO_ROOT_HYPHEN" +else + echo "ERROR: allowlist not found at either $REPO_ROOT_DOTTED or $REPO_ROOT_HYPHEN" >&2 + exit 2 +fi + +# Build the allow set: strip comments, trailing slashes, and blank lines. +# A leading '?' marks the entry optional; it is not part of the name. +mapfile -t ALLOW_RAW < <( + sed -E 's/[[:space:]]*#.*$//' "$ALLOW_FILE" \ + | sed -E 's|/$||' \ + | awk 'NF' \ + | sed -E 's/[[:space:]]+$//' +) + +declare -A ALLOW_SET=() +REQUIRED=() +ALLOW=() +for raw in "${ALLOW_RAW[@]}"; do + if [[ "$raw" == '?'* ]]; then + entry="${raw#\?}" + else + entry="$raw" + REQUIRED+=("$entry") + fi + ALLOW_SET["$entry"]=1 + ALLOW+=("$entry") +done + +# Enumerate everything at the repository root, EXCLUDING git-ignored entries. +# +# This was a bare `find`, which contradicted the contract root-allow.txt states +# ("Anything tracked at root that is not in this list is drift"): a plain +# filesystem scan also sees build output. Any repo with a root-level build +# directory -- `target/` for Cargo, `node_modules/`, `_build/` for Mix -- +# therefore failed this gate the moment someone built before running it, and +# the tempting "fix" was to allowlist an artifact directory that must never be +# committed. +# +# Filtering through `git check-ignore` makes the check mean what it says. The +# fallback keeps the script working outside a git worktree. +mapfile -t ACTUAL < <( + cd "$REPO_ROOT" && \ + find . -mindepth 1 -maxdepth 1 \ + ! -name '.' \ + -printf '%f\n' \ + | { if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + git check-ignore --stdin --non-matching --verbose 2>/dev/null \ + | sed -n 's/^::[[:space:]]//p' + else + cat + fi; } \ + | sort +) + +declare -A ACTUAL_SET=() +for entry in "${ACTUAL[@]}"; do + ACTUAL_SET["$entry"]=1 +done + +# Direction 1 — present at root but not permitted. +EXTRAS=() +for entry in "${ACTUAL[@]}"; do + if [ -z "${ALLOW_SET[$entry]+x}" ]; then + EXTRAS+=("$entry") + fi +done + +# Direction 2 — required by the allowlist but not present. +MISSING=() +for entry in "${REQUIRED[@]}"; do + if [ -z "${ACTUAL_SET[$entry]+x}" ]; then + MISSING+=("$entry") + fi +done + +if [ ${#EXTRAS[@]} -eq 0 ] && [ ${#MISSING[@]} -eq 0 ]; then + OPTIONAL_COUNT=$(( ${#ALLOW[@]} - ${#REQUIRED[@]} )) + echo "PASS: root matches allowlist (${#ACTUAL[@]} entries; ${#REQUIRED[@]} required, ${OPTIONAL_COUNT} optional)" + exit 0 +fi + +if [ ${#EXTRAS[@]} -gt 0 ]; then + echo "FAIL: ${#EXTRAS[@]} root entries are not on the allowlist:" >&2 + for e in "${EXTRAS[@]}"; do + if [ -d "$REPO_ROOT/$e" ]; then + echo " - $e/ (directory)" >&2 + else + echo " - $e" >&2 + fi + done + echo "" >&2 + echo "Either move them into the appropriate subdirectory, or add a justified" >&2 + echo "entry to ${ALLOW_FILE#"$REPO_ROOT"/}." >&2 +fi + +if [ ${#MISSING[@]} -gt 0 ]; then + echo "FAIL: ${#MISSING[@]} allowlist entries are required but absent:" >&2 + for e in "${MISSING[@]}"; do + echo " - $e" >&2 + done + echo "" >&2 + echo "Either restore them, or - if they are legitimately absent in this repo -" >&2 + echo "mark the entry optional with a leading '?' in root-allow.txt and say why." >&2 + echo "Do not mark an entry optional merely to silence this." >&2 +fi +exit 1 diff --git a/scripts/check-variant-drift.sh b/scripts/check-variant-drift.sh new file mode 100755 index 0000000..c8ada3c --- /dev/null +++ b/scripts/check-variant-drift.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# check-variant-drift.sh — verify the shared RSR spine of this variant +# template stays convergent with its parent at the pinned commit. +# +# Reads the contract at .machine_readable/descriptiles/VARIANT.a2ml: +# - every tracked file NOT declared added/removed/diverged/pending/operational +# must be identical to the parent's copy at parent-pin, modulo the +# [normalise] rules (action-pin SHAs, self-name substitution); +# - declared additions must exist here and not in the parent; +# - declared removals must exist in the parent and not here. +# +# Usage: check-variant-drift.sh [self-dir] +# Exit: 0 = spine convergent; 1 = undeclared drift (listed on stdout). + +set -euo pipefail + +PARENT_DIR="${1:?usage: check-variant-drift.sh [self-dir]}" +SELF_DIR="${2:-.}" +CONTRACT="$SELF_DIR/.machine_readable/descriptiles/VARIANT.a2ml" + +[ -f "$CONTRACT" ] || { echo "FAIL: contract not found: $CONTRACT"; exit 1; } + +SELF_NAME=$(sed -n 's/^project = "\(.*\)"/\1/p' "$CONTRACT" | head -1) +PARENT_SLUG=$(sed -n 's/^parent = "\(.*\)"/\1/p' "$CONTRACT" | head -1) +PARENT_NAME="${PARENT_SLUG##*/}" +PIN=$(sed -n 's/^parent-pin = "\([0-9a-f]*\)".*/\1/p' "$CONTRACT" | head -1) + +# Extract the paths array of one [paths.
] block. +section_paths() { + awk -v sec="[paths.$1]" ' + $0 == sec { insec = 1; next } + insec && /^\[/ { insec = 0 } + insec && /^ *"/ { + line = $0 + sub(/^ *"/, "", line); sub(/".*$/, "", line) + print line + } + ' "$CONTRACT" +} + +ADDED=$(section_paths added) +REMOVED=$(section_paths removed) +SKIP=$(printf '%s\n' "$(section_paths diverged)" \ + "$(section_paths diverged-pending-upstream)" \ + "$(section_paths operational-state)") + +in_list() { # $1 = path, $2 = newline list (entries ending in / are prefixes) + local p="$1" e + while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) case "$p" in "$e"*) return 0;; esac ;; + *) [ "$p" = "$e" ] && return 0 ;; + esac + done <<< "$2" + return 1 +} + +# Fold operational state out of a file before comparison: action-pin SHAs, +# then BOTH repo names → SELF (variant name first — it does not contain the +# parent name as a substring, so order is safe). Folding both names on both +# sides keeps inherited files that legitimately mention the parent by name +# convergent, while still matching self-identity substitutions. +normalise() { # $1 = file + sed -E -e 's/@[0-9a-f]{40}[^ ]*( # v[^ ]*)?/@PIN/g' \ + -e "s/$SELF_NAME/SELF/g" -e "s/$PARENT_NAME/SELF/g" "$1" +} + +DRIFT=0 +report() { DRIFT=1; echo "DRIFT: $*"; } + +if [ -n "$PIN" ] && [ -d "$PARENT_DIR/.git" ]; then + ACTUAL=$(git -C "$PARENT_DIR" rev-parse HEAD) + [ "$ACTUAL" = "$PIN" ] || echo "WARN: parent checkout is $ACTUAL, contract pins $PIN" +fi + +# 1. Spine files must match, modulo normalisation. +while IFS= read -r f; do + in_list "$f" "$ADDED" && continue + in_list "$f" "$SKIP" && continue + if [ ! -f "$PARENT_DIR/$f" ]; then + report "$f exists here but not in parent (declare in paths.added or remove)" + continue + fi + if ! diff -q <(normalise "$PARENT_DIR/$f") \ + <(normalise "$SELF_DIR/$f") >/dev/null 2>&1; then + report "$f differs from parent (declare in paths.diverged or re-converge)" + fi +done < <(git -C "$SELF_DIR" ls-files) + +# 2. Parent files absent here must be declared removed. +while IFS= read -r f; do + [ -f "$SELF_DIR/$f" ] && continue + in_list "$f" "$REMOVED" && continue + in_list "$f" "$SKIP" && continue + report "parent has $f but it is absent here (declare in paths.removed)" +done < <(git -C "$PARENT_DIR" ls-files) + +# 3. Declared additions must exist (and not silently exist in parent). +while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) [ -d "$SELF_DIR/$e" ] || report "declared-added directory $e is missing" ;; + *) [ -f "$SELF_DIR/$e" ] || report "declared-added file $e is missing" + [ -e "$PARENT_DIR/$e" ] && report "declared-added $e also exists in parent (not an addition)" ;; + esac +done <<< "$ADDED" + +# 4. Declared removals must still exist in the parent. +while IFS= read -r e; do + [ -z "$e" ] && continue + [ -e "$PARENT_DIR/$e" ] || report "declared-removed $e no longer exists in parent (stale entry)" +done <<< "$REMOVED" + +if [ "$DRIFT" -eq 0 ]; then + echo "PASS: spine convergent with $PARENT_SLUG@${PIN:0:12} (modulo declared variant paths)" +else + echo "FAIL: undeclared drift against $PARENT_SLUG@${PIN:0:12} — update VARIANT.a2ml or re-converge" + exit 1 +fi diff --git a/scripts/gen-repo-map.sh b/scripts/gen-repo-map.sh new file mode 100755 index 0000000..a9cdb64 --- /dev/null +++ b/scripts/gen-repo-map.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# gen-repo-map.sh — generate docs/architecture/REPOSITORY-MAP.adoc from the +# tree plus the annotations in .machine_readable/root-allow.txt. +# +# WHY THIS IS GENERATED. Before this existed the repo carried FIVE hand-written +# maps and not one was accurate: README's table omitted 12 directories, +# .github/DIRECTORY.adoc was a 3-line stub, .github/CONTRIBUTING.md drew a tree +# naming lib/, extensions/, plugins/ and spec/ (none of which exist), +# docs/README.adoc was wrong on every path it named, and docs/RSR_OUTLINE.adoc +# was a second whole-repo README contradicting the first. They rotted because +# nothing checked them. A map that is regenerated and diffed in CI cannot. +# +# Usage: bash scripts/gen-repo-map.sh [repo_root] +set -euo pipefail + +# Byte order, everywhere. `sort` is LOCALE-DEPENDENT: under en_US.UTF-8 it +# ignores leading punctuation, so dotfiles interleave with ordinary names; +# under LC_ALL=C (what CI runs) they sort first. The generator was therefore +# deterministic WITHIN an environment but not ACROSS environments, and the +# CI freshness check caught precisely that on its first real run. Running the +# generator twice in one shell cannot detect it - the check must vary the +# locale, which is what tests/shape/repo_map_determinism_test.sh now does. +export LC_ALL=C + +REPO_ROOT="${1:-.}" +cd "$REPO_ROOT" +ALLOW=".machine_readable/root-allow.txt" +OUT="docs/architecture/REPOSITORY-MAP.adoc" + +[ -f "$ALLOW" ] || { echo "ERROR: $ALLOW not found" >&2; exit 2; } +mkdir -p "$(dirname "$OUT")" + +# Root entries, tracked shape only (mirrors check-root-shape.sh). +mapfile -t ENTRIES < <(git ls-files | awk -F/ '{print $1}' | sort -u) + +# name -> annotation, harvested from the allowlist's own comments. +declare -A NOTE=() OPTIONAL=() +while IFS= read -r line; do + [[ "$line" =~ ^[[:space:]]*# ]] && continue + [[ -z "${line// }" ]] && continue + raw="${line%%#*}"; raw="$(echo "$raw" | xargs || true)" + [ -z "$raw" ] && continue + comment="${line#*#}"; [ "$comment" = "$line" ] && comment="" + comment="$(echo "$comment" | xargs || true)" + key="${raw%/}" + if [[ "$key" == '?'* ]]; then key="${key#\?}"; OPTIONAL["$key"]=1; fi + NOTE["$key"]="$comment" +done < "$ALLOW" + +# Directories whose own level holds nothing but a stub README (or .gitkeep). +# The owner ruling (2026-08-26) is that these stay: they are DECLARED STRUCTURE, +# a statement of intended shape for repos minted from this template, not +# abandoned work. Saying so explicitly is the difference between the two. +# Recursive: a directory counts as unfilled only when its WHOLE subtree holds +# no substantive file. Checking just its own level would flag archetypes/, whose +# julia-library/ child is real content. (The ply manifests that once padded +# these counts were folded into the repo deed — standards#837 pilot.) +declared_only() { + local d="$1" n + n=$(git ls-files "$d" | awk -F/ '{print $NF}' \ + | grep -vxE 'README\.adoc|\.gitkeep' | wc -l) + [ "$n" -eq 0 ] +} + +{ + echo "// SPDX-License-Identifier: CC-BY-SA-4.0" + echo "// Copyright (c) 2026 Jonathan D.A. Jewell " + echo "//" + echo "// GENERATED by scripts/gen-repo-map.sh - do not hand-edit." + echo "// Regenerate with \`just repo-map\`. CI fails if this file is stale." + echo "= Repository map" + echo ":toc:" + echo + echo "The single authoritative map of this repository. It is generated from the" + echo "tree and from the annotations in \`.machine_readable/root-allow.txt\`, and CI" + echo "fails if it drifts, so it cannot rot the way its five hand-written" + echo "predecessors did." + echo + echo "== Root" + echo + echo '[cols="2,1,4",options="header"]' + echo '|===' + echo "| Path | Required | What it is, and who reads it" + echo + for e in "${ENTRIES[@]}"; do + [ -d "$e" ] && disp="\`$e/\`" || disp="\`$e\`" + req="yes"; [ -n "${OPTIONAL[$e]+x}" ] && req="optional" + note="${NOTE[$e]:-}" + [ -z "$note" ] && note="-" + echo "| $disp" + echo "| $req" + echo "| $note" + echo + done + echo '|===' + echo + echo "== Declared structure not yet filled" + echo + echo "These directories currently hold only a stub \`README.adoc\` and a level" + echo "manifest. That is deliberate. They declare the shape a repository minted" + echo "from this template is expected to grow into; they are *intended" + echo "structure, not abandoned work*. Add content, or delete the directory in" + echo "your own repo - but do not read their emptiness as neglect here." + echo + first=1 + for d in $(git ls-files | grep '/' | sed 's|/[^/]*$||' | sort -u); do + if declared_only "$d"; then + [ $first -eq 1 ] && { echo "[cols=\"1\"]"; echo '|==='; first=0; } + echo "| \`$d/\`" + fi + done + [ $first -eq 0 ] && echo '|===' + echo + echo "== Where things are enforced" + echo + echo "* Root shape - \`scripts/check-root-shape.sh\` against" + echo " \`.machine_readable/root-allow.txt\`, run by \`.github/workflows/estate-rules.yml\`." + echo " The check is bidirectional: unlisted entries fail, and required entries" + echo " that are absent also fail." + echo "* This map - \`just repo-map\` must produce no diff." + echo "* Community health - GitHub reads \`.github/\` and no other path." + echo "* Variant divergence - \`scripts/check-variant-drift.sh\` compares a child" + echo " to its parent BY PATH, so any move here invalidates every child's" + echo " declared path lists until they are re-anchored." +} > "$OUT" + +echo "repo-map: wrote $OUT" diff --git a/scripts/invariant-path.sh b/scripts/invariant-path.sh new file mode 100755 index 0000000..f469534 --- /dev/null +++ b/scripts/invariant-path.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" +IP_ROOT="${REPO_ROOT}/../invariant-path" + +if [[ ! -f "${IP_ROOT}/Cargo.toml" ]]; then + echo "invariant-path workspace not found at ${IP_ROOT}" >&2 + exit 1 +fi + +if [[ $# -eq 0 ]]; then + set -- scan --profile generic --file "${REPO_ROOT}/README.adoc" --artifact-uri "repo://README.adoc" +elif [[ "$1" == "scan" ]]; then + shift + has_profile="false" + for arg in "$@"; do + if [[ "$arg" == "--profile" ]]; then + has_profile="true" + break + fi + done + if [[ "${has_profile}" == "true" ]]; then + set -- scan "$@" + else + set -- scan --profile generic "$@" + fi +fi + +exec cargo run --manifest-path "${IP_ROOT}/Cargo.toml" -p invariant-path-cli -- "$@" diff --git a/scripts/migrate-wellknown-to-www.sh b/scripts/migrate-wellknown-to-www.sh new file mode 100755 index 0000000..7b7332f --- /dev/null +++ b/scripts/migrate-wellknown-to-www.sh @@ -0,0 +1,246 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# migrate-wellknown-to-www.sh — conflict-safe migration of a repository-root +# .well-known/ tree into www/.well-known/ (dicta-task#53). +# +# Stage 1 established the canonical location. Stage 5 (#119) sweeps it across +# ~270 repositories, which changes what "conflict-safe" has to mean: a sweep +# is unattended, so a conflict may not halt it, and may not be silent either. +# +# Semantics, per the #53 migration requirements: +# * identical content in both locations -> root copy removed, www kept; +# * content only at the root -> moved into www/.well-known/; +# * content only under www/ -> no-op; +# * DIVERGENT content in both -> the ROOT copy is quarantined to +# www/.legacy-well-known-/ and the www/ copy is left untouched: +# never overwritten, never silent. Exit 0 by default so a batch sweep can +# continue; --strict turns a quarantine into a non-zero exit. +# +# --in-place restores the pre-stage-5 contract (both copies stay where they +# are, exit 1) for anyone who prefers to resolve divergence by hand before the +# tree is touched at all. +# +# Why the quarantine lives under www/ and not at the root: +# #106's commit message specifies `.legacy-well-known-YYYYMMDD/` at the +# repository root. The root allowlist (.machine_readable/root-allow.txt) is +# checked BIDIRECTIONALLY by scripts/check-root-shape.sh and matches entries +# literally, with no glob support — a root directory whose name carries a +# date can never be allowlisted, so every swept repository would report root +# drift the moment the migrator ran. Under www/ it is inside the site- +# operations bundle the allowlist already permits, and it is NOT under +# www/public/, so the publication boundary still holds. +# +# Run from the repository root. Uses `git mv`/`git rm` when the tree is a git +# checkout and the files are tracked, plain mv/rm otherwise, so it is safe on +# minted-but-uncommitted trees too. Propagation runners should combine this +# with a `git status --porcelain` / unpushed-commit check of their own; this +# script compares CONTENT, and content comparison is what "divergent" means +# here. +# +# Exit codes: +# 0 — clean migration (quarantines may have occurred; see the report) +# 1 — divergence left unresolved (--in-place), --strict and something was +# quarantined, or files unexpectedly remain at the root +# 2 — usage / setup error + +set -euo pipefail + +ROOT=".well-known" +DEST="www/.well-known" + +IN_PLACE=0 +STRICT=0 +DRY_RUN=0 +REPORT="" + +usage() { + cat <<'EOF' +Usage: scripts/migrate-wellknown-to-www.sh [options] + +Migrate a repository-root .well-known/ into www/.well-known/. + +Options: + --dry-run report what would change; write nothing (always exit 0) + --in-place divergent content stays put, both copies kept, exit 1 + (pre-stage-5 contract; for hand resolution) + --strict exit 1 if anything had to be quarantined + --report FILE write a TSV report: actionpathdetail + -h, --help this message + +Report actions: moved, deduped, quarantined, conflict, left. +EOF +} + +die() { echo "migrate-wellknown: ERROR — $*" >&2; exit 2; } + +while [ $# -gt 0 ]; do + case "$1" in + --in-place) IN_PLACE=1; shift ;; + --strict) STRICT=1; shift ;; + --dry-run) DRY_RUN=1; shift ;; + --report) [ $# -ge 2 ] || die "--report requires a path"; REPORT="$2"; shift 2 ;; + --report=*) REPORT="${1#--report=}"; shift ;; + -h|--help) usage; exit 0 ;; + *) die "unknown option: $1" ;; + esac +done + +if [ "$IN_PLACE" -eq 1 ] && [ "$STRICT" -eq 1 ]; then + die "--in-place and --strict are mutually exclusive (--in-place already fails on conflict)" +fi + +# The report is written under --dry-run too: a dry run that only prints is +# useless to a batch driver, which needs the planned actions in a form it can +# read. Under --dry-run the rows describe what WOULD happen. +if [ -n "$REPORT" ]; then + repdir="$(dirname "$REPORT")" + if [ ! -d "$repdir" ]; then mkdir -p "$repdir"; fi + printf 'action\tpath\tdetail\n' > "$REPORT" +fi + +rep() { + if [ -n "$REPORT" ]; then + printf '%s\t%s\t%s\n' "$1" "$2" "${3:-}" >> "$REPORT" + fi + return 0 +} + +if [ ! -d "$ROOT" ]; then + echo "migrate-wellknown: no $ROOT/ at repository root — nothing to migrate." + exit 0 +fi + +STAMP="$(date -u +%Y%m%d)" +QUARANTINE="www/.legacy-well-known-$STAMP" + +git_tracked() { git ls-files --error-unmatch "$1" >/dev/null 2>&1; } + +move_file() { # src dst — never overwrites; caller guarantees dst is free + local src="$1" dst="$2" + # The dry-run guard precedes every filesystem effect, mkdir included: + # an empty directory is invisible to `git status` but is still a change + # to the tree, and --dry-run promises to leave nothing behind. + if [ "$DRY_RUN" -eq 1 ]; then return 0; fi + mkdir -p "$(dirname "$dst")" + if git_tracked "$src"; then + git mv "$src" "$dst" + else + mv "$src" "$dst" + fi +} + +remove_file() { # src + local src="$1" + if [ "$DRY_RUN" -eq 1 ]; then return 0; fi + if git_tracked "$src"; then + git rm -q "$src" + else + rm "$src" + fi +} + +# A quarantine target is never overwritten: if the dated name is taken, the +# next free `.N` suffix is used instead. +quarantine_dest() { # rel -> path + # Declared one per line on purpose: in `local a="$1" b="$a"` every + # expansion happens before any assignment, so $a is still unset when it + # is read — fatal under `set -u`. + local rel="$1" + local base="$QUARANTINE/$rel" + local cand="$base" + local n=1 + while [ -e "$cand" ]; do + cand="$base.$n" + n=$((n + 1)) + done + printf '%s' "$cand" +} + +if [ "$DRY_RUN" -eq 0 ] && [ ! -d "$DEST" ]; then + mkdir -p "$DEST" +fi + +moved=0; deduped=0; conflicts=0; quarantined=0 + +while IFS= read -r -d '' src; do + rel="${src#"$ROOT"/}" + dst="$DEST/$rel" + + if [ -f "$dst" ]; then + if cmp -s "$src" "$dst"; then + # Identical: the duplicate root copy goes; www is canonical. + remove_file "$src" + deduped=$((deduped + 1)) + echo " identical, root copy removed: $rel" + rep deduped "$rel" "identical to $DEST/$rel; root copy removed" + elif [ "$IN_PLACE" -eq 1 ]; then + conflicts=$((conflicts + 1)) + echo " CONFLICT (both preserved): $rel differs between $ROOT/ and $DEST/" >&2 + echo " root sha256: $(sha256sum "$src" | cut -d' ' -f1)" >&2 + echo " www sha256: $(sha256sum "$dst" | cut -d' ' -f1)" >&2 + rep conflict "$rel" "divergent; both preserved in place" + else + qdst="$(quarantine_dest "$rel")" + if [ -e "$qdst" ]; then + die "refusing to overwrite quarantine target $qdst" + fi + # Hashes are read BEFORE the move: afterwards $src no longer + # exists, and a failing sha256sum inside $( ) would abort the + # script under `set -e` before the conflict was ever reported. + root_sha="$(sha256sum "$src" | cut -d' ' -f1)" + www_sha="$(sha256sum "$dst" | cut -d' ' -f1)" + move_file "$src" "$qdst" + quarantined=$((quarantined + 1)) + echo " DIVERGENT — root copy quarantined: $rel -> $qdst" >&2 + echo " root sha256: $root_sha" >&2 + echo " www sha256: $www_sha (kept, untouched)" >&2 + rep quarantined "$rel" "divergent; root copy -> $qdst" + fi + else + if [ -e "$dst" ]; then + die "refusing to overwrite existing $dst" + fi + move_file "$src" "$dst" + moved=$((moved + 1)) + echo " moved: $rel -> $dst" + rep moved "$rel" "root-only; -> $DEST/$rel" + fi +done < <(find "$ROOT" -type f -print0 | sort -z) + +# Drop the root directory only when it is fully empty of files. +left=0 +if [ -z "$(find "$ROOT" -type f -print -quit 2>/dev/null)" ]; then + if [ "$DRY_RUN" -eq 0 ]; then + find "$ROOT" -depth -type d -empty -delete 2>/dev/null || true + fi +else + left=$(find "$ROOT" -type f | wc -l | tr -d '[:space:]') +fi + +echo "migrate-wellknown: moved=$moved deduped=$deduped quarantined=$quarantined conflicts=$conflicts left_in_root=$left" + +if [ "$DRY_RUN" -eq 1 ]; then + echo "migrate-wellknown: dry run — nothing written." + exit 0 +fi + +if [ "$left" -gt 0 ]; then + echo "migrate-wellknown: WARNING — $left file(s) remain under $ROOT/ (unexpected)." >&2 + exit 1 +fi + +if [ "$conflicts" -gt 0 ]; then + echo "migrate-wellknown: DIVERGENT CONTENT — both locations preserved in place; resolve by hand." >&2 + exit 1 +fi + +if [ "$quarantined" -gt 0 ]; then + echo "migrate-wellknown: $quarantined divergent file(s) quarantined under $QUARANTINE/ —" >&2 + echo "migrate-wellknown: resolve by hand, then delete that directory. Nothing was overwritten." >&2 + if [ "$STRICT" -eq 1 ]; then + exit 1 + fi +fi + +exit 0 diff --git a/scripts/prune-dependabot-ecosystems.rs b/scripts/prune-dependabot-ecosystems.rs new file mode 100644 index 0000000..fb0cacf --- /dev/null +++ b/scripts/prune-dependabot-ecosystems.rs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// Keep only explicitly selected Dependabot ecosystems after project minting. +// +// Ported from prune-dependabot-ecosystems.rb: Ruby is not an estate-authorised +// language, and this runs on every mint. Single file, std only, no +// dependencies — compiled on demand by `just repo-init` (see the rust_tool +// helper in build/just/repo-init.just). +// +// Two rules that are easy to get wrong and are load-bearing: +// +// * Nix is not a valid Dependabot ecosystem, so it is dropped from the +// keep-list rather than matched against entries. +// * Pruning to an EMPTY updates list is refused. A dependabot.yml with no +// ecosystems is worse than one with unused entries: it silently stops +// watching everything. Refusing is the safe failure. +// +// Usage: prune-dependabot-ecosystems.rs + +use std::env; +use std::fs; +use std::path::Path; + +/// True for a line that begins a new `updates:` entry, i.e. matches +/// `^[ \t]*-[ \t]*package-ecosystem:`. +fn is_entry_start(line: &str) -> bool { + let t = line.trim_start_matches(|c| c == ' ' || c == '\t'); + let t = match t.strip_prefix('-') { + Some(t) => t, + None => return false, + }; + t.trim_start_matches(|c| c == ' ' || c == '\t') + .starts_with("package-ecosystem:") +} + +/// Extract the ecosystem name from one entry: `package-ecosystem: "cargo"`. +fn ecosystem_name(entry: &str) -> String { + let needle = "package-ecosystem:"; + match entry.find(needle) { + Some(p) => { + let rest = &entry[p + needle.len()..]; + let rest = rest.trim_start_matches(|c| c == ' ' || c == '\t'); + let rest = rest.trim_start_matches(|c| c == '\'' || c == '"'); + rest.chars() + .take_while(|c| c.is_ascii_alphabetic() || *c == '-') + .collect() + } + None => "?".to_string(), + } +} + +fn main() { + let args: Vec = env::args().skip(1).collect(); + if args.len() < 2 { + eprintln!("Usage: prune-dependabot-ecosystems.rs "); + std::process::exit(1); + } + let path = &args[0]; + + // Nix is not a Dependabot ecosystem; keeping it would match nothing and + // could only ever produce an empty keep-list. + let keep: Vec<&str> = args[1..] + .iter() + .map(|s| s.as_str()) + .filter(|s| *s != "nix") + .collect(); + + if !Path::new(path).is_file() { + println!(" dependabot: {} absent, nothing to prune", path); + return; + } + + let text = match fs::read_to_string(path) { + Ok(t) => t, + Err(_) => return, + }; + + // Split the document at each entry start, keeping the preamble separate. + // `split_inclusive` retains the newline, so re-joining is byte-exact for + // anything we do not drop. + let mut head = String::new(); + let mut entries: Vec = Vec::new(); + for line in text.split_inclusive('\n') { + if is_entry_start(line) { + entries.push(String::new()); + } + match entries.last_mut() { + Some(e) => e.push_str(line), + None => head.push_str(line), + } + } + + if entries.is_empty() { + println!(" dependabot: no ecosystem entries found"); + return; + } + + let mut kept: Vec = Vec::new(); + let mut kept_names: Vec = Vec::new(); + let mut dropped_names: Vec = Vec::new(); + + for entry in &entries { + let name = ecosystem_name(entry); + if keep.contains(&name.as_str()) { + kept_names.push(name); + kept.push(entry.clone()); + } else { + dropped_names.push(name); + } + } + + if kept.is_empty() { + println!(" dependabot: refusing to prune every entry; left unchanged"); + return; + } + if dropped_names.is_empty() { + println!(" dependabot: nothing to prune"); + return; + } + + let mut out = head; + for k in &kept { + out.push_str(k); + } + if fs::write(path, out).is_err() { + return; + } + println!( + " dependabot: kept {} / dropped {}", + kept_names.join(", "), + dropped_names.join(", ") + ); +} diff --git a/scripts/rsr-campaign.sh b/scripts/rsr-campaign.sh new file mode 100644 index 0000000..27431f6 --- /dev/null +++ b/scripts/rsr-campaign.sh @@ -0,0 +1,415 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# rsr-campaign.sh — the RSR update mechanism. +# +# WHY THIS EXISTS +# +# ADR-0003 (Forge and Sustain) says a forged repository is *sustained* in four +# modes, of which one is: +# +# adaptive — parent-pin bumps + fan-out campaigns +# +# The check half shipped: scripts/check-variant-drift.sh verifies a child is +# convergent with its parent at the pinned commit, modulo declared divergences. +# The *update* half never existed. That is the gap ADR-0001 lists as a negative +# consequence in one line ("Template updates need propagation mechanism to +# existing repos") while listing "fix once in template, propagate to all repos" +# as a positive one. Both statements were in the same document; only the +# pessimistic one was true. +# +# WHY IT IS A CAMPAIGN AND NOT A MERGE +# +# Measured 2026-09-18 against three representative estate repositories: +# +# repo tracked shared with spine byte-identical +# Axiom.jl 328 55 6 +# wokelang 447 56 6 +# zotero-tools 959 36 6 +# +# and the spine has 546 files. So ~490 spine files are absent from a typical +# repository and only SIX agree exactly. "Converge the estate to the template" +# would therefore rewrite ~50 files and add ~490 in every one of 269 repos — +# roughly 130,000 file additions, against repositories that have legitimately +# spent years diverging. That is not an update mechanism; it is a re-mint, and +# it would destroy customisation at a scale nobody could review. +# +# The honest conclusion: the estate cannot be *converged*, only *updated along +# declared axes*. So a campaign declares exactly what travels. Everything else +# is left alone, by construction rather than by care. +# +# WHAT A CAMPAIGN IS +# +# [meta] +# name / description / why +# [paths] +# spine-relative paths to propagate ('dir/' means the whole directory) +# [exclude] +# paths never to touch, even inside a propagated directory +# +# Per repository, per path: +# +# declared diverged -> SKIPPED (the repo's VARIANT.a2ml says so) +# absent in repo -> ADDED +# present, differs -> UPDATED +# present, identical -> UNCHANGED +# +# SAFETY PROPERTIES, each deliberate: +# +# * Dry-run by default. --push is required to write anything at all. +# * Nothing is ever deleted. A campaign adds and converges; deletion is a +# different operation with a different blast radius and needs its own gate. +# * A repository that declares a path diverged is never touched there, so a +# campaign cannot silently reverse a decision the repo already recorded. +# * Files containing an unfilled {{TOKEN}} are REFUSED by default, because +# propagating one plants a placeholder in a repo whose own placeholder gate +# will then fail its every push. --allow-tokens overrides, deliberately +# loudly. +# * A repository with no changes is not committed to. +# * The spine's own name is rewritten to the target's name, so the campaign +# does not re-plant template identity (the ADR-0003 mint criterion). +# +# Usage: +# rsr-campaign.sh --manifest FILE --repos-file FILE [options] +# +# --manifest FILE campaign manifest (required) +# --repos-file FILE repositories, one per line (required) +# --spine DIR spine checkout; default: the repo this script is in +# --work-dir DIR where to clone; default: a temp dir +# --batch N batch number, 1-based +# --batch-size M repositories per batch (default 25) +# --push commit and push; without it, --apply changes nothing +# --apply write changes into the checkout (still no push) +# --base-ref REF branch to stack on (default: the repo default branch). +# Set this when the campaign depends on work that is not +# merged yet — e.g. the stage-5 sweep branches. +# --branch NAME branch to push to (default: the campaign name) +# --report FILE write a TSV report +# --allow-tokens permit propagating files with {{TOKEN}} placeholders +# --no-substitute do not rewrite the spine name to the repo name +# --quiet less per-repo output +# +# Exit: 0 = every targeted repo reached a terminal state; 1 = at least one failed. + +set -euo pipefail + +PROG="$(basename "$0")" + +MANIFEST=""; REPOS_FILE=""; SPINE=""; WORK_DIR="" +BATCH=""; BATCH_SIZE=25; PUSH=0; APPLY=0; BRANCH=""; REPORT=""; BASE_REF="" +ALLOW_TOKENS=0; SUBSTITUTE=1; QUIET=0 + +die() { printf '%s: %s\n' "$PROG" "$1" >&2; exit 2; } +note() { [ "$QUIET" -eq 1 ] || printf '%s\n' "$*"; } +hr() { [ "$QUIET" -eq 1 ] || printf -- '------------------------------------------------------------\n'; } + +while [ $# -gt 0 ]; do + case "$1" in + --manifest) MANIFEST="${2:?}"; shift 2 ;; + --repos-file) REPOS_FILE="${2:?}"; shift 2 ;; + --spine) SPINE="${2:?}"; shift 2 ;; + --work-dir) WORK_DIR="${2:?}"; shift 2 ;; + --batch) BATCH="${2:?}"; shift 2 ;; + --batch-size) BATCH_SIZE="${2:?}"; shift 2 ;; + --branch) BRANCH="${2:?}"; shift 2 ;; + --base-ref) BASE_REF="${2:?}"; shift 2 ;; + --report) REPORT="${2:?}"; shift 2 ;; + --push) PUSH=1; shift ;; + --apply) APPLY=1; shift ;; + --allow-tokens) ALLOW_TOKENS=1; shift ;; + --no-substitute) SUBSTITUTE=0; shift ;; + --quiet|-q) QUIET=1; shift ;; + -h|--help) sed -n '2,80p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) die "unknown argument: $1" ;; + esac +done + +[ -n "$MANIFEST" ] || die "missing --manifest" +[ -n "$REPOS_FILE" ] || die "missing --repos-file" +[ -f "$MANIFEST" ] || die "manifest not found: $MANIFEST" +[ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" + +# The spine is the source of truth. Default to the checkout this script lives in. +if [ -z "$SPINE" ]; then + SPINE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +fi +[ -d "$SPINE/.git" ] || die "spine is not a git checkout: $SPINE" + +if [ -z "$WORK_DIR" ]; then + WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/rsr-campaign.XXXXXX")" +fi +mkdir -p "$WORK_DIR" + +# --------------------------------------------------------------------------- +# Manifest parsing +# --------------------------------------------------------------------------- +# Deliberately a tiny INI reader rather than a dependency: the manifest is a +# declaration, not a program, and a campaign that needs a parser installed is a +# campaign that cannot be run during an incident. +MANIFEST_SECTION="" +CAMP_NAME="${MANIFEST##*/}"; CAMP_NAME="${CAMP_NAME%.campaign}" +CAMP_DESC="" +PATHS=(); EXCLUDES=() + +while IFS= read -r line || [ -n "$line" ]; do + # strip comments and surrounding space + line="${line%%#*}" + line="$(printf '%s' "$line" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + [ -z "$line" ] && continue + case "$line" in + \[*\]) MANIFEST_SECTION="$(printf '%s' "$line" | tr -d '[]')"; continue ;; + esac + case "$MANIFEST_SECTION" in + meta) + key="${line%%=*}"; val="${line#*=}" + key="$(printf '%s' "$key" | tr -d '[:space:]')" + val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" + case "$key" in + name) [ -n "$val" ] && CAMP_NAME="$val" ;; + description) CAMP_DESC="$val" ;; + esac + ;; + paths) PATHS+=("$line") ;; + exclude) EXCLUDES+=("$line") ;; + *) die "line outside a known section in $MANIFEST: $line" ;; + esac +done < "$MANIFEST" + +[ "${#PATHS[@]}" -gt 0 ] || die "manifest declares no [paths]: $MANIFEST" +[ -n "$BRANCH" ] || BRANCH="$CAMP_NAME" + +note "campaign: $CAMP_NAME" +[ -n "$CAMP_DESC" ] && note " $CAMP_DESC" +note " spine: $SPINE" +note " manifest: $MANIFEST" +note " paths: ${#PATHS[@]} declared, ${#EXCLUDES[@]} excluded" +note " branch: $BRANCH" +[ -n "$BASE_REF" ] && note " stacked on: $BASE_REF" +if [ "$PUSH" -eq 1 ]; then + note " mode: PUSH (commit and push each repository)" +elif [ "$APPLY" -eq 1 ]; then + note " mode: apply (write locally, do not push)" +else + note " mode: dry-run (nothing written)" +fi +hr + +# --------------------------------------------------------------------------- +# Repo selection — same batch arithmetic as the stage-5 sweep, so operators +# only have to know one set of flags. +# --------------------------------------------------------------------------- +mapfile -t ALL_REPOS < <(grep -vE '^\s*(#|$)' "$REPOS_FILE" | sed -e 's/[[:space:]]*$//') +TOTAL=${#ALL_REPOS[@]} +[ "$TOTAL" -gt 0 ] || die "no repositories in $REPOS_FILE" + +if [ -n "$BATCH" ]; then + start=$(( (BATCH - 1) * BATCH_SIZE )) + end=$(( start + BATCH_SIZE )) + [ "$start" -lt "$TOTAL" ] || die "batch $BATCH is past the end ($TOTAL repositories)" + SELECTED=("${ALL_REPOS[@]:start:end-start}") + note "batch $BATCH of size $BATCH_SIZE: ${#SELECTED[@]} of $TOTAL repositories" +else + SELECTED=("${ALL_REPOS[@]}") +fi +hr + +# --------------------------------------------------------------------------- +# Token safety +# --------------------------------------------------------------------------- +# {{TOKEN}} and friends are META tokens: they name a placeholder kind rather +# than being one, and the spine's own gate exempts them. A repository running an +# OLDER check-no-placeholders.sh has no such exemption, so a propagated comment +# mentioning the token would fail that repo's every push. Refusing is the safe +# default; the override exists for a deliberate decision, not a convenience. +META_TOKENS='PLACEHOLDER|ANYTHING|TOKEN|UPPER_SNAKE' + +# --------------------------------------------------------------------------- +# Self-name substitution +# --------------------------------------------------------------------------- +# ADR-0003's mint criterion: no template identity survives in the child. The +# spine's own name is a literal, so propagating it verbatim would re-plant the +# identity the mint pass exists to remove. +SPINE_NAME="$(basename "$SPINE")" +substitute() { # src-file repo-name -> stdout + local f="$1" repo="$2" + if [ "$SUBSTITUTE" -eq 1 ] && [ "$repo" != "$SPINE_NAME" ]; then + sed "s/\b${SPINE_NAME}\b/${repo}/g" "$f" + else + cat "$f" + fi +} + +# --------------------------------------------------------------------------- +# Divergence declarations +# --------------------------------------------------------------------------- +# If the repository carries a VARIANT.a2ml, honour it: a path it declares +# diverged is a decision already made, and a campaign must not silently reverse +# a decision. Matches check-variant-drift.sh's in_list semantics, including the +# 'dir/' prefix form. +declared_diverged() { # repo-path path -> 0 if declared diverged + local dir="$1" want="$2" contract="$1/.machine_readable/descriptiles/VARIANT.a2ml" + [ -f "$contract" ] || return 1 + local e + while IFS= read -r e; do + [ -z "$e" ] && continue + case "$e" in + */) case "$want" in "$e"*) return 0 ;; esac ;; + *) [ "$want" = "$e" ] && return 0 ;; + esac + done < <(awk ' + $0 == "[paths.diverged]" || $0 == "[paths.diverged-pending-upstream]" || + $0 == "[paths.operational-state]" { insec = 1; next } + insec && /^\[/ { insec = 0 } + insec && /^ *"/ { line = $0; sub(/^ *"/,"",line); sub(/".*$/,"",line); print line } + ' "$contract") + return 1 +} + +excluded() { # path -> 0 if excluded by the manifest + local want="$1" e + for e in ${EXCLUDES+"${EXCLUDES[@]}"}; do + case "$e" in + */) case "$want" in "$e"*) return 0 ;; esac ;; + *) [ "$want" = "$e" ] && return 0 ;; + esac + done + return 1 +} + +# --------------------------------------------------------------------------- +# The campaign itself +# --------------------------------------------------------------------------- +REPORT="${REPORT:-$WORK_DIR/campaign-report.tsv}" +printf 'repo\tpath\toutcome\tdetail\n' > "$REPORT" + +ADDED=0; UPDATED=0; UNCHANGED=0; SKIPPED=0; REFUSED=0; NOCHANGE=0; FAILED=0 + +for repo in "${SELECTED[@]}"; do + [ -z "$repo" ] && continue + dir="$WORK_DIR/repos/$repo" + rm -rf "$dir" + mkdir -p "$WORK_DIR/repos" + + # A repos file normally lists bare names under one owner; owner/name is + # accepted too, so a campaign can span owners without a second flag. + case "$repo" in + */*) slug="$repo" ;; + *) slug="${RSR_OWNER:-hyperpolymath}/$repo" ;; + esac + + # --base-ref stacks the campaign on work that is not merged yet. Without it + # the campaign branches from the default branch, which is the honest + # default: a campaign should not silently depend on an unmerged branch. + clone_args=(-q --depth 1) + [ -n "$BASE_REF" ] && clone_args+=(-b "$BASE_REF") + if ! git clone "${clone_args[@]}" "https://github.com/$slug.git" "$dir" 2>/dev/null; then + if [ -n "$BASE_REF" ]; then + printf '%s\t-\tCLONE-FAIL\tbase ref %s not found\n' "$repo" "$BASE_REF" >> "$REPORT" + note " $repo: CLONE-FAIL (no $BASE_REF)" + else + printf '%s\t-\tCLONE-FAIL\t-\n' "$repo" >> "$REPORT" + note " $repo: CLONE-FAIL" + fi + FAILED=$((FAILED+1)); continue + fi + + repo_changed=0 + repo_added=0; repo_updated=0; repo_skipped=0; repo_refused=0 + + # Collect the spine's file list for the declared paths. + while IFS= read -r rel; do + [ -z "$rel" ] && continue + case "$rel" in + *.gitkeep|*/.gitkeep) continue ;; + esac + if excluded "$rel"; then + printf '%s\t%s\tEXCLUDED\tmanifest exclude\n' "$repo" "$rel" >> "$REPORT" + repo_skipped=$((repo_skipped+1)); continue + fi + if declared_diverged "$dir" "$rel"; then + printf '%s\t%s\tDIVERGED\tdeclared in VARIANT.a2ml\n' "$repo" "$rel" >> "$REPORT" + repo_skipped=$((repo_skipped+1)); continue + fi + + src="$SPINE/$rel" + dst="$dir/$rel" + [ -f "$src" ] || continue + + if [ "$ALLOW_TOKENS" -eq 0 ]; then + # Name the actual tokens. A report that says "carries {{TOKEN}}" + # whatever it found is a claim wider than its evidence, which is + # the specific failure mode this estate keeps auditing itself for. + toks="$(grep -ohE '\{\{[A-Z_]+\}\}' "$src" 2>/dev/null \ + | grep -vE "^\{\{($META_TOKENS)\}\}$" | sort -u | tr '\n' ' ')" + if [ -n "$toks" ]; then + # shellcheck disable=SC2086 # deliberate word split for -n + printf '%s\t%s\tREFUSED\tcarries unfilled token(s): %s\n' \ + "$repo" "$rel" "${toks% }" >> "$REPORT" + repo_refused=$((repo_refused+1)); continue + fi + fi + + tmp="$(mktemp)" + substitute "$src" "${repo##*/}" > "$tmp" + + if [ ! -f "$dst" ]; then + printf '%s\t%s\tADDED\t-\n' "$repo" "$rel" >> "$REPORT" + repo_added=$((repo_added+1)); repo_changed=1 + if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then + mkdir -p "$(dirname "$dst")"; cp "$tmp" "$dst" + fi + elif cmp -s "$tmp" "$dst"; then + printf '%s\t%s\tUNCHANGED\t-\n' "$repo" "$rel" >> "$REPORT" + else + printf '%s\t%s\tUPDATED\t-\n' "$repo" "$rel" >> "$REPORT" + repo_updated=$((repo_updated+1)); repo_changed=1 + if [ "$APPLY" -eq 1 ] || [ "$PUSH" -eq 1 ]; then + cp "$tmp" "$dst" + fi + fi + rm -f "$tmp" + done < <(git -C "$SPINE" ls-files -- "${PATHS[@]}" 2>/dev/null | sort) + + ADDED=$((ADDED+repo_added)); UPDATED=$((UPDATED+repo_updated)) + SKIPPED=$((SKIPPED+repo_skipped)); REFUSED=$((REFUSED+repo_refused)) + + if [ "$repo_changed" -eq 0 ]; then + note " $repo: no change ($repo_skipped skipped, $repo_refused refused)" + NOCHANGE=$((NOCHANGE+1)) + continue + fi + + detail="+$repo_added ~$repo_updated" + if [ "$APPLY" -eq 0 ] && [ "$PUSH" -eq 0 ]; then + note " $repo: would change ($detail$([ "$repo_skipped" -gt 0 ] && printf ', %s skipped' "$repo_skipped"))" + continue + fi + + if [ "$PUSH" -eq 1 ]; then + ( cd "$dir" \ + && git checkout -q -b "$BRANCH" \ + && git add -A -- . \ + && git -c user.name="${RSR_COMMIT_NAME:-rsr-campaign}" \ + -c user.email="${RSR_COMMIT_EMAIL:-rsr-campaign@users.noreply.github.com}" \ + commit -q -m "chore(rsr): campaign '$CAMP_NAME' — $detail" \ + && git push -q origin "$BRANCH" ) >/dev/null 2>&1 || { + printf '%s\t-\tPUSH-FAIL\t-\n' "$repo" >> "$REPORT" + note " $repo: PUSH-FAIL" + FAILED=$((FAILED+1)); continue + } + note " $repo: pushed ($detail)" + else + note " $repo: applied ($detail)" + fi +done + +hr +note "campaign '$CAMP_NAME' complete" +note " paths ADDED: $ADDED" +note " paths UPDATED: $UPDATED" +note " paths SKIPPED: $SKIPPED (declared diverged / excluded)" +note " paths REFUSED: $REFUSED (carried an unfilled placeholder)" +note " repos unchanged: $NOCHANGE" +note " repos failed: $FAILED" +note " report: $REPORT" +[ "$FAILED" -eq 0 ] || exit 1 diff --git a/scripts/rust-tool.sh b/scripts/rust-tool.sh new file mode 100644 index 0000000..436fdba --- /dev/null +++ b/scripts/rust-tool.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# rust-tool.sh — compile-on-demand runner for the single-file Rust mint tools. +# +# Ruby was the previous implementation of these tools; it is not an +# estate-authorised language, so they are Rust now. Each tool is a single +# std-only file, which `rustc` compiles in well under a second with no +# dependency resolution and no build system. +# +# Binaries are cached, and rebuilt only when the source is newer, so a machine +# pays the compile cost once rather than once per mint. Override the cache +# location with RSR_MINT_TOOL_CACHE. +# +# Usage: scripts/rust-tool.sh [args...] + +set -euo pipefail + +name="${1:-}" +[ -n "$name" ] || { echo "rust-tool: usage: rust-tool.sh [args...]" >&2; exit 2; } +shift + +scripts_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +src="$scripts_dir/$name.rs" + +[ -f "$src" ] || { echo "rust-tool: no such tool: $src" >&2; exit 2; } + +cache="${RSR_MINT_TOOL_CACHE:-${TMPDIR:-/tmp}/rsr-mint-tools}" +mkdir -p "$cache" +bin="$cache/$name" + +# Recompile only when the binary is missing or older than its source, so the +# common case is a straight exec. +if [ ! -x "$bin" ] || [ "$src" -nt "$bin" ]; then + # The toolchain is needed to BUILD, not to RUN. Checking for rustc up + # front made a cached binary unusable on a machine without one, which is + # the common case in CI: the run is a straight exec. Only ask for rustc + # when a build actually has to happen. + command -v rustc >/dev/null 2>&1 || { + echo "rust-tool: rustc not found, and $name is not built yet." >&2 + echo " The mint tools are Rust and need a Rust toolchain." >&2 + echo " Install one from https://rustup.rs, then re-run." >&2 + exit 2 + } + rustc -O -o "$bin" "$src" >&2 || { + echo "rust-tool: failed to compile $src" >&2 + exit 2 + } +fi + +exec "$bin" "$@" diff --git a/scripts/scan-dangerous.sh b/scripts/scan-dangerous.sh new file mode 100755 index 0000000..9377ce7 --- /dev/null +++ b/scripts/scan-dangerous.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# scan-dangerous.sh — flag dangerous/unsafe constructs USED in proof code. +# +# Dangerous constructs (believe_me, assert_total, postulate, sorry, Admitted, +# unsafeCoerce, Obj.magic) escape the proof obligation and must not appear in +# real proofs. BUT the previous `proof-scan-dangerous` recipe grepped raw +# lines, so a comment that merely NAMED a banned construct — +# -- All proofs MUST be constructive (no believe_me, no assert_total). +# — tripped the gate. A check that fires on its own documentation is a +# false-positive gate: it cries wolf, trains people to override it (violating +# "squabble, don't bypass"), and wired into CI it turns the tree red for +# nothing. This version strips comments first, so only real usage is flagged. +# +# Comment syntax handled: `--` line + `{- -}` block (Idris2/Lean4/Agda), +# `(* *)` block (Coq). Comment bodies are blanked in place, so reported line +# numbers still match the source. +# +# Exit: 0 = clean; 1 = a proof uses a dangerous construct in code. + +set -euo pipefail +cd "$(dirname "${BASH_SOURCE[0]}")/.." + +PATTERNS='believe_me|assert_total|postulate|sorry|Admitted|unsafeCoerce|Obj\.magic' +dangerous=0 + +# Blank comment content while preserving line count (so grep -n stays accurate). +strip_comments() { + local ext="${1##*.}" lc bo bc + case "$ext" in + idr|lean|agda) lc='--'; bo='{-'; bc='-}' ;; # line + block comments + v) lc=''; bo='(*'; bc='*)' ;; # Coq block comments only + *) lc=''; bo=''; bc='' ;; + esac + awk -v lc="$lc" -v bo="$bo" -v bc="$bc" ' + BEGIN { inblk = 0 } + { + line = $0; out = ""; i = 1; n = length(line) + while (i <= n) { + if (inblk) { + if (bc != "" && substr(line,i,length(bc)) == bc) { inblk = 0; i += length(bc) } + else { i++ } + } else if (bo != "" && substr(line,i,length(bo)) == bo) { + inblk = 1; i += length(bo) + } else if (lc != "" && substr(line,i,length(lc)) == lc) { + break # rest of the line is a line-comment + } else { + out = out substr(line,i,1); i++ + } + } + print out + }' "$1" +} + +while IFS= read -r f; do + [ -z "$f" ] && continue + matches="$(strip_comments "$f" | grep -nE "$PATTERNS" || true)" + if [ -n "$matches" ]; then + echo " DANGEROUS (used in code): $f" + printf '%s\n' "$matches" | sed 's/^/ /' + dangerous=$((dangerous + 1)) + fi +done < <(find verification/proofs \ + \( -name '*.idr' -o -name '*.lean' -o -name '*.agda' -o -name '*.v' \) \ + -not -path '*/build/*' 2>/dev/null | sort) + +echo +if [ "$dangerous" -gt 0 ]; then + echo "FAIL: $dangerous file(s) use dangerous constructs in proof CODE (not comments)" + exit 1 +fi +echo "PASS: no dangerous constructs used in proof code" diff --git a/scripts/strip-instruction-blocks.rs b/scripts/strip-instruction-blocks.rs new file mode 100644 index 0000000..eb2060d --- /dev/null +++ b/scripts/strip-instruction-blocks.rs @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// Remove only HTML comments containing TEMPLATE INSTRUCTIONS after minting. +// +// Ported from strip-instruction-blocks.rb: Ruby is not an estate-authorised +// language, and this runs on every mint. Single file, std only, no +// dependencies — compiled on demand by `just repo-init` (see the rust_tool +// helper in build/just/repo-init.just). +// +// The match is deliberately tempered: an HTML comment is only removed when the +// marker appears BEFORE its own `-->`, so a block cannot swallow the +// terminator of a preceding comment. That is what keeps an SPDX comment that +// sits immediately above an instructions block intact. +// +// Usage: strip-instruction-blocks.rs [ROOT] (default ROOT = .) + +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; + +const MARKER: &str = "TEMPLATE INSTRUCTIONS"; +const OPEN: &str = ""; + +/// Directories never descended into. Mirrors the Ruby original. +const SKIP_DIRS: [&str; 5] = [".git", "node_modules", ".venv", "target", "dist"]; + +/// Remove every HTML comment whose body contains MARKER, then collapse runs of +/// three or more newlines to two. +/// +/// Indexing is by byte, which is safe because every boundary we slice at is an +/// ASCII delimiter (``); a multi-byte character can never be split. +fn strip_blocks(text: &str) -> String { + let bytes = text.as_bytes(); + let mut out = String::with_capacity(text.len()); + let mut i = 0usize; + + while i < bytes.len() { + let start = match find_from(text, OPEN, i) { + Some(p) => p, + None => { + out.push_str(&text[i..]); + break; + } + }; + // Everything before this comment is copied verbatim. + out.push_str(&text[i..start]); + + let end = match find_from(text, CLOSE, start + OPEN.len()) { + Some(p) => p, + // Unterminated comment: not ours to touch. + None => { + out.push_str(&text[start..]); + break; + } + }; + + let body = &text[start + OPEN.len()..end]; + if body.contains(MARKER) { + // Drop the comment, plus trailing horizontal space, plus one + // newline, so a removed block does not leave a blank line behind. + let mut j = end + CLOSE.len(); + while j < bytes.len() && (bytes[j] == b' ' || bytes[j] == b'\t') { + j += 1; + } + if j < bytes.len() && bytes[j] == b'\n' { + j += 1; + } + i = j; + } else { + // A comment without the marker is preserved exactly. + out.push_str(&text[start..end + CLOSE.len()]); + i = end + CLOSE.len(); + } + } + + collapse_blank_runs(&out) +} + +fn find_from(haystack: &str, needle: &str, from: usize) -> Option { + haystack[from..].find(needle).map(|p| from + p) +} + +/// Ruby's `gsub(/\n{3,}/, "\n\n")`: three or more newlines become two. +fn collapse_blank_runs(s: &str) -> String { + let mut out = String::with_capacity(s.len()); + let mut run = 0usize; + for ch in s.chars() { + if ch == '\n' { + run += 1; + if run <= 2 { + out.push(ch); + } + } else { + run = 0; + out.push(ch); + } + } + out +} + +fn process(path: &Path, changed: &mut usize) { + let bytes = match fs::read(path) { + Ok(b) => b, + Err(_) => return, + }; + // Non-UTF-8 and unreadable files are skipped, never rewritten blind. + let text = match String::from_utf8(bytes) { + Ok(t) => t, + Err(_) => return, + }; + if !text.contains(MARKER) { + return; + } + let updated = strip_blocks(&text); + if updated == text { + return; + } + if fs::write(path, updated).is_err() { + return; + } + println!(" instruction block: stripped from {}", path.display()); + *changed += 1; +} + +fn walk(root: &Path, changed: &mut usize) { + // Explicit stack rather than recursion: deep trees must not blow the stack. + let mut stack: Vec = vec![root.to_path_buf()]; + while let Some(dir) = stack.pop() { + let entries = match fs::read_dir(&dir) { + Ok(e) => e, + Err(_) => continue, + }; + for entry in entries.flatten() { + let path = entry.path(); + let ft = match entry.file_type() { + Ok(ft) => ft, + Err(_) => continue, + }; + if ft.is_symlink() { + continue; + } + if ft.is_dir() { + let skip = path + .file_name() + .and_then(|n| n.to_str()) + .map(|n| SKIP_DIRS.contains(&n)) + .unwrap_or(false); + if skip { + continue; + } + stack.push(path); + continue; + } + if ft.is_file() { + process(&path, changed); + } + } + } +} + +fn main() { + let root = env::args().nth(1).unwrap_or_else(|| ".".to_string()); + let mut changed = 0usize; + walk(Path::new(&root), &mut changed); + if changed > 0 { + println!(" instruction blocks: {} file(s) cleaned", changed); + } else { + println!(" instruction blocks: none found"); + } +} diff --git a/scripts/sweep-wellknown.sh b/scripts/sweep-wellknown.sh new file mode 100755 index 0000000..212f74e --- /dev/null +++ b/scripts/sweep-wellknown.sh @@ -0,0 +1,421 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# +# sweep-wellknown.sh — stage 5 (#119) batch driver. +# +# #119 enumerated the denominator: 270 of 348 owner repositories carry a root +# .well-known/. This drives the sweep the issue asked for — +# +# per repo: classify -> run migrator -> run suite -> commit +# +# in batches, rather than as 270 hand-made pull requests. It is deliberately +# a driver and not a bot: it never pushes unless you pass --push, and it +# refuses to guess on the one class of repository where migrating is not +# obviously correct. +# +# Classification (one GitHub API call per repo) decides who gets swept: +# +# sweep — identifiably RSR-derived and not served from the repo +# root: migrate it. RSR-ness is judged on a tiered marker +# (root-allow.txt under either spelling, else a +# .machine_readable/ tree), because the allowlist only +# entered the template in August 2026 and most of the +# estate predates it. The marker that fired is recorded. +# review-pages — GitHub Pages / CDN-served from the repo root. #119 names +# these as undecided: for a Pages repo the root +# .well-known/ may be a SERVING REQUIREMENT rather than +# legacy layout, and moving it out of the served root can +# break live discovery. Not swept without --include-review. +# review-other — has a root .well-known/ but no RSR marker at all, so the +# arrangement is not known to be template-derived. +# Not swept without --include-review. +# +# Requirements: bash, git, curl, jq. A token with `repo` scope (public repos +# need only `public_repo`) in GITHUB_TOKEN, or a signed-in `gh` — or pass +# --no-auth with --trees-dir to do everything except push, unauthenticated. +# +# Exit codes: +# 0 — every selected repository ended clean or already-clean +# 1 — at least one repository failed, or quarantined content needs a human +# (suppress the latter with --allow-conflicts) +# 2 — usage / setup error + +set -euo pipefail + +OWNER="hyperpolymath" +REPOS_FILE="" +BATCH="" +BATCH_SIZE=25 +LIMIT="" +DRY_RUN=0 +PUSH=0 +INCLUDE_REVIEW=0 +ALLOW_CONFLICTS=0 +CLASSIFY_ONLY=0 +NO_AUTH=0 +TREES_DIR="" +WORK="" +BRANCH="chore/well-known-to-www" +MIGRATOR="" +SUITE="" + +usage() { + cat <<'EOF' +Usage: scripts/sweep-wellknown.sh [options] [repo ...] + +Classify, migrate and test repositories in batches (dicta-task#119). + +Selecting repositories: + --repos-file FILE one repository name per line ('#' comments allowed) + --owner OWNER default: hyperpolymath + --batch N process only batch N (1-based) of --batch-size + --batch-size N default: 25 + --limit N process only the first N selected repositories + +What to do: + --classify-only classify and write classification.csv; migrate nothing + --include-review also sweep review-pages / review-other (see header) + --dry-run classify, migrate --dry-run, run the suite; commit nothing + --push push the branch to origin (default: commit only) + --branch NAME branch to commit on (default: chore/well-known-to-www) + --allow-conflicts exit 0 even where content was quarantined + --work-dir DIR where to put clones and reports (default: mktemp -d) + --no-auth run without a token: clone read-only over https and + classify from --trees-dir. Classify, migrate, test and + commit all work; only --push needs a credential. + --trees-dir DIR classify from cached `git/trees` listings, one file per + repository, one path per line, as written by a previous + run's /trees/. Required by --no-auth: the + unauthenticated API is capped at 60 requests/hour. + +Environment: + GITHUB_TOKEN / GH_TOKEN required; falls back to `gh auth token` +EOF +} + +die() { echo "sweep: ERROR — $*" >&2; exit 2; } +note() { echo "sweep: $*"; } + +POSITIONAL=() + +while [ $# -gt 0 ]; do + case "$1" in + --owner) [ $# -ge 2 ] || die "--owner requires a value"; OWNER="$2"; shift 2 ;; + --repos-file) [ $# -ge 2 ] || die "--repos-file requires a path"; REPOS_FILE="$2"; shift 2 ;; + --batch) [ $# -ge 2 ] || die "--batch requires a number"; BATCH="$2"; shift 2 ;; + --batch-size) [ $# -ge 2 ] || die "--batch-size requires a number"; BATCH_SIZE="$2"; shift 2 ;; + --limit) [ $# -ge 2 ] || die "--limit requires a number"; LIMIT="$2"; shift 2 ;; + --classify-only) CLASSIFY_ONLY=1; shift ;; + --include-review) INCLUDE_REVIEW=1; shift ;; + --no-auth) NO_AUTH=1; shift ;; + --trees-dir) [ $# -ge 2 ] || die "--trees-dir requires a path"; TREES_DIR="$2"; shift 2 ;; + --dry-run) DRY_RUN=1; shift ;; + --push) PUSH=1; shift ;; + --allow-conflicts) ALLOW_CONFLICTS=1; shift ;; + --branch) [ $# -ge 2 ] || die "--branch requires a value"; BRANCH="$2"; shift 2 ;; + --work-dir) [ $# -ge 2 ] || die "--work-dir requires a path"; WORK="$2"; shift 2 ;; + --migrator) [ $# -ge 2 ] || die "--migrator requires a path"; MIGRATOR="$2"; shift 2 ;; + -h|--help) usage; exit 0 ;; + -*) die "unknown option: $1" ;; + *) POSITIONAL+=("$1"); shift ;; + esac +done + +for c in git curl jq; do + command -v "$c" >/dev/null 2>&1 || die "$c is required but not installed" +done + +# Fail before cloning anything: discovering on repo 200 of 270 that commits +# cannot be made wastes the whole run and leaves 199 half-swept checkouts. +# `git var` honours config and GIT_COMMITTER_* alike, so either setup passes. +if [ "$CLASSIFY_ONLY" -eq 0 ] && [ "$DRY_RUN" -eq 0 ]; then + if ! git var GIT_COMMITTER_IDENT >/dev/null 2>&1; then + die "git cannot determine a committer identity, and the sweep commits. Either: + git config --global user.name \"Your Name\" + git config --global user.email \"you@example.com\" +or export GIT_COMMITTER_NAME / GIT_COMMITTER_EMAIL (plus GIT_AUTHOR_*)." + fi +fi + +# ── token ─────────────────────────────────────────────────────────────────── +TOKEN="${GITHUB_TOKEN:-${GH_TOKEN:-}}" +if [ -z "$TOKEN" ] && command -v gh >/dev/null 2>&1; then + TOKEN="$(gh auth token 2>/dev/null || true)" +fi +if [ -z "$TOKEN" ]; then + if [ "$NO_AUTH" -eq 0 ]; then + die "no token: set GITHUB_TOKEN (repo scope), sign in with gh, or pass --no-auth to run read-only" + fi + note "no token available; running unauthenticated (--no-auth) — push unavailable" + [ -n "$TREES_DIR" ] || die "--no-auth requires --trees-dir (unauthenticated API is 60 req/hour)" + [ -d "$TREES_DIR" ] || die "trees dir not found: $TREES_DIR" +fi +if [ "$PUSH" -eq 1 ] && [ -z "$TOKEN" ]; then + die "--push requires a token; re-run with GITHUB_TOKEN set" +fi + +# ── paths ─────────────────────────────────────────────────────────────────── +# The migrator is located rather than assumed: this script is run from a +# template checkout, from a copy on $PATH, or from an unrelated directory, and +# each of those puts the scripts/ directory somewhere different. +resolve_migrator() { + if [ -n "$MIGRATOR" ]; then printf '%s' "$MIGRATOR"; return; fi + local c + for c in "$(git rev-parse --show-toplevel 2>/dev/null || true)" \ + "$(cd "$(dirname "${BASH_SOURCE[0]}")/.." 2>/dev/null && pwd)" \ + "$PWD"; do + if [ -n "$c" ] && [ -f "$c/scripts/migrate-wellknown-to-www.sh" ]; then + printf '%s' "$c/scripts/migrate-wellknown-to-www.sh" + return + fi + done + printf '' +} +MIGRATOR="$(resolve_migrator)" +[ -n "$MIGRATOR" ] || die "migrator not found; pass --migrator /path/to/migrate-wellknown-to-www.sh" + +if [ -z "$WORK" ]; then + WORK="$(mktemp -d "${TMPDIR:-/tmp}/wellknown-sweep.XXXXXX")" +else + mkdir -p "$WORK" +fi +mkdir -p "$WORK/reports" "$WORK/repos" + +CLASS_CSV="$WORK/classification.csv" +RESULT_CSV="$WORK/sweep-results.csv" +printf 'repo,classification,root_wellknown,www_wellknown,notes\n' > "$CLASS_CSV" +printf 'repo,classification,status,detail\n' > "$RESULT_CSV" + +# ── repository list ───────────────────────────────────────────────────────── +REPOS=() +if [ -n "$REPOS_FILE" ]; then + [ -f "$REPOS_FILE" ] || die "repos file not found: $REPOS_FILE" + while IFS= read -r line; do + line="${line%%#*}" + line="$(printf '%s' "$line" | tr -d '[:space:]')" + [ -n "$line" ] && REPOS+=("$line") + done < "$REPOS_FILE" +fi +REPOS+=(${POSITIONAL[@]+"${POSITIONAL[@]}"}) + +if [ ${#REPOS[@]} -eq 0 ]; then + die "no repositories selected: pass --repos-file FILE or names as arguments" +fi + +if [ -n "$BATCH" ]; then + start=$(( (BATCH - 1) * BATCH_SIZE )) + REPOS=(${REPOS[@]:$start:$BATCH_SIZE}) + [ ${#REPOS[@]} -gt 0 ] || die "batch $BATCH is empty" + note "batch $BATCH of size $BATCH_SIZE: ${#REPOS[@]} repository(ies)" +fi +if [ -n "$LIMIT" ] && [ "$LIMIT" -lt ${#REPOS[@]} ]; then + REPOS=(${REPOS[@]:0:$LIMIT}) +fi +note "selected ${#REPOS[@]} repository(ies) from $OWNER; work dir $WORK" + +# ── classification ───────────────────────────────────────────────────────── +gh_api() { # path... + curl -sS --fail --retry 2 --retry-delay 1 --max-time 60 \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "$@" +} + +classify_repo() { # repo -> "class|root_wk|www_wk|notes"; paths cached in $WORK/trees/ + local repo="$1" + local json paths="" + mkdir -p "$WORK/trees" + + # A cached tree listing is the same data the API would return, so prefer + # it: unauthenticated classification is capped at 60 requests/hour. + if [ -n "$TREES_DIR" ] && [ -f "$TREES_DIR/$repo" ]; then + paths="$(cat "$TREES_DIR/$repo")" + fi + if [ -n "$paths" ]; then + printf '%s\n' "$paths" > "$WORK/trees/$repo" + else + if ! json="$(gh_api "https://api.github.com/repos/$OWNER/$repo/git/trees/HEAD?recursive=1" 2>/dev/null)"; then + printf 'unknown|?|?|API error (missing/empty/private, or rate limited)\n' + return 0 + fi + if [ "$(printf '%s' "$json" | jq -r '.truncated // false')" = "true" ]; then + printf 'review-other|?|?|tree truncated by API — classify by hand\n' + return 0 + fi + paths="$(printf '%s' "$json" | jq -r '.tree[]?.path // empty')" + printf '%s\n' "$paths" > "$WORK/trees/$repo" + fi + + has() { grep -qxF "$1" <<<"$paths"; } + + local root_wk=no www_wk=no rsr=no pages=no notes="" + has ".well-known/security.txt" && root_wk=yes + has "www/.well-known/security.txt" && www_wk=yes + # Any file under either location counts, not just security.txt. + grep -qx "\.well-known/..*" <<<"$paths" && root_wk=yes + grep -qx "www/\.well-known/..*" <<<"$paths" && www_wk=yes + + # RSR-derived, tiered by evidence strength. The root allowlist is the + # marker check-root-shape.sh itself resolves — but it only entered the + # template in August 2026, so a repository minted before then is still + # RSR-derived and simply does not carry it. Measured over the #119 + # denominator: 269/270 have a .machine_readable/ tree while only 59 have + # root-allow.txt. Treating the allowlist as the sole marker therefore + # misclassifies two thirds of the estate as "not an RSR instance", which + # is wrong in the safe direction only by accident. The marker used is + # recorded in the notes column so the call is auditable. + local marker="" + if has ".machine_readable/root-allow.txt" || has "machine-readable/root-allow.txt"; then + rsr=yes + marker="root-allow.txt" + elif grep -q "^\.machine_readable/" <<<"$paths" \ + || grep -q "^machine-readable/" <<<"$paths"; then + rsr=yes + marker=".machine_readable/ (predates root-allow.txt)" + fi + + # Pages / CDN served from the repo root: migrating .well-known/ out of the + # served root is not obviously safe here (#119 special cases). + case "$repo" in + *.github.io) pages=yes; notes="repo name is a Pages site" ;; + esac + if has "CNAME"; then pages=yes; notes="${notes:+$notes; }CNAME at root (custom domain)" ; fi + for f in netlify.toml vercel.json _config.yml wrangler.toml wrangler.jsonc; do + if has "$f"; then pages=yes; notes="${notes:+$notes; }$f present" ; fi + done + + local class + if [ "$pages" = yes ]; then + class=review-pages + elif [ "$rsr" = yes ]; then + class=sweep + else + class=review-other + notes="${notes:+$notes; }no RSR marker found" + fi + [ -n "$marker" ] && notes="${notes:+$notes; }marker: $marker" + printf '%s|%s|%s|%s\n' "$class" "$root_wk" "$www_wk" "$notes" +} + +# ── migration ─────────────────────────────────────────────────────────────── +sweep_repo() { # repo class -> appends to RESULT_CSV + local repo="$1" class="$2" + local dir="$WORK/repos/$repo" + rm -rf "$dir" + + if [ "$CLASSIFY_ONLY" -eq 1 ]; then + printf '%s,%s,skipped,classify-only\n' "$repo" "$class" >> "$RESULT_CSV" + return 0 + fi + + export GIT_TERMINAL_PROMPT=0 + # Public repositories clone fine over plain https; the token only buys + # push access (and a higher rate limit), so do not require it to read. + local clone_url="https://github.com/${OWNER}/${repo}.git" + [ -n "$TOKEN" ] && clone_url="https://x-access-token:${TOKEN}@github.com/${OWNER}/${repo}.git" + if ! git clone --depth 1 -q "$clone_url" "$dir" 2>"$WORK/reports/$repo.clone.log"; then + printf '%s,%s,failed,clone failed (see %s)\n' "$repo" "$class" "$WORK/reports/$repo.clone.log" >> "$RESULT_CSV" + return 0 + fi + + if [ ! -d "$dir/.well-known" ]; then + printf '%s,%s,clean,no root .well-known/ (already migrated or never had one)\n' "$repo" "$class" >> "$RESULT_CSV" + return 0 + fi + + local report="$WORK/reports/$repo.tsv" + local migflags=() + [ "$DRY_RUN" -eq 1 ] && migflags+=(--dry-run) + + local status=migrated detail="" + if (cd "$dir" && bash "$MIGRATOR" "${migflags[@]}" --report "$report") >"$WORK/reports/$repo.migrate.log" 2>&1; then + if [ "$DRY_RUN" -eq 1 ]; then + status=would-migrate + fi + if [ -f "$report" ] && grep -qP '^quarantined\t' "$report"; then + status=quarantined + detail="$(grep -cP '^quarantined\t' "$report") divergent file(s) quarantined — needs a human" + fi + else + status=failed + detail="migrator exited non-zero (see $WORK/reports/$repo.migrate.log)" + fi + + # Post-migration suite: only meaningful where the repo carries the bundle. + if [ -f "$dir/www/tests/run-all.sh" ]; then + if ! (cd "$dir" && bash www/tests/run-all.sh) >"$WORK/reports/$repo.tests.log" 2>&1; then + status=failed + detail="${detail:+$detail; }www/tests/run-all.sh failed (see $WORK/reports/$repo.tests.log)" + fi + else + detail="${detail:+$detail; }no www/tests bundle — run the RSR update mechanism first" + fi + + if [ "$DRY_RUN" -eq 0 ] && [ "$status" != failed ]; then + if [ -n "$(cd "$dir" && git status --porcelain)" ]; then + # A commit failure is recorded, never fatal: one repository with an + # unusual hook or an unwritable ref must not abandon the batch. + if (cd "$dir" && git add -A && git commit -qm "chore(www): migrate root .well-known/ to www/.well-known/ (dicta-task#119)"); then + if [ "$PUSH" -eq 1 ]; then + (cd "$dir" && git push -q origin "HEAD:$BRANCH") \ + || detail="${detail:+$detail; }push failed" + fi + else + status=failed + detail="${detail:+$detail; }commit failed" + fi + else + [ "$status" = migrated ] && status=clean + fi + fi + + printf '%s,%s,%s,%s\n' "$repo" "$class" "$status" "$detail" >> "$RESULT_CSV" + return 0 +} + +# ── main ──────────────────────────────────────────────────────────────────── +declare -i total=0 swept=0 review=0 +for repo in "${REPOS[@]}"; do + total+=1 + IFS='|' read -r class root_wk www_wk notes <<<"$(classify_repo "$repo")" + printf '%s,%s,%s,%s,%s\n' "$repo" "$class" "$root_wk" "$www_wk" "$notes" >> "$CLASS_CSV" + printf ' %-40s %s\n' "$repo" "$class${notes:+ — $notes}" + + if [ "$class" = sweep ] || [ "$INCLUDE_REVIEW" -eq 1 ]; then + swept+=1 + # Belt and braces: sweep_repo records its own failures and returns 0, + # but an unexpected abort must still not take the batch down with it. + if ! sweep_repo "$repo" "$class"; then + printf '%s,%s,failed,unexpected abort (see %s)\n' "$repo" "$class" "$WORK" >> "$RESULT_CSV" + fi + else + review+=1 + printf '%s,%s,skipped,%s\n' "$repo" "$class" "needs a decision: $class" >> "$RESULT_CSV" + fi +done + +echo +note "classification: $CLASS_CSV" +note "results: $RESULT_CSV" +echo +echo "--- results by status ---" +tail -n +2 "$RESULT_CSV" | cut -d, -f3 | sort | uniq -c | sort -rn + +failed=$(tail -n +2 "$RESULT_CSV" | grep -c ',failed,' || true) +quarantined=$(tail -n +2 "$RESULT_CSV" | grep -c ',quarantined,' || true) + +if [ "$failed" -gt 0 ]; then + echo + echo "sweep: $failed repository(ies) FAILED:" >&2 + grep ',failed,' "$RESULT_CSV" >&2 + exit 1 +fi +if [ "$quarantined" -gt 0 ] && [ "$ALLOW_CONFLICTS" -eq 0 ]; then + echo + echo "sweep: $quarantined repository(ies) quarantined divergent content — resolve before merging." >&2 + grep ',quarantined,' "$RESULT_CSV" >&2 + exit 1 +fi +note "done: $total classified, $swept swept, $review held for review" +exit 0 diff --git a/scripts/validate-session-contracts.sh b/scripts/validate-session-contracts.sh new file mode 100644 index 0000000..2fe6531 --- /dev/null +++ b/scripts/validate-session-contracts.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Validate the two session policies with their actual Nickel evaluator. +set -euo pipefail +command -v nickel >/dev/null || { + echo "nickel is required to validate .k9.ncl session policies" >&2 + exit 2 +} +if [[ "${1:-}" == --typecheck ]]; then + shift + [[ $# -gt 0 ]] || { echo 'Supply the instantiated Nickel or K9 files to typecheck' >&2; exit 2; } + for file in "$@"; do + envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") + if [[ -n "$envelope_line" ]]; then + sed "${envelope_line}d" "$file" | (cd -- "$(dirname -- "$file")" && nickel typecheck) + else + nickel typecheck "$file" + fi + echo "$file: Nickel typecheck passed (deployment not executed)" + done + exit 0 +fi +[[ $# -eq 0 ]] || { echo 'Usage: validate-session-contracts.sh [--typecheck FILE...]' >&2; exit 2; } +for file in coordination.k9.ncl session/custom-checks.k9.ncl; do + envelope_line=$(awk '/[^ ]/ { if ($0 == "K9!") print NR; exit }' "$file") + if [[ -z "$envelope_line" ]]; then + echo "$file: missing K9! envelope" >&2 + exit 1 + fi + # K9! is a transport envelope, not a Nickel expression. These standalone + # records have no imports; evaluation also exercises their field contracts. + sed "${envelope_line}d" "$file" | nickel export --format json >/dev/null + echo "$file: Nickel evaluation passed" +done diff --git a/scripts/validate-template.sh b/scripts/validate-template.sh new file mode 100755 index 0000000..e7c53e4 --- /dev/null +++ b/scripts/validate-template.sh @@ -0,0 +1,491 @@ +#!/bin/bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Template Validation Script +# Verifies that a repository follows the RSR template structure and contains all required files +# +# Exit codes: +# 0 = validation passed +# 1 = validation failed with errors +# 2 = validation failed with warnings (but can proceed) + +set -euo pipefail + +REPO_ROOT="${1:-.}" +VERBOSE="${2:-0}" +ERRORS=0 +WARNINGS=0 + +# ANSI colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Helper functions +log_error() { + echo -e "${RED}ERROR${NC}: $*" >&2 + ERRORS=$((ERRORS + 1)) +} + +log_warning() { + echo -e "${YELLOW}WARN${NC}: $*" >&2 + WARNINGS=$((WARNINGS + 1)) +} + +log_info() { + echo -e "${BLUE}INFO${NC}: $*" >&2 +} + +log_pass() { + echo -e "${GREEN}PASS${NC}: $*" >&2 +} + +check_file_exists() { + local file="$1" + local description="${2:-}" + if [ -f "$REPO_ROOT/$file" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: $file" + return 0 + else + log_error "Required file missing: $file ${description:+(${description})}" + return 1 + fi +} + +# The repo deed's FILENAME carries the repository name (filename dispatch: +# _chora.deed, stem = repo slug — deed.abnf v1.0.0), so the check is a +# glob, not a literal. Pre-deed era this slot was 0-AI-MANIFEST.a2ml; the +# family-7 allocation manifest and the ply tree folded into the deed +# (standards#837 pilot). +check_deed_exists() { + local description="${1:-}" + local f + for f in "$REPO_ROOT"/*_chora.deed; do + if [ -f "$f" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Repo deed exists: ${f#"$REPO_ROOT"/}" + return 0 + fi + done + log_error "Required repo deed missing: no _chora.deed at root ${description:+(${description})}" + return 1 +} + +check_file_either() { + local first="$1" + local second="$2" + local description="${3:-}" + if [ -f "$REPO_ROOT/$first" ] || [ -f "$REPO_ROOT/$second" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: $first or $second" + return 0 + fi + log_error "Required file missing: $first or $second ${description:+($description)}" + return 1 +} + +check_dir_exists() { + local dir="$1" + local description="${2:-}" + if [ -d "$REPO_ROOT/$dir" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $dir" + return 0 + else + log_error "Required directory missing: $dir ${description:+(${description})}" + return 1 + fi +} + +# The machine tree has two estate spellings: `.machine_readable/` is canonical, +# `machine-readable/` is the minority form ~9 repositories still carry. This +# file already had check_file_either for exactly this reason; directories had no +# such helper, and the gap let the matrix check below name the hyphenated form +# while its own message said `.machine_readable/`. Naming only one spelling +# fails whichever half of the estate has not migrated. +check_dir_either() { + local first="$1" + local second="$2" + local description="${3:-}" + if [ -d "$REPO_ROOT/$first" ] || [ -d "$REPO_ROOT/$second" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: $first or $second" + return 0 + fi + log_error "Required directory missing: $first or $second ${description:+(${description})}" + return 1 +} + +# Case-tolerant ABI seam checks: accept the canonical case-consistent +# src/interface/Abi/ (matches `module Abi.*`) OR a lowercase src/interface/abi/ +# that some downstream repos ship. Never require BOTH (that would be a case-fold +# collision on case-insensitive filesystems). +check_abi_dir_exists() { + local description="${1:-}" + if [ -d "$REPO_ROOT/src/interface/Abi" ] || [ -d "$REPO_ROOT/src/interface/abi" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Directory exists: src/interface/{Abi,abi}" + return 0 + fi + log_error "Required directory missing: src/interface/Abi ${description:+(${description})}" + return 1 +} +check_abi_file_exists() { + local fname="$1" + local description="${2:-}" + if [ -f "$REPO_ROOT/src/interface/Abi/$fname" ] || [ -f "$REPO_ROOT/src/interface/abi/$fname" ]; then + [ "$VERBOSE" = "1" ] && log_pass "File exists: src/interface/{Abi,abi}/$fname" + return 0 + fi + log_error "Required file missing: src/interface/Abi/$fname ${description:+(${description})}" + return 1 +} + +has_spdx_header() { + local file="$1" + if head -10 "$file" | grep -q "SPDX-License-Identifier"; then + return 0 + fi + return 1 +} + +has_placeholder() { + local file="$1" + if grep -q "{{REPO\|{{OWNER\|{{FORGE\|{{PROJECT\|{{project\|{{AUTHOR" "$file" 2>/dev/null; then + return 0 + fi + return 1 +} + +#============================================================================== +# VALIDATION PHASE 1: CORE STRUCTURE +#============================================================================== + +echo "" +log_info "Phase 1: Core repository structure" +echo "" + +# Root files +check_deed_exists "repo deed (universal AI entry point)" +check_file_exists "README.adoc" "High-level pitch" +check_file_either "EXPLAINME.adoc" "docs/EXPLAINME.adoc" "Developer deep-dive" +check_file_exists "LICENSE" "License file" +check_file_exists "Justfile" "Task runner" +check_file_either "AUDIT.adoc" "docs/AUDIT.adoc" "Release audit gate" + +# Directories +check_dir_either ".machine_readable" "machine-readable" "Machine-readable metadata" +check_dir_exists ".github" "GitHub community metadata" +check_abi_dir_exists "Idris2 ABI definitions" +check_dir_exists "src/interface/ffi" "Zig FFI implementation" +check_dir_exists "src/interface/generated/abi" "Generated C headers" +check_dir_exists "docs" "Documentation" + +#============================================================================== +# VALIDATION PHASE 2: MACHINE-READABLE METADATA +#============================================================================== + +echo "" +log_info "Phase 2: Machine-readable metadata (.machine_readable/)" +echo "" + +check_file_exists ".machine_readable/descriptiles/STATE.a2ml" "Project state" +check_file_exists ".machine_readable/descriptiles/META.a2ml" "Architecture decisions" +check_file_exists ".machine_readable/descriptiles/ECOSYSTEM.a2ml" "Ecosystem position" +check_file_exists ".machine_readable/descriptiles/anchors/ANCHOR.a2ml" "Semantic boundary anchor" +check_file_exists ".machine_readable/policies/MAINTENANCE-AXES.a2ml" "Maintenance axes" + +#============================================================================== +# VALIDATION PHASE 3: REQUIRED WORKFLOWS (17 minimum) +#============================================================================== + +echo "" +log_info "Phase 3: GitHub Actions workflows" +echo "" + +REQUIRED_WORKFLOWS=( + "hypatia-scan.yml" + "codeql.yml" + "scorecard.yml" + "quality.yml" + "mirror.yml" + "guix-policy.yml" + "security-policy.yml" + "wellknown-enforcement.yml" + "workflow-linter.yml" + # npm-bun-blocker.yml and ts-blocker.yml were retired in #14 and replaced by + # runtime-policy.yml: the old gate failed any build carrying bun.lockb with + # "Use Deno instead", which blocked the estate's new first-choice runtime, so + # none of the 55 repos carrying it ever adopted Bun. This list was not + # updated, so it has required two files the template deliberately no longer + # ships — which is why validate-template.sh has been red since that merge. + "runtime-policy.yml" + "secret-scanner.yml" +) + +# A workflow renamed upstream must not read as a missing workflow here. #106 +# moved wellknown-enforcement.yml to dot-wellknown-enforcement.yml to match the +# .well-known/ URL convention and left this list requiring the old name, so this +# gate failed on main — the same way it failed when the two retired files above +# stayed listed. Resolve EITHER spelling and let the alias be dropped once the +# rename has propagated, exactly as check-root-shape.sh resolves both root +# spellings instead of assuming the migration is finished everywhere. +WORKFLOW_ALIASES=( + "wellknown-enforcement.yml:dot-wellknown-enforcement.yml" +) + +resolve_required_workflow() { + local want="$1" pair alt + if [ -f "$REPO_ROOT/.github/workflows/$want" ]; then + printf '%s\n' "$want" + return 0 + fi + for pair in "${WORKFLOW_ALIASES[@]}"; do + [ "${pair%%:*}" = "$want" ] || continue + alt="${pair#*:}" + if [ -f "$REPO_ROOT/.github/workflows/$alt" ]; then + printf '%s\n' "$alt" + return 0 + fi + done + return 1 +} + +# Check required workflows +for workflow in "${REQUIRED_WORKFLOWS[@]}"; do + if found="$(resolve_required_workflow "$workflow")"; then + [ "$VERBOSE" = "1" ] && log_pass "Workflow found: $found" + else + log_error "Required workflow missing: $workflow" + fi +done + +# Verify all workflows have SPDX headers and proper structure +WORKFLOW_FILES=$(find "$REPO_ROOT/.github/workflows" -name "*.yml" -type f 2>/dev/null || true) +WORKFLOW_COUNT=$(echo "$WORKFLOW_FILES" | grep -c "." || true) + +if [ "$WORKFLOW_COUNT" -ge 15 ]; then + log_pass "Found $WORKFLOW_COUNT workflows (>= 15 expected)" +else + log_warning "Found only $WORKFLOW_COUNT workflows (expected >= 15)" +fi + +# Spot-check workflow files for issues +while IFS= read -r workflow_file; do + if [ -z "$workflow_file" ]; then continue; fi + + # Check for SPDX header (optional in YAML workflows, but best practice) + if ! head -5 "$workflow_file" | grep -q "SPDX-License-Identifier"; then + log_warning "Workflow missing SPDX header: $(basename "$workflow_file")" + fi + + # Check for proper YAML structure + if ! grep -q "^name:" "$workflow_file"; then + log_error "Workflow missing 'name' field: $(basename "$workflow_file")" + fi +done <<< "$WORKFLOW_FILES" + +#============================================================================== +# VALIDATION PHASE 4: ABI/FFI SOURCE FILES +#============================================================================== + +echo "" +log_info "Phase 4: Idris2 ABI and Zig FFI source files" +echo "" + +# Idris2 ABI files +check_abi_file_exists "Types.idr" "Core type definitions" +check_abi_file_exists "Layout.idr" "Memory layout specifications" +check_abi_file_exists "Foreign.idr" "FFI foreign declarations" + +# Zig FFI files +check_file_exists "src/interface/ffi/build.zig" "Zig build configuration" +check_file_exists "src/interface/ffi/src/main.zig" "Zig implementation" +check_file_exists "src/interface/ffi/test/integration_test.zig" "Integration tests" + +#============================================================================== +# VALIDATION PHASE 5: PLACEHOLDER TOKENS +#============================================================================== + +echo "" +# The heading is unconditional; the skip below is not. It previously read +# "(skipped in template repo)" on every run, so in an instantiated repo — where +# the check DOES run — the log said it had been skipped. A live check that +# reports itself as skipped is worse than a silent one: it invites people to +# stop reading the phase. The skip announces itself on the line that performs it. +log_info "Phase 5: Placeholder token replacement" +echo "" + +# Note: Template repo is allowed to have placeholders +# For derived repos, we'd check that placeholders are replaced +if [ "$(basename "$REPO_ROOT")" = "dicta-task" ]; then + log_pass "Skipping placeholder check for template repo" +else + # Check that key files don't have unresolved placeholders + for file in "$REPO_ROOT/README.adoc" "$REPO_ROOT/Justfile" "$REPO_ROOT/.machine_readable/descriptiles/STATE.a2ml"; do + if [ -f "$file" ]; then + if has_placeholder "$file"; then + log_warning "File contains unresolved placeholders: $(basename "$file")" + fi + fi + done +fi + +#============================================================================== +# VALIDATION PHASE 6: SPDX LICENSE HEADERS +#============================================================================== + +echo "" +log_info "Phase 6: SPDX License Headers" +echo "" + +# Check source files for SPDX headers (excluding build artifacts). +# +# Scans the whole repository, not just src/, and every estate source language — +# not just Idris2 and Zig. +# +# This used to be `find "$REPO_ROOT/src" ... \( -name "*.idr" -o -name "*.zig" \)`. +# That is fine for the template, whose only sources are src/interface/{abi,ffi}, +# but wrong for the repos instantiated from it: a multi-language project keeps +# code in core-zig/, beam/, clients/, normalizer/ and so on, so the scan saw a +# handful of files and printed "SPDX headers: 10/10 (100%)". Measured across all +# languages the same repo was at 172/188 (91%) — the number was not wrong, it was +# answering a much narrower question than it appeared to. +# +# Uses `git ls-files` so it follows .gitignore and never descends into vendored +# or build directories; falls back to `find` outside a work tree. +SPDX_EXTS='idr|zig|rs|ex|exs|res|resi|factor|fs|lean|jl|gleam|ml|mli|hs|sh|nix|scm' +if git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + SOURCE_FILES=$(git -C "$REPO_ROOT" ls-files \ + | grep -E "\.($SPDX_EXTS)$" \ + | sed "s|^|$REPO_ROOT/|" || true) +else + SOURCE_FILES=$(find "$REPO_ROOT" -type f \ + ! -path "*/.git/*" ! -path "*/.zig-cache/*" ! -path "*/zig-cache/*" \ + ! -path "*/node_modules/*" ! -path "*/target/*" ! -path "*/_build/*" \ + ! -path "*/.lake/*" ! -path "*/deps/*" \ + 2>/dev/null | grep -E "\.($SPDX_EXTS)$" || true) +fi +SOURCE_COUNT=$(echo "$SOURCE_FILES" | grep -c "." || true) +SPDX_COUNT=0 + +while IFS= read -r src_file; do + if [ -z "$src_file" ]; then continue; fi + if has_spdx_header "$src_file"; then + SPDX_COUNT=$((SPDX_COUNT + 1)) + else + log_warning "Source file missing SPDX header: $(basename "$src_file")" + fi +done <<< "$SOURCE_FILES" + +if [ "$SOURCE_COUNT" -gt 0 ]; then + PERCENT=$((SPDX_COUNT * 100 / SOURCE_COUNT)) + log_pass "SPDX headers: $SPDX_COUNT/$SOURCE_COUNT ($PERCENT%)" + if [ "$PERCENT" -lt 100 ]; then + log_warning "Not all source files have SPDX headers" + fi +fi + +#============================================================================== +# VALIDATION PHASE 7: BUILD VERIFICATION +#============================================================================== + +echo "" +log_info "Phase 7: Build system verification" +echo "" + +# Check Zig build +if [ -f "$REPO_ROOT/src/interface/ffi/build.zig" ]; then + if command -v zig &> /dev/null; then + cd "$REPO_ROOT/src/interface/ffi" + if zig build 2>&1 | grep -q "error"; then + log_error "Zig build failed" + else + log_pass "Zig build successful" + fi + cd - > /dev/null + else + log_warning "Zig compiler not found - skipping Zig build check" + fi +else + log_error "Zig build.zig not found" +fi + +# Check Idris2. Prefer a REAL typecheck via the package (abi.ipkg sets the +# sourcedir so the `module Abi.*` namespace resolves); this catches namespace / +# path / import breakage that a bare per-file `idris2 --check` masks as a +# tolerated "module name does not match file name" warning. +if command -v idris2 &> /dev/null; then + if [ -f "$REPO_ROOT/src/interface/abi.ipkg" ]; then + if (cd "$REPO_ROOT" && idris2 --typecheck src/interface/abi.ipkg) > /dev/null 2>&1; then + log_pass "Idris2 ABI typechecks (abi.ipkg)" + else + log_error "Idris2 ABI does NOT typecheck (abi.ipkg)" + fi + else + # No package: fall back to a best-effort per-file syntax check (warns on + # the expected namespace/path mismatch). Look in either case of the dir. + IDS_FILES=$(find "$REPO_ROOT/src/interface/Abi" "$REPO_ROOT/src/interface/abi" -name "*.idr" -type f 2>/dev/null || true) + while IFS= read -r ids_file; do + if [ -z "$ids_file" ]; then continue; fi + if ! idris2 --check "$ids_file" 2>&1 | grep -q "Error"; then + log_pass "Idris2 syntax OK: $(basename "$ids_file")" + else + log_warning "Idris2 syntax issue: $(basename "$ids_file")" + fi + done <<< "$IDS_FILES" + fi +else + log_warning "Idris2 compiler not found - skipping Idris2 syntax checks" +fi + +#============================================================================== +# VALIDATION PHASE 8: DOCUMENTATION +#============================================================================== + +echo "" +log_info "Phase 8: Documentation requirements" +echo "" + +check_file_exists "docs/developer/ABI-FFI-README.adoc" "ABI/FFI documentation" +# TOPOLOGY may live at root or under docs/architecture/, .md or .adoc +if [ -f "$REPO_ROOT/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/TOPOLOGY.md" ] || \ + [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.adoc" ] || [ -f "$REPO_ROOT/docs/architecture/TOPOLOGY.md" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Architecture topology found" +else + log_error "Required file missing: TOPOLOGY (root or docs/architecture/, .adoc or .md)" +fi +# CONTRIBUTING.md may live at root or in .github/ (GitHub auto-discovers either) +if [ -f "$REPO_ROOT/CONTRIBUTING.md" ] || [ -f "$REPO_ROOT/.github/CONTRIBUTING.md" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Contribution guide found" +else + log_error "Required file missing: CONTRIBUTING.md (root or .github/)" +fi + +# Governance can be at root or in docs/governance/ +if [ -f "$REPO_ROOT/GOVERNANCE.adoc" ] || [ -f "$REPO_ROOT/GOVERNANCE.md" ] || [ -d "$REPO_ROOT/docs/governance" ]; then + [ "$VERBOSE" = "1" ] && log_pass "Governance files found" +else + log_warning "Governance documentation not found" +fi + +#============================================================================== +# VALIDATION SUMMARY +#============================================================================== + +echo "" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "VALIDATION SUMMARY" +echo "═══════════════════════════════════════════════════════════════════════════════" +echo "" +echo -e "Errors: ${RED}${ERRORS}${NC}" +echo -e "Warnings: ${YELLOW}${WARNINGS}${NC}" +echo "" + +if [ "$ERRORS" -eq 0 ]; then + echo -e "${GREEN}✓ Validation PASSED${NC}" + [ "$WARNINGS" -gt 0 ] && echo -e " (with $WARNINGS warnings)" + exit 0 +else + echo -e "${RED}✗ Validation FAILED${NC}" + echo " Please fix the errors above." + exit 1 +fi diff --git a/selur-compose.toml b/selur-compose.toml deleted file mode 100644 index ed1870d..0000000 --- a/selur-compose.toml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Stapeln service definition for dictask -# -# Usage: -# podman-compose -f selur-compose.toml up -d -# just stack-up - -[project] -name = "dictask" - -[services.app] -build = { context = ".", dockerfile = "Containerfile" } -restart = "unless-stopped" -networks = ["default"] -healthcheck = { test = "exit 0", interval = "30s", timeout = "5s", retries = 3 } diff --git a/session/README.adoc b/session/README.adoc new file mode 100644 index 0000000..ffa0c27 --- /dev/null +++ b/session/README.adoc @@ -0,0 +1,50 @@ +== Session Bindings (Thin Local Layer) + +This directory provides local integration for central session-management +standards. + +Authoritative protocols live in: + +* `+../standards/3-practice/session-management-standards/+` (or +`+$SESSION_STANDARDS_DIR+`) + +This repo keeps only thin bindings: + +* `+dispatch.sh+` maps canonical commands to central protocol paths. +* `+custom-checks.k9.ncl+` defines repo-local policy checks as a Nickel record. +* `+local-hooks.sh+` provides optional repo-specific hook behavior. + +Run `bash scripts/validate-session-contracts.sh` from the repository root +with Nickel installed to evaluate both session records. The script removes +the `K9!` transport envelope before invoking the actual Nickel evaluator; +a missing evaluator or invalid expression fails validation. + +=== Canonical Commands + +* `+intake repo +` +* `+checkpoint change +` +* `+verify maintenance +` +* `+verify substantial +` +* `+verify release +` +* `+close planned +` +* `+close urgent +` +* `+recover repo +` +* `+handover full +` +* `+handover split +` +* `+handover model +` +* `+handover human +` + +=== Justfile Aliases + +Run `+just session-help+` to list aliases, then use recipes such as: + +* `+just intake-repo path=.+` +* `+just checkpoint-change path=.+` +* `+just verify-maintenance path=.+` +* `+just close-planned path=.+` +* `+just handover-model path=.+` + +=== Runtime Artifacts + +Runtime files are generated per repository in `+.session/+` and are not +canonical standards text. diff --git a/session/custom-checks.k9.ncl b/session/custom-checks.k9.ncl new file mode 100644 index 0000000..6f6f36b --- /dev/null +++ b/session/custom-checks.k9.ncl @@ -0,0 +1,51 @@ +K9! +# SPDX-License-Identifier: MPL-2.0 +# Local repository session checks (thin policy layer) + +{ + pedigree = { + schema_version = "1.0.0", + metadata = { + name = "custom-session-checks", + version = "0.1.0", + }, + security = { + leash = 'Kennel, + }, + }, + + checks = [ + { + id = "session-state-has-next-action", + applies_to = [ + "close planned", + "close urgent", + "handover full", + "handover split", + "handover model", + "handover human", + ], + requirement = "LAST-CANONICAL-COMMAND.md contains next intended action", + }, + { + id = "session-state-has-residual-risks", + applies_to = [ + "verify maintenance", + "verify substantial", + "verify release", + "recover repo", + ], + requirement = "Residual risks field is not left blank", + }, + { + id = "session-state-has-recommended-next-protocol", + applies_to = [ + "intake repo", + "checkpoint change", + "recover repo", + "handover full", + ], + requirement = "Recommended next protocol is set", + }, + ], +} diff --git a/session/dispatch.sh b/session/dispatch.sh new file mode 100755 index 0000000..dd4126c --- /dev/null +++ b/session/dispatch.sh @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +if [ "$#" -lt 3 ]; then + cat >&2 <<'USAGE' +Usage: ./session/dispatch.sh + +Canonical commands: + intake repo + checkpoint change + verify maintenance + verify substantial + verify release + close planned + close urgent + recover repo + handover full + handover split + handover model + handover human +USAGE + exit 2 +fi + +verb="$1" +object="$2" +repo_path="$3" +cmd_pair="$verb $object" + +if [ ! -d "$repo_path" ]; then + echo "error: repository path '$repo_path' does not exist" >&2 + exit 2 +fi + +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "$script_dir/.." && pwd)" + +standards_dir="${SESSION_STANDARDS_DIR:-}" +if [ -z "$standards_dir" ]; then + if [ -d "$repo_root/../standards/3-practice/session-management-standards" ]; then + standards_dir="$repo_root/../standards/3-practice/session-management-standards" + elif [ -d "$repo_root/standards/3-practice/session-management-standards" ]; then + standards_dir="$repo_root/standards/3-practice/session-management-standards" + fi +fi + +case "$cmd_pair" in + "intake repo") + protocol_rel="continuity/repo-intake" + ;; + "checkpoint change") + protocol_rel="continuity/checkpoint-before-major-change" + ;; + "verify maintenance") + protocol_rel="verify/maintenance-sweep" + ;; + "verify substantial") + protocol_rel="verify/substantial-completion" + ;; + "verify release") + protocol_rel="verify/release-audit" + ;; + "close planned") + protocol_rel="continuity/planned-session-close" + ;; + "close urgent") + protocol_rel="continuity/emergency-termination" + ;; + "recover repo") + protocol_rel="continuity/recovery-operation" + ;; + "handover full") + protocol_rel="handover/full-transfer" + ;; + "handover split") + protocol_rel="handover/collaborative-transfer" + ;; + "handover model") + protocol_rel="handover/model-transfer" + ;; + "handover human") + protocol_rel="handover/human-transfer" + ;; + *) + echo "error: unsupported canonical command '$cmd_pair'" >&2 + exit 2 + ;; +esac + +session_dir="$repo_path/.session" +mkdir -p "$session_dir" + +command_record="$session_dir/LAST-CANONICAL-COMMAND.md" + +cat > "$command_record" <&2 + echo "canonical: $cmd_pair $repo_path" + echo "mapped protocol: $protocol_rel" +fi + +hooks="$script_dir/local-hooks.sh" +if [ -x "$hooks" ]; then + "$hooks" "$verb" "$object" "$repo_path" +fi + +echo "recorded: $command_record" diff --git a/session/local-hooks.sh b/session/local-hooks.sh new file mode 100755 index 0000000..a726387 --- /dev/null +++ b/session/local-hooks.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +set -euo pipefail + +verb="${1:-}" +object="${2:-}" +repo_path="${3:-.}" + +session_dir="$repo_path/.session" +mkdir -p "$session_dir" +log_file="$session_dir/local-hooks.log" + +echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) hook: $verb $object $repo_path" >> "$log_file" + +case "$verb $object" in + "verify release") + echo "release hook: ensure AUDIT.adoc and session reports are reviewed" >> "$log_file" + ;; + "close urgent") + echo "urgent hook: prioritize EMERGENCY-CHECKPOINT.md generation" >> "$log_file" + ;; +esac diff --git a/setup.sh b/setup.sh deleted file mode 100755 index 8b4b617..0000000 --- a/setup.sh +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# Dictask — Universal Setup Script -# Detects platform and shell, installs just, then hands off to Justfile. - -set -euo pipefail - -echo "═══════════════════════════════════════════════════" -echo " Dictask — Setup" -echo "═══════════════════════════════════════════════════" -echo "" - -# Platform detection -OS="$(uname -s)" -ARCH="$(uname -m)" -echo "Platform: $OS $ARCH" - -# Shell detection -CURRENT_SHELL="$(basename "$SHELL" 2>/dev/null || echo "unknown")" -echo "Shell: $CURRENT_SHELL" -echo "" - -# Check for just -if ! command -v just >/dev/null 2>&1; then - echo "just (command runner) is required but not installed." - echo "" - case "$OS" in - Linux) - if command -v cargo >/dev/null 2>&1; then - echo "Installing just via cargo..." - cargo install just - elif command -v brew >/dev/null 2>&1; then - echo "Installing just via Homebrew..." - brew install just - else - echo "Install just from: https://just.systems/man/en/installation.html" - exit 1 - fi - ;; - Darwin) - if command -v brew >/dev/null 2>&1; then - echo "Installing just via Homebrew..." - brew install just - else - echo "Install Homebrew first: https://brew.sh" - echo "Then: brew install just" - exit 1 - fi - ;; - *) - echo "Install just from: https://just.systems/man/en/installation.html" - exit 1 - ;; - esac - echo "" -fi - -echo "Running diagnostics..." -just doctor - -echo "" -echo "Setup complete. Run 'just help-me' for common workflows." diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..786b580 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: MPL-2.0 +# SonarQube Cloud (SonarCloud) configuration. +# Project: https://sonarcloud.io/project/overview?id=hyperpolymath_dicta-task +# Requires the SONAR_TOKEN repository secret + a SonarCloud project (owner setup). +sonar.organization=hyperpolymath +sonar.projectKey=hyperpolymath_dicta-task + +# Analysable surface = shell scripts + any JS/TS the template carries. Idris2, +# Zig, Elixir and AffineScript have no SonarCloud analyser; vendored, generated, +# build, proof, doc-template and dependency trees are excluded to keep findings +# signal-rich (mirrors the boj-server arrangement). +# .machine_readable/ (formerly machine-readable/) is DELIBERATELY NOT excluded. +# Un-hiding that tree exposed 108 files to analysis for the first time, 11 of +# them shell scripts. Excluding them here would have bought human legibility +# while preserving the scanner blind spot, which is the defect - not the fix. +# Any findings it surfaces were always there; they were merely unscanned. +# NOTE: the finding-count delta against main must still be measured on the +# branch before merge, since it is unverified whether each scanner skipped +# dot-directories by default. +# +# The rename back to the dotted spelling (2026-09-17) makes that question live +# again rather than moot. Un-hiding the tree is what exposed 108 files to +# analysis; re-hiding it may restore the blind spot this whole decision was +# about, IF a scanner skips dot-directories. Measure before assuming. +sonar.exclusions=build/**,generated/**,**/node_modules/**,**/_build/**,**/deps/**,**/.lake/**,verification/**,build/docs-seed/**,machine-readable-design/**,**/*.idr,**/*.ipkg,**/*.zig +sonar.coverage.exclusions=tests/**,**/*test* diff --git a/src/0.1-AI-MANIFEST.a2ml b/src/0.1-AI-MANIFEST.a2ml deleted file mode 100644 index c92e124..0000000 --- a/src/0.1-AI-MANIFEST.a2ml +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-pillar" -level: 1 -parent: "../0-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Primary source code directory. Organized by role and architectural - aspect. - -canonical_locations: - core: "core/" - interface: "interface/" - bridges: "bridges/" - contracts: "contracts/" - errors: "errors/" - definitions: "definitions/" - aspects: "aspects/" - -invariants: - - "Core logic MUST reside in core/" - - "Verified seams MUST reside in interface/" - - "Safety constraints MUST reside in contracts/" - - "Failure dictionaries MUST reside in errors/" diff --git a/src/README.adoc b/src/README.adoc index 5529f66..9e5bd3c 100644 --- a/src/README.adoc +++ b/src/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = src Pillar diff --git a/src/aspects/0.2-AI-MANIFEST.a2ml b/src/aspects/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 3d5b209..0000000 --- a/src/aspects/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-aspects" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Cross-cutting concerns and domain-specific aspects (Security, - Observability, Integrity). - -canonical_locations: - security: "security/" - observability: "observability/" - integrity: "integrity/" diff --git a/src/aspects/README.adoc b/src/aspects/README.adoc index 6456f96..2b39ea8 100644 --- a/src/aspects/README.adoc +++ b/src/aspects/README.adoc @@ -1 +1,56 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Aspects Pillar +:icons: font + +[.lead] +The **Aspects Pillar** is where a project declares the cross-cutting +_capabilities_ it wears — the **Aspect-Oriented Language Design (AOLD)** seam of +an RSR repository. + +== What an "aspect" is + +An aspect is a capability woven in from a _specialist language_ without adopting +that language wholesale: distribution (Chapel), GPU kernels (Futhark), +data-race freedom (Pony), fault tolerance (OTP), correct-by-construction proof +(Dafny), energy-awareness (Eclexia), reversibility (Oblíbený), and so on. + +Each aspect is delivered by an **-iser** — a Rust CLI that injects the +capability through a uniform, proof-carrying pipeline: + +[literal] +.... + manifest ──► Idris2 ABI ──► Zig FFI ──► target-language codegen ──► build/run +.... + +Your application stays itself; the aspect is an _addable, removable, reversible_ +exoskeleton bolted on at the boundary (the "mech suit" model). Multiple aspects +compose over the same Idris2-ABI/Zig-FFI seam — the _Integrated Stack of Stacks_ +(iSOS). + +== What lives in `src/aspects/` + +Sub-pillars for the cross-cutting concerns this repo weaves in, e.g.: + +* `integrity/` — attestation, tamper-evidence, provenance. +* `observability/` — logging, metrics, tracing. +* `security/` — authz, sandboxing, supply-chain controls. + +A project records _which_ aspects it wears (and the manifest that configures +each -iser) here, so the augmentation is explicit, auditable, and reversible — +never hidden in the core. + +== Keeping aspects honest + +When an aspect crosses the FFI seam into the host, its proven invariant must not +be silently over-generalised (`theorem → guarantee → "universal claim"`). The +**invariant-path** tool is the conscience of this pillar: it traces each aspect's +proven invariant from its Idris2 ABI into the host call sites and flags any point +where the guarantee is carried further than it was proved. + +== See also + +* https://github.com/hyperpolymath/iseriser/blob/main/docs/ATLAS.adoc[The -iser Atlas] — route a need to the right aspect. +* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/AOLD.adoc[AOLD] — the design philosophy. +* https://github.com/hyperpolymath/iseriser/blob/main/docs/theory/iSOS.adoc[iSOS] — composing aspects. +* https://github.com/hyperpolymath/invariant-path[invariant-path] — the claim-path conscience. diff --git a/src/aspects/integrity/0.3-AI-MANIFEST.a2ml b/src/aspects/integrity/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index f114cbd..0000000 --- a/src/aspects/integrity/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "aspect-unit-integrity" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Implementation logic for the integrity aspect. diff --git a/src/aspects/integrity/README.adoc b/src/aspects/integrity/README.adoc index f15d829..d3e1ee1 100644 --- a/src/aspects/integrity/README.adoc +++ b/src/aspects/integrity/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Integrity Aspect diff --git a/src/aspects/observability/0.3-AI-MANIFEST.a2ml b/src/aspects/observability/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index e16cbdf..0000000 --- a/src/aspects/observability/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "aspect-unit-observability" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Implementation logic for the observability aspect. diff --git a/src/aspects/observability/README.adoc b/src/aspects/observability/README.adoc index 7852ee6..8a2151c 100644 --- a/src/aspects/observability/README.adoc +++ b/src/aspects/observability/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Observability Aspect diff --git a/src/aspects/security/0.3-AI-MANIFEST.a2ml b/src/aspects/security/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 0996536..0000000 --- a/src/aspects/security/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "aspect-unit-security" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Implementation logic for the security aspect. diff --git a/src/aspects/security/README.adoc b/src/aspects/security/README.adoc index 3c3536e..e28b5da 100644 --- a/src/aspects/security/README.adoc +++ b/src/aspects/security/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Security Aspect diff --git a/docs/practice/.gitkeep b/src/bridges/.gitkeep similarity index 100% rename from docs/practice/.gitkeep rename to src/bridges/.gitkeep diff --git a/src/bridges/0.2-AI-MANIFEST.a2ml b/src/bridges/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 3d3e27a..0000000 --- a/src/bridges/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-bridges" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Integration logic for external systems (API, Database, RPC, etc.). diff --git a/src/contracts/0.2-AI-MANIFEST.a2ml b/src/contracts/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 0bd9198..0000000 --- a/src/contracts/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-contracts" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Contracts unit for high-rigor source code. diff --git a/src/contracts/README.adoc b/src/contracts/README.adoc index 9cfa209..657be1f 100644 --- a/src/contracts/README.adoc +++ b/src/contracts/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Contracts Unit diff --git a/docs/theory/.gitkeep b/src/core/.gitkeep similarity index 100% rename from docs/theory/.gitkeep rename to src/core/.gitkeep diff --git a/src/core/0.2-AI-MANIFEST.a2ml b/src/core/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 22846c7..0000000 --- a/src/core/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-core" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Primary application logic and core domain models. diff --git a/src/definitions/0.2-AI-MANIFEST.a2ml b/src/definitions/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index e54f4da..0000000 --- a/src/definitions/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-definitions" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Definitions unit for high-rigor source code. diff --git a/src/definitions/README.adoc b/src/definitions/README.adoc index 9548349..02ecc4f 100644 --- a/src/definitions/README.adoc +++ b/src/definitions/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Definitions Unit diff --git a/configs/config.ncl b/src/definitions/config.ncl similarity index 100% rename from configs/config.ncl rename to src/definitions/config.ncl diff --git a/schemas/candidate-intent.json b/src/definitions/schemas/candidate-intent.json similarity index 94% rename from schemas/candidate-intent.json rename to src/definitions/schemas/candidate-intent.json index 4c9def2..c71ad33 100644 --- a/schemas/candidate-intent.json +++ b/src/definitions/schemas/candidate-intent.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/hyperpolymath/dictask/schemas/candidate-intent.json", + "$id": "https://github.com/hyperpolymath/dicta-task/schemas/candidate-intent.json", "title": "Candidate Intent", "description": "Structured output from the Haskell NLP parser — a candidate task intent extracted from a transcript segment.", "type": "object", @@ -18,7 +18,7 @@ }, "parser_version": { "type": "string", - "description": "Version of dictask-parse that produced this intent" + "description": "Version of dicta-task-parse that produced this intent" }, "intent_type": { "type": "string", diff --git a/schemas/config.cue b/src/definitions/schemas/config.cue similarity index 100% rename from schemas/config.cue rename to src/definitions/schemas/config.cue diff --git a/schemas/tasks.sql b/src/definitions/schemas/tasks.sql similarity index 98% rename from schemas/tasks.sql rename to src/definitions/schemas/tasks.sql index f3f49c6..b39be91 100644 --- a/schemas/tasks.sql +++ b/src/definitions/schemas/tasks.sql @@ -1,7 +1,7 @@ -- SPDX-License-Identifier: MPL-2.0 -- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -- --- Canonical task store schema for dictask +-- Canonical task store schema for dicta-task -- SQLite with WAL mode and JSON1 extension PRAGMA journal_mode = WAL; @@ -45,7 +45,7 @@ CREATE TABLE IF NOT EXISTS transcripts ( CREATE TABLE IF NOT EXISTS candidate_intents ( id TEXT PRIMARY KEY, -- UUID v7 transcript_id TEXT NOT NULL REFERENCES transcripts(id), - parser_version TEXT NOT NULL, -- dictask-parse version + parser_version TEXT NOT NULL, -- dicta-task-parse version intent_type TEXT NOT NULL CHECK (intent_type IN ('add_task', 'update_task', 'delete_task', 'set_deadline', 'set_priority', 'add_tag', 'note')), raw_text TEXT NOT NULL, -- original text segment diff --git a/src/errors/0.2-AI-MANIFEST.a2ml b/src/errors/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index dddcc6c..0000000 --- a/src/errors/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "source-unit-errors" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Errors unit for high-rigor source code. diff --git a/src/errors/README.adoc b/src/errors/README.adoc index 460fc1e..b03a1c4 100644 --- a/src/errors/README.adoc +++ b/src/errors/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Errors Unit diff --git a/src/ingest/Cargo.toml b/src/ingest/Cargo.toml index 5cd3e38..55955b6 100644 --- a/src/ingest/Cargo.toml +++ b/src/ingest/Cargo.toml @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MPL-2.0 [package] -name = "dictask-ingest" +name = "dicta-task-ingest" version = "0.1.0" edition = "2024" authors = ["Jonathan D.A. Jewell "] -description = "Device detection, audio archival, and cloud backup for dictask" +description = "Device detection, audio archival, and cloud backup for dicta-task" license = "LicenseRef-MPL-2.0" [dependencies] diff --git a/src/ingest/src/main.rs b/src/ingest/src/main.rs index c592e6c..8e8f241 100644 --- a/src/ingest/src/main.rs +++ b/src/ingest/src/main.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -//! dictask ingest: detect recorder, archive audio files, compute checksums. +//! dicta-task ingest: detect recorder, archive audio files, compute checksums. //! //! Pipeline stages 1-3: //! 1. Detect recorder USB insertion (triggered by udev/systemd) @@ -91,10 +91,10 @@ fn archive_file(source: &Path, archive_dir: &Path) -> Result { async fn main() -> Result<()> { tracing_subscriber::init(); - let data_dir = env::var("DICTASK_DATA_DIR") + let data_dir = env::var("DICTA_TASK_DATA_DIR") .unwrap_or_else(|_| { let home = env::var("HOME").expect("HOME not set"); - format!("{}/.local/share/dictask", home) + format!("{}/.local/share/dicta-task", home) }); let archive_dir = PathBuf::from(&data_dir).join("audio-archive"); @@ -106,7 +106,7 @@ async fn main() -> Result<()> { .nth(1) .map(PathBuf::from) .unwrap_or_else(|| { - warn!("No mount point provided; use: dictask-ingest /path/to/recorder"); + warn!("No mount point provided; use: dicta-task-ingest /path/to/recorder"); PathBuf::from("/media/recorder") }); diff --git a/src/interface/0.2-AI-MANIFEST.a2ml b/src/interface/0.2-AI-MANIFEST.a2ml deleted file mode 100644 index 7f0f471..0000000 --- a/src/interface/0.2-AI-MANIFEST.a2ml +++ /dev/null @@ -1,24 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "interface-seams-unit" -level: 2 -parent: "../0.1-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Consolidated "Verified Interface Seams" unit. This directory unifies the - formal specification (ABI), the bridge implementation (FFI), and the - resulting artifacts (Generated). - -canonical_locations: - abi: "abi/" - ffi: "ffi/" - generated: "generated/" - -invariants: - - "ABI MUST be Idris2 (.idr)" - - "FFI MUST be Zig (.zig)" - - "Generated artifacts MUST be C-compatible" - - "The 'Truth' lives in abi/; the 'Implementation' lives in ffi/" diff --git a/src/interface/Abi/Foreign.idr b/src/interface/Abi/Foreign.idr new file mode 100644 index 0000000..ca66b08 --- /dev/null +++ b/src/interface/Abi/Foreign.idr @@ -0,0 +1,83 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +||| Foreign Function Interface Bridge +||| +||| This module defines the raw FFI calls and their safe wrappers, +||| implemented in the Zig FFI layer. + +module Abi.Foreign + +import Abi.Types +import Abi.Layout + +%default total + +-------------------------------------------------------------------------------- +-- Library Lifecycle +-------------------------------------------------------------------------------- + +||| Raw FFI call to initialize the library +%foreign "C:rsr_init,librsr" +prim__init : PrimIO Bits64 + +||| Raw FFI call to free library resources +%foreign "C:rsr_free,librsr" +prim__free : Bits64 -> PrimIO () + +||| Safe wrapper for initialization +export +init : IO (Maybe Handle) +init = do + ptr <- primIO prim__init + pure (createHandle ptr) + +||| Safe wrapper for cleanup +export +free : Handle -> IO () +free h = primIO (prim__free h.ptr) + +-------------------------------------------------------------------------------- +-- Core Operations +-------------------------------------------------------------------------------- + +||| Raw FFI call for main processing +%foreign "C:rsr_process,librsr" +prim__process : Bits64 -> Bits32 -> PrimIO Bits32 + +||| Safe wrapper with error handling +export +process : Handle -> Bits32 -> IO (Either Result Bits32) +process h input = do + result <- primIO (prim__process h.ptr input) + if result == 0 + then pure (Left Error) + else pure (Right result) + +-------------------------------------------------------------------------------- +-- Status and Metrics +-------------------------------------------------------------------------------- + +||| Get the current error description from the library +%foreign "C:rsr_get_error,librsr" +prim__getError : Bits64 -> PrimIO (Ptr String) + +||| Detailed error string helper +export +errorDescription : Result -> String +errorDescription Ok = "Success" +errorDescription Error = "Generic error" +errorDescription InvalidParam = "Invalid parameter" +errorDescription Busy = "Library is busy" + +-------------------------------------------------------------------------------- +-- Documentation +-------------------------------------------------------------------------------- + +||| Summary of ABI safety properties: +||| 1. All functions are total (total keyword enforced). +||| 2. Pointers are verified non-null before being wrapped in Handle. +||| 3. Memory layouts are proven C-ABI compliant in Abi.Layout. +||| 4. FFI boundary uses explicitly tagged types from Abi.Types. +public export +abiSafetyGuarantees : String +abiSafetyGuarantees = "RSR-Template ABI: 4 proven safety properties for FFI integration" diff --git a/src/interface/Abi/Layout.idr b/src/interface/Abi/Layout.idr new file mode 100644 index 0000000..e1f4275 --- /dev/null +++ b/src/interface/Abi/Layout.idr @@ -0,0 +1,128 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +||| ABI Layout Verification +||| +||| This module provides formal proofs about memory layout, alignment, +||| and padding for C-compatible structs. + +module Abi.Layout + +import Abi.Types +import Data.Vect +import Data.So + +%default total + +-------------------------------------------------------------------------------- +-- Alignment Invariants +-------------------------------------------------------------------------------- + +||| Predicate: n divides m +public export +data Divides : (n, m : Nat) -> Type where + MkDivides : (k : Nat) -> (0 prf : m = k * n) -> Divides n m + +||| Implementation of divides for common sizes +public export +div8_24 : Divides 8 24 +div8_24 = MkDivides 3 Refl + +public export +div4_0 : Divides 4 0 +div4_0 = MkDivides 0 Refl + +public export +div8_8 : Divides 8 8 +div8_8 = MkDivides 1 Refl + +public export +div8_16 : Divides 8 16 +div8_16 = MkDivides 2 Refl + +||| Calculate padding required for an offset to meet alignment +public export +paddingFor : (offset : Nat) -> (alignment : Nat) -> Nat +paddingFor offset 0 = 0 +paddingFor offset alignment = + let m = offset `mod` alignment in + if m == 0 + then 0 + else alignment `minus` m + +||| Align a size up to the next multiple of alignment +public export +alignUp : (size : Nat) -> (alignment : Nat) -> Nat +alignUp size alignment = + size + paddingFor size alignment + +-------------------------------------------------------------------------------- +-- Struct Model +-------------------------------------------------------------------------------- + +||| Representation of a single field in a struct +public export +record Field where + constructor MkField + name : String + offset : Nat + size : Nat + alignment : Nat + +||| Valid memory layout for a C struct +public export +record StructLayout where + constructor MkStructLayout + {n : Nat} + fields : Vect n Field + totalSize : Nat + alignment : Nat + {auto 0 aligned : Divides alignment totalSize} + +-------------------------------------------------------------------------------- +-- Compliance Predicates +-------------------------------------------------------------------------------- + +||| Proof that all fields in a struct are correctly aligned +public export +data FieldsAligned : Vect n Field -> Type where + NoFields : FieldsAligned [] + ConsField : + (f : Field) -> + (rest : Vect n Field) -> + (0 prf : Divides f.alignment f.offset) -> + FieldsAligned rest -> + FieldsAligned (f :: rest) + +||| Predicate: Struct is C-ABI compliant +public export +data CABICompliant : StructLayout -> Type where + CABIOk : (l : StructLayout) -> + (0 prf : FieldsAligned l.fields) -> + CABICompliant l + +-------------------------------------------------------------------------------- +-- Example and Proofs +-------------------------------------------------------------------------------- + +||| Example: struct { int32_t x; int64_t y; double z; } +||| On 64-bit Linux, this should have size 24, alignment 8. +public export +exampleLayout : StructLayout +exampleLayout = + MkStructLayout + [ MkField "x" 0 4 4 -- Bits32 at offset 0 + , MkField "y" 8 8 8 -- Bits64 at offset 8 (4 bytes padding) + , MkField "z" 16 8 8 -- Double at offset 16 + ] + 24 -- Total size: 24 bytes + 8 -- Alignment: 8 bytes + {aligned = div8_24} + +||| Proof that example layout is valid +public export +exampleLayoutValid : CABICompliant Abi.Layout.exampleLayout +exampleLayoutValid = CABIOk Abi.Layout.exampleLayout ( + ConsField (MkField "x" 0 4 4) _ div4_0 ( + ConsField (MkField "y" 8 8 8) _ div8_8 ( + ConsField (MkField "z" 16 8 8) _ div8_16 ( + NoFields)))) diff --git a/src/interface/Abi/README.adoc b/src/interface/Abi/README.adoc new file mode 100644 index 0000000..4a99517 --- /dev/null +++ b/src/interface/Abi/README.adoc @@ -0,0 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell += abi Logic diff --git a/src/interface/abi/Task.idr b/src/interface/Abi/Task.idr similarity index 98% rename from src/interface/abi/Task.idr rename to src/interface/Abi/Task.idr index c81c2a4..e70bcda 100644 --- a/src/interface/abi/Task.idr +++ b/src/interface/Abi/Task.idr @@ -1,10 +1,10 @@ -- SPDX-License-Identifier: MPL-2.0 -- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -- --- | Idris2 ABI definitions for the dictask Task type. +-- | Idris2 ABI definitions for the dicta-task Task type. -- Dependent types prove schema correctness at compile time. -- These definitions are the authoritative specification for the task schema. -module Task +module Abi.Task import Data.Fin import Data.Vect diff --git a/src/interface/Abi/Types.idr b/src/interface/Abi/Types.idr new file mode 100644 index 0000000..9d30051 --- /dev/null +++ b/src/interface/Abi/Types.idr @@ -0,0 +1,112 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +||| ABI Type Definitions Template +||| +||| This module defines the Application Binary Interface (ABI) for this library. +||| All type definitions include formal proofs of correctness. + +module Abi.Types + +import Data.Bits +import Data.So +import Data.Vect +import Decidable.Equality + +%default total + +-------------------------------------------------------------------------------- +-- Platform Model +-------------------------------------------------------------------------------- + +||| Target platforms for the FFI bridge +public export +data Platform = Linux | MacOS | Windows | WASM | RISCV + +||| Pointer size in bits per platform +public export +ptrSize : Platform -> Nat +ptrSize Linux = 64 +ptrSize MacOS = 64 +ptrSize Windows = 64 +ptrSize WASM = 32 +ptrSize RISCV = 64 + +||| Current target platform (detected at compile-time) +public export +thisPlatform : Platform +thisPlatform = Linux -- Simplified for template + +-------------------------------------------------------------------------------- +-- Core Types +-------------------------------------------------------------------------------- + +||| Return codes for FFI calls +public export +data Result = Ok | Error | InvalidParam | Busy + +||| Results are decidably equal +public export +implementation DecEq Result where + decEq Ok Ok = Yes Refl + decEq Error Error = Yes Refl + decEq InvalidParam InvalidParam = Yes Refl + decEq Busy Busy = Yes Refl + decEq Ok Error = No (\case Refl impossible) + decEq Ok InvalidParam = No (\case Refl impossible) + decEq Ok Busy = No (\case Refl impossible) + decEq Error Ok = No (\case Refl impossible) + decEq Error InvalidParam = No (\case Refl impossible) + decEq Error Busy = No (\case Refl impossible) + decEq InvalidParam Ok = No (\case Refl impossible) + decEq InvalidParam Error = No (\case Refl impossible) + decEq InvalidParam Busy = No (\case Refl impossible) + decEq Busy Ok = No (\case Refl impossible) + decEq Busy Error = No (\case Refl impossible) + decEq Busy InvalidParam = No (\case Refl impossible) + +||| Opaque handle for library resources +||| Invariant: Handle pointer must be non-null +public export +record Handle where + constructor MkHandle + ptr : Bits64 + 0 prf : So (ptr /= 0) + +||| Returns Nothing if pointer is null +public export +createHandle : Bits64 -> Maybe Handle +createHandle 0 = Nothing +createHandle ptr = case decSo (ptr /= 0) of + Yes p => Just (MkHandle ptr p) + No _ => Nothing + +-------------------------------------------------------------------------------- +-- C-Types Mapping +-------------------------------------------------------------------------------- + +||| Tagged types for C-FFI boundary +public export +data CType = CInt | CUInt | CLong | CULong | CPtrType + +||| Pointer type for platform +public export +CPtr : Platform -> CType -> Type +CPtr p _ = Bits64 -- Simplified for 64-bit template + +||| Size of C types (platform-specific) +public export +cSizeOf : (p : Platform) -> (t : CType) -> Nat +cSizeOf p CInt = 4 +cSizeOf p CUInt = 4 +cSizeOf p CLong = 8 +cSizeOf p CULong = 8 +cSizeOf p CPtrType = 8 + +||| Alignment of C types (platform-specific) +public export +cAlignOf : (p : Platform) -> (t : CType) -> Nat +cAlignOf p CInt = 4 +cAlignOf p CUInt = 4 +cAlignOf p CLong = 8 +cAlignOf p CULong = 8 +cAlignOf p CPtrType = 8 diff --git a/src/interface/README.adoc b/src/interface/README.adoc index 8faf0aa..4fde708 100644 --- a/src/interface/README.adoc +++ b/src/interface/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = interface Unit diff --git a/src/interface/abi.ipkg b/src/interface/abi.ipkg new file mode 100644 index 0000000..4fe929c --- /dev/null +++ b/src/interface/abi.ipkg @@ -0,0 +1,37 @@ +-- SPDX-License-Identifier: MPL-2.0 +-- Copyright (c) Jonathan D.A. Jewell +-- +-- Idris2 package for the formally-typed ABI seam. +-- +-- The seam lives in a single, case-consistent directory src/interface/Abi/ +-- (uppercase) so the physical path matches the `module Abi.*` namespace on +-- EVERY filesystem. Idris2 requires capitalised namespace components, and a +-- lowercase abi/ directory cannot satisfy that without a case-twin symlink +-- (src/interface/Abi -> abi) that collides on case-insensitive filesystems +-- (macOS/Windows). One case-consistent directory has exactly one path identity +-- under case-folding, so the collision is impossible by construction. +-- +-- A bare `idris2 --check src/interface/Abi/Foo.idr` still warns ("module name +-- does not match file name") because Idris derives the expected module from the +-- full path; that is expected. Use the package for a real typecheck: +-- idris2 --typecheck src/interface/abi.ipkg (or --build) +-- +-- The RSR validators accept either Abi/ (canonical, case-consistent) or a +-- lowercase abi/ for downstream repos that ship lowercase — but never both. + +package abi + +version = 0.1.0 + +authors = "Jonathan D.A. Jewell" + +brief = "Formally-typed ABI/FFI seam (Idris2 type + layout proofs) for an RSR-templated repository" + +sourcedir = "." + +depends = base + +modules = Abi.Types + , Abi.Layout + , Abi.Foreign + , Abi.Task diff --git a/src/interface/abi/0.3-AI-MANIFEST.a2ml b/src/interface/abi/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 91cafa0..0000000 --- a/src/interface/abi/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "abi-logic" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised Level 3 logic for abi. diff --git a/src/interface/abi/README.adoc b/src/interface/abi/README.adoc deleted file mode 100644 index 2330304..0000000 --- a/src/interface/abi/README.adoc +++ /dev/null @@ -1 +0,0 @@ -= abi Logic diff --git a/src/interface/ffi/0.3-AI-MANIFEST.a2ml b/src/interface/ffi/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index bf456ae..0000000 --- a/src/interface/ffi/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "ffi-logic" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised Level 3 logic for ffi. diff --git a/src/interface/ffi/README.adoc b/src/interface/ffi/README.adoc index 8fe57d3..ca14320 100644 --- a/src/interface/ffi/README.adoc +++ b/src/interface/ffi/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = ffi Logic diff --git a/src/interface/ffi/build.zig b/src/interface/ffi/build.zig index 6d2b213..4bee6f8 100644 --- a/src/interface/ffi/build.zig +++ b/src/interface/ffi/build.zig @@ -1,35 +1,37 @@ // SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +// +// dicta-task FFI build (Zig 0.15+ module API). +// zig build -> libdicta_task_ffi (shared + static) +// zig build test -> unit tests (src/main.zig) + integration tests (test/) const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); - // Shared library for Rust consumers - const lib = b.addSharedLibrary(.{ - .name = "dictask_ffi", + const ffi_mod = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - b.installArtifact(lib); - // Static library for linking - const static_lib = b.addStaticLibrary(.{ - .name = "dictask_ffi", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - b.installArtifact(static_lib); + const shared = b.addLibrary(.{ .name = "dicta_task_ffi", .linkage = .dynamic, .root_module = ffi_mod }); + b.installArtifact(shared); + const static = b.addLibrary(.{ .name = "dicta_task_ffi", .linkage = .static, .root_module = ffi_mod }); + b.installArtifact(static); - // Tests - const main_tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), + const unit = b.addTest(.{ .root_module = ffi_mod }); + + const integ_mod = b.createModule(.{ + .root_source_file = b.path("test/integration_test.zig"), .target = target, .optimize = optimize, }); - const run_main_tests = b.addRunArtifact(main_tests); - const test_step = b.step("test", "Run FFI tests"); - test_step.dependOn(&run_main_tests.step); + integ_mod.addImport("dicta_task_ffi", ffi_mod); + const integ = b.addTest(.{ .root_module = integ_mod }); + + const test_step = b.step("test", "Run FFI unit + integration tests"); + test_step.dependOn(&b.addRunArtifact(unit).step); + test_step.dependOn(&b.addRunArtifact(integ).step); } diff --git a/src/interface/ffi/src/0.4-AI-MANIFEST.a2ml b/src/interface/ffi/src/0.4-AI-MANIFEST.a2ml deleted file mode 100644 index 5b5f1b1..0000000 --- a/src/interface/ffi/src/0.4-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "src-unit" -level: 4 -parent: "../0.3-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Src logic at level 4. diff --git a/src/interface/ffi/src/README.adoc b/src/interface/ffi/src/README.adoc index a5c0c6d..d5631f6 100644 --- a/src/interface/ffi/src/README.adoc +++ b/src/interface/ffi/src/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Src Logic diff --git a/src/interface/ffi/src/main.zig b/src/interface/ffi/src/main.zig index bae81a7..e711a56 100644 --- a/src/interface/ffi/src/main.zig +++ b/src/interface/ffi/src/main.zig @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -// Zig FFI bridge for dictask — C-compatible interface between +// Zig FFI bridge for dicta-task — C-compatible interface between // Idris2 ABI definitions and Rust consumer components. const std = @import("std"); @@ -110,27 +110,27 @@ pub const CCandidateIntent = extern struct { // ============================================================================ /// Create a new Confidence value, clamped to [0.0, 1.0]. -export fn dictask_confidence_new(value: f64) f64 { +pub export fn dicta_task_confidence_new(value: f64) f64 { return Confidence.init(value).value; } /// Check if confidence is high (>= 0.8). -export fn dictask_confidence_is_high(value: f64) bool { +pub export fn dicta_task_confidence_is_high(value: f64) bool { return Confidence.init(value).isHigh(); } /// Check if confidence is medium (0.3-0.8). -export fn dictask_confidence_is_medium(value: f64) bool { +pub export fn dicta_task_confidence_is_medium(value: f64) bool { return Confidence.init(value).isMedium(); } /// Check if confidence is low (< 0.3). -export fn dictask_confidence_is_low(value: f64) bool { +pub export fn dicta_task_confidence_is_low(value: f64) bool { return Confidence.init(value).isLow(); } /// Compute priority score from weighted components. -export fn dictask_priority_compute(urgency: f64, importance: f64, deadline_proximity: f64) f64 { +pub export fn dicta_task_priority_compute(urgency: f64, importance: f64, deadline_proximity: f64) f64 { const ps = PriorityScore{ .urgency = urgency, .importance = importance, diff --git a/src/interface/ffi/src/verisimdb.zig b/src/interface/ffi/src/verisimdb.zig index c6ad6ef..bbe15a9 100644 --- a/src/interface/ffi/src/verisimdb.zig +++ b/src/interface/ffi/src/verisimdb.zig @@ -1,15 +1,15 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -// dictask :: src/interface/ffi/src/verisimdb.zig +// dicta-task :: src/interface/ffi/src/verisimdb.zig // // VeriSimDB persistence client for extracted task store. // -// Dual-writes task records to VeriSimDB (collection: dictask:tasks) alongside +// Dual-writes task records to VeriSimDB (collection: dicta-task:tasks) alongside // the primary SQLite store. This enables Hypatia analysis of task patterns // across recording sessions and cross-device sync via VeriSimDB replication. // -// ## Collection schema (dictask:tasks) +// ## Collection schema (dicta-task:tasks) // // ```json // { @@ -42,7 +42,7 @@ const std = @import("std"); // --------------------------------------------------------------------------- const DEFAULT_URL = "http://localhost:8080"; -const COLLECTION = "dictask:tasks"; +const COLLECTION = "dicta-task:tasks"; // --------------------------------------------------------------------------- // Public types @@ -64,9 +64,9 @@ pub const TaskRecord = struct { // Public API // --------------------------------------------------------------------------- -/// Dual-write a task record to VeriSimDB (collection: dictask:tasks). +/// Dual-write a task record to VeriSimDB (collection: dicta-task:tasks). /// -/// Uses HTTP PUT to `/v1/dictask:tasks/`. +/// Uses HTTP PUT to `/v1/dicta-task:tasks/`. /// Fail-open: caller should log the error and continue with SQLite. pub fn persistTask(allocator: std.mem.Allocator, task: TaskRecord) !void { const base_url = std.posix.getenv("VERISIMDB_URL") orelse DEFAULT_URL; diff --git a/src/interface/ffi/test/0.4-AI-MANIFEST.a2ml b/src/interface/ffi/test/0.4-AI-MANIFEST.a2ml deleted file mode 100644 index e02427f..0000000 --- a/src/interface/ffi/test/0.4-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "test-unit" -level: 4 -parent: "../0.3-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Test logic at level 4. diff --git a/src/interface/ffi/test/abi_header_smoke.c b/src/interface/ffi/test/abi_header_smoke.c index ee42196..84fb4f4 100644 --- a/src/interface/ffi/test/abi_header_smoke.c +++ b/src/interface/ffi/test/abi_header_smoke.c @@ -4,23 +4,23 @@ * Compile-only consumer test for the generated C ABI. */ -#include "../../generated/dictask_ffi.h" +#include "../../generated/dicta_task_ffi.h" -_Static_assert(DICTASK_STATUS_PENDING == 0, "task status ABI changed"); -_Static_assert(DICTASK_STATUS_CANCELLED == 4, "task status ABI changed"); -_Static_assert(DICTASK_REVIEW_PENDING == 0, "review state ABI changed"); -_Static_assert(DICTASK_REVIEW_REJECTED == 2, "review state ABI changed"); -_Static_assert(DICTASK_INTENT_ADD_TASK == 0, "intent type ABI changed"); -_Static_assert(DICTASK_INTENT_NOTE == 6, "intent type ABI changed"); +_Static_assert(DICTA_TASK_STATUS_PENDING == 0, "task status ABI changed"); +_Static_assert(DICTA_TASK_STATUS_CANCELLED == 4, "task status ABI changed"); +_Static_assert(DICTA_TASK_REVIEW_PENDING == 0, "review state ABI changed"); +_Static_assert(DICTA_TASK_REVIEW_REJECTED == 2, "review state ABI changed"); +_Static_assert(DICTA_TASK_INTENT_ADD_TASK == 0, "intent type ABI changed"); +_Static_assert(DICTA_TASK_INTENT_NOTE == 6, "intent type ABI changed"); -double dictask_ffi_header_smoke_test(double confidence) +double dicta_task_ffi_header_smoke_test(double confidence) { - const double bounded = dictask_confidence_new(confidence); + const double bounded = dicta_task_confidence_new(confidence); - if (dictask_confidence_is_high(bounded) || - dictask_confidence_is_medium(bounded) || - dictask_confidence_is_low(bounded)) { - return dictask_priority_compute(bounded, bounded, bounded); + if (dicta_task_confidence_is_high(bounded) || + dicta_task_confidence_is_medium(bounded) || + dicta_task_confidence_is_low(bounded)) { + return dicta_task_priority_compute(bounded, bounded, bounded); } return 0.0; diff --git a/src/interface/ffi/test/integration_test.zig b/src/interface/ffi/test/integration_test.zig index 0341994..41165b2 100644 --- a/src/interface/ffi/test/integration_test.zig +++ b/src/interface/ffi/test/integration_test.zig @@ -1,182 +1,48 @@ -// {{PROJECT}} Integration Tests // SPDX-License-Identifier: MPL-2.0 +// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -// These tests verify that the Zig FFI correctly implements the Idris2 ABI - +// dicta-task FFI integration tests: exercise the exported C ABI exactly as a +// Rust/C consumer would, and pin the invariants declared in Abi/Task.idr. const std = @import("std"); const testing = std.testing; - -// Import FFI functions -extern fn {{project}}_init() ?*opaque {}; -extern fn {{project}}_free(?*opaque {}) void; -extern fn {{project}}_process(?*opaque {}, u32) c_int; -extern fn {{project}}_get_string(?*opaque {}) ?[*:0]const u8; -extern fn {{project}}_free_string(?[*:0]const u8) void; -extern fn {{project}}_last_error() ?[*:0]const u8; -extern fn {{project}}_version() [*:0]const u8; -extern fn {{project}}_is_initialized(?*opaque {}) u32; - -//============================================================================== -// Lifecycle Tests -//============================================================================== - -test "create and destroy handle" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - try testing.expect(handle != null); -} - -test "handle is initialized" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const initialized = {{project}}_is_initialized(handle); - try testing.expectEqual(@as(u32, 1), initialized); -} - -test "null handle is not initialized" { - const initialized = {{project}}_is_initialized(null); - try testing.expectEqual(@as(u32, 0), initialized); -} - -//============================================================================== -// Operation Tests -//============================================================================== - -test "process with valid handle" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const result = {{project}}_process(handle, 42); - try testing.expectEqual(@as(c_int, 0), result); // 0 = ok -} - -test "process with null handle returns error" { - const result = {{project}}_process(null, 42); - try testing.expectEqual(@as(c_int, 4), result); // 4 = null_pointer -} - -//============================================================================== -// String Tests -//============================================================================== - -test "get string result" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const str = {{project}}_get_string(handle); - defer if (str) |s| {{project}}_free_string(s); - - try testing.expect(str != null); -} - -test "get string with null handle" { - const str = {{project}}_get_string(null); - try testing.expect(str == null); -} - -//============================================================================== -// Error Handling Tests -//============================================================================== - -test "last error after null handle operation" { - _ = {{project}}_process(null, 0); - - const err = {{project}}_last_error(); - try testing.expect(err != null); - - if (err) |e| { - const err_str = std.mem.span(e); - try testing.expect(err_str.len > 0); +const ffi = @import("dicta_task_ffi"); + +test "confidence is clamped to [0,1]" { + try testing.expectEqual(@as(f64, 1.0), ffi.dicta_task_confidence_new(7.0)); + try testing.expectEqual(@as(f64, 0.0), ffi.dicta_task_confidence_new(-3.0)); + try testing.expectEqual(@as(f64, 0.42), ffi.dicta_task_confidence_new(0.42)); +} + +test "confidence bands partition [0,1] with no gaps or overlaps" { + var i: usize = 0; + while (i <= 100) : (i += 1) { + const v = @as(f64, @floatFromInt(i)) / 100.0; + var bands: u8 = 0; + if (ffi.dicta_task_confidence_is_high(v)) bands += 1; + if (ffi.dicta_task_confidence_is_medium(v)) bands += 1; + if (ffi.dicta_task_confidence_is_low(v)) bands += 1; + try testing.expectEqual(@as(u8, 1), bands); } } -test "no error after successful operation" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - _ = {{project}}_process(handle, 0); - - // Error should be cleared after successful operation - // (This depends on implementation) -} - -//============================================================================== -// Version Tests -//============================================================================== - -test "version string is not empty" { - const ver = {{project}}_version(); - const ver_str = std.mem.span(ver); - - try testing.expect(ver_str.len > 0); -} - -test "version string is semantic version format" { - const ver = {{project}}_version(); - const ver_str = std.mem.span(ver); - - // Should be in format X.Y.Z - try testing.expect(std.mem.count(u8, ver_str, ".") >= 1); -} - -//============================================================================== -// Memory Safety Tests -//============================================================================== - -test "multiple handles are independent" { - const h1 = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(h1); - - const h2 = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(h2); - - try testing.expect(h1 != h2); - - // Operations on h1 should not affect h2 - _ = {{project}}_process(h1, 1); - _ = {{project}}_process(h2, 2); -} - -test "double free is safe" { - const handle = {{project}}_init() orelse return error.InitFailed; - - {{project}}_free(handle); - {{project}}_free(handle); // Should not crash +test "confidence band boundaries (0.3 medium, 0.8 high)" { + try testing.expect(ffi.dicta_task_confidence_is_low(0.2999)); + try testing.expect(ffi.dicta_task_confidence_is_medium(0.3)); + try testing.expect(ffi.dicta_task_confidence_is_medium(0.7999)); + try testing.expect(ffi.dicta_task_confidence_is_high(0.8)); } -test "free null is safe" { - {{project}}_free(null); // Should not crash +test "priority = 0.5*urgency + 0.3*importance + 0.2*deadline" { + try testing.expectApproxEqAbs(@as(f64, 1.0), ffi.dicta_task_priority_compute(1, 1, 1), 1e-12); + try testing.expectApproxEqAbs(@as(f64, 0.0), ffi.dicta_task_priority_compute(0, 0, 0), 1e-12); + try testing.expectApproxEqAbs(@as(f64, 0.5), ffi.dicta_task_priority_compute(1, 0, 0), 1e-12); + try testing.expectApproxEqAbs(@as(f64, 0.3), ffi.dicta_task_priority_compute(0, 1, 0), 1e-12); + try testing.expectApproxEqAbs(@as(f64, 0.2), ffi.dicta_task_priority_compute(0, 0, 1), 1e-12); } -//============================================================================== -// Thread Safety Tests (if applicable) -//============================================================================== - -test "concurrent operations" { - const handle = {{project}}_init() orelse return error.InitFailed; - defer {{project}}_free(handle); - - const ThreadContext = struct { - h: *opaque {}, - id: u32, - }; - - const thread_fn = struct { - fn run(ctx: ThreadContext) void { - _ = {{project}}_process(ctx.h, ctx.id); - } - }.run; - - var threads: [4]std.Thread = undefined; - for (&threads, 0..) |*thread, i| { - thread.* = try std.Thread.spawn(.{}, thread_fn, .{ - ThreadContext{ .h = handle, .id = @intCast(i) }, - }); - } - - for (threads) |thread| { - thread.join(); - } +test "enum discriminants match SQLite CHECK / Idris2 ABI ordering" { + try testing.expectEqual(@as(u8, 0), @intFromEnum(ffi.TaskStatus.pending)); + try testing.expectEqual(@as(u8, 4), @intFromEnum(ffi.TaskStatus.cancelled)); + try testing.expectEqual(@as(u8, 2), @intFromEnum(ffi.ReviewState.rejected)); + try testing.expectEqual(@as(u8, 6), @intFromEnum(ffi.IntentType.note)); } diff --git a/src/interface/generated/0.3-AI-MANIFEST.a2ml b/src/interface/generated/0.3-AI-MANIFEST.a2ml deleted file mode 100644 index 0088b80..0000000 --- a/src/interface/generated/0.3-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "generated-logic" -level: 3 -parent: "../0.2-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Specialised Level 3 logic for generated. diff --git a/src/interface/generated/README.adoc b/src/interface/generated/README.adoc index 3691b06..9e94b29 100644 --- a/src/interface/generated/README.adoc +++ b/src/interface/generated/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = generated Logic diff --git a/src/interface/generated/abi/.gitkeep b/src/interface/generated/abi/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/interface/generated/abi/0.4-AI-MANIFEST.a2ml b/src/interface/generated/abi/0.4-AI-MANIFEST.a2ml deleted file mode 100644 index 4eeb580..0000000 --- a/src/interface/generated/abi/0.4-AI-MANIFEST.a2ml +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-License-Identifier: MPL-2.0 ---- -### [META] -id: "abi-unit" -level: 4 -parent: "../0.3-AI-MANIFEST.a2ml" - ---- -### [AI_MANIFEST] -description: | - Abi logic at level 4. diff --git a/src/interface/generated/abi/README.adoc b/src/interface/generated/abi/README.adoc index aff61a9..f3b3808 100644 --- a/src/interface/generated/abi/README.adoc +++ b/src/interface/generated/abi/README.adoc @@ -1 +1,3 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// Copyright (c) Jonathan D.A. Jewell = Abi Logic diff --git a/src/interface/generated/dictask_ffi.h b/src/interface/generated/dicta_task_ffi.h similarity index 51% rename from src/interface/generated/dictask_ffi.h rename to src/interface/generated/dicta_task_ffi.h index 884667e..7e04d2f 100644 --- a/src/interface/generated/dictask_ffi.h +++ b/src/interface/generated/dicta_task_ffi.h @@ -1,12 +1,12 @@ /* SPDX-License-Identifier: MPL-2.0 * Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) * - * Generated C header for dictask FFI bridge. + * Generated C header for dicta-task FFI bridge. * DO NOT EDIT — regenerate from Idris2 ABI + Zig FFI. */ -#ifndef DICTASK_FFI_H -#define DICTASK_FFI_H +#ifndef DICTA_TASK_FFI_H +#define DICTA_TASK_FFI_H #include #include @@ -18,30 +18,30 @@ extern "C" { /* Task status enumeration */ typedef enum { - DICTASK_STATUS_PENDING = 0, - DICTASK_STATUS_IN_PROGRESS = 1, - DICTASK_STATUS_DONE = 2, - DICTASK_STATUS_REVIEW_NEEDED = 3, - DICTASK_STATUS_CANCELLED = 4, -} dictask_task_status_t; + DICTA_TASK_STATUS_PENDING = 0, + DICTA_TASK_STATUS_IN_PROGRESS = 1, + DICTA_TASK_STATUS_DONE = 2, + DICTA_TASK_STATUS_REVIEW_NEEDED = 3, + DICTA_TASK_STATUS_CANCELLED = 4, +} dicta_task_task_status_t; /* Review state enumeration */ typedef enum { - DICTASK_REVIEW_PENDING = 0, - DICTASK_REVIEW_APPROVED = 1, - DICTASK_REVIEW_REJECTED = 2, -} dictask_review_state_t; + DICTA_TASK_REVIEW_PENDING = 0, + DICTA_TASK_REVIEW_APPROVED = 1, + DICTA_TASK_REVIEW_REJECTED = 2, +} dicta_task_review_state_t; /* Intent type enumeration */ typedef enum { - DICTASK_INTENT_ADD_TASK = 0, - DICTASK_INTENT_UPDATE_TASK = 1, - DICTASK_INTENT_DELETE_TASK = 2, - DICTASK_INTENT_SET_DEADLINE = 3, - DICTASK_INTENT_SET_PRIORITY = 4, - DICTASK_INTENT_ADD_TAG = 5, - DICTASK_INTENT_NOTE = 6, -} dictask_intent_type_t; + DICTA_TASK_INTENT_ADD_TASK = 0, + DICTA_TASK_INTENT_UPDATE_TASK = 1, + DICTA_TASK_INTENT_DELETE_TASK = 2, + DICTA_TASK_INTENT_SET_DEADLINE = 3, + DICTA_TASK_INTENT_SET_PRIORITY = 4, + DICTA_TASK_INTENT_ADD_TAG = 5, + DICTA_TASK_INTENT_NOTE = 6, +} dicta_task_intent_type_t; /* C-compatible task struct */ typedef struct { @@ -50,16 +50,16 @@ typedef struct { size_t title_len; const uint8_t *description_ptr; /* NULL if no description */ size_t description_len; - dictask_task_status_t status; + dicta_task_task_status_t status; double priority_urgency; double priority_importance; double priority_deadline_proximity; const uint8_t *due_date_ptr; /* ISO 8601, NULL if none */ size_t due_date_len; bool due_date_tentative; - dictask_review_state_t review_state; + dicta_task_review_state_t review_state; double confidence; -} dictask_task_t; +} dicta_task_task_t; /* C-compatible candidate intent struct */ typedef struct { @@ -67,24 +67,24 @@ typedef struct { uint8_t transcript_id[36]; const uint8_t *parser_version_ptr; size_t parser_version_len; - dictask_intent_type_t intent_type; + dicta_task_intent_type_t intent_type; const uint8_t *raw_text_ptr; size_t raw_text_len; double confidence; bool review_required; -} dictask_candidate_intent_t; +} dicta_task_candidate_intent_t; /* Confidence functions */ -double dictask_confidence_new(double value); -bool dictask_confidence_is_high(double value); -bool dictask_confidence_is_medium(double value); -bool dictask_confidence_is_low(double value); +double dicta_task_confidence_new(double value); +bool dicta_task_confidence_is_high(double value); +bool dicta_task_confidence_is_medium(double value); +bool dicta_task_confidence_is_low(double value); /* Priority computation */ -double dictask_priority_compute(double urgency, double importance, double deadline_proximity); +double dicta_task_priority_compute(double urgency, double importance, double deadline_proximity); #ifdef __cplusplus } #endif -#endif /* DICTASK_FFI_H */ +#endif /* DICTA_TASK_FFI_H */ diff --git a/src/parse/dictask-parse.cabal b/src/parse/dicta-task-parse.cabal similarity index 71% rename from src/parse/dictask-parse.cabal rename to src/parse/dicta-task-parse.cabal index 0985d3a..a61d61c 100644 --- a/src/parse/dictask-parse.cabal +++ b/src/parse/dicta-task-parse.cabal @@ -1,12 +1,12 @@ -- SPDX-License-Identifier: MPL-2.0 cabal-version: 3.0 -name: dictask-parse +name: dicta-task-parse version: 0.1.0.0 synopsis: NLP task extraction from speech transcripts description: Haskell parser using megaparsec to extract structured task intents from speech transcripts. Produces candidate intents with confidence - scores for the dictask pipeline. + scores for the dicta-task pipeline. license: LicenseRef-MPL-2.0 license-file: ../../LICENSE author: Jonathan D.A. Jewell @@ -16,15 +16,15 @@ build-type: Simple library exposed-modules: - Dictask.Parse.Intent - Dictask.Parse.Types - Dictask.Parse.Confidence - Dictask.Parse.DateResolver - Dictask.Parse.Priority - Dictask.Parse.Dedup + DictaTask.Parse.Intent + DictaTask.Parse.Types + DictaTask.Parse.Confidence + DictaTask.Parse.DateResolver + DictaTask.Parse.Priority + DictaTask.Parse.Dedup other-modules: - Dictask.Parse.Internal.Patterns - Dictask.Parse.Internal.Tokeniser + DictaTask.Parse.Internal.Patterns + DictaTask.Parse.Internal.Tokeniser build-depends: base >= 4.17 && < 5 , megaparsec >= 9.0 @@ -40,17 +40,17 @@ library ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints -test-suite dictask-parse-test +test-suite dicta-task-parse-test type: exitcode-stdio-1.0 main-is: Main.hs other-modules: - Test.Dictask.Parse.IntentSpec - Test.Dictask.Parse.ConfidenceSpec - Test.Dictask.Parse.DateResolverSpec - Test.Dictask.Parse.DedupSpec + Test.DictaTask.Parse.IntentSpec + Test.DictaTask.Parse.ConfidenceSpec + Test.DictaTask.Parse.DateResolverSpec + Test.DictaTask.Parse.DedupSpec build-depends: base - , dictask-parse + , dicta-task-parse , hspec >= 2.11 , QuickCheck >= 2.14 , megaparsec diff --git a/src/parse/src/Dictask/Parse/Confidence.hs b/src/parse/src/DictaTask/Parse/Confidence.hs similarity index 98% rename from src/parse/src/Dictask/Parse/Confidence.hs rename to src/parse/src/DictaTask/Parse/Confidence.hs index 484e420..9943f4b 100644 --- a/src/parse/src/Dictask/Parse/Confidence.hs +++ b/src/parse/src/DictaTask/Parse/Confidence.hs @@ -13,7 +13,7 @@ -- - Tentative language ("I might need to", "possibly") -- - Contradictory context ("do that but also don't") -- - Incomplete sentences (transcription cut off) -module Dictask.Parse.Confidence +module DictaTask.Parse.Confidence ( -- * Confidence adjustment adjustConfidence , confidenceFromPatternMatch @@ -29,7 +29,7 @@ module Dictask.Parse.Confidence import Data.Text (Text) import qualified Data.Text as T -import Dictask.Parse.Types +import DictaTask.Parse.Types -- | Where an intent should be routed based on confidence. data ReviewRoute diff --git a/src/parse/src/Dictask/Parse/DateResolver.hs b/src/parse/src/DictaTask/Parse/DateResolver.hs similarity index 98% rename from src/parse/src/Dictask/Parse/DateResolver.hs rename to src/parse/src/DictaTask/Parse/DateResolver.hs index 491bb3b..896713b 100644 --- a/src/parse/src/Dictask/Parse/DateResolver.hs +++ b/src/parse/src/DictaTask/Parse/DateResolver.hs @@ -9,7 +9,7 @@ -- "next week" → tentative (which day?), medium confidence -- "after Easter" → tentative (holiday lookup needed), low confidence -- "sometime soon" → no date, flag as tentative -module Dictask.Parse.DateResolver +module DictaTask.Parse.DateResolver ( -- * Date resolution ResolvedDate (..) , resolveDate @@ -20,7 +20,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time (Day, addDays, DayOfWeek (..), dayOfWeek) -import Dictask.Parse.Types (Confidence, mkConfidence) +import DictaTask.Parse.Types (Confidence, mkConfidence) -- | A resolved date with its confidence and tentative flag. data ResolvedDate = ResolvedDate diff --git a/src/parse/src/Dictask/Parse/Dedup.hs b/src/parse/src/DictaTask/Parse/Dedup.hs similarity index 97% rename from src/parse/src/Dictask/Parse/Dedup.hs rename to src/parse/src/DictaTask/Parse/Dedup.hs index ed32e17..f3e5f4e 100644 --- a/src/parse/src/Dictask/Parse/Dedup.hs +++ b/src/parse/src/DictaTask/Parse/Dedup.hs @@ -7,7 +7,7 @@ -- Exact match: Same title + same project → auto-merge -- Semantic similar: Similar title, different wording → flag for review -- Recurring: Same pattern across recordings → link with supersedes -module Dictask.Parse.Dedup +module DictaTask.Parse.Dedup ( -- * Deduplication DedupResult (..) , DedupAction (..) @@ -20,7 +20,7 @@ import Data.Char (isAlphaNum, isSpace, toLower) import Data.Text (Text) import qualified Data.Text as T -import Dictask.Parse.Types (Confidence, mkConfidence) +import DictaTask.Parse.Types (Confidence, mkConfidence) -- | What action to take after dedup check. data DedupAction diff --git a/src/parse/src/Dictask/Parse/Intent.hs b/src/parse/src/DictaTask/Parse/Intent.hs similarity index 98% rename from src/parse/src/Dictask/Parse/Intent.hs rename to src/parse/src/DictaTask/Parse/Intent.hs index b139411..4a584e3 100644 --- a/src/parse/src/Dictask/Parse/Intent.hs +++ b/src/parse/src/DictaTask/Parse/Intent.hs @@ -8,12 +8,12 @@ -- values, each with a confidence score. High-confidence intents (>= 0.8) are -- auto-applied to the canonical store; medium-confidence (0.3–0.8) go to the -- review queue; low-confidence (< 0.3) are logged only. -module Dictask.Parse.Intent +module DictaTask.Parse.Intent ( -- * Parsing parseTranscript , parseIntent -- * Re-exports - , module Dictask.Parse.Types + , module DictaTask.Parse.Types ) where import Data.Text (Text) @@ -23,7 +23,7 @@ import Text.Megaparsec import Text.Megaparsec.Char import qualified Text.Megaparsec.Char.Lexer as L -import Dictask.Parse.Types +import DictaTask.Parse.Types -- | Parser type alias for megaparsec. type Parser = Parsec Void Text diff --git a/src/parse/src/Dictask/Parse/Internal/Patterns.hs b/src/parse/src/DictaTask/Parse/Internal/Patterns.hs similarity index 94% rename from src/parse/src/Dictask/Parse/Internal/Patterns.hs rename to src/parse/src/DictaTask/Parse/Internal/Patterns.hs index 8a73381..bc679cd 100644 --- a/src/parse/src/Dictask/Parse/Internal/Patterns.hs +++ b/src/parse/src/DictaTask/Parse/Internal/Patterns.hs @@ -2,8 +2,8 @@ -- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -- -- | Internal pattern definitions for intent detection. --- Not exported directly — used by Dictask.Parse.Intent. -module Dictask.Parse.Internal.Patterns where +-- Not exported directly — used by DictaTask.Parse.Intent. +module DictaTask.Parse.Internal.Patterns where import Data.Text (Text) diff --git a/src/parse/src/Dictask/Parse/Internal/Tokeniser.hs b/src/parse/src/DictaTask/Parse/Internal/Tokeniser.hs similarity index 88% rename from src/parse/src/Dictask/Parse/Internal/Tokeniser.hs rename to src/parse/src/DictaTask/Parse/Internal/Tokeniser.hs index 845ad96..80b5d94 100644 --- a/src/parse/src/Dictask/Parse/Internal/Tokeniser.hs +++ b/src/parse/src/DictaTask/Parse/Internal/Tokeniser.hs @@ -2,8 +2,8 @@ -- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -- -- | Internal tokeniser for splitting transcripts into parseable segments. --- Not exported directly — used by Dictask.Parse.Intent. -module Dictask.Parse.Internal.Tokeniser +-- Not exported directly — used by DictaTask.Parse.Intent. +module DictaTask.Parse.Internal.Tokeniser ( -- * Tokenisation splitIntoSegments , normaliseSegment diff --git a/src/parse/src/Dictask/Parse/Priority.hs b/src/parse/src/DictaTask/Parse/Priority.hs similarity index 97% rename from src/parse/src/Dictask/Parse/Priority.hs rename to src/parse/src/DictaTask/Parse/Priority.hs index c569998..04c3a80 100644 --- a/src/parse/src/Dictask/Parse/Priority.hs +++ b/src/parse/src/DictaTask/Parse/Priority.hs @@ -8,7 +8,7 @@ -- Urgency is derived from keywords like "urgent", "asap", "critical". -- Importance is derived from keywords like "important", "key", "essential". -- Deadline proximity is computed from how close the due date is. -module Dictask.Parse.Priority +module DictaTask.Parse.Priority ( -- * Keyword detection detectUrgency , detectImportance @@ -22,7 +22,7 @@ import Data.Text (Text) import qualified Data.Text as T import Data.Time (Day, diffDays) -import Dictask.Parse.Types (PriorityScore, computePriority) +import DictaTask.Parse.Types (PriorityScore, computePriority) -- | Detect urgency from keywords in the text. -- Returns a value in [0.0, 1.0]. diff --git a/src/parse/src/Dictask/Parse/Types.hs b/src/parse/src/DictaTask/Parse/Types.hs similarity index 96% rename from src/parse/src/Dictask/Parse/Types.hs rename to src/parse/src/DictaTask/Parse/Types.hs index 5c1ea93..bd1891e 100644 --- a/src/parse/src/Dictask/Parse/Types.hs +++ b/src/parse/src/DictaTask/Parse/Types.hs @@ -1,10 +1,10 @@ -- SPDX-License-Identifier: MPL-2.0 -- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -- --- | Core types for the dictask NLP parser. +-- | Core types for the dicta-task NLP parser. -- These mirror the Idris2 ABI definitions in src/interface/abi/ --- and the SQLite schema in schemas/tasks.sql. -module Dictask.Parse.Types +-- and the SQLite schema in src/definitions/schemas/tasks.sql. +module DictaTask.Parse.Types ( -- * Task types Task (..) , TaskStatus (..) diff --git a/src/parse/test/Main.hs b/src/parse/test/Main.hs index db44ce5..7f041bd 100644 --- a/src/parse/test/Main.hs +++ b/src/parse/test/Main.hs @@ -3,10 +3,10 @@ module Main (main) where import Test.Hspec -import qualified Test.Dictask.Parse.IntentSpec as IntentSpec -import qualified Test.Dictask.Parse.ConfidenceSpec as ConfidenceSpec -import qualified Test.Dictask.Parse.DateResolverSpec as DateResolverSpec -import qualified Test.Dictask.Parse.DedupSpec as DedupSpec +import qualified Test.DictaTask.Parse.IntentSpec as IntentSpec +import qualified Test.DictaTask.Parse.ConfidenceSpec as ConfidenceSpec +import qualified Test.DictaTask.Parse.DateResolverSpec as DateResolverSpec +import qualified Test.DictaTask.Parse.DedupSpec as DedupSpec main :: IO () main = hspec $ do diff --git a/src/parse/test/Test/Dictask/Parse/ConfidenceSpec.hs b/src/parse/test/Test/DictaTask/Parse/ConfidenceSpec.hs similarity index 92% rename from src/parse/test/Test/Dictask/Parse/ConfidenceSpec.hs rename to src/parse/test/Test/DictaTask/Parse/ConfidenceSpec.hs index f0e9641..f6b4e2f 100644 --- a/src/parse/test/Test/Dictask/Parse/ConfidenceSpec.hs +++ b/src/parse/test/Test/DictaTask/Parse/ConfidenceSpec.hs @@ -1,9 +1,9 @@ -- SPDX-License-Identifier: MPL-2.0 -module Test.Dictask.Parse.ConfidenceSpec (spec) where +module Test.DictaTask.Parse.ConfidenceSpec (spec) where import Test.Hspec -import Dictask.Parse.Types -import Dictask.Parse.Confidence +import DictaTask.Parse.Types +import DictaTask.Parse.Confidence spec :: Spec spec = do diff --git a/src/parse/test/Test/Dictask/Parse/DateResolverSpec.hs b/src/parse/test/Test/DictaTask/Parse/DateResolverSpec.hs similarity index 89% rename from src/parse/test/Test/Dictask/Parse/DateResolverSpec.hs rename to src/parse/test/Test/DictaTask/Parse/DateResolverSpec.hs index 8341e39..accf7f5 100644 --- a/src/parse/test/Test/Dictask/Parse/DateResolverSpec.hs +++ b/src/parse/test/Test/DictaTask/Parse/DateResolverSpec.hs @@ -1,10 +1,10 @@ -- SPDX-License-Identifier: MPL-2.0 -module Test.Dictask.Parse.DateResolverSpec (spec) where +module Test.DictaTask.Parse.DateResolverSpec (spec) where import Test.Hspec import Data.Time (fromGregorian) -import Dictask.Parse.DateResolver -import Dictask.Parse.Types (unConfidence) +import DictaTask.Parse.DateResolver +import DictaTask.Parse.Types (unConfidence) spec :: Spec spec = do diff --git a/src/parse/test/Test/Dictask/Parse/DedupSpec.hs b/src/parse/test/Test/DictaTask/Parse/DedupSpec.hs similarity index 85% rename from src/parse/test/Test/Dictask/Parse/DedupSpec.hs rename to src/parse/test/Test/DictaTask/Parse/DedupSpec.hs index c04e26b..f8af451 100644 --- a/src/parse/test/Test/Dictask/Parse/DedupSpec.hs +++ b/src/parse/test/Test/DictaTask/Parse/DedupSpec.hs @@ -1,8 +1,8 @@ -- SPDX-License-Identifier: MPL-2.0 -module Test.Dictask.Parse.DedupSpec (spec) where +module Test.DictaTask.Parse.DedupSpec (spec) where import Test.Hspec -import Dictask.Parse.Dedup +import DictaTask.Parse.Dedup spec :: Spec spec = do @@ -27,8 +27,8 @@ spec = do describe "checkDuplicate" $ do it "detects exact duplicates" $ do - let existing = [("id-1", "Fix the build", Just "dictask")] - let result = checkDuplicate "Fix the build" (Just "dictask") existing + let existing = [("id-1", "Fix the build", Just "dicta-task")] + let result = checkDuplicate "Fix the build" (Just "dicta-task") existing drAction result `shouldBe` ExactDuplicate it "detects semantic duplicates" $ do diff --git a/src/parse/test/Test/Dictask/Parse/IntentSpec.hs b/src/parse/test/Test/DictaTask/Parse/IntentSpec.hs similarity index 93% rename from src/parse/test/Test/Dictask/Parse/IntentSpec.hs rename to src/parse/test/Test/DictaTask/Parse/IntentSpec.hs index dd3be0d..1f16ce9 100644 --- a/src/parse/test/Test/Dictask/Parse/IntentSpec.hs +++ b/src/parse/test/Test/DictaTask/Parse/IntentSpec.hs @@ -1,9 +1,9 @@ -- SPDX-License-Identifier: MPL-2.0 -module Test.Dictask.Parse.IntentSpec (spec) where +module Test.DictaTask.Parse.IntentSpec (spec) where import Test.Hspec -import Dictask.Parse.Intent -import Dictask.Parse.Types +import DictaTask.Parse.Intent +import DictaTask.Parse.Types spec :: Spec spec = do diff --git a/src/store/Cargo.toml b/src/store/Cargo.toml index 8bac463..0568548 100644 --- a/src/store/Cargo.toml +++ b/src/store/Cargo.toml @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MPL-2.0 [package] -name = "dictask-store" +name = "dicta-task-store" version = "0.1.0" edition = "2024" authors = ["Jonathan D.A. Jewell "] -description = "SQLite canonical task store and view generation for dictask" +description = "SQLite canonical task store and view generation for dicta-task" license = "LicenseRef-MPL-2.0" [dependencies] diff --git a/src/store/src/lib.rs b/src/store/src/lib.rs index 8017e5b..f23186e 100644 --- a/src/store/src/lib.rs +++ b/src/store/src/lib.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -//! dictask store: SQLite canonical task store and view generation. +//! dicta-task store: SQLite canonical task store and view generation. //! //! This is the single source of truth for all tasks. All reads and writes //! go through this module. Views (Markdown, JSON, CSV) are generated from @@ -35,7 +35,7 @@ pub fn generate_markdown_view(conn: &Connection) -> Result { ORDER BY priority_score DESC" )?; - let mut output = String::from("# dictask — Active Tasks\n\n"); + let mut output = String::from("# dicta-task — Active Tasks\n\n"); output.push_str("| Priority | Title | Status | Due Date | Project | Review |\n"); output.push_str("|----------|-------|--------|----------|---------|--------|\n"); diff --git a/src/transcribe/Cargo.toml b/src/transcribe/Cargo.toml index a916a11..a039b03 100644 --- a/src/transcribe/Cargo.toml +++ b/src/transcribe/Cargo.toml @@ -1,10 +1,10 @@ # SPDX-License-Identifier: MPL-2.0 [package] -name = "dictask-transcribe" +name = "dicta-task-transcribe" version = "0.1.0" edition = "2024" authors = ["Jonathan D.A. Jewell "] -description = "Offline ASR transcription for dictask (Whisper/Vosk)" +description = "Offline ASR transcription for dicta-task (Whisper/Vosk)" license = "LicenseRef-MPL-2.0" [dependencies] diff --git a/src/transcribe/src/lib.rs b/src/transcribe/src/lib.rs index 19e7996..7cc03a7 100644 --- a/src/transcribe/src/lib.rs +++ b/src/transcribe/src/lib.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 // Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) // -//! dictask transcription: offline ASR using Whisper or Vosk. +//! dicta-task transcription: offline ASR using Whisper or Vosk. //! //! Pipeline stage 4: Convert audio files to text transcripts. //! Produces structured JSON with word-level timing and confidence. diff --git a/tests/aspect_tests.sh b/tests/aspect_tests.sh new file mode 100755 index 0000000..8968951 --- /dev/null +++ b/tests/aspect_tests.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard Aspect Test Template +# +# Aspect tests validate cross-cutting architectural invariants that span +# the entire codebase. These are NOT functional tests — they verify that +# coding standards, safety rules, and structural contracts hold. +# +# Usage: +# bash tests/aspect_tests.sh +# just aspect +# +# Standard aspects (enable what applies to your project): +# 1. SPDX compliance — all source files have license headers +# 2. Dangerous patterns — no believe_me, assert_total, sorry, unsafeCoerce, etc. +# 3. ABI/FFI contract — declarations match exports +# 4. Thread safety — mutex in FFI modules +# 5. Error handling — no panic/unreachable in production paths + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$PROJECT_DIR" + +PASS=0 +FAIL=0 +WARN=0 + +green() { printf '\033[32m%s\033[0m\n' "$*"; } +red() { printf '\033[31m%s\033[0m\n' "$*"; } +yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } +bold() { printf '\033[1m%s\033[0m\n' "$*"; } + +pass() { green " PASS: $1"; PASS=$((PASS + 1)); } +fail() { red " FAIL: $1"; FAIL=$((FAIL + 1)); } +warn() { yellow " WARN: $1"; WARN=$((WARN + 1)); } + +echo "═══════════════════════════════════════════════════════════════" +echo " DICTA_TASK — Aspect Tests (Cross-Cutting Concerns)" +echo "═══════════════════════════════════════════════════════════════" +echo "" + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 1: SPDX License Headers +# ═══════════════════════════════════════════════════════════════════════ +bold "Aspect 1: SPDX license headers" + +MISSING_SPDX=0 +while IFS= read -r -d '' f; do + if ! head -5 "$f" | grep -q "SPDX-License-Identifier"; then + warn "Missing SPDX header: $f" + MISSING_SPDX=$((MISSING_SPDX + 1)) + fi +done < <(find src/ -type f \( -name "*.rs" -o -name "*.zig" -o -name "*.res" -o -name "*.ex" -o -name "*.exs" -o -name "*.gleam" -o -name "*.idr" -o -name "*.sh" \) -print0 2>/dev/null) + +if [ "$MISSING_SPDX" -eq 0 ]; then + pass "All source files have SPDX headers" +else + fail "$MISSING_SPDX files missing SPDX headers" +fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 2: Dangerous Patterns (BANNED) +# ═══════════════════════════════════════════════════════════════════════ +bold "Aspect 2: Dangerous patterns" + +# Idris2 dangerous patterns +DANGEROUS_IDRIS=$(grep -rn 'believe_me\|assert_total\|really_believe_me' src/abi/ 2>/dev/null | grep -v "^Binary" | grep -v "test" || true) +if [ -n "$DANGEROUS_IDRIS" ]; then + fail "Dangerous Idris2 patterns found:" + echo "$DANGEROUS_IDRIS" | head -5 +else + pass "No dangerous Idris2 patterns (believe_me, assert_total)" +fi + +# Coq/Lean dangerous patterns +DANGEROUS_PROOF=$(grep -rn '\bAdmitted\b\|\bsorry\b\|\bunsafeCoerce\b\|\bObj\.magic\b' src/ verification/ 2>/dev/null | grep -v "test" | grep -v "comment" || true) +if [ -n "$DANGEROUS_PROOF" ]; then + fail "Dangerous proof patterns found:" + echo "$DANGEROUS_PROOF" | head -5 +else + pass "No dangerous proof patterns (Admitted, sorry, unsafeCoerce)" +fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 3: ABI/FFI Contract (if applicable) +# ═══════════════════════════════════════════════════════════════════════ +# Uncomment if your project has Idris2 ABI + Zig FFI: + +# bold "Aspect 3: ABI/FFI contract" +# if [ -d "src/abi" ] && [ -d "ffi/zig" ]; then +# # Check that every exported function in Idris2 ABI has a Zig FFI implementation +# ABI_EXPORTS=$(grep -h 'export' src/abi/*.idr 2>/dev/null | wc -l) +# FFI_EXPORTS=$(grep -h 'pub export fn' ffi/zig/src/*.zig 2>/dev/null | wc -l) +# if [ "$ABI_EXPORTS" -gt 0 ] && [ "$FFI_EXPORTS" -gt 0 ]; then +# pass "ABI ($ABI_EXPORTS exports) and FFI ($FFI_EXPORTS exports) both present" +# else +# fail "ABI/FFI mismatch: $ABI_EXPORTS ABI exports, $FFI_EXPORTS FFI exports" +# fi +# else +# pass "ABI/FFI not applicable (no src/abi or ffi/zig)" +# fi + +# ═══════════════════════════════════════════════════════════════════════ +# Aspect 4: Error Handling (no raw panic in production code) +# ═══════════════════════════════════════════════════════════════════════ +# Uncomment for Rust projects: + +# bold "Aspect 4: Error handling" +# UNWRAP_COUNT=$(grep -rn '\.unwrap()' src/ 2>/dev/null | grep -v "test" | grep -v "example" | wc -l) +# if [ "$UNWRAP_COUNT" -gt 20 ]; then +# warn "$UNWRAP_COUNT .unwrap() calls in src/ — consider replacing with ? or expect()" +# else +# pass "Acceptable unwrap count: $UNWRAP_COUNT" +# fi + +# ═══════════════════════════════════════════════════════════════════════ +# Summary +# ═══════════════════════════════════════════════════════════════════════ +echo "" +echo "═══════════════════════════════════════════════════════════════" +printf " Results: " +green "PASS=$PASS" | tr -d '\n' +echo -n " " +if [ "$FAIL" -gt 0 ]; then red "FAIL=$FAIL" | tr -d '\n'; else echo -n "FAIL=0"; fi +echo -n " " +if [ "$WARN" -gt 0 ]; then yellow "WARN=$WARN"; else echo "WARN=0"; fi +echo "" +echo "═══════════════════════════════════════════════════════════════" + +exit "$FAIL" diff --git a/tests/e2e.sh b/tests/e2e.sh new file mode 100755 index 0000000..a05918e --- /dev/null +++ b/tests/e2e.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# RSR Standard E2E Test Template +# +# End-to-end tests validate the full pipeline: build → run → verify output. +# Customise this file for your project. Delete the examples that don't apply. +# +# Usage: +# bash tests/e2e.sh +# just e2e +# +# Merge requirements (STANDING): All 6 test categories must pass before merge: +# P2P, E2E (this file), aspect, execution, lifecycle, benchmarks + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +PASS=0 +FAIL=0 +SKIP=0 + +# ─── Colour helpers ────────────────────────────────────────────────── +green() { printf '\033[32m%s\033[0m\n' "$*"; } +red() { printf '\033[31m%s\033[0m\n' "$*"; } +yellow(){ printf '\033[33m%s\033[0m\n' "$*"; } +bold() { printf '\033[1m%s\033[0m\n' "$*"; } + +# ─── Assertion helpers ─────────────────────────────────────────────── + +# check