diff --git a/.Rprofile b/.Rprofile new file mode 100644 index 00000000..81b960f5 --- /dev/null +++ b/.Rprofile @@ -0,0 +1 @@ +source("renv/activate.R") diff --git a/.bun-version b/.bun-version new file mode 100644 index 00000000..0c00f610 --- /dev/null +++ b/.bun-version @@ -0,0 +1 @@ +1.3.10 diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..9a5a1504 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,145 @@ +# SPDX-License-Identifier: MPL-2.0 +# Hyperpolymath estate canonical .editorconfig (standards#343 phase 1) +# Canon lives in rsr-template-repo; 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_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 + +[*.zig] +indent_size = 4 + +[*.jl] +indent_size = 4 + +[*.py] +indent_size = 4 + +[*.c] +indent_size = 4 + +[*.h] +indent_size = 4 + +[*.php] +indent_size = 4 + +# --- 3-space languages (Ada / GNAT house style) ---------------------------- +[*.ada] +indent_size = 3 + +[*.adb] +indent_size = 3 + +[*.ads] +indent_size = 3 + +[*.gpr] +indent_size = 3 + +# --- 2-space languages (explicit; matches the [*] default) ----------------- +[*.hs] +indent_size = 2 + +[*.ex] +indent_size = 2 + +[*.exs] +indent_size = 2 + +[*.ncl] +indent_size = 2 + +[*.rkt] +indent_size = 2 + +[*.scm] +indent_size = 2 + +[*.idr] +indent_size = 2 + +[*.ipkg] +indent_size = 2 + +[*.k9] +indent_size = 2 + +[*.a2ml] +indent_size = 2 + +[*.agda] +indent_size = 2 + +[*.lean] +indent_size = 2 + +[*.ebnf] +indent_size = 2 + +# --- Tab-mandatory formats ------------------------------------------------- +[Makefile] +indent_style = tab + +[*.go] +indent_style = tab + +# --- Task runners ---------------------------------------------------------- +[Justfile] +indent_style = space +indent_size = 4 + +[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. +[*.res] +indent_size = 2 + +[*.resi] +indent_size = 2 + +[*.nix] +indent_size = 2 diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..64a11c52 --- /dev/null +++ b/.envrc @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# Activate the MetaManifold-WebUI dev environment on `cd` (direnv). +# Install direnv: https://direnv.net/ + +# Precedence: mise (exact CI-pinned binaries) first, Guix shell otherwise. +if has mise; then + use mise +elif has guix && [ -f guix.scm ]; then + use guix +fi + +# Estate launcher discovery. +export METAMANIFOLD_REPO_DIR="$PWD" + +# Real secrets belong in .env (gitignored) — never commit them here. +dotenv_if_exists diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3b7d0da9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,177 @@ +# SPDX-License-Identifier: MPL-2.0 +# Hyperpolymath estate canonical .gitattributes (standards#343 phase 1) +# Canon lives in rsr-template-repo; 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 ---------------------------------------------------------------- +*.rs text eol=lf diff=rust +*.ex text eol=lf diff=elixir +*.exs text eol=lf diff=elixir +*.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 +*.py text eol=lf diff=python +*.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 +*.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 +*.k9 text eol=lf linguist-language=Nickel +*.a2ml text eol=lf linguist-language=TOML +*.ebnf text eol=lf +*.ts 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 ------------------------------------------------------------------ +*.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 / 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 + +# --- Repo control files ---------------------------------------------------- +.gitignore text eol=lf +.gitattributes text eol=lf +.editorconfig text eol=lf +.tool-versions text eol=lf +Justfile text eol=lf +justfile text eol=lf +*.just text eol=lf +Mustfile text eol=lf +Makefile text eol=lf +Containerfile text eol=lf + +# --- 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 + +# --- Sequencing data ------------------------------------------------------- +# History carried 269 MiB of these. They are binary, they must never be +# line-ending-normalised (a CRLF rewrite corrupts a gzip member), and they are +# not source. The recurrence guard with teeth is scripts/check-blob-hygiene.sh, +# run by .githooks/pre-commit and by CI; these lines are hygiene, not the gate. +*.fastq binary -diff linguist-generated=true +*.fastq.gz binary -diff linguist-generated=true +*.fq binary -diff linguist-generated=true +*.fq.gz binary -diff linguist-generated=true +*.fasta binary -diff linguist-generated=true +*.fa binary -diff linguist-generated=true +*.sam binary -diff linguist-generated=true +*.bam binary -diff linguist-generated=true + +# --- 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 + +# --- 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 + +# --- Integration / merge hygiene (fork↔upstream) --------------------------- # +# This fork and upstream share NO git ancestor, so a naive merge conflicts on +# every shared-but-different path. These rules keep the classes of file that +# must NEVER be hand-merged out of the line-conflict set: git will not produce +# conflict markers inside them, so a maintainer resolves them by regenerating +# (`just heal`) or by an explicit `--ours`/`--theirs` pick, never by editing. +# +# `-merge` treats the path as binary for merge purposes (built-in driver; needs +# no per-clone config, so it is safe the moment this file lands). For those who +# want lockfiles to auto-resolve to the target branch and then be regenerated, +# `just merge-drivers` wires the stronger `merge.lockfile` custom driver. + +# Generated lockfiles — regenerate, never line-merge. +Manifest.toml text eol=lf -diff -merge linguist-generated=true +renv.lock text eol=lf -diff -merge linguist-generated=true +frontend/bun.lock text eol=lf -diff -merge linguist-generated=true +test/doi/bun.lock text eol=lf -diff -merge linguist-generated=true +bun.lockb binary -diff -merge linguist-generated=true +package-lock.json text eol=lf -diff -merge linguist-generated=true +pnpm-lock.yaml text eol=lf -diff -merge linguist-generated=true +# renv scaffold / generated dependency scan (produced by renv, not authored). +renv/activate.R text eol=lf -diff -merge linguist-generated=true +R/_renv_dependencies.R text eol=lf -diff linguist-generated=true +renv/library/** binary -diff linguist-generated=true +# Machine-local tool-path map (written by scripts/gen-tools-yml.sh). +config/tools.yml text eol=lf -diff linguist-generated=true +config/ci/tools.yml text eol=lf -diff linguist-generated=true + +# Build output — never source, never hand-merged. (Upstream committed web/dist; +# the fork builds frontend/dist instead. Neither belongs in a diff.) +web/dist/** binary -diff linguist-generated=true +frontend/dist/** binary -diff linguist-generated=true +**/dist/** binary -diff linguist-generated=true + +# Test / coverage / benchmark artefacts. +**/coverage/** binary -diff linguist-generated=true +lcov.info text eol=lf -diff linguist-generated=true +*.cov text eol=lf -diff linguist-generated=true +**/test-results/** binary -diff linguist-generated=true +**/*junit.xml text eol=lf -diff linguist-generated=true +**/bench/results/*.json text eol=lf -diff linguist-generated=true diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 00000000..0cb941ed --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# commit-msg hook — enforces the estate conventional-commit subject. +# Enable once per clone: git config core.hooksPath .githooks +# (The corresponding template is .gitmessage; CI re-checks on push.) +msg_file=$1 +subject=$(head -1 "$msg_file") + +# Types per .gitmessage "Allowed Types" list. +if ! printf '%s' "$subject" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_/-]+\))?!?: .{1,72}$'; then + cat >&2 <(): (type in: feat fix docs style + refactor perf test build ci chore revert; subject 1..72 chars) +EOF + exit 1 +fi +exit 0 diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 00000000..2ff02254 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# pre-commit hook -- refuses a commit that would reintroduce the sequencing +# blobs stripped from history. Enable once per clone with `just hooks` +# (or `git config core.hooksPath .githooks`). +# +# The rule itself lives in scripts/check-blob-hygiene.sh and is shared verbatim +# with the CI blob-hygiene step, so the local gate and the remote gate cannot +# disagree about what is allowed. +exec "$(git rev-parse --show-toplevel)/scripts/check-blob-hygiene.sh" --staged diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..6af142bf --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: MPL-2.0 +# Ownership for the hyperpolymath fork. Application/domain questions are +# routed to upstream (JoshuaJewell/MetaManifold-WebUI). + +* @hyperpolymath + +# Engineering estate (types, tests, CI, tooling) — fork owner +frontend/src/types/ @hyperpolymath +frontend/tests/ @hyperpolymath +frontend/bench/ @hyperpolymath +.github/ @hyperpolymath +scripts/ @hyperpolymath diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..fa464f77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: CC-BY-SA-4.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Bug report +description: Something in this fork's engineering estate 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. + Note: application/bioinformatics bugs belong to + [upstream](https://github.com/JoshuaJewell/MetaManifold-WebUI/issues). + - type: textarea + id: what-happened + attributes: + label: What happened + description: The observed behaviour, with the exact command and its output. + placeholder: | + $ cd frontend && bun run check + 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, `bun --version`, and Julia/R versions when relevant (see docs/reproducibility.md). + 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 + - label: This is engineering/tooling, not an application behaviour report (those go upstream). + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..c498e9e0 --- /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/MetaManifold-WebUI/security/advisories/new + about: Report privately via a security advisory. Do NOT open a public issue. + - name: Application behaviour (pipeline, analyses, UI features) + url: https://github.com/JoshuaJewell/MetaManifold-WebUI/issues + about: Domain changes belong to upstream — file them there, not here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..547f8c87 --- /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 an engineering capability this fork 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: destination + attributes: + label: Belongs to + description: Engineering/tooling stays in this fork; application behaviour goes upstream. + options: + - This fork — types, tests, CI, tooling, alignment + - Upstream — pipeline, analyses, server routes, UI features + - Not sure + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..15ab72bc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: MPL-2.0 +# Dependabot configuration — ecosystems actually present in this repo. +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + groups: + actions: + patterns: + - "*" + open-pull-requests-limit: 2 + + - package-ecosystem: "bun" + directory: "/frontend" + schedule: + interval: "weekly" + groups: + frontend-dependencies: + patterns: + - "*" + open-pull-requests-limit: 2 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..96e4569f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,44 @@ + +## Summary + + + +## Base check + +- [ ] Base is `hyperpolymath/MetaManifold-WebUI:main` (not the upstream + parent; application changes go to `JoshuaJewell/MetaManifold-WebUI`) + +## Changes + + + +- + +## Engineering checklist + +### Required + +- [ ] `bun run check` passes (`frontend/`: typecheck 0 errors, tests, + benchmarks) +- [ ] `scripts/check-spdx.sh` / `check-format.sh` / `check-lint.sh` pass + (advisory in CI — does not block merge) +- [ ] Conventional commit subjects (see `CONTRIBUTING.md`; advisory in CI) +- [ ] New source files carry the correct SPDX header (`NOTICE` explains + the authorship rule) +- [ ] No secrets, credentials, `.env`, or sequencing data included +- [ ] No application-logic changes hidden inside alignment/tooling PRs + +### As applicable + +- [ ] `CHANGELOG.md` updated for user/developer-visible changes +- [ ] `docs/types/architecture.md` updated if type boundaries moved +- [ ] `docs/testing/coverage.md` updated if test coverage moved +- [ ] New dependencies reviewed for licence compatibility +- [ ] `docs/reproducibility.md` updated if environment requirements changed + +## Testing + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e2e51955 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,887 @@ +# SPDX-License-Identifier: AGPL-3.0-only +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +# Least privilege for the default token, declared at the workflow level so every +# job inherits it; jobs that need more declare their own block below (this is +# what the CodeQL "Workflow does not contain permissions" rule asks for — see +# the re-anchor of PR #71's salvageable delta). +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + # A push to `main` QUEUES; a pull_request head update still cancels. + # + # Measured 2026-09-21: the Julia job takes ~31 min, and `main` was merged to at + # 14:25, 14:28 and 14:43. Every run was cancelled 17-18 min in by the next one, + # so three consecutive commits to `main` produced NO test verdict at all -- + # not a pass, not a failure, nothing. Cancelling is right for a PR, where a + # verdict on a superseded head is worthless; it is wrong for `main`, where each + # commit is a thing we actually want a recorded answer about. + # + # Trade-off, stated plainly: pushes to `main` now run serially, so a burst of + # N merges takes N x ~31 min to drain. That is the cost of getting an answer. + # This does NOT rescue an upstream PR whose head keeps moving (e.g. + # JoshuaJewell#6, whose head IS this fork's `main`) -- only letting `main` + # settle for ~31 min does that. + cancel-in-progress: ${{ github.event_name != 'push' }} + +jobs: + # Estate hygiene gates (standards/RSR alignment): cheap, run alongside the + # heavyweight Julia/frontend matrix. Each gate has a documented local + # equivalent — scripts/check-*.sh (see CONTRIBUTING.md). + # + # ENFORCING on this fork, ADVISORY upstream. The original blanket + # `continue-on-error: true` (67f2faaf) was correct about upstream and wrong + # about here: it left the fork with a check that literally could not fail, + # so its green carried no information. The expression below keeps the + # upstream guarantee — JoshuaJewell/MetaManifold-WebUI does not use + # conventional-commit / SPDX / format / lint as a merge gate, and a + # `pull_request` run against that base evaluates `github.repository` as the + # BASE repo, so the checks stay non-blocking for the owner's PR — while + # restoring real teeth on pushes and PRs to this fork. + # Local hooks remain available via core.hooksPath=.githooks. + repo-hygiene: + name: Repo hygiene (licence · format · lint · commit) + runs-on: ubuntu-24.04 + permissions: + contents: read + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # Full history, deliberately: the commit convention check below grades + # the whole base..head range a PR proposes (#37), and a shallow clone + # would make that range unresolvable. The check treats an unresolvable + # range as a hard failure rather than grading zero commits, so the + # depth it depends on is pinned here instead of left to a default. + fetch-depth: 0 + + - name: Set up Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version-file: .bun-version + + - name: Install frontend dependencies + working-directory: frontend + run: bun install --frozen-lockfile --ignore-scripts + + - name: Licence header check + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + run: scripts/check-spdx.sh + + - name: Formatting check + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + run: scripts/check-format.sh + + - name: Lint check + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + run: scripts/check-lint.sh + + # Blob-hygiene mirror of .githooks/pre-commit. Both callers exec the SAME + # script, so the local gate and this one cannot disagree about what is + # allowed -- a CI check that re-implements a hook drifts from it, and the + # drift is invisible because both keep passing. + # + # --tree, not a commit range: a whole-tree check also catches anything + # that landed before the guard existed. + - name: Blob hygiene check + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + run: scripts/check-blob-hygiene.sh --tree HEAD + + # Commit convention mirror of .githooks/commit-msg (available locally via + # the hooksPath setting documented in CONTRIBUTING.md). Advisory only when + # this runs under upstream, whose commit subjects need not match. + # + # Grades EVERY commit a change proposes, not the tip (#37). The tip can be + # a merge commit that GitHub's own "Update branch" button created; grading + # it fails a conforming PR, while grading ONLY it lets a branch of nine + # non-conforming commits pass because the tenth is clean. Hence: + # - pull_request: the base..head range; push: the before..head range + # - merge commits excluded (--no-merges): nobody typed their subjects, + # and they cannot be rewritten without a force-push + # - every offender named by its sha + # - an unresolvable range, or one with zero non-merge commits, is a + # hard failure: a pass must mean the gate examined something, never + # that it declined to run + - name: Commit convention check + continue-on-error: ${{ github.repository != 'hyperpolymath/MetaManifold-WebUI' }} + env: + EVENT: ${{ github.event_name }} + HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + BASE_SHA: ${{ github.event.pull_request.base.sha }} + BEFORE_SHA: ${{ github.event.before }} + run: | + set -euo pipefail + pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_/-]+\))?!?: .{1,72}$' + + if [ "$EVENT" = pull_request ]; then + range="$BASE_SHA..$HEAD_SHA" + elif [ -n "$BEFORE_SHA" ] && ! printf '%s' "$BEFORE_SHA" | grep -qE '^0+$'; then + range="$BEFORE_SHA..$HEAD_SHA" + else + # A new ref has no recorded before-sha: grade the tip alone. + range="$HEAD_SHA^..$HEAD_SHA" + fi + + # Resolve both ends explicitly. If either is absent from this clone + # the gate cannot see the commits it was asked to grade, and must say + # so loudly rather than print a vacuous pass. + for sha in "${range%..*}" "${range#*..}"; do + if ! git rev-parse --verify --quiet "$sha^{commit}" >/dev/null; then + echo "::error::commit convention check: cannot resolve $sha in range $range; refusing to grade zero commits" + exit 1 + fi + done + + count="$(git rev-list --no-merges --count "$range")" + if [ "$count" -eq 0 ]; then + echo "::error::commit convention check: range $range contains no non-merge commits; refusing a vacuous pass" + exit 1 + fi + + commits="$(git log --no-merges --pretty=format:'%H%x09%s' "$range")" + fail=0 + while IFS= read -r line; do + [ -n "$line" ] || continue + sha="${line:0:40}" + subject="${line:41}" + if ! printf '%s' "$subject" | grep -qE "$pattern"; then + echo "::error::commit $sha fails the conventional pattern: $subject" + fail=1 + fi + done <<< "$commits" + if [ "$fail" -ne 0 ]; then + exit 1 + fi + echo "commit convention ok: $count non-merge commit(s) graded in $range" + + # Agda proof gate for the compositional transforms (issues #20, #21); scope, + # layering and residue are in docs/formal/verification-plan.md. The toolchain + # is the estate's pin (epistemic-types, residual-evidence-types): Debian 13 + # packages agda-bin 2.6.4.3-1+b2 and agda-stdlib 2.1-4, installed from the + # signed Debian archive rather than a downloaded binary. The same script runs + # locally with AGDA=... AGDA_STDLIB_LIB=... overrides. + proofs: + name: Proofs (Agda) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + container: debian:13-slim@sha256:d7e12182ce18b85b93007c1dedf31f2d29e01ccf3182cc4017c709b6259bc132 + permissions: + contents: read + env: + LANG: C.UTF-8 + LC_ALL: C.UTF-8 + steps: + - name: Install the pinned Agda toolchain (Debian archive) + run: | + apt-get update + apt-get install --no-install-recommends -y ca-certificates git \ + agda-bin=2.6.4.3-1+b2 agda-stdlib=2.1-4 + + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Guard, type-check, negative controls + run: | + test "$(agda --version)" = 'Agda version 2.6.4.3' + AGDA_STDLIB_LIB=$(dpkg -L agda-stdlib | grep 'standard-library\.agda-lib$' | head -1) + export AGDA_STDLIB_LIB + bash scripts/check-proofs.sh + + test: + # The name is a fixed string, it carries no version, and the job has NO matrix. + # Both are required, and the first without the second is a trap that has already + # been sprung here once. + # + # A branch ruleset matches a required status check by the DISPLAY NAME GitHub posts + # for the job. A renamed check does not fail -- it is simply absent, and an absent + # required check can never be satisfied, so every pull request deadlocks until an + # admin bypasses the rule, triggered by nothing more alarming than editing a version + # number. + # + # MEASURED on PR #39 (2026-09-21): this job, named exactly `Julia tests` but still + # carrying a 1x1 `strategy.matrix`, posted `Julia tests (1.12.5, ubuntu-24.04)`. + # GitHub appends the matrix combination whenever the name does not already reference + # the matrix, so a 1x1 matrix is still a matrix and the pins were still embedded in + # the check name. The matrix was therefore removed rather than merely renamed around: + # it selected exactly one combination and bought nothing. + # + # The two values it held are literals below, each keeping its own reasoning. + # `test/unit/test_install_pins.jl` asserts, for EVERY job in this file, that the name + # interpolates nothing AND that the job has no matrix -- so this cannot regress + # silently. If a matrix is ever genuinely wanted here, the required context must move + # to a matrix-free aggregator job first. + name: Julia tests + # Explicit, not ubuntu-latest: the R pin below names an apt package revision built + # for 24.04, and a runner that silently rolled to the next LTS would take that pin + # with it. `runs-on` cannot read `env`, so this is a literal. + runs-on: ubuntu-24.04 + permissions: + contents: read + # The repository's root .Rprofile sources renv/activate.R, so R started from + # the checkout auto-activates renv and rewrites the library paths. renv is a + # local-development convenience; CI installs into the system library and + # reaches it through R_LIBS_SITE, so the autoloader is switched off here. + # This variable covers the unprivileged R that Julia embeds through RCall. + # It cannot cover the installs themselves, which run under sudo: sudo resets + # the environment, so those commands pass --no-init-file instead. + env: + RENV_CONFIG_AUTOLOADER_ENABLED: "FALSE" + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Julia + uses: julia-actions/setup-julia@fa02766e078afaaf09b14210362cee14137e6a32 # v3.0.2 + with: + # Pinned rather than tracking latest stable, because a pinned Manifest.toml + # resolved by an unpinned Julia is not a reproducible build. This must equal + # julia_version in Manifest.toml; it cannot be read from the pin file, since + # nothing can be read before Julia exists. test/unit/test_install_pins.jl + # fails if the three ever disagree. + version: "1.12.5" + + - name: Cache Julia packages + uses: julia-actions/cache@a7bed9df697e5d7309d68afe7542a87621a8b6c8 # v3.3.0 + + # Static source lint, deliberately dependency-free (`--project=no`) so it can + # run before instantiation and fail in seconds rather than after the ~26 + # minute build-and-test pipeline. Every check corresponds to a defect class + # that previously reached CI and cost a full run to diagnose: + # - module/struct name collision (the `using MetaManifold.AnalysisConfig` + # shadowing that killed 80+ qualified references) + # - `\$var` inside interpolating strings (silently degraded 50 diagnostics + # and broke tests matching on the interpolated value) + # - two adjacent docstrings ("cannot document the following expression") + # - packages used in src/ but missing from Project.toml + # - modules referenced by tests but never imported (UndefVarError) + # - files that do not parse + - name: Source lint (fail fast, no dependencies) + run: julia --project=no --startup-file=no config/ci/lint_source.jl + + # The KYAML gate (docs/pilots/kyaml-pilot.md) lands in the same commit as the conversion + # itself: `just use-kyaml` writes the canonical bytes, this step then holds them. It runs + # here rather than in repo-hygiene because it needs Julia, which this job has installed. + # Until the conversion is committed the step is deliberately absent rather than red. + + # Every external version CI installs is read from the committed pin file, so CI + # and a developer's machine cannot drift apart. A temporary environment is used + # because the project environment cannot be instantiated until R is present: + # RCall's build step requires it. + - name: Read pinned versions + run: | + julia -e ' + using Pkg + Pkg.activate(temp = true) + Pkg.add("YAML") + using YAML + pins = YAML.load_file("config/defaults/tool_versions.yml") + open(ENV["GITHUB_ENV"], "a") do io + println(io, "R_APT_VERSION=", pins["runtimes"]["r"]["apt_version"]) + println(io, "BUN_VERSION=", pins["toolchain"]["bun"]["version"]) + println(io, "CUTADAPT_SPEC=cutadapt==", pins["tools"]["cutadapt"]["version"]) + println(io, "MULTIQC_SPEC=multiqc==", pins["tools"]["multiqc"]["version"]) + # FastQC ships one platform-neutral zip, so its archive hangs off the + # "any" key rather than a per-platform one, and it cannot join the loop + # below. test_install_pins.jl asserts that shape, so a pin file that + # grew a linux-x86_64 fastqc entry would fail there before it failed here. + fastqc = pins["tools"]["fastqc"]["archives"]["any"] + println(io, "FASTQC_VERSION=", pins["tools"]["fastqc"]["version"]) + println(io, "FASTQC_URL=", fastqc["url"]) + println(io, "FASTQC_SHA256=", fastqc["sha256"]) + for tool in ("vsearch", "swarm") + archive = pins["tools"][tool]["archives"]["linux-x86_64"] + println(io, uppercase(tool), "_VERSION=", pins["tools"][tool]["version"]) + println(io, uppercase(tool), "_URL=", archive["url"]) + println(io, uppercase(tool), "_SHA256=", archive["sha256"]) + end + end' + + # R + required packages (needed by RCall) + # Install R directly via apt to avoid r-lib/actions/setup-r mangling + # R_HOME and stripping default packages (utils, methods, stats, etc.) + # The version is pinned to the apt revision named in the pin file, which tracks + # R.Version in renv.lock; CRAN's Ubuntu repository retains older revisions, so + # this resolves rather than merely requesting the newest. + - name: Set up R + run: | + wget --https-only -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc \ + | sudo gpg --dearmor -o /usr/share/keyrings/r-project.gpg + echo "deb [signed-by=/usr/share/keyrings/r-project.gpg] https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/" \ + | sudo tee /etc/apt/sources.list.d/r-project.list + sudo apt-get update -qq + sudo apt-get install -y \ + r-base-core="$R_APT_VERSION" \ + r-base-dev="$R_APT_VERSION" \ + r-base-html="$R_APT_VERSION" \ + r-recommended="$R_APT_VERSION" \ + r-base="$R_APT_VERSION" + Rscript -e 'cat(R.version.string, "\n")' + + - name: Install R system dependencies + run: sudo apt-get install -y libcurl4-openssl-dev libssl-dev libxml2-dev libfontconfig1-dev + + # The R packages are restored from renv.lock, the same pin a developer restores + # from, rather than resolved against whatever the repositories serve that day. + # Asking for a bare package name pins nothing: a Bioconductor release is frozen, + # so dada2 and its kin held still, but CRAN publishes only its newest revision, + # so vegan silently moved off the pinned 2.7-3 and test_provenance caught the + # drift. Restoring into .Library keeps the system library that the RCall steps + # and R_LIBS_SITE below expect, and renv is invoked explicitly here rather than + # left to activate itself. --no-init-file stops R sourcing the repository's + # .Rprofile, and with it the renv autoloader, which would otherwise rebind + # .Library to its own sandbox under root's cache; the flag is used rather than + # the environment variable above because sudo resets the environment first. + + # renv keeps a content-addressed cache: a package already in it is linked into + # the library instead of being downloaded and compiled again. Persisting that + # cache across runs is what makes a failed restore RESUMABLE. Without it all 79 + # packages are rebuilt from source on every run -- measured at 10m08s and 13m21s + # on two consecutive runs, the largest single cost in this workflow -- and a + # restore that dies at package 60 throws away all 60. + # + # Reordering the packages is not an alternative: renv derives install order from + # the dependency graph, so a package cannot be pulled to the front of the queue + # ahead of the packages it links against. + # + # The cache path is pinned rather than left at the default ~/.cache/R/renv, + # because the restore runs under sudo where ~ is root's home, not the runner's. + - name: Restore the renv cache + id: renv-cache + uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: /opt/renv-cache + # The run id keeps every key unique so each attempt writes a NEW entry and + # progress accumulates; restore-keys then picks the most recent entry that + # matches the prefix. A changed renv.lock still falls through to the second + # key and re-uses every package whose version did not move. + key: renv-${{ runner.os }}-R${{ env.R_APT_VERSION }}-${{ hashFiles('renv.lock') }}-${{ github.run_id }} + restore-keys: | + renv-${{ runner.os }}-R${{ env.R_APT_VERSION }}-${{ hashFiles('renv.lock') }}- + renv-${{ runner.os }}-R${{ env.R_APT_VERSION }}- + + - name: Install R packages + env: + RENV_PATHS_CACHE: /opt/renv-cache + # renv draws its download counter by hiding the cursor (ESC[?25l), rewriting + # the line in place, then showing it again (ESC[?25h) -- the "25l25h" residue + # that litters the log. A captured CI log is not a terminal, so the rewrite + # never lands and the counter appears frozen at (0/79) for the whole ten + # minutes while the download is in fact progressing. Disabling cli's dynamic + # output makes each update print on its own line, so the log shows real + # progress and a genuine hang becomes distinguishable from a working step. + R_CLI_DYNAMIC: "false" + TERM: dumb + run: | + sudo mkdir -p "$RENV_PATHS_CACHE" + sudo chmod 777 "$RENV_PATHS_CACHE" + sudo env R_CLI_DYNAMIC=false TERM=dumb Rscript --no-init-file -e 'install.packages(c("renv", "BiocManager"), repos="https://cloud.r-project.org", lib=.Library)' + # `sudo env VAR=...` rather than `sudo -E` or a bare VAR=value prefix: it sets + # the variable for Rscript directly and so does not depend on the runner's + # sudoers env_reset policy, which the comment above already notes resets it. + sudo env RENV_PATHS_CACHE="$RENV_PATHS_CACHE" R_CLI_DYNAMIC=false TERM=dumb Rscript --no-init-file -e 'renv::restore(project=".", library=.Library, prompt=FALSE)' + Rscript --no-init-file -e 'for (pkg in c("dada2","Biostrings","ShortRead","vegan","dplyr")) if (!require(pkg,character.only=TRUE,quietly=TRUE)) stop(pkg, " failed to install")' + + # Both of the next two steps run on failure ON PURPOSE. actions/cache saves only + # when the job succeeds, which would discard exactly the partial progress this + # cache exists to preserve: the packages that DID build before the restore died + # are the ones the next attempt must not build again. The cache is written by + # root, so it is made readable before it is packed. + - name: Make the renv cache readable + if: always() + run: sudo chmod -R a+rX /opt/renv-cache || true + + - name: Save the renv cache + if: always() + uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 + with: + path: /opt/renv-cache + key: renv-${{ runner.os }}-R${{ env.R_APT_VERSION }}-${{ hashFiles('renv.lock') }}-${{ github.run_id }} + + - name: Install cutadapt + run: pip install "$CUTADAPT_SPEC" + + # The one tool CI does not pin. Ubuntu ships no versioned cd-hit revision worth + # naming, and 24.04 is frozen, so its cd-hit does not move under us; the version + # that arrives is recorded at preflight, where a discrepancy is visible. + - name: Install cd-hit + run: sudo apt-get install -y cd-hit + + # Downloaded against the pinned URL and refused unless it hashes to the pinned + # SHA256, so CI runs the same bytes install.jl puts on a developer's machine. + # + # fetch_pinned adds retries that cover the TLS class (curl does not retry a + # certificate failure on its own) and, when a download still fails, annotates + # WHICH failure it was. On 2026-09-22T07:09Z the FastQC host's expired + # certificate reddened main on a commit that had touched nothing in this area; + # the log said only "exit code 60". See scripts/ci/fetch_pinned.sh. + - name: Install vsearch + run: | + source scripts/ci/fetch_pinned.sh + fetch_pinned "$VSEARCH_URL" vsearch.tar.gz + echo "$VSEARCH_SHA256 vsearch.tar.gz" | sha256sum -c - + tar xzf vsearch.tar.gz --warning=no-unknown-keyword + sudo mv "vsearch-$VSEARCH_VERSION-linux-x86_64/bin/vsearch" /usr/local/bin/vsearch + rm -rf vsearch.tar.gz "vsearch-$VSEARCH_VERSION-linux-x86_64" + vsearch --version + + - name: Install swarm + run: | + source scripts/ci/fetch_pinned.sh + fetch_pinned "$SWARM_URL" swarm.tar.gz + echo "$SWARM_SHA256 swarm.tar.gz" | sha256sum -c - + tar xzf swarm.tar.gz --warning=no-unknown-keyword + sudo mv "swarm-$SWARM_VERSION-linux-x86_64/bin/swarm" /usr/local/bin/swarm + rm -rf swarm.tar.gz "swarm-$SWARM_VERSION-linux-x86_64" + swarm --version + + - name: Install multiqc + run: pip install "$MULTIQC_SPEC" + + # FastQC is a Perl launcher round a Java jar, so it is installed as a directory + # rather than a single binary: the launcher resolves its jars relative to its own + # location, and moving it out of FastQC/ breaks it. Unpacked to /opt and reached + # through a symlink, which is what config/ci/tools.yml means by `path: "fastqc"`. + # The launcher runs `java` off PATH, so `fastqc --version` proves both that the + # install landed and that a JRE is present on the runner. + - name: Install fastqc + run: | + source scripts/ci/fetch_pinned.sh + fetch_pinned "$FASTQC_URL" fastqc.zip + echo "$FASTQC_SHA256 fastqc.zip" | sha256sum -c - + sudo unzip -q -d /opt fastqc.zip + rm -f fastqc.zip + sudo chmod +x /opt/FastQC/fastqc + sudo ln -sf /opt/FastQC/fastqc /usr/local/bin/fastqc + fastqc --version + + - name: Instantiate Julia project + run: julia --project=. -e 'import Pkg; Pkg.instantiate()' + + # Smoke test: precompile and load the package before committing to the full + # suite. An undeclared dependency or a load-time lowering error used to + # surface only partway through the ~9 minute test run; this catches it in + # about a minute, and proves the precompile cache CI just built is usable. + - name: Precompile and load smoke test + run: julia --project=. -e 'using MetaManifold; @info "MetaManifold precompiled and loaded" version=string(pkgversion(MetaManifold))' + + - name: Set up Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version: ${{ env.BUN_VERSION }} + + - name: Install frontend dependencies + working-directory: frontend + run: bun install --frozen-lockfile --ignore-scripts + + # Explicit strict-typecheck gate (strict foundation; see + # docs/types/strict-mode-status.md). Runs before the (heavier) build so + # type regressions fail fast instead of surfacing inside vite's bundling. + - name: Typecheck frontend + working-directory: frontend + run: bun run typecheck + + # Scaffold smoke battery (bun:test) — machine-readable results + coverage + # profile, both shipped as artifacts. No coverage gate yet by design + # (docs/testing/infrastructure.md). + - name: Test frontend + working-directory: frontend + run: bun test --coverage --coverage-reporter=lcov --coverage-dir tests/coverage --reporter=junit --reporter-outfile tests/results/junit.xml + + # Benchmark harness (proven-tests-and-benches discipline): medians vs committed baseline; JSON result ships as an artifact. + # Milestone 2: now includes table_loading, epistemic_parsing, duckdb_aggregation, permanova_nmds, tree_rendering workloads + - name: Benchmark frontend + working-directory: frontend + run: bun run bench -- --json bench/results/results.json + + # Benchmark deltas vs the committed baseline are REPORTED, never gated. + # The harness workloads (frontend/bench/index.ts) are frozen and + # self-contained — they import no app code — so a delta cannot be caused + # by PR contents; it measures the runner, not the commit. Evidence on + # hosted ubuntu-24.04: two consecutive runs of identical benchmark code + # produced per-workload deltas between -16% and +52%, flapping in both + # directions (median-of-5 samples of 2-8 ms on shared vCPUs ride + # co-tenant throttling). A hard 10% gate below that noise floor can only + # block at random — which the harness header anticipates ("Baseline + # comparison is INFORMATIONAL only — there is no regression gate"). The + # real signals remain: checksums (hard-fail), the freeze policy + # (workload edits must re-cut the baseline and are visible in review), + # and the deltas + machine factor printed here and shipped as artifacts + # for human review. A same-runner A/B gate can revisit once the harness + # exists on the base branch (measure the base in-job, compare like-for-like). + - name: Report frontend benchmark deltas (informational) + working-directory: frontend + run: | + node -e ' + const fs = require("fs"); + const baselinePath = "bench/baseline.json"; + const resultsPath = "bench/results/results.json"; + if (!fs.existsSync(baselinePath) || !fs.existsSync(resultsPath)) { + console.log("No baseline or results — nothing to report (first run)"); + process.exit(0); + } + const baseline = JSON.parse(fs.readFileSync(baselinePath, "utf8")); + const results = JSON.parse(fs.readFileSync(resultsPath, "utf8")); + const deltas = []; + for (const r of results.results) { + const b = baseline.results.find(x => x.name === r.name); + if (!b) { console.log(`NEW ${r.name}: no baseline entry`); continue; } + const delta = (r.median_ns - b.median_ns) / b.median_ns * 100; + deltas.push({ name: r.name, delta, base: b.median_ns, cur: r.median_ns, checksum: r.checksum }); + } + if (!deltas.length) process.exit(0); + // Machine factor: median delta across workloads — the systematic + // speed offset of this runner vs the one that cut the baseline. + const sorted = [...deltas].sort((a, b) => a.delta - b.delta); + const machine = sorted[Math.floor(sorted.length / 2)].delta; + console.log(`machine factor (median delta): ${machine >= 0 ? "+" : ""}${machine.toFixed(1)}%`); + for (const d of deltas) { + const rel = d.delta - machine; + console.log(`${d.name}: ${d.delta >= 0 ? "+" : ""}${d.delta.toFixed(1)}% vs baseline ${d.base} ns (current ${d.cur} ns)`); + if (!d.checksum) { + console.error(`::error::checksum failure in workload ${d.name} — workload output changed`); + process.exitCode = 1; + } + if (rel > 25) { + console.error(`::warning::${d.name} runs ${rel.toFixed(1)}pp above machine factor — expected wobble on shared runners; investigate only if bench/index.ts changed`); + } + } + ' + + - name: Upload frontend test & benchmark artifacts + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: frontend-tests-benchmarks + path: | + frontend/tests/results/junit.xml + frontend/tests/coverage/lcov.info + frontend/bench/results/results.json + frontend/bench/baseline.json + if-no-files-found: warn + + - name: Build frontend + working-directory: frontend + run: bun run build + + - name: Download PR2 databases + run: julia --project=. config/ci/download_databases.jl + + - name: Rebuild RCall against installed R + run: julia --project=. -e 'import Pkg; Pkg.build("RCall")' + + - name: Verify R packages visible from RCall + run: | + julia --project=. -e ' + using RCall + R"cat(.libPaths(), sep=\"\n\")" + for pkg in ["dada2", "dplyr", "vegan", "tibble"] + R"if (!require($pkg, character.only=TRUE, quietly=TRUE)) stop($pkg, \" not found\")" + end + ' + + - name: Run tests + env: + CI_SKIP_TAXONOMY: "1" + R_LIBS_SITE: /usr/local/lib/R/site-library:/usr/lib/R/site-library:/usr/lib/R/library + run: | + set +e + julia --project=. -t 2 --code-coverage=user --compiled-modules=no test/runtests.jl --integration --server 2>&1 | tee /tmp/julia-tests.log + status="${PIPESTATUS[0]}" + if [ "$status" -ne 0 ]; then + # A failing test's message is the entire point of running it, and the job log + # is served from a storage host that not every environment can reach. The + # checks API serves annotations as data, but caps each message at 4096 bytes, + # so one annotation of the tail only ever showed the passing half of the + # summary. Instead: one annotation per failing assertion (the Test.jl block + # from "Test Failed at" / "Error During Test at" onward, 60 lines, 3500 + # bytes), then the summary rows that report a failure or an error. + enc() { head -c 3500 | sed -e 's/%/%25/g' -e 's/\r/%0D/g' | awk '{printf "%s%%0A", $0}'; } + awk ' + /Test Summary:/ { left = 0 } + /(Test Failed at|Error During Test at)/ || (left == 0 && /Got exception outside of a @test/) { n++; left = 60 } + left > 0 { print > ("/tmp/julia-failure-" n ".txt"); left-- } + ' /tmp/julia-tests.log + shown=0 + for f in $(ls /tmp/julia-failure-*.txt 2>/dev/null | sort -t- -k3 -n); do + shown=$((shown + 1)) + [ "$shown" -gt 8 ] && break + echo "::error title=Julia test failure ${shown}::$(enc < "$f")" + done + # Summary rows whose Fail or Error column is non-empty. Test.jl right-aligns + # counts under the "Test Summary: | Pass Fail Error Total Time" header, so + # a digit between the end of "Pass" and the start of "Total" is a failure. + awk ' + /Test Summary:/ { print; p = index($0, "Pass"); pe = (p ? p + 3 : 0); t = index($0, "Total"); next } + pe && t && index($0, "|") && substr($0, pe + 1, t - pe - 1) ~ /[0-9]/ { print; next } + /ERROR:|Some tests did not pass/ { print } + ' /tmp/julia-tests.log | tail -n 60 > /tmp/julia-tests-summary.txt + echo "::error title=Julia tests failed (summary)::$(enc < /tmp/julia-tests-summary.txt)" + if [ "$shown" -eq 0 ]; then + echo "::error title=Julia tests failed (tail)::$(tail -c 3500 /tmp/julia-tests.log | enc)" + fi + fi + exit "$status" + + - name: Process coverage + uses: julia-actions/julia-processcoverage@03114f09f119417c3242a9fb6e0b722676aedf38 # v1 + + - name: Upload coverage artifact (local, Codecov removed per Milestone 2) + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: julia-coverage-lcov + path: lcov.info + if-no-files-found: warn + + # Comprehensive benchmarks (Milestone 2) — table loading, epistemic parsing, DuckDB aggregation, PERMANOVA/NMDS, tree rendering + - name: Benchmark Julia comprehensive + run: | + julia --project=. bench/table_loading/benchmark.jl + julia --project=. bench/epistemic_parsing/benchmark.jl + julia --project=. bench/duckdb_aggregation/benchmark.jl + julia --project=. bench/permanova_nmds/benchmark.jl + julia --project=. bench/tree_rendering/benchmark.jl + # bench/analysis_config existed but was never run here, which is why its + # 13 broken qualified references survived until the unit tests tripped + # over the same shadowing. Running it means the next AnalysisConfig API + # change breaks this step immediately instead of at test time. + julia --project=. bench/analysis_config/benchmark.jl + # ILR bases (issue #20): CLR, default ILR and the three new bases at 100, + # 1 000 and 10 000 taxa. Emits ::warning:: for any workload over 5 minutes + # or over 1 GiB (allocation or peak-RSS growth); never fails the job — the + # hard CLR/ILR gate is the same-runner base-vs-head step below. + julia --project=. bench/ilr_bases/benchmark.jl + # issue #21's lane: 100 / 1000 / 10000 taxa for both replacement operators and the + # dispersion pipeline. Informational by default (see the file's header for why the + # issue's "fail CI at >10%" is behind METAMANIFOLD_BENCH_STRICT). + julia --project=. bench/zero_replacement/benchmark.jl + julia --project=. bench/comprehensive_benchmark.jl + + - name: Summarise Julia benchmark deltas (informational) + run: | + echo "Julia benchmark deltas are informational (same cross-host noise argument as the frontend step above; the bench scripts no longer exit non-zero on >10%)." + # Record that baseline.json files exist for each category + for cat in table_loading epistemic_parsing duckdb_aggregation permanova_nmds tree_rendering; do + if [ ! -f bench/$cat/baseline.json ]; then + echo "::warning::No baseline.json for $cat — first run will create it" + else + echo "Found baseline for $cat: $(cat bench/$cat/baseline.json | head -c 200)" + fi + done + if [ -f bench/results/comprehensive_results.json ]; then + echo "Comprehensive results: $(cat bench/results/comprehensive_results.json | head -c 500)" + fi + if [ -f bench/results/ilr_bases_results.json ]; then + echo "ILR bases results: $(head -c 500 bench/results/ilr_bases_results.json)" + fi + + # Issue #20: "fail CI if CLR/ILR performance regresses more than 10%". A committed + # baseline cannot carry that (cross-host noise, as above), so this measures the PR's + # base commit and its head on this runner, interleaved base/head/base/head, and fails + # if head allocates >10% more or its minimum time is >10% slower. Rationale and the + # statistic are documented in bench/ilr_bases/regression_gate.jl. Pull requests only: + # a push to main has no base to compare against. + - name: CLR/ILR regression gate (same runner, base vs head) + if: github.event_name == 'pull_request' + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + R_LIBS_SITE: /usr/local/lib/R/site-library:/usr/lib/R/site-library:/usr/lib/R/library + run: | + set -euo pipefail + git fetch --no-tags --depth=1 origin "$BASE_SHA" + git worktree add --detach "$RUNNER_TEMP/base" "$BASE_SHA" + julia --project="$RUNNER_TEMP/base" -e 'using Pkg; Pkg.instantiate()' + gate=bench/ilr_bases/regression_gate.jl + out="$RUNNER_TEMP/ilr_gate" + mkdir -p "$out" + for round in 1 2; do + julia --project="$RUNNER_TEMP/base" "$gate" measure base "$out/base_$round.json" + julia --project=. "$gate" measure head "$out/head_$round.json" + done + julia --project=. "$gate" compare --base "$out"/base_*.json --head "$out"/head_*.json + + - name: Upload Julia benchmark artifacts + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: julia-benchmarks-comprehensive + path: | + bench/*/baseline.json + bench/results/comprehensive_results.json + bench/results/ilr_bases_results.json + bench/**/baseline.json + if-no-files-found: warn + + # Milestone 2 category. Deliberately NOT wrapped in `if [ -f … ]`: that + # shape reports success when the file is absent, so it cannot distinguish + # "passed" from "never ran". If the file is deleted, `include` raises and + # this step goes red, which is the intended behaviour. + # + # The companion `test_clade_cumulus.jl` step was removed: the file has + # never existed on main (only src/analysis/clade_cumulus.jl), so the step + # was a permanent no-op reporting success. Restore it alongside the file. + - name: Test analysis-config category + run: | + julia --project=. -e 'using Test; using MetaManifold; include("test/unit/test_analysis_config.jl")' + + # ───────────────────────────────────────────────────────────────────────── + # cicd-squabbler — gate-deadlock triage + # https://github.com/hyperpolymath/cicd-squabbler (MPL-2.0) + # + # WHAT THIS DOES AND DOES NOT DO. + # + # squabbler will NOT turn a red test suite green, and that is deliberate: its + # charter puts red→green *code* fixes out of scope for v0.1 because "fixing" a + # failing test by weakening it would violate the squabble ≠ bypass invariant + # (proved in SPARK: the only transition into Green is a required check that + # actually ran and passed). Do not read a green triage job as a green build. + # + # What it does cover is the *gate* layer, which is a distinct failure mode from + # a failing test: a required check whose name drifted from what the workflow + # emits, an `on.*.paths` filter that strands a required check so it never runs, + # a reusable workflow pinned to a stale SHA, and modify/delete or rebase + # conflicts. Those deadlock a PR that is otherwise fine, and they are invisible + # from inside the test job. + # + # This runs in propose mode. `--apply` is intentionally NOT used: it only + # enacts the path-filter self-win by editing a workflow file, and never + # commits, pushes or re-runs CI, so on a runner it would edit a checkout that + # is then discarded. Landing a move stays a human decision. + # + # ubuntu-latest ships a Rust toolchain, so no third-party setup action is + # needed and the SHA-pinning convention of this file is preserved. + # ───────────────────────────────────────────────────────────────────────── + cicd-squabbler: + name: Gate triage (cicd-squabbler) + runs-on: ubuntu-latest + needs: [test] + # Gate triage needs a PR: every substantive step below takes / + # . On a push it could only build squabbler and run a bundled + # fixture, then report 'Gate triage: success' having triaged nothing. + # !cancelled() stays so triage still runs when the test job FAILS — that + # is the case it exists for. + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + permissions: + contents: read + actions: read + checks: read + pull-requests: read + env: + GH_TOKEN: ${{ github.token }} + # Pinned, matching this file's convention for every other external ref. + # FLOOR: this pin must be at or after hyperpolymath/cicd-squabbler#99 + # (merged 2026-09-21T18:55Z as 9846169c), which is what introduced the + # distinct exit 3 the Fetch step below branches on. At any earlier + # revision "no gate" is exit 2, falls into the `*` arm, and hard-fails -- + # i.e. moving this pin backwards silently reverts the fix below without + # touching it. Bump it forwards freely; never behind 9846169c. + SQUABBLER_SHA: 9846169c1dc8e549edd72fa20efc6680d324d484 + SQUABBLE: /tmp/squabbler/target/release/squabble + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Build squabble at the pinned revision + run: | + git clone --quiet https://github.com/hyperpolymath/cicd-squabbler.git /tmp/squabbler + git -C /tmp/squabbler checkout --quiet "$SQUABBLER_SHA" + echo "building cicd-squabbler at $(git -C /tmp/squabbler rev-parse HEAD)" + cargo build --release --locked --quiet --manifest-path /tmp/squabbler/Cargo.toml -p squabble-cli + "$SQUABBLE" --version + + # Proves the engine itself works before we trust its verdict on this repo. + # Same fixture the upstream `just demo` recipe uses. + - name: Engine self-check (bundled gate-deadlock fixture) + run: | + "$SQUABBLE" diagnose /tmp/squabbler/examples/gate-deadlock.json + + # `squabble fetch` and `squabble fight` both take / + # ; the job-level gate above guarantees a PR is present. + # + # Exit codes, fixed by SHA pin so their meaning cannot drift underneath: + # 0 a gate exists and was fetched -> triage it + # 3 the base branch has no `required_status_checks` ruleset rule + # -> there is nothing to triage. A finding, not a breakage. + # * anything else is a real failure and still fails this job. + # + # Before hyperpolymath/cicd-squabbler#99 every one of those was exit 2, so + # this step could not tell "nothing to triage" from "squabble is broken". + # It went red on a legitimate non-finding, and the only alternative was to + # swallow rc=2 -- which would have muted genuine breakage along with it. + # Discriminating on the message, or inferring the state from the error + # code, would both be guesses; the producer answers it instead. + - name: Fetch the live gate for this PR + id: fetch + run: | + set +e + "$SQUABBLE" fetch "${{ github.repository }}" \ + "${{ github.event.pull_request.number }}" > gate.json 2> fetch.err + rc=$? + set -e + cat fetch.err >&2 + case "$rc" in + 0) + echo "has_gate=true" >> "$GITHUB_OUTPUT" + echo "--- gate.json ---"; cat gate.json + ;; + 3) + echo "has_gate=false" >> "$GITHUB_OUTPUT" + rm -f gate.json + { + echo "## Gate triage: no gate to triage" + echo + echo "\`squabble fetch\` exited 3. Base branch \`${{ github.event.pull_request.base.ref }}\`" + echo "of \`${{ github.repository }}\` carries no \`required_status_checks\` ruleset rule," + echo "so there is no gate to squabble over and triage was skipped." + echo + echo "**This job is green because nothing was triaged, not because a gate passed.**" + echo "Merges into that branch are gated by no required status check." + echo + echo "Classic branch protection is a separate API and is not visible to this query," + echo "so this says nothing about it." + } | tee triage-outcome.md >> "$GITHUB_STEP_SUMMARY" + echo "::warning title=No gate to triage::base branch has no required_status_checks ruleset rule -- triage skipped, nothing was verified" + ;; + *) + # The redirect leaves a 0-byte gate.json even when the fetch + # failed; uploading it would look like an empty gate was fetched. + rm -f gate.json + echo "::error title=squabble fetch failed::exit $rc -- this is a real failure, not a missing gate" + exit "$rc" + ;; + esac + + - name: Diagnose the gate + if: steps.fetch.outputs.has_gate == 'true' + run: | + "$SQUABBLE" diagnose gate.json | tee squabble-diagnose.txt + + # Propose only. `|| true` because fight exits non-zero when it has work it + # cannot legitimately land — that is a finding to report, not a build break, + # and failing here would mask the very deadlock we are trying to surface. + - name: Fight (propose only — never commits, pushes or re-runs CI) + if: steps.fetch.outputs.has_gate == 'true' + run: | + "$SQUABBLE" fight "${{ github.repository }}" "${{ github.event.pull_request.number }}" \ + --repo-root . --json > squabble-fight.json || true + echo "--- fight report ---"; cat squabble-fight.json || true + + - name: Upload triage report + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: cicd-squabbler-report + path: | + gate.json + squabble-diagnose.txt + squabble-fight.json + triage-outcome.md + if-no-files-found: ignore diff --git a/.github/workflows/doi.yml b/.github/workflows/doi.yml new file mode 100644 index 00000000..5e5b4c53 --- /dev/null +++ b/.github/workflows/doi.yml @@ -0,0 +1,78 @@ +# SPDX-License-Identifier: MPL-2.0 +name: DOI publication contracts + +on: + pull_request: + paths: ['src/doi/**', 'src/server/**', 'src/analysis/AnalysisConfig.jl', 'test/doi/**', 'test/unit/test_doi*', 'bench/doi/**', 'scripts/link-doi.sh', 'config/schemas/doi*', 'config/templates/doi*', 'Project.toml', 'Manifest.toml', '.github/workflows/doi.yml'] + push: + branches: [main, 'arena/**'] + paths: ['src/doi/**', 'src/server/**', 'src/analysis/AnalysisConfig.jl', 'test/doi/**', 'test/unit/test_doi*', 'bench/doi/**', 'scripts/link-doi.sh', 'config/schemas/doi*', 'config/templates/doi*', 'Project.toml', 'Manifest.toml', '.github/workflows/doi.yml'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + contracts: + name: DOI contracts (no credentials or live deposits) + runs-on: ubuntu-24.04 + timeout-minutes: 20 + env: + JULIA_PKG_PRECOMPILE_AUTO: '0' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: julia-actions/setup-julia@fa02766e078afaaf09b14210362cee14137e6a32 # v3.0.2 + with: + version: '1.12.5' + - uses: julia-actions/cache@a7bed9df697e5d7309d68afe7542a87621a8b6c8 # v3.3.0 + - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 + with: + bun-version-file: .bun-version + - name: Syntax and source dependency contracts + run: | + julia --startup-file=no config/ci/lint_source.jl + bash -n scripts/link-doi.sh test/doi/check-nickel.sh + command -v zip + command -v jq + command -v flock + - name: Instantiate isolated HTTP-only test environment + run: julia --project=test/doi -e 'using Pkg; Pkg.instantiate()' + - name: Configure temporary contract outputs + run: echo "DOI_CONTRACT_ARTIFACTS=$RUNNER_TEMP/doi-contracts" >> "$GITHUB_ENV" + - name: Publication lifecycle, security, restart and streamed HTTP contracts + run: julia --project=test/doi test/doi/runtests.jl + - name: Install pinned browser and schema tools + working-directory: test/doi + run: | + bun install --frozen-lockfile --ignore-scripts + bunx playwright install --with-deps chromium + - name: Linker, JSON Schema, CFF and projection roundtrips + working-directory: test/doi + run: bun run test + - name: Evidence Mode, typed confirmation and browser recovery + working-directory: test/doi + run: bun run test:browser + - name: Verify Nickel contracts with a checksum-pinned CLI + run: | + curl --fail --location --retry 2 --max-time 120 https://github.com/nickel-lang/nickel/releases/download/1.18.0/nickel-x86_64-linux -o "$RUNNER_TEMP/nickel" + printf '9cba4dd65ae9915ec61f73033aafcff307a377665a83fd8f530df086763318cb %s\n' "$RUNNER_TEMP/nickel" | sha256sum --check + chmod +x "$RUNNER_TEMP/nickel" + NICKEL="$RUNNER_TEMP/nickel" bash test/doi/check-nickel.sh + - name: Publication performance and bounded-memory smoke + run: | + julia --project=test/doi bench/doi/benchmark.jl > "$RUNNER_TEMP/doi-performance.json" + if [[ -f bench/doi/baseline.json ]]; then + bun bench/doi/compare.js bench/doi/baseline.json "$RUNNER_TEMP/doi-performance.json" + else + echo '::warning::First DOI benchmark baseline is not established. Report is informational; no regression verdict is claimed. Review and retain a same-host baseline before enabling the >10% comparison.' + fi + - name: Preserve contract outputs and resolved environment + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: doi-contract-evidence + path: | + test/doi/Manifest.toml + ${{ runner.temp }}/doi-contracts/ + ${{ runner.temp }}/doi-performance.json + if-no-files-found: warn diff --git a/.github/workflows/proofs.yml b/.github/workflows/proofs.yml new file mode 100644 index 00000000..e0b1ceaf --- /dev/null +++ b/.github/workflows/proofs.yml @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: AGPL-3.0-only +name: Proofs + +# The formal-verification gate for issue #1. +# +# Design rules, all of which are here because the alternative is a gate that lies: +# +# * An absent prover is a FAILURE, never a skip. `proofs/bootstrap.sh` exits +# non-zero if it cannot install Agda. There is no `if: always()` escape and +# no `continue-on-error`. +# * The self-test runs on every push. A gate that has never been observed to +# reject anything is not evidence, so `proofs/tests/gate-selftest.sh` breaks +# the proofs on purpose in nine ways and requires each to be rejected. +# * The axiom audit runs as its own step, so a failure says which check failed +# rather than "the proofs job failed". +# * The type-check output is uploaded as an artifact whether it passed or not, +# because "it passed" and "it printed warnings nobody read" look identical in +# a green tick. + +on: + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: proofs-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + agda: + name: Agda proofs (2.7.0.1 / stdlib 2ffa8b7d) + runs-on: ubuntu-24.04 + timeout-minutes: 45 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Cache the vendored toolchain + uses: actions/cache@v4 + with: + path: | + proofs/.vendor + ~/.config/agda + key: agda-2.7.0.1-stdlib-2ffa8b7d-${{ hashFiles('proofs/agda/**/*.agda', 'proofs/agda/*.agda-lib') }} + restore-keys: agda-2.7.0.1-stdlib-2ffa8b7d- + + - name: Bootstrap the prover + run: proofs/bootstrap.sh --bootstrap + + - name: Axiom audit (postulates, FFI, unsound flags, holes, reachability) + run: proofs/tests/axiom-audit.sh + + - name: Type-check every proof module + run: | + set -o pipefail + proofs/bootstrap.sh --check 2>&1 | tee proofs-typecheck.log + # A clean run prints nothing but the harness lines. Any Agda warning + # is treated as a failure here rather than being left in the log. + if grep -qE '^(warning|Warning)' proofs-typecheck.log; then + echo "::error::Agda emitted warnings" + grep -nE '^(warning|Warning)' proofs-typecheck.log + exit 1 + fi + + - name: Gate self-test (nine deliberate breakages must be rejected) + run: proofs/tests/gate-selftest.sh + + - name: Upload the type-check transcript + if: always() + uses: actions/upload-artifact@v4 + with: + name: agda-typecheck-transcript + path: proofs-typecheck.log + if-no-files-found: error + + # The proof status document is a claim about the tree. This job checks the + # claims that are mechanically checkable, so PROOF-STATUS.md cannot drift away + # from what the modules actually contain. + status-consistency: + name: PROOF-STATUS.md matches the tree + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + + - name: Every module is listed, and every listed module exists + run: | + set -euo pipefail + fail=0 + # Every .agda module under proofs/agda/MetaManifold must appear in the + # status document, and every `MetaManifold.X` named in the document + # must exist on disk. + for f in proofs/agda/MetaManifold/*.agda; do + mod="$(basename "$f" .agda)" + if ! grep -q "MetaManifold.$mod" proofs/PROOF-STATUS.md; then + echo "::error::PROOF-STATUS.md does not mention MetaManifold.$mod" + fail=1 + fi + done + for named in $(grep -oE 'MetaManifold\.[A-Za-z]+' proofs/PROOF-STATUS.md | sort -u); do + path="proofs/agda/${named//./\/}.agda" + if [[ ! -f "$path" ]]; then + echo "::error::PROOF-STATUS.md names $named but $path does not exist" + fail=1 + fi + done + # Residue files referenced by the document must exist. + for r in $(grep -oE '[a-z-]+\.residue' proofs/PROOF-STATUS.md | sort -u); do + if [[ ! -f "proofs/residue/$r" ]]; then + echo "::error::PROOF-STATUS.md references proofs/residue/$r, which is missing" + fail=1 + fi + done + exit $fail diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml new file mode 100644 index 00000000..03904de6 --- /dev/null +++ b/.github/workflows/ui.yml @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +name: Stipple UI contracts + +on: + pull_request: + paths: ['ui/**', '.github/workflows/ui.yml'] + push: + branches: [main] + paths: ['ui/**', '.github/workflows/ui.yml'] + workflow_dispatch: + +permissions: + contents: read + +jobs: + contracts: + runs-on: ubuntu-24.04 + timeout-minutes: 15 + env: + JULIA_PKG_PRECOMPILE_AUTO: '0' + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: julia-actions/setup-julia@fa02766e078afaaf09b14210362cee14137e6a32 # v3.0.2 + with: + version: '1.12.5' + - uses: julia-actions/cache@a7bed9df697e5d7309d68afe7542a87621a8b6c8 # v3.3.0 + - name: Instantiate isolated UI environment + run: julia --project=ui -e 'using Pkg; Pkg.instantiate()' + - name: Test contracts and backend URL validation + run: julia --compiled-modules=no --compile=min -O0 --project=ui ui/test/runtests.jl diff --git a/.gitignore b/.gitignore index 97e509a0..5067a8ec 100644 --- a/.gitignore +++ b/.gitignore @@ -18,11 +18,7 @@ deps/src/ docs/build/ docs/site/ -# File generated by Pkg, the package manager, based on a corresponding Project.toml -# It records a fixed state of all packages used by the project. As such, it should not be -# committed for packages, but should be committed for applications that require a static -# environment. -Manifest.toml +# Manifest.toml is committed for reproducibility (this is an application, not a library) ### Python ### # Byte-compiled / optimized / DLL files @@ -197,6 +193,16 @@ poetry.toml pyrightconfig.json ### R ### +# renv project library and caches: large, machine-specific, and reproducible +# from renv.lock. Commit only the lockfile, the activation script, and renv +# settings; leave the installed packages on disk untracked. +renv/library/ +renv/local/ +renv/staging/ +renv/lock/ +renv/python/ +renv/sandbox/ + # History files .Rhistory .Rapp.history @@ -238,19 +244,141 @@ vignettes/*.pdf # R Environment Variables .Renviron -# pkgdown site -docs/ +## Documentation kept local except published release notes and toolchain +## migration/audit records, which are part of the maintained repository state. +## Milestone reports and deferred issues are part of maintained state for project board tracking. +docs/* +!docs/release-notes/ +!docs/audit/ +!docs/compliance/ +!docs/migration/ +!docs/milestones/ +!docs/milestones/** +!docs/statistics/ +!docs/statistics/** +# Pilots: repository-scoped experiments with an owner ruling behind them +# (docs/pilots/kyaml-pilot.md is the first one). +!docs/pilots/ +!docs/pilots/** +!docs/issues/ +!docs/issues/** +!docs/testing/ +!docs/type-system/ +!docs/types/ +!docs/reproducibility.md +!docs/doi-publication.md +!docs/owner-review-2026-09-25.md +!docs/integration/ +!docs/integration/** +!docs/wikis/ +!docs/wikis/** +!docs/formal/ +!docs/formal/** +!docs/triage/ +!docs/triage/** +# The run-notice model and its catalogue: the notice ids are a maintained +# contract between the backend, the manifest and the UI, and they are +# drift-tested against src/, so they are repository state, not local notes. +!docs/notices/ +!docs/notices/** +# …but the generated PR-#7 stack patches are derived artefacts: regenerate +# with docs/triage/pr7-split/make-stacks.sh instead of committing them. +docs/triage/pr7-split/patches/*.patch + +# Test + benchmark run artifacts (not the committed fixtures/baselines) +frontend/tests/results/ +frontend/tests/coverage/ +frontend/bench/results/ +bench/results/ # translation temp files + +# Local Julia precompile trace used for sysimage generation +generate_precompile_trace.jl po/*~ # RStudio Connect folder rsconnect/ -### R.Bookdown Stack ### +### R.Bookdown Stack # R package: bookdown caching files /*_files/ -### Other ### +# vscode +.vscode + +### Other # Things with ambiguous intellectual property internal +data/* +# MiSeq SOP integration test dataset (3-sample subset, public domain) +!data/MiSeq_SOP/ +data/MiSeq_SOP/* +!data/MiSeq_SOP/pipeline.yml +!data/MiSeq_SOP/run_A/ +data/MiSeq_SOP/run_A/* +!data/MiSeq_SOP/run_A/*.fastq.gz +!data/MiSeq_SOP/run_B/ +data/MiSeq_SOP/run_B/* +!data/MiSeq_SOP/run_B/*.fastq.gz + +### Frontend +node_modules/ +# Optional declaration-emit output (tsc -p tsconfig.build.json) +frontend/dist-types/ + +### Lock files +.~lock.* +.csv# + +### Tool paths, downloaded binaries and databases +bin/ +databases/ + +# Machine-level config: generated from config/defaults/ on first run +# (new_project) and edited in place; never committed. +config/tools.yml +config/pipeline.yml +config/databases.yml +config/primers.yml +config/composition.yml + +# Filter presets saved from the Tables view at runtime (see routes/results.jl +# _presets_dir): your own saved table filters, not shipped defaults. Distinct +# from the composition library. Machine data; never committed. +config/presets/ + +# Migration backups written by scripts/migrate_composition.jl +config/backup-*/ + +### Bits I don't want to share yet +hide.* +projects/ +pipelinesteps.txt +archive/ +.* +# RSR/estate canon dotfiles (see docs/compliance/rsr-alignment.md) +!.editorconfig +!.gitattributes +!.gitmessage +!.githooks/ +!.envrc + +### Exceptions +!.github +!.github/** +!.Rprofile +!.bun-version + +# Brainstorming visual-companion scratch (mock-ups, server state) +.superpowers/ + +# Track the Stipple/Vue migration recon and implementation plan. +!docs/migration/ +!docs/migration/** + +# Agda interface cache — build output, regenerated by `proofs/bootstrap.sh`. +proofs/agda/_build/ + +# Agda libraries file generated by proofs/bootstrap.sh +proofs/.agda-libraries diff --git a/.gitmessage b/.gitmessage new file mode 100644 index 00000000..ef6021d4 --- /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/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..09b0e3a6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,348 @@ + +# Changelog + +All notable changes to this repository (the hyperpolymath fork of +MetaManifold-WebUI) are documented here, following +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) conventions. +Application *behaviour* changes belong to upstream release notes +(`docs/release-notes/`); this log records the fork's engineering work on +types, tests, infrastructure, and alignment. + +## [Unreleased] + +### Added — the README/EXPLAINME pair, the wiki, and the autolink specification (2026-09-26) + +- **`README.adoc` replaces `README.md`**, per the estate README/EXPLAINME authoring + standard (`standards:docs/README-EXPLAINME-STANDARD.adoc`). The README is now the + three-layer design history — base R/Python design around raw DADA2, the origin + MetaManifold augmentation (JoshuaJewell), and the fork's honesty/typing steps — with + diagrammatic progression and shipped/planned markers throughout. The configuration + chapters moved to the wiki (they made the README unreadable); the third-party tools + table and acknowledgements moved into `NOTICE` (their natural home). +- **`EXPLAINME.adoc`** (new): the receipts file — claim→implementation→caveat map over + every README claim, the dogfooding table, known gaps as CAUTION blocks, and an + evidence index. The type-theory/enhanced-statistics deep material deliberately lives + in the wiki; EXPLAINME cross-references it rather than re-deriving it. +- **The GitHub wiki is now the full BerryWiki-format documentation** + (`metadatastician/berrywiki` page format: hidden metadata blocks, generated + `_Sidebar.md`), sourced from `docs/wikis/` and synced to `MetaManifold-WebUI.wiki.git`: + three audience sections (users — with academics and lab-professional tracks; platform + maintainers — operator and steward tracks; developers), seven deep dives (design + progression, type theory meets statistics, exact arithmetic, maximum likelihood and + refusals, compositional statistics and offsets, epistemic status, advanced + functionality), and a Status-and-Roadmap board marking everything IN PLACE / PARTIAL / + COMING / BLOCKED. +- **`docs/integration/autolink-references.md`** (new): the complete elaboration of the + repository's Settings → Autolink references set (lineage/estate, upstream tools, + toolchain, registries), paste-ready and machine-readable, with reserved/omitted cases + reasoned. Application in Settings needs Administration permission (one human pass); + the file is the source of truth for it. + +### Added — the three deferred ILR bases are implemented, with proofs (#20, 2026-09-26) + +- **`phylogenetic` (PhILR), `sequential_binary_partition` and `balance_dendrogram`** are + computed, no longer refused (`DEFERRED_ILR_BASIS` is now empty; it stays, so a future + basis can be deferred the same way). `src/analysis/ilr_basis.jl` is one engine for all + three: each basis is a rooted binary tree, and balances are clade sums in one post-order + pass, `O(D)` per sample, with no dense basis matrix. Pure Julia; no new dependency. +- **Inputs and contracts.** `advanced.ilr_phylo_tree_path`, `ilr_sbp_matrix_path`, + `ilr_balance_dendrogram_method` (plus philr's part/balance weights and the SBP history) + are identical in the Julia validator, the Nickel contract, the JSON schema, DEED and the + frontend: each basis requires its input and refuses the others'. Configurations that + use none of them hash exactly as before; the default Helmert balances are byte-identical. +- **Validity is refused, not repaired.** Trees must be rooted, bifurcating and cover the + retained taxa (tips outside them are pruned and recorded); an SBP must be the SBP of a + binary tree (Egozcue & Pawlowsky-Glahn 2005), over exactly the retained taxa. +- **Provenance and guards.** Each run records the tree or SBP SHA-256, the dendrogram + method, the weights and the balance-id rule. More than 3 distinct SBPs tried in a project + raises a DANGER (p-hacking guard). BH stays mandatory. +- **Evidence.** Known answers; an independent Julia reference + (`test/fixtures/ilr/ilr_reference.jl`) that recomputes all 17 committed expectations + (philr, `compositions::ilr`, R `hclust`) every run; R cross-checks where philr, + compositions and robCompositions are installed; negative controls. The balance algebra + (contrast sums, orthonormality, injectivity with its positive-weight hypothesis, scale + and perturbation invariance, comb = Helmert, SBP validity) is proved in Agda + (`proofs/agda/`, new `proofs` CI job) and mapped to the tests in + `docs/formal/verification-plan.md`. The conditions document is + `docs/statistics/method-conditions/ilr-bases.md`. +- **Benchmarks.** `bench/ilr_bases/benchmark.jl` (100 / 1 000 / 10 000 taxa; warns over + 5 minutes or 1 GiB) and a pull-request gate that fails when CLR or default ILR allocate + or run more than 10 % worse than the base commit on the same runner. +- **Removed** the Python fixture generator: Python is not permitted by the estate language + policy; the Julia reference replaces it (`docs/compliance/standards-alignment.md`). + +### Added — advanced zero handling and the glmGamPoi dispersion port (issue #21, 2026-09-26) + +- **`src/analysis/zero_replacement.jl`** — the two operators the issue names, implemented + rather than aliased: + - *multiplicative replacement* (Martín-Fernández et al. 2003), the operator of + `zCompositions::multRepl`: zeros become `delta x detection limit`, observed parts are + scaled by `1 - Delta`, and the sample total and the ratios among observed parts are + preserved **exactly**; + - *Bayesian multiplicative replacement* (Martín-Fernández et al. 2015), the GBM of + `cmultRepl`: the inserted value is the posterior mean of a Dirichlet-multinomial whose + prior mean is the leave-one-out profile and whose concentration is `1/gmean(t)` unless the + caller supplies `alpha`, with the reference's `frac x colmins` cap and its `adjust` + switch. + Both refuse what they cannot do (a delta outside (0,1); an imputed mass that would consume + the sample, naming the largest admissible delta; all-zero samples; never-observed parts; + parts seen in fewer than two samples) and both record a full provenance block, including + the sentence that matters: **all replacement is biased**. +- **`src/analysis/dispersion.jl`** — a pure-Julia port of glmGamPoi's dispersion pipeline + (Ahlmann-Eltze & Huber 2020): Cox-Reid adjusted NB maximum likelihood with the reference's + `0.99` factor and its early returns, the `dnorm`-weighted local-median trend, the + quasi-likelihood conversion, and the inverse-chisquare prior by Nelder-Mead. The reference's + **natural-spline abundance trend is not ported and is refused by name** rather than being + silently replaced by the non-trended prior; `glmgampoi_abundance_trend = false` runs the + reference's own non-trended form and records the deviation. +- **`dispersion_method = "glmGamPoi"` in `estimation.jl`** — the by-name refusal is replaced + by the real two-pass path: pass 1 fits the mean sweep in R, the port estimates the + dispersions on those means, pass 2 refits at the fixed dispersion (`theta = 1/alpha`, with + `stats::glm(poisson())` where alpha is 0). +- **Configuration** — `normalization.bayesian_multiplicative_alpha`, and + `advanced.{zero_replacement_method, multiplicative_delta, bayesian_alpha, + glmgampoi_abundance_trend}` in the Julia model, the Nickel contract, the JSON schema and the + frontend types; validation at the door (`delta` in (0,1), `alpha` > 0), warnings for + `delta < 0.01` and `delta >= 0.9`, a DEED echo of every value, and the DANGER banner when + three or more deltas have been tried — the p-hacking case the issue names. The Advanced + expander gains the delta slider **with a replacement preview**, the alpha field, and the + trend selector. +- **Proofs** — `proofs/agda/` (Agda 2.7.0.1, stdlib 2.1.1, `--safe`, no postulates): + `ZeroReplacement.agda` (totals and observed-part ratios preserved, imputed values strictly + positive and below their detection limit), `NoRigidReplacement.agda` (no rule determined by + the observed data can be faithful — the theorem behind "all replacement is biased"), and + `DispersionShrinkage.agda` (the shrinkage lies between the prior and the sample estimate and + is exact when they coincide). `proofs/agda/README.md` says what each proves, what is + deliberately *not* proved, and what would falsify them. +- **Tests and benchmarks** — `test/unit/test_zero_replacement.jl` and + `test/unit/test_dispersion.jl` against the pinned fixture `test/fixtures/issue21/golden.json` + (with direct comparisons against `zCompositions` and `glmGamPoi` wherever R has them, and + explicit "this comparison did not run" notices where it does not); + `bench/zero_replacement/benchmark.jl` at 100/1000/10000 taxa with the issue's 5-minute + warning and a 10% regression report behind `METAMANIFOLD_BENCH_STRICT`. +- **Docs** — `docs/statistics/zero-handling.md` (what each policy does, its cost, the exact + relation to the two reference packages, and the alternatives that insert nothing) and + `docs/statistics/method-conditions/dispersion-glmGamPoi.md` (the conditions of use and the + residues). + +### Added — the KYAML pilot (2026-09-26) + +- **`scripts/kyaml/KYAML.jl`** — `just use-kyaml`, `just use-yaml`, `just check-kyaml`: the + switch between block-style YAML and KYAML (the KEP-5295 strict subset), with comments kept + and associated with their entries, canonical-form checking that is idempotent by + construction, and refusals (anchors, aliases, tags, multi-document files, duplicate keys, + multi-line plain scalars) that name the file and line and write nothing. +- **`docs/pilots/kyaml-pilot.md`** — the operating manual for this repository being the + estate's KYAML pilot: the owner ruling of 2026-09-26, what the switch guarantees, what it + refuses, the decisions it takes and prints, the proof obligations from + `standards :: 3-practice/YAML-POLICY.adoc`, and how to revert. +- **`config/kyaml/drift.txt`** — the two workflow files Dependabot and `gh actions-lock` + rewrite: converted, not gated, accepted in writing as the policy's §5 step 6 requires. +- **`stapeln.toml` + `Containerfile` + the `proofs` CI job** — the proof lane as a standalone + deployment (Guix environment, mise pins, Agda from the channels pin) rather than a local + convenience. + +### Fixed — the NB test fixture is data a negative binomial describes (2026-09-26) + +- The estimation tests' synthetic table was **under-dispersed** (variance below the mean, + e.g. 10.2 vs 3.1). The negative binomial maximum-likelihood dispersion is then infinite, + `MASS::theta.ml` stops at its iteration limit, and the estimator — correctly — reported + two of the three fits as failed. The tests asserted `status == ok` on those fits. The + fixture now keeps the same group means with variance near mu + mu^2/6 (theta ~ 6), + checked outside R with two independent NB2 likelihood fits under both offsets the tests + use. The estimator's handling of an infinite theta is unchanged. + +### Fixed — every parametric fit returned `not_run`; CI failures are now readable (2026-09-26) + +- **R's `NA` is read as missing.** The estimator writes its fits with + `write.csv(..., na = "NA")` and read them back with CSV.jl's default + `missingstring = ""`. One `NA` in a numeric column made the whole column a string + column, `isfinite` threw, and every NB/logistic/Gaussian fit came back as `not_run` + with no statistics. The Julia side now reads `"NA"` as missing (`R_NA_STRINGS`). + Julia tests on `main` had been red since #60 for this reason. +- **`glmGamPoi` reaches its by-name refusal.** The configuration lower-cases + `dispersion_method` but compared it against a table spelling `glmGamPoi`, so the name + was turned away as unknown and the issue #21 explanation was never shown. The compare + is now lower-case against lower-case, and the estimator looks its refusals up + case-insensitively. A test covers three spellings. +- **Source-scan tests no longer trip on comments.** Comments quoting the removed + hash-derived p-value code were reworded so the "no placeholder statistics" scan stays + strict without flagging its own history. +- **CI names the failing assertion.** The "Run tests" step posts one annotation per + `Test Failed` / `Error During Test` block and one with only the failing summary rows. + The old single annotation was cut to 4096 bytes by the checks API, which removed the + failing testset, and the job log is served from a host some environments cannot reach. + +### Fixed — the ILR path stops substituting a basis it was not asked for (2026-09-25) + +- **Unimplemented ILR bases are refused rather than substituted.** The configuration + accepted `phylogenetic`, `sequential_binary_partition` and `balance_dendrogram`, and + the execution path warned and computed the default Helmert basis instead. An ILR + balance is only interpretable under the basis that defined it, so substituting a + basis computes numbers that mean something other than what the analyst asked for. + The three deferred bases (`DEFERRED_ILR_BASIS`) are refused at construction and in + `prepare_analysis_table`. +- **Balances are labelled as balances.** An ILR table of $n$ taxa has $n-1$ balances, + not $n$ features. The rows are relabelled `balance_1`..`balance_n-1`, + `diagnostics.checks["ilr"]` records the basis, definition, input taxa count and + taxa order, and the all-zero-taxa healing block no longer restores taxon labels onto + balance rows. + +### Fixed — `method = "TSS"` was refused by the layer that claimed to have shipped it (2026-09-25) + +- **The allowed-normalisation table held the three names in a different case from the + one the configuration stored.** `NormalizationConfig` canonicalises its method to + lower case (`"TSS"` becomes `"tss"`), while `VALID_NORMALIZATION_FOR_METHOD` listed + `"TSS"`, `"CSS"`, `"RSS"`. The membership test therefore failed for every one of those + spellings, and `AnalysisConfig` raised + `normalization.method 'tss' incompatible with method 'nb_glm'` — including for + `test/unit/test_execution.jl`'s `TSS offset for NB_GLM` testset, which constructed + `method="TSS"` exactly as the documentation instructs. That failure is what reddened + the CI run for the merged TSS-offsets commit; it was not a flaky test. +- Both the table and the two comparisons (`AnalysisConfig` constructor, `validate_config`) + are lower case now, `test/unit/test_scaling.jl` asserts that every admissible spelling + of every method name is accepted, and the JSON schema enum still accepts the upper-case + spellings for existing documents. + +### Added — TSS/CSS/RSS are computed as offsets and recorded as such (2026-09-25) + +- **`src/analysis/scaling.jl` is wired into the execution path** (issue #16). Under + `nb_glm` the response stays the counts and the offset is the declared scaling factor's + logarithm: `tss` = log library size, `css` = log cumulative sum at the declared + `css_quantile` (Paulson et al. 2013), `rss` = log of the trimmed weighted mean of + log-ratios to a reference sample (TMM, Robinson & Oshlack 2010), `size_factors` = + median-of-ratios (DESeq2/RLE) — the estimator the name claimed all along and the code + did not compute. `none` keeps the plain log library size it has always meant. +- **The declared parameters travel with the configuration**: `css_quantile` (default + 0.75), `tmm_ref_column` (default: chosen the way edgeR chooses it, and recorded), + `tmm_log_ratio_trim` (0.3) and `tmm_sum_trim` (0.05) are validated in the constructor, + included in the config hash and canonical JSON, round-tripped through `to_json`/ + `from_json`/Nickel/DEED, mirrored in the JSON schema and the Nickel contracts, exposed + through the server's configuration route, and documented in `context_help` and the + frontend help. A run that asked for a different quantile is a different run. +- **What was computed is recorded**: `diagnostics.checks["scaling"]` and the manifest + provenance carry the kind, the definition sentence, the parameters, the raw quantities + before centring and a SHA-256 of the offset vector, so two runs can be shown to agree + and a run whose offset changed is detectable from the manifest alone. +- **Refusals instead of substitutions**: `css` and `rss` are refused for a response with + no counts to offset (a non-count response is what the old alias silently produced), + a zero or non-finite sample total is refused by name, a CSS cumulative sum of zero is + refused as `log(0)` rather than replaced, and an unknown `tmm_ref_column` is refused + with the real sample names instead of falling back to the data-driven choice. +- Evidence: `test/unit/test_scaling.jl` (known answers with the arithmetic written + beside them, properties a wrong implementation fails, the CSS robustness property, + negative controls, the integration path through `prepare_analysis_table`, and a + base-R transcription of the same conditions), plus the conditions document + `docs/statistics/method-conditions/scaling-and-offsets.md`. Parity with + `metagenomeSeq::cumNorm` and `edgeR::calcNormFactors` is **not** claimed: neither + package is in `renv.lock`, and the outstanding condition is recorded in issue #16. + +### Fixed — the analysis path no longer returns placeholder statistics (2026-09-25) + +- **`run_analysis` computed nothing and returned numbers anyway.** It derived + `pvalue` from `0.01 + (hash(taxon_id) % 100) / 1000.0`, `padj` from + `pvalue * 1.5` and `log2FoldChange` from `(hash % 20) / 10 - 1`: every one of + those is a deterministic function of the feature's *name*, carrying no + information about the counts, and they were returned to callers as results. The + placeholder is deleted, not deprecated, and `test/unit/test_estimation.jl` + reads `Execution.jl` as source and asserts it does not come back. +- **Real estimation** (`src/analysis/estimation.jl`, catalogue item 2): per-feature + `MASS::glm.nb` for `nb_glm` (with a required offset), `stats::lm` for + `clr_lm`/`ilr_lm`, and `stats::glm(family = binomial)` for `logistic` on a 0/1 + response — all through the shared R runtime lock, all with R and MASS versions, + offset hash and result-table hashes in the provenance. +- **Unsuccessful states are states.** A feature whose fit fails gets + `status = "failed"`, a note naming the cause, and `null` for every statistic; it + is excluded from the BH family and counted. A run that cannot happen at all — + no design, R unreachable, R busy past the timeout — returns `status = + "not_run"` with a reason and an empty result set, never an empty table that + reads as "nothing was significant". +- **Refusals instead of substitutions**: unsupported formula syntax + (interactions, transformations, random effects, nesting), a `glmGamPoi`/ + `local`/`mean`/`pooled` dispersion method that has no implementation here, an + offset on a non-count model, a count model without one, and a binomial fit on + proportions all raise with the reason attached. +- BH is implemented in Julia and compared against R's `p.adjust(method = "BH")` + in the tests; conditions are published in + `docs/statistics/method-conditions/parametric-fits.md`. + +### Added — Type-system engineering series (2026-09) + +- **Epistemic claims with receipts**: added `Standpoint`, `TaxonWarrant`, + `ProjectionY`, `Receipt`, and SHA-256 signing/verification (`make_receipt`, + `verify_receipt`) in `src/core/epistemic.jl`, with compact `echo:v1?...` + encoding/parsing for the `avec_fibre` column. Aligned directly with + `EpistemicTypes.jl` and `echo-types`. +- **Zero observation disambiguation**: added `disambiguate_zero` + (`Val(:true_absence)` vs `Val(:undetected)`), grounding presence/absence + claims in sequencing depth and formalizing the boundary between biological + absence and observation limits (`absolute-zero` and Issue #18). +- **Exact Multiplicative and Bayesian Zero Replacement**: implemented exact + Martín-Fernández (2003) multiplicative replacement and Martín-Fernández (2015) + Bayesian Dirichlet prior replacement in `Execution.prepare_analysis_table`, + strictly preserving total sample depth and subcompositional ratios between all + non-zero components (Issue #21). +- **Exact TSS offsets for count models**: implemented exact Total Sum Scaling + offsets for `NB_GLM` in `Execution.prepare_analysis_table`, preserving the + count nature of response tables and providing `log(lib_sizes)` offsets to + prevent double-normalization and retain negative binomial model + interpretability (Issue #16). +- **bun migration**: `package.json`/`bun.lock` replace the mixed npm+Deno + tooling; bun 1.3.10 pinned via `.bun-version`; CI installs bun. + `docs/migration/npm-deno-to-bun.md`. +- **Strict TypeScript foundation**: 165 type errors → 0 without + suppressions; `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`, + `verbatimModuleSyntax` et al. `docs/type-system/strict-mode-foundation.md`. +- **Type-estate closure**: ambient `FIXME(types)` stubs consolidated in + `src/types/declarations.d.ts`; dead `@types/*` removed; skipLibCheck + exception documented; CI gains a `bun run typecheck` gate. +- **Test + benchmark infrastructure**: bun:test battery (unit/integration + lanes), proven-discipline benchmark harness with frozen workloads and + checksums, JUnit+lcov CI artefacts, playwright e2e lane (opt-in), and + `docs/testing/infrastructure.md`. +- **Domain type system**: `src/types/api` boundary leaves with SOURCE + anchors, `src/types/plotly.ts` manual vocabulary, domain/component/state + layers, type-level assertion suite, `docs/types/architecture.md`. +- **Type-driven behavioural tests**: 204 assertions across the + prompt-4 boundaries (67 pass / 5 DOM-lane todos / 0 fail); + `docs/testing/coverage.md`. +- **RSR/standards alignment** (this commit): estate `.editorconfig`, + `.gitattributes`, `.gitmessage`, `LICENSES/`, SPDX identifier sweep, + community files (`NOTICE`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, + `CONTRIBUTING.md`, `ROADMAP.md`), issue/PR templates, dependabot, + licence/format/lint/commit-convention CI gates, and + `docs/reproducibility.md` + `docs/compliance/` checklist documents. + +### Changed + +- CI: pipeline extended to licence-header, formatting, lint, and commit + convention checks ahead of typecheck/test/bench/build. + +### Fixed + +- **Zero-depth samples poisoned the transform stage.** A sample with no reads + reached the transform, where every transform divides by a sample total: + `relative` wrote `0.0` for every feature (a value where the answer is + *undefined*), `rarefy` took `min_lib = minimum(lib_sizes) = 0` and scaled + **every** sample by zero, and `clr` took `log(0) = -Inf` that the NaN/Inf + healing later replaced with `epsilon`. All-zero samples are now healed + *before* the transform rather than after, which also removes the + inconsistency where `prepared` and `filtered_counts` described different + data under `drop_policy=impute`. Visible results change only for `impute` + runs over inputs containing a zero-depth sample; `drop` and `refuse` are + unchanged. Owner-authorised behaviour change, recorded in + `docs/statistics/behaviour-change-zero-depth-samples.md`. + +## [0.1.0] — 2026-05-21 (upstream baseline) + +Initial public state of the application as inherited from upstream +(`JoshuaJewell/MetaManifold-WebUI`): Julia orchestrator wrapping cutadapt, +DADA2, SWARM, vsearch, and cd-hit-est; DuckDB-backed per-run results; +React frontend with results explorer, annotation, composition building, +cross-run charts, and configuration views. Application-level history +continues in `docs/release-notes/`. + +[Unreleased]: https://github.com/hyperpolymath/MetaManifold-WebUI/compare/main...HEAD +[0.1.0]: https://github.com/hyperpolymath/MetaManifold-WebUI/releases diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..81b8b246 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,89 @@ +cff-version: 1.2.0 +message: "If you use MetaManifold in academic work, please cite it as follows." +title: "MetaManifold: a Julia orchestrator for amplicon sequencing pipelines with an interactive web interface" +type: software +authors: + - given-names: Joshua Benjamin + family-names: Jewell + email: jjewell23@rvc.ac.uk + affiliation: "Royal Veterinary College" + orcid: "https://orcid.org/0009-0007-0289-9741" +version: "0.1.0" +date-released: "2026-05-21" +license: AGPL-3.0-only +repository-code: "https://github.com/JoshuaJewell/MetaManifold-WebUI" +url: "https://github.com/JoshuaJewell/MetaManifold-WebUI" +abstract: >- + MetaManifold is a Julia-native orchestrator for amplicon sequencing + workflows. It wraps cutadapt, DADA2, SWARM, vsearch, and cd-hit-est into a + single configurable pipeline, storing per-run results in DuckDB and exposing + them through an interactive React frontend. Analyses include alpha diversity, + taxonomic composition, NMDS, and PERMANOVA via R/vegan, together with + cross-run comparison and a functional-database annotation layer. +keywords: + - amplicon sequencing + - metabarcoding + - DADA2 + - SWARM + - bioinformatics + - Julia + - DuckDB +references: + - type: article + title: "DADA2: High-resolution sample inference from Illumina amplicon data" + authors: + - family-names: Callahan + given-names: "Benjamin J." + - family-names: McMurdie + given-names: "Paul J." + - family-names: Rosen + given-names: "Michael J." + - family-names: Han + given-names: "Andrew W." + - family-names: Johnson + given-names: "Amy Jo A." + - family-names: Holmes + given-names: "Susan P." + journal: "Nature Methods" + year: 2016 + volume: 13 + issue: 7 + start: 581 + end: 583 + doi: "10.1038/nmeth.3869" + - type: article + title: "Swarm v2: highly-scalable and high-resolution amplicon clustering" + authors: + - family-names: Mahe + given-names: "Frederic" + - family-names: Rognes + given-names: "Torbjorn" + - family-names: Quince + given-names: "Christopher" + - family-names: "de Vargas" + given-names: "Colomban" + - family-names: Dunthorn + given-names: "Micah" + journal: "PeerJ" + year: 2015 + volume: 3 + start: e1420 + doi: "10.7717/peerj.1420" + - type: article + title: "VSEARCH: a versatile open source tool for metagenomics" + authors: + - family-names: Rognes + given-names: "Torbjorn" + - family-names: Flouri + given-names: "Tomas" + - family-names: Nichols + given-names: "Ben" + - family-names: Quince + given-names: "Christopher" + - family-names: Mahe + given-names: "Frederic" + journal: "PeerJ" + year: 2016 + volume: 4 + start: e2584 + doi: "10.7717/peerj.2584" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..2a7d2c13 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,106 @@ + +# Contributor Covenant Code of Conduct + +Version 2.1 — https://www.contributor-covenant.org/version/2/1/code_of_conduct/ + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community 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, color, 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. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior: + +* The use of sexualized language or imagery, and sexual attention or advances + of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies +when an individual is officially representing the community in public spaces. +Examples of representation include using an official email address, posting +via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement via the +repository's **private security advisory channel** (see `SECURITY.md`) or by +opening an issue marked `[CONDUCT]`; a maintainer will respond privately. +All complaints will be reviewed and investigated promptly and fairly, and all +community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines: + +### 1. Correction +**Community Impact**: Inappropriate language or other unprofessional behavior. +**Consequence**: A private, written warning with clarity around the violation +and an explanation of why the behavior was inappropriate. A public apology may +be requested. + +### 2. Warning +**Community Impact**: A violation through a single incident or series of actions. +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved for a specified period of time, including +unsolicited interaction with those enforcing the Code of Conduct. Violating +these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban +**Community Impact**: A serious violation of community standards. +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. + +### 4. Permanent Ban +**Community Impact**: A pattern of violation, sustained harassment, or +aggression toward or disparagement of classes of individuals. +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 2.1, +available at https://www.contributor-covenant.org/version/2/1/code_of_conduct/. +Community Impact Guidelines were inspired by Mozilla's code of conduct +enforcement ladder. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..72b94bb0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,119 @@ + +# Contributing + +## Where to contribute + +- **Application behaviour / bioinformatics** (pipeline, analyses, server + routes, UI features): propose to **upstream**, + `JoshuaJewell/MetaManifold-WebUI`. +- **Engineering alignment work** (types, test infrastructure, CI, + tooling): this fork, `hyperpolymath/MetaManifold-WebUI`. + +Pull requests against this fork must use base +`hyperpolymath/MetaManifold-WebUI:main`. GitHub's fork PR page defaults the +base to the upstream parent — change it before clicking *Create*. + +## Landing fork work on upstream (low-friction integration) + +This fork and upstream (`JoshuaJewell/MetaManifold-WebUI`) share no git +ancestor, so a naive merge conflicts on every shared-but-different file. To +integrate without a wall of conflicts — and to let the maintainer adopt the work +incrementally, from "pure upstream" to "partially transitional" to "everything", +without ever breaking a running system — see: + +- **`docs/integration/README.md`** — the guide (profiles, merge hygiene, staging). +- **`docs/integration/conflict-map-2026-09-25.md`** — the measured conflict set. +- **`docs/integration/HANDOFF-granular-reanchor.md`** — the brief to actually land it. +- `just reanchor-plan` / `just reanchor` — replay the fork onto upstream as ~206 granular commits (3 decisions, 0 conflicts). +- `just integrate status | profiles | plan | triage` and `just augment`/`just suspend `. +- `just bootstrap` / `just setup-full` / `just heal` / `just doctor` — the turnkey environment. + +## Development setup + +```bash +# Frontend (gates run here) +cd frontend +bun install # bun 1.3.10 — see .bun-version +bun run typecheck # tsc semantic gate (0 errors required) +bun test # unit + integration batteries (no DOM lane) +bun run bench/ # benchmark harness (informational) +bun run check # all three in sequence — must be green + +# Full application (requires Julia + R/renv per README.adoc § Quick start) +./install.sh # upstream flow +./start.sh +``` + +Environment requirements and the clean-clone reproducibility procedure are +in `docs/reproducibility.md`. + +## Commit conventions + +Conventional commit subjects (enforced locally by the commit-msg hook; +reported in CI as an advisory check, not a merge gate — this fork's work +lands on upstream, which does not require conventional commits): + +``` +(): # ≤ 72 chars +``` + +Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, +`test`, `build`, `ci`, `chore`, `revert`. A template with the full +checklist is in `.gitmessage`: + +```bash +just hooks # one-time, enables the commit-msg + # and pre-commit gates +git config commit.template .gitmessage # one-time, loads the template +``` + +`just hooks` is already a dependency of `just bootstrap`, so a clone that was +bootstrapped has both gates live. It only sets `core.hooksPath`, which is local +config and therefore cannot be committed — that is why it has to be a command +rather than a file. The hooks are `commit-msg` (conventional-commit subject) and +`pre-commit` (blob hygiene: no uncompressed sequencing data, nothing over 4 MiB). +CI re-checks both, so forgetting to run this costs a red build, not a bad commit +on `main`. + +--- + + + +--- + +## Branch naming + +``` +feat/ new capability +fix/ defect repair +chore/ alignment / tooling / metadata +test/ test-only change +docs/ documentation-only change +``` + +Lowercase, hyphenated, one concept per branch. Long-lived topic branches +are rebased onto `main` before PR; merge commits from topic branches are +not used. + +## Before opening a PR + +1. `bun run check` green (`frontend/`) +2. `scripts/check-spdx.sh`, `scripts/check-format.sh`, + `scripts/check-lint.sh` green (repo root; these run in CI as + advisory checks and do not block merge) +3. New source files carry the right `SPDX-License-Identifier` header + (`NOTICE` explains the authorship rule) +4. Docs touched if behaviour/developer workflow changed +5. No secrets, tokens, `.env`, or sequencing data in the diff + +The PR template (`.github/pull_request_template.md`) lists the same gates. + +## Licence headers + +- Files you create in this fork: `MPL-2.0` for code/config/scripts, + `CC-BY-SA-4.0` for prose — per `NOTICE` and the estate Licence Policy + (Rule 3a inside an AGPL work). +- Files that exist upstream keep their upstream licence; do not relicense + them — annotate with `SPDX-License-Identifier: AGPL-3.0-only` only. diff --git a/Containerfile b/Containerfile new file mode 100644 index 00000000..456ccdb3 --- /dev/null +++ b/Containerfile @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# Containerfile — the standalone deployment of this repository's toolchain and proof lane. +# +# Layered to match stapeln.toml (which is the source of truth for what each layer is for). +# The purpose is not "a dev container": it is that `just prove-agda`, `just check-kyaml` and +# the Julia test lane run somewhere reproducible, from an image, instead of from whatever a +# runner happens to have. Owner ruling, 2026-09-26. +# +# UNVERIFIED IN THE AUTHORING SANDBOX: this file has not been built (no container runtime, +# no registry access where it was written). The first CI run that builds it is the check; the +# layer that fails will name itself. +# +# Build: podman build -t ghcr.io/hyperpolymath/metamanifold-webui:0.1.0 -f Containerfile . +# Run: podman run --rm ghcr.io/hyperpolymath/metamanifold-webui:0.1.0 +# (its entrypoint is `just prove-agda`) + +FROM docker.io/library/debian:12-slim AS base +# Guix is layered on rather than replacing the base so the R lane's system packages are the +# ones renv.lock was generated against. The locale is set because R's message catalogue and +# Agda's error output both depend on it. +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + ca-certificates curl xz-utils git bash gnupg locales; \ + sed -i 's/# en_GB.UTF-8 UTF-8/en_GB.UTF-8 UTF-8/' /etc/locale.gen; \ + locale-gen; \ + rm -rf /var/lib/apt/lists/* +ENV LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8 + +# ── Layer: guix-toolchain ──────────────────────────────────────────────────── +# guix.scm names the toolchain; channels.scm pins the revision. It includes agda and +# agda-stdlib for the proof lane (see the proof-lane comment in guix.scm). +FROM base AS guix-toolchain +ARG GUIX_VERSION=1.4.0 +RUN set -eux; \ + curl -fsSL "https://ftp.gnu.org/gnu/guix/guix-binary-${GUIX_VERSION}.x86_64-linux.tar.xz" -o /tmp/guix.tar.xz; \ + cd /tmp; tar -xf guix.tar.xz; \ + mv var/guix /var/guix; \ + mv gnu /gnu; \ + mkdir -p /root/.config/guix; \ + ln -sf /var/guix/profiles/per-user/root/current-guix /root/.config/guix/current; \ + mkdir -p /usr/local/bin; \ + ln -sf /root/.config/guix/current/bin/guix /usr/local/bin/guix; \ + ln -sf /root/.config/guix/current/bin/guix-daemon /usr/local/bin/guix-daemon; \ + rm -rf /tmp/guix.tar.xz /tmp/var /tmp/gnu; \ + guix --version +COPY channels.scm guix.scm /work/ +WORKDIR /work +# Resolving the environment is the expensive step and the reason this layer exists separately: +# it is cached until channels.scm or guix.scm changes. +RUN set -eux; \ + guix time-machine -C channels.scm -- shell -D -f guix.scm -- true; \ + guix time-machine -C channels.scm -- shell -D -f guix.scm -- agda --version + +# ── Layer: mise-toolchain ──────────────────────────────────────────────────── +# mise pins the exact versions CI uses (julia 1.12.5, bun 1.3.10, node 20.20.2, just 1.43.1), +# so the image and CI cannot drift. Guix supplies versions that follow the channels commit; +# mise supplies the exact binaries. Both are present on purpose. +FROM guix-toolchain AS mise-toolchain +RUN set -eux; \ + curl -fsSL https://mise.run | bash; \ + /root/.local/bin/mise install; \ + /root/.local/bin/mise ls +ENV PATH=/root/.local/share/mise/shims:/root/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin + +# ── Layer: proofs ──────────────────────────────────────────────────────────── +# The build refuses to produce the image unless every law checks and the YAML is canonical. +# A proof nobody runs at build time is a file, not a check. +FROM mise-toolchain AS proofs +COPY . /work +WORKDIR /work +RUN set -eux; \ + just prove-agda; \ + mkdir -p /proofs; \ + cp proofs/agda/*.agdai /proofs/ 2>/dev/null || true + +# ── Layer: runtime ─────────────────────────────────────────────────────────── +# A small runtime that carries the toolchain and the repository: `just prove-agda` and +# `just check-kyaml` work without a checkout, which is what "standalone deployment" means here. +FROM debian:12-slim AS runtime +RUN set -eux; \ + apt-get update; \ + apt-get install -y --no-install-recommends ca-certificates git bash locales; \ + rm -rf /var/lib/apt/lists/* +ENV LANG=en_GB.UTF-8 LC_ALL=en_GB.UTF-8 \ + PATH=/root/.local/share/mise/shims:/root/.local/bin:/usr/local/bin:/usr/bin:/bin +COPY --from=proofs /work /work +COPY --from=proofs /root/.local /root/.local +COPY --from=guix-toolchain /gnu /gnu +COPY --from=guix-toolchain /var/guix /var/guix +COPY --from=guix-toolchain /root/.config/guix /root/.config/guix +RUN ln -sf /root/.config/guix/current/bin/guix /usr/local/bin/guix +WORKDIR /work +ENTRYPOINT ["/usr/bin/env", "just", "prove-agda"] diff --git a/EXPLAINME.adoc b/EXPLAINME.adoc new file mode 100644 index 00000000..69395495 --- /dev/null +++ b/EXPLAINME.adoc @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) += MetaManifold — EXPLAINME +:toc: preamble +:toc-title: Contents +:icons: font +:doctype: article + +This is the receipts file: every factual claim in link:README.adoc[README.adoc] +mapped to the code that implements it, with the honest caveat attached. It is +written for the sceptical developer or reviewer doing due diligence after the +README has interested them. The mathematics and type theory behind the +statistical layer are *not* re-derived here — they run to pages, so they live +in the link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki[project wiki] +and this file points at them. README sells; this file proves; the wiki teaches. + +== Claim-to-implementation map + +=== "MetaManifold is three designs stacked honestly on top of each other." + +[quote, README.adoc] +____ +MetaManifold is three designs stacked honestly on top of each other. Each +layer is still visible in the code; none of them pretends to be the whole +story. +____ + +How this is implemented:: +Layer 1 (raw R/DADA2 + shell/swarm-vsearch) survives as the R bridge in +`src/pipeline/dada2/dada2_functions.r` and the external-tool stages in +`src/pipeline/{swarm,vsearch,tools}.jl`. Layer 2 (the orchestration and +web workbench) is `src/core/config.jl`, `src/core/duckdb_store.jl`, +`src/server/`, `frontend/`. Layer 3 (the honesty and typing discipline) is +`src/analysis/{estimation,exact_summaries,numeric_policy,scaling,AnalysisConfig}.jl` +plus `src/core/epistemic.jl` and the `test/`, `bench/`, CI estate. The +lineage boundaries are recorded in link:NOTICE[NOTICE] and the full visual +progression is +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Design-Progression[Deep-Dives — Design Progression]. + +Caveat:: +"Stacked honestly" is a claim about presentation as much as code: the +cutadapt/DADA2/vsearch semantics remain upstream's, and this fork tracks +application changes, not the science (link:ROADMAP.md[Roadmap], "Out of +scope"). + +=== "One run takes raw paired-end FASTQs to chimera-filtered, taxonomy-annotated ASV *and* OTU tables" + +[quote, README.adoc] +____ +One run takes raw paired-end FASTQs to chimera-filtered, taxonomy-annotated +ASV **and** OTU tables, with per-stage read accounting and QC (FastQC/MultiQC +plus DADA2 diagnostics) embedded in the UI. +____ + +How this is implemented:: +Typed stage results (`TrimmedReads`, `ASVResult`, `OTUResult`, +`TaxonomyHits`, `MergedTables`) in `src/core/types.jl`; stage bodies in +`src/pipeline/`; per-stage counts land in `pipeline_stats.csv`; QC endpoints +in `src/server/routes/` feed the MultiQC report and DADA2 figures to the UI. +Stages skip when outputs are fresh (mtime for files, content hash for +configuration) — `src/pipeline/pipeline freshness logic`. + +Caveat:: +FastQC and MultiQC were absent from CI for a while (issue #30) so prefilter +QC was never exercised there; fixed by issue #44 with sha256-pinned installs +(`config/defaults/tool_versions.yml`). Freshness skipping trusts mtimes for +files — editing an output behind the engine's back is not detected. + +=== "Analysis configuration is typed and validated (`AnalysisConfig`)" + +[quote, README.adoc] +____ +Analysis configuration is typed and validated (`AnalysisConfig`): negative +binomial GLM, CLR/ILR linear models, logistic models — each with explicit +constraints, convergence and boundary reporting, or a refusal. Benjamini– +Hochberg correction is mandatory wherever several tests are reported. +____ + +How this is implemented:: +`src/analysis/AnalysisConfig.jl` (immutable struct mirroring the user's +answers, Nickel schema `config/schemas/analysis_config.ncl`); fits in +`src/analysis/estimation.jl` called through `Execution.run_analysis` — R +`MASS::glm.nb`, `stats::lm`, `stats::glm(family=binomial)` with +identifiability, boundary-estimate and convergence checks that produce named +unsuccessful states instead of plausible parameters; BH via R `p.adjust`, +mandatory, with the DANGER banner if anyone tries to disable it. Conditions +published before implementation in +link:docs/statistics/method-conditions/parametric-fits.md[method conditions — parametric fits]. + +Caveat:: +[CAUTION] +==== +There has been **no independent statistical review** of this layer. Issue +#1 requires one before acceptance; it is outstanding. The fits are real and +tested against R references, but "computed correctly" is not "appropriate for +your data" — the method conditions say exactly this. +==== + +=== "Normalisation as honest bookkeeping … never silently swapped" + +[quote, README.adoc] +____ +Normalisation as honest bookkeeping: none, rarefaction, or exact +TSS/CSS/RSS size-factor **offsets** (depth modelled, response unchanged) — +never silently swapped for relative abundances. +____ + +How this is implemented:: +`src/analysis/scaling.jl` implements scaling factors (one positive number per +sample) and offsets (their logs, after a stated centring) per +link:docs/statistics/method-conditions/scaling-and-offsets.md[scaling-and-offsets conditions]; +unknown or aliased methods are refused, lower-case method names compare +correctly (issue #62), offsets are wired into the fits (issue #62/61), and +zero-depth samples are healed before any transform (issue #58). Regression +tests assert the substitutions cannot return. + +Caveat:: +Offsets are a depth-modelling device, not a compositional solution — the +conditions document says so explicitly. CSS without `metagenomeSeq` is a +repo-local implementation of the idea; treat equivalence to the original +package as unreviewed (covered by the same #1 caution). + +=== "an exact/approximate/rounded numeric policy where higher precision is never sold as exactness" + +[quote, README.adoc] +____ +an exact/approximate/rounded numeric policy where higher precision is never +sold as exactness +____ + +How this is implemented:: +`src/analysis/numeric_policy.jl` — an immutable `NumericPolicySpec` with +modes `:ordinary`, `:exact_counts`, `:high_precision`; only integer and +rational arithmetic is exact, `BigFloat` is approximate by definition, and a +float claiming exactness is refused at the boundary. `exact_summaries.jl` +carries counts and proportions at exact precision (integers beyond 2^53−1 +included) and labels any float-derived input as approximate. Contracts: +link:docs/statistics/numeric-contracts.md[numeric contracts]. + +Caveat:: +None currently known in the policy itself; the scope limit is real though — +exactness covers descriptive summaries only. Inference is not exact just +because its inputs are (see +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Exact-Arithmetic[Deep-Dives — Exact Arithmetic]). + +=== "maximum-likelihood fits (or explicit unsuccessful states) published under method conditions written *before* implementation" + +[quote, README.adoc] +____ +Placeholder statistics were removed and guarded +against by test; maximum-likelihood fits (or explicit unsuccessful states) +published under method conditions written *before* implementation +(link:docs/statistics[method catalogue]) +____ + +How this is implemented:: +The replaced placeholder derived p-values from `hash(taxon_id)` — a stub +returned as a result. Its history and the guard against its return are +documented in +link:docs/statistics/method-conditions/parametric-fits.md[parametric fits]; +`test/unit/test_estimation.jl` contains a source-level guard that such +placeholders do not come back, plus known answers written into fixture data +and an independent R reference comparison. The catalogue that gates all of +this is link:docs/statistics/method-catalogue-v1.md[method catalogue v1] +(owner-approved 2026-09-22). + +Caveat:: +[CAUTION] +==== +Catalogue item 3 (nonparametric tests) and item 4 (exact tests, issue #3) +are approved in scope but not implemented. Nothing here claims them. +==== + +=== "0 TypeScript errors under strict + `exactOptionalPropertyTypes`" + +[quote, README.adoc] +____ +0 TypeScript errors under strict + +`exactOptionalPropertyTypes` +____ + +How this is implemented:: +`frontend/tsconfig.json` (no-emit gate) with a build sidecar +(`tsconfig.build.json`); type estate mapped in +link:docs/types/architecture.md[types architecture]; `bun run typecheck` is +CI-gated. Third-party coverage is audited in +link:docs/type-system/category-d-e-closure.md[category D & E closure] — +including two `FIXME(types)` stubs that declare `unknown`-safe surfaces, never +`any`. + +Caveat:: +Two documented exceptions ride alongside: `skipLibCheck: true` (react-router +6.30.x ships 7 erroneous `.d.ts` entries; retried on react-router 7) and the +`useAnalysis.alphaFig` `unknown` awaiting a `PlotFigure` narrowing. Both are +tracked in `docs/compliance/fixme-index.md`. + +=== "pinned toolchains (`mise` + Guix lanes) with sha256-pinned pipeline tools" + +[quote, README.adoc] +____ +pinned toolchains (`mise` + Guix lanes) with +sha256-pinned pipeline tools +____ + +How this is implemented:: +`mise.toml` pins julia/bun/node/just to the exact CI versions (R is a +documented registry-absent exception, restored by `renv.lock`); `guix.scm` + +`channels.scm` provide the time-machine-pinned peer lane; `install.sh` +fetches cutadapt/FastQC/MultiQC/vsearch/cd-hit-est/swarm byte-exact against +the sha256 records in `config/defaults/tool_versions.yml`. Pin +single-sourcing is enforced by the `coupling-toolchain-pins` test. + +Caveat:: +The Guix lane carries functional equivalents, not binary identity with the +download lane (documented in the `guix.scm` header) — use `mise` for exact CI +parity. Swarm is download-lane-only under Guix. + +=== "Everything at a run's configuration is editable in the browser at every cascade level" + +[quote, README.adoc] +____ +Everything at a run's configuration is editable in the browser at every +cascade level; stale stages are flagged with the exact keys that changed. +____ + +How this is implemented:: +The cascade (instance → study → group → run; omitted keys inherit) resolves +in `src/core/config.jl` and is materialised per run to `run_config.yml` as +provenance; the REST config routes accept patches at any level and report +downstream overrides; staleness recomputes stage freshness and the tooltip +diffs the changed keys. + +Caveat:: +`run_config.yml` is the only place the fully merged truth appears — editing +YAML files directly while the server runs can surprise the freshness hash. +The editors validate whole documents before writing (primers, databases, +composition) and refuse dangling renames only by *reporting* them, not +blocking them. + +=== "Functional annotation with dual-classifier consensus (DADA2 bootstrap × vsearch identity)" + +[quote, README.adoc] +____ +Functional annotation with dual-classifier consensus (DADA2 bootstrap × +vsearch identity), contamination curation, manual BLAST override, and an +append-only FuncDB ledger that survives re-annotation. +____ + +How this is implemented:: +`src/annotation/` computes the consensus rank (finest rank where the +classifiers agree) and a composite confidence score; curation writes +(contamination flags, manual assignments) are stored separately from the +derived annotation and re-applied after regeneration; FuncDB entries append to +a ledger file. + +Caveat:: +The composite confidence is explicitly "mostly for the sake of curiosity" +(the README says so) — it is not a calibrated probability. Consensus is +string equality of labels, which is why both reference formats must come from +the same release. + +=== Quick start and the gate claim + +[quote, README.adoc] +____ +From a clean +checkout, `just ci` runs every gate that CI runs. +____ + +How this is implemented:: +`Justfile` recipes are thin wrappers over the canonical entry points +(`frontend/package.json` scripts, `scripts/check-*.sh`, the Julia test lanes); +`.github/workflows/ci.yml` invokes the same commands, so local and CI gates +are one set. + +Caveat:: +Fail-loud by design: Julia lanes error without Julia, `test-e2e` errors +without browsers. "Every gate" means the gated set — benchmarks are +informational and do not gate. + +== The mathematics, in the wiki + +The type-theoretic and statistical reasoning behind layer 3 is long-form by +nature (this is closer to a statistics package than a game). It lives in the +wiki, cross-linked both ways. Start from the +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives[deep dives hub]. + +[cols="1,2,2", options="header"] +|=== +| README claim area | Type-theory thread | Statistics thread (wiki page) + +| Typed, validated configuration; refusals as results +| Refinement-style validation of `AnalysisConfig`; third-party type closure (category D/E) +| ML estimation with identifiability, convergence, boundary states — link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Maximum-Likelihood[Deep-Dives — Maximum Likelihood] + +| Exact counts and rationals +| Mode-indexed numeric policy; exact/approximate/rounded as distinct types of claim +| Exact descriptive summaries; what exact inference cannot be — link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Exact-Arithmetic[Deep-Dives — Exact Arithmetic] + +| Offsets vs transforms +| `Offset` is not `Transform` — the type distinction is the safety property +| Size factors, TSS/CSS/RSS, compositional bias — link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Compositional-Statistics[Deep-Dives — Compositional Statistics] + +| Epistemic receipts; DANGER banner +| Σ-types, identity types, factive modalities and warrants without soundness (`src/core/epistemic.jl`, shadows of the Agda echo/epistemic/residual-evidence types) +| Unsuccessful states as first-class statistical output — link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Epistemic-Status[Deep-Dives — Epistemic Status] + +| Advanced functionality (planned) +| Symbolic engine (#2) — blocked on the numeric layer's review +| Exact tests, multinomial/DM, occupancy, constrained ordination, PhILR/SBP, zero handling — link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Advanced-Functionality[Deep-Dives — Advanced Functionality] +|=== + +Where the two threads meet — the central argument that a statistical result +is only as good as the *type of claim* its numbers can carry — is developed in +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Type-Theory-Meets-Statistics[Deep-Dives — Type theory meets statistics]. + +== Dogfooded across the account + +[cols="1,2,2", options="header"] +|=== +| Technology / pattern | Used here | Also used in + +| README + EXPLAINME authoring standard (the pair you are reading) +| Root `README.adoc` + `EXPLAINME.adoc`, claim→implementation map and all +| link:https://github.com/hyperpolymath/standards[standards] (the standard itself), link:https://github.com/hyperpolymath/rsr-template-repo[rsr-template-repo] + +| BerryWiki page format (hidden metadata, generated sidebar, plain-Markdown survival) +| The whole link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki[project wiki], sourced from `docs/wikis/` +| link:https://github.com/metadatastician/berrywiki[berrywiki]'s own wiki — the format's first dogfood + +| Justfile command surface (Makefiles banned estate-wide) +| `Justfile` (43 thin-wrapper recipes incl. pin codegen + drift gate) +| link:https://github.com/hyperpolymath/standards[standards], link:https://github.com/hyperpolymath/rsr-template-repo[rsr-template-repo] + +| Pinned dual-lane toolchain (mise exact pins + Guix time-machine) +| `mise.toml`, `guix.scm`, `channels.scm`, sha256-pinned `install.sh` +| rsr-template-repo doctrine; the estate's reproducibility practice + +| Publish method conditions *before* implementation, then hold code to them +| link:docs/statistics/method-conditions/[method conditions] + `test/unit/test_estimation.jl` +| The estate statistics track (link:https://github.com/hyperpolymath/statistikles[statistikles]) adopts the same gate +|=== + +== Known gaps + +[CAUTION] +==== +*Independent statistical review (issue #1):* the estimation and exact-summary +layers are implemented and tested, but issue #1's acceptance criteria include +an independent review that has not happened. Read every inference result as +"computed as documented", not "reviewed". +==== + +[CAUTION] +==== +*Exact statistical tests (issue #3) and the milestone-3 deferred suite +(issues #17–21: multinomial/DM, occupancy, constrained ordinations, PhILR/SBP, +advanced zero handling):* approved in scope, specified in +link:docs/issues/milestone3/[docs/issues/milestone3], **not implemented**. +The README marks them COMING and this file repeats that. +==== + +[CAUTION] +==== +*Symbolic engine (issue #2):* deliberately **BLOCKED** until the numeric +statistics layer passes real-data validation. Do not read the AnalysisConfig +formula strings as a symbolic algebra — they are parsed and fitted, not +manipulated. +==== + +[CAUTION] +==== +*Stipple/Vue migration and standalone releases:* agreed requirements, partial +delivery (the first read-only UI slice). The legacy React app is the default; +no standalone offline archive has been built yet. Source of truth: +link:docs/migration/STATUS.md[migration status]. +==== + +[CAUTION] +==== +*Deployment posture:* local single-user operation only. The current server is +not multiuser-ready and must not be exposed as if it were (a standing line in +link:docs/migration/STATUS.md[migration status]). +==== + +[CAUTION] +==== +*README screenshots:* the UI captures referenced by the origin README were +never committed to this repository; the design progression in `README.adoc` +is therefore diagrammatic. Screenshots will land on the wiki pages when +captured — the pages say which ones lack them. +==== + +Type-estate gaps (`skipLibCheck` exception, `alphaFig` `unknown`, the two +`FIXME(types)` stubs) are documented with their exit criteria in +`docs/compliance/fixme-index.md` and are not repeated as cautions here. + +== Evidence index + +[cols="2,3", options="header"] +|=== +| Path | Proves + +| `test/unit/test_estimation.jl` +| Known-answer fits, independent R reference comparison (coefficients and BH against `p.adjust`), negative controls for every refusal path, and the guard that placeholder statistics never return. + +| `test/unit/test_exact_summaries.jl` +| Hand-derived exact answers, an independent `fractions.Fraction` cross-check, negative controls (float claiming exactness, negative counts, budget overrun) and the "pipeline does not call this module by default" property. + +| `test/unit/` numeric boundary suites +| Boundary values (e.g. counts beyond 2^53−1) are measured, not assumed (issue #52's lesson). + +| `src/analysis/scaling.jl` + its tests +| TSS/CSS/RSS produce offsets; aliases and silent substitutions are refused (issues #16, #61–62). + +| `docs/statistics/method-conditions/*.md` +| The conditions documents that predate their implementations — the receipts that code is held to documents, not the reverse. + +| `frontend/tests/` + `frontend/tsconfig.json` +| The strict type gate (0 errors) and the behavioural suite behind `bun run check`. + +| `test/` coupling pins test +| Toolchain pins are single-sourced (`.bun-version` generated from `mise.toml`; overlaps drift-checked). + +| `bench/*/baseline.json` +| Recorded performance baselines the informational bench lane compares against. +|=== + +== Licence + +This document is licensed under CC BY-SA 4.0. Code receipts point at +AGPL-3.0-only / MPL-2.0 sources per link:NOTICE[NOTICE]. + +SPDX-License-Identifier: CC-BY-SA-4.0 + + +== Language and format rulings (2026-09-26) + +Nothing in this file is a receipt yet — these are rulings, recorded where a reader will +find them before they wonder why something is written the way it is. + +**Julia is the default language for everything we can put in it**: analysis, configuration, +gates, benchmarks, glue. Two standing exceptions: + +* **The dada2 pipeline itself.** The original R/dada2 pipeline is the one the field trusts. + It is not rewritten, wrapped into another language, or "modernised" for tidiness; new + analysis work happens in Julia around it. +* **The TypeScript view, for now.** The marid project will eventually move the view without a + detour through Genie. That is a plan, not a task, and no action is taken here for it; + recorded so the next reader does not mistake the TS surface for a decision never revisited. + +Banned languages in the estate (`standards :: 3-practice/LANGUAGE-POLICY.adoc`) are banned +here too: no new Python, no Makefiles. Nothing in this repository is generated or checked by a +banned language. + +**This repository is the estate's KYAML pilot** (owner ruling, 2026-09-26; +`docs/pilots/kyaml-pilot.md`). YAML here is deprecated but stays first-class until KYAML has +proven itself, and the switch goes both ways: + +[source,shell] +---- +just use-kyaml # write the tree as KYAML (the target dialect) +just use-yaml # write it back as block-style YAML +just check-kyaml # the gate: every non-exempt file is canonical KYAML +---- + +`git revert` of the pilot commit is the byte-exact way back. The migration itself is one +command, run where Julia is; the gate lands in the same commit as the conversion so it is +never red for a reason unrelated to the change under review. diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..fd918785 --- /dev/null +++ b/Justfile @@ -0,0 +1,636 @@ +# SPDX-License-Identifier: MPL-2.0 +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +# +# Justfile — MetaManifold-WebUI task runner. +# +# Design doctrine (rsr-template / standards estate): +# - Every recipe either works or FAILS LOUDLY. A check that cannot +# fail is not a check; a lane that cannot run in this environment +# exits non-zero with an actionable message, never a vacuous pass. +# - Recipes are thin wrappers over the canonical entry points: +# frontend/package.json scripts, scripts/check-*.sh, the estate +# launcher, and the Julia project. No duplicated logic lives here. +# - `just` with no arguments lists the available recipes. +# +# Quick start: `just setup` once, then `just ci` before every push. + +set shell := ["bash", "-uc"] +set positional-arguments + +# ----------------------------------------------------------------------- # +# Configuration +# ----------------------------------------------------------------------- # + +# Julia command. The repo-standard lane is mise (pins 1.12.5 exactly behind +# plain `julia`). juliaup users override: JULIA_CMD="julia +1.12.5". +# CI pins 1.12.5 (see .github/workflows/ci.yml). +export JULIA_CMD := env_var_or_default("JULIA_CMD", "julia") + +# Estate launcher (standards repo). Override with METAMANIFOLD_LAUNCHER. +LAUNCHER := env_var_or_default("METAMANIFOLD_LAUNCHER", justfile_directory() / "../standards/launcher/metamanifold-webui-launcher.sh") + +export METAMANIFOLD_REPO_DIR := justfile_directory() + +FRONTEND := justfile_directory() / "frontend" + +# Integration helper (fork↔upstream profiles, triage, component toggles). +INTEGRATE := justfile_directory() / "scripts/integrate.sh" + +# Re-anchor helper (turn the divergent histories into a granular, mergeable one). +REANCHOR := justfile_directory() / "scripts/reanchor.sh" + +# Free-RAM floor (KB) for the heavy Julia lanes: cold JIT-compilation of the +# server dependency closure needs several GB; below this the lane fails +# loudly instead of thrashing the box into an OOM kill. +JULIA_MIN_AVAIL_KB := "2500000" + +# ----------------------------------------------------------------------- # +# Default / orientation +# ----------------------------------------------------------------------- # + +# List all recipes (default action). +[private] +default: + @just --list --unsorted + +# Show help for one recipe, or the whole list. +help recipe="": + #!/usr/bin/env bash + if [[ -z "{{recipe}}" ]]; then + echo "MetaManifold-WebUI Justfile — entries: setup, dev, test*," + echo "bench*, hygiene (spdx/format/lint), ci, start/stop/status." + echo "Run 'just help ' for a recipe's doc comment & body." + echo + just --list --unsorted + else + just --show "{{recipe}}" + fi + +# Report tool versions (never fails; reports ABSENT for missing tools). +info: + #!/usr/bin/env bash + line() { printf '%-12s %s\n' "$1:" "$2"; } + line "just" "$(just --version)" + line "bun" "$(command -v bun >/dev/null 2>&1 && bun --version || echo ABSENT)" + line "node" "$(command -v node >/dev/null 2>&1 && node --version || echo ABSENT)" + line "julia" "$({ $JULIA_CMD --version; } 2>/dev/null || echo "ABSENT (or missing 1.12.5 channel)")" + line "R" "$(command -v R >/dev/null 2>&1 && R --version | head -1 || echo ABSENT)" + line "git" "$(git --version)" + line "head" "$(git rev-parse --short HEAD) on $(git branch --show-current)" + line "dirty" "$(git status --porcelain | wc -l) files" + +# Environment health report; exit 1 if an essential tool is missing. +doctor: + #!/usr/bin/env bash + rc=0 + # Hard requirement: absent => FAIL and non-zero exit. + need() { + if command -v "$1" >/dev/null 2>&1; then printf 'PASS %-12s %s\n' "$1" "$($1 --version 2>&1 | head -1)"; + else printf 'FAIL %-12s %s\n' "$1" "$2"; rc=1; fi + } + # Soft requirement: absent => WARN, exit stays 0 (a documented lane is just unavailable). + soft() { + if command -v "$1" >/dev/null 2>&1; then printf 'PASS %-12s %s\n' "$1" "$($1 --version 2>&1 | head -1)"; + else printf 'WARN %-12s %s\n' "$1" "$2"; fi + } + need bun "install: curl -fsSL https://bun.sh/install | bash (or: just setup-tools)" + need git "install via package manager" + soft bunx "ships with bun; if absent reinstall bun" + soft node "needed by vite's production build: just setup-tools" + soft mise "toolchain pins (mise.toml): curl https://mise.run | sh (Guix lane is the alternative)" + if $JULIA_CMD --version >/dev/null 2>&1; then + printf 'PASS %-12s %s\n' "julia" "$($JULIA_CMD --version)" + else + printf 'WARN %-12s %s\n' "julia" "Julia lanes unavailable — install via juliaup (install.sh) or just setup-tools" + fi + if command -v Rscript >/dev/null 2>&1; then + printf 'PASS %-12s %s\n' "R" "$(Rscript --version 2>&1 | head -1)" + [ -f renv/activate.R ] && echo "PASS renv renv/activate.R present (restore with: just renv-restore)" \ + || echo "WARN renv renv/activate.R missing — R lane cannot restore" + else + printf 'WARN %-12s %s\n' "R" "system R >= 4.5 not found (documented exception; not in mise registry)" + fi + # Merge drivers make lockfiles auto-resolve on the next fork↔upstream merge. + if git config --get merge.lockfile.driver >/dev/null 2>&1; then + echo "PASS merge-drv merge.lockfile wired (just merge-drivers)" + else + echo "WARN merge-drv not wired — run: just merge-drivers" + fi + [[ -x "{{LAUNCHER}}" ]] && echo "PASS launcher {{LAUNCHER}}" || { echo "WARN launcher not executable: {{LAUNCHER}}"; } + echo "-----" + echo "Integration profile: $({{INTEGRATE}} profile 2>/dev/null || echo base)" + exit $rc + +# Quick repo statistics. +stats: + #!/usr/bin/env bash + printf 'tracked files : %s\n' "$(git ls-files | wc -l)" + printf 'TypeScript : %s\n' "$(git ls-files '*.ts' '*.tsx' | wc -l)" + printf 'Julia : %s\n' "$(git ls-files '*.jl' | wc -l)" + printf 'unit tests : %s\n' "$(git ls-files 'frontend/tests/unit/*.test.ts' | wc -l)" + printf 'test asserts : %s\n' "$(grep -roh 'expect(\|assert' frontend/tests --include='*.ts' | wc -l)" + printf 'FIXME/TODO : %s\n' "$(git grep -oh 'FIXME(types)\|TODO(tests)' -- '*.ts' '*.tsx' 2>/dev/null | wc -l)" + +# ----------------------------------------------------------------------- # +# Setup +# ----------------------------------------------------------------------- # + +# One-time setup: install frontend dependencies. +setup: install + +# One-time setup on a BARE machine: provision the pinned toolchain from +# mise.toml (julia 1.12.5, bun 1.3.10, node 20.20.2, just 1.43.1), then +# install frontend dependencies. R is a documented exception: system R + +# renv.lock (R is not in the mise registry — verified 2026-09-18). +bootstrap: setup-tools install codegen-tools hooks merge-drivers + @echo "bootstrap: toolchain + deps + hooks + merge drivers + machine tool map ready — next: just ci" + +# Point git at .githooks so the commit-msg gate actually runs. core.hooksPath is +# per-clone local config -- it cannot be committed -- so documenting it in +# CONTRIBUTING.md left it unset in every clone that did not read that line. +# Wiring it here makes the enablement a consequence of bootstrapping rather than +# of remembering. Idempotent; safe to re-run. +hooks: + @git config core.hooksPath .githooks + @echo "hooks: core.hooksPath -> .githooks (commit-msg gate live)" + +# Wire a git merge driver that keeps lockfiles / generated files out of the +# fork↔upstream conflict set. merge.lockfile auto-resolves such a path to the +# branch being merged INTO (ours) and reminds you to regenerate — never a +# line-merged lockfile. Applied via .git/info/attributes (local, overrides the +# tree, never committed) so it is fully opt-in and cannot break a merge on a +# clone that has not run it. The committed .gitattributes already stops git from +# line-merging these (merge: unset); this just makes the choice automatic. +# Local git config, like core.hooksPath — hence a command, not a committed file. +# Idempotent; safe to re-run. +merge-drivers: + #!/usr/bin/env bash + git config merge.lockfile.name "keep target-branch lockfile, then regenerate (just heal)" + git config merge.lockfile.driver 'echo "merge-drivers: kept target-branch copy of %P — regenerate with: just heal" >&2' + attrs="{{justfile_directory()}}/.git/info/attributes" + mkdir -p "$(dirname "$attrs")"; touch "$attrs" + for p in Manifest.toml renv.lock frontend/bun.lock bun.lockb package-lock.json pnpm-lock.yaml renv/activate.R; do + grep -qxF "$p merge=lockfile" "$attrs" 2>/dev/null || printf '%s merge=lockfile\n' "$p" >> "$attrs" + done + echo "merge-drivers: merge.lockfile wired for lockfiles via .git/info/attributes (opt-in, local)" + +# Provision the pinned toolchain via mise (fail-loud with the installer +# one-liner when mise is absent; the Guix lane in guix.scm is the +# alternative, see docs/reproducibility.md). +setup-tools: + #!/usr/bin/env bash + if ! command -v mise >/dev/null 2>&1; then + echo "MISE UNAVAILABLE: install with: curl https://mise.run | sh" >&2 + echo "(or use the Guix lane: guix time-machine -C channels.scm -- shell -D -f guix.scm)" >&2 + exit 1 + fi + mise install + mise ls + +# Install frontend dependencies (bun). +install: + cd frontend && bun install + +# CODEGEN: regenerate generated pin artefacts from their source of truth. +# .bun-version is generated FROM mise.toml (CI consumes it via +# bun-version-file); config/defaults/tool_versions.yml is upstream-owned and +# only cross-CHECKED (by the coupling-toolchain-pins drift test), never +# written by this lane. Idempotent; safe to run any time. +sync-pins: + #!/usr/bin/env bash + bunver=$(grep -E '^bun\s*=' mise.toml | sed -E 's/^bun\s*=\s*"([^"]+)".*/\1/') + [[ -n "$bunver" ]] || { echo "sync-pins: no bun pin in mise.toml" >&2; exit 1; } + printf '%s\n' "$bunver" > .bun-version + echo "sync-pins: .bun-version <- mise.toml (bun $bunver)" + +# Pin-web drift check (coupling category): mise.toml == .bun-version == +# tool_versions.yml == CI matrix. Run standalone or via the bun suite. +drift: + cd frontend && bun test tests/unit/coupling-toolchain-pins.test.ts + +# CODEGEN: machine tool-path map. config/tools.yml is gitignored +# (machine-specific); this writes it so a fresh clone is runnable with zero +# manual config — PATH-found tools (inside guix/managed envs) become bare +# names, everything else falls back to install.sh's sha256-pinned download +# lane. Version authority stays with the pipeline preflight. +codegen-tools: + ./scripts/gen-tools-yml.sh + +# Complete first-run on a bare machine, clone-to-launchable in one recipe: +# toolchain + JS deps + machine tool map + hooks + merge drivers (bootstrap), +# Julia package instantiate, R package restore (renv), then install.sh's +# sha256-pinned external pipeline tools. After this: just start. +# (install-tools downloads several hundred MB by design — skip it when you only +# develop the frontend.) +setup-full: bootstrap julia-instantiate renv-restore install-tools + @echo "setup-full: complete — launch with: just start" + +# Pipeline tools via the byte-exact lane: install.sh fetches the archives +# recorded in config/defaults/tool_versions.yml (sha256-verified per tool). +install-tools: + bash install.sh + +# All codegen lanes (repo pins + machine tool map). +codegen: sync-pins codegen-tools + @echo "codegen: pins synced, machine tool map written" + +# Report outdated frontend packages (informational only). +outdated: + cd frontend && bun outdated || true + +# Instantiate the Julia project (downloads + precompiles; heavy first run). +julia-instantiate: + $JULIA_CMD --project=. -e 'using Pkg; Pkg.instantiate(); println("instantiate OK")' + +# Restore the R package set from renv.lock (byte-exact; the R lane). Requires +# system R >= 4.5 (documented exception — R is not in the mise registry). Fails +# loudly if R is absent rather than silently skipping the lane. +renv-restore: + #!/usr/bin/env bash + if ! command -v Rscript >/dev/null 2>&1; then + echo "R LANE UNAVAILABLE: system R (>= 4.5) not found." >&2 + echo "Install R for your OS, then re-run: just renv-restore" >&2 + exit 1 + fi + Rscript --no-init-file -e 'if (!requireNamespace("renv", quietly=TRUE)) { message("installing renv..."); install.packages("renv", repos="https://cloud.r-project.org") }; renv::restore(prompt=FALSE)' + +# ----------------------------------------------------------------------- # +# Hygiene gates (scripts/check-*.sh — the canonical bash lanes) +# ----------------------------------------------------------------------- # + +# SPDX licence-header gate. +spdx: + ./scripts/check-spdx.sh + +# Whitespace / final-newline format gate. +format: + ./scripts/check-format.sh + +# ESLint over the typed surface. +lint: + ./scripts/check-lint.sh + +# All hygiene gates together. +hygiene: spdx format lint check-kyaml + @echo "hygiene: OK" + +# Agda proof gate (docs/formal/verification-plan.md): guard, type-check, +# negative controls. Honours AGDA=... and AGDA_STDLIB_LIB=... overrides. +proofs: + ./scripts/check-proofs.sh + +# ----------------------------------------------------------------------- # +# YAML <-> KYAML (pilot: docs/pilots/kyaml-pilot.md) +# +# Authority: hyperpolymath/standards 3-practice/YAML-POLICY.adoc, rules Y-2 and +# Y-3, owner ruling 2026-09-26 making this repository the pilot. YAML is +# deprecated here but stays first-class until KYAML has proven itself: these two +# recipes are the switch, and `git revert` of the pilot commit is the byte-exact +# way back. +# ----------------------------------------------------------------------- # + +# Rewrite this repository's YAML as KYAML (the target authoring dialect). +use-kyaml: + {{JULIA_CMD}} --project=no --startup-file=no scripts/kyaml/KYAML.jl --to-kyaml + +# Rewrite it back as ordinary block-style YAML. +use-yaml: + {{JULIA_CMD}} --project=no --startup-file=no scripts/kyaml/KYAML.jl --to-yaml + +# Gate: every non-exempt YAML file is canonical KYAML (config/kyaml/drift.txt +# names the bot-owned exceptions, with reasons). +check-kyaml: + {{JULIA_CMD}} --project=no --startup-file=no scripts/kyaml/KYAML.jl --check + +# What would switching either way do? Nothing is written; decisions are printed. +kyaml-report: + {{JULIA_CMD}} --project=no --startup-file=no scripts/kyaml/KYAML.jl --to-kyaml --report + + +# Lint a commit message against the canonical format (default: HEAD). +commit-check msg="": + #!/usr/bin/env bash + f=$(mktemp) + if [[ -n "{{msg}}" ]]; then printf '%s\n' "{{msg}}" > "$f"; else git log -1 --pretty=%B > "$f"; fi + ./.githooks/commit-msg "$f"; rc=$? + rm -f "$f"; exit $rc + +# Count tracked annotations (informational; see docs/compliance/fixme-index.md). +todo: + @git grep -oh 'FIXME(types)\|TODO(tests)\|\[VERIFY\]' -- '*.ts' '*.tsx' '*.jl' 2>/dev/null | wc -l + +# ----------------------------------------------------------------------- # +# TypeScript: build, types, tests +# ----------------------------------------------------------------------- # + +# Static typecheck (also THE type-level test lane; .type-test.ts files). +typecheck: + cd frontend && bun run typecheck + +# Alias with the estate name: type-safe category = tsc over .type-test.ts. +test-types: typecheck + +# Full bun test suite (unit + integration). +test: + cd frontend && bun test + +# Unit category only. +test-unit: + cd frontend && bun test tests/unit + +# Integration (process-to-process boundary) tests. +test-integration: + cd frontend && bun test tests/integration + +# Test coverage report (informational — no gates, by policy). +coverage: + cd frontend && bun test --coverage + +# End-to-end lane (Playwright). Fails loudly if browsers are missing. +test-e2e: + #!/usr/bin/env bash + cd frontend + if ! bunx playwright --version >/dev/null 2>&1 || ! ls ~/.cache/ms-playwright 2>/dev/null | grep -q chromium; then + echo "E2E LANE UNAVAILABLE: Playwright chromium not installed." >&2 + echo "Install with: cd frontend && bunx playwright install --with-deps chromium" >&2 + exit 1 + fi + bunx playwright test + +# Julia test suite (test/runtests.jl). Fails loudly without Julia; fails +# honestly when the environment cannot fit a cold JIT compile. +julia-test: + #!/usr/bin/env bash + if ! timeout 15 $JULIA_CMD --version >/dev/null 2>&1; then + echo "JULIA LANE UNAVAILABLE: '$JULIA_CMD' not usable." >&2 + echo "Install via juliaup, then: just julia-instantiate" >&2 + exit 1 + fi + avail=$(awk '/MemAvailable/{print $2}' /proc/meminfo) + if [[ $avail -lt {{JULIA_MIN_AVAIL_KB}} ]]; then + echo "JULIA LANE ENVIRONMENT-BLOCKED: cold compile needs ~2.5 GB free RAM (avail: $((avail/1024)) MB)." >&2 + echo "Run on a CI/dev machine: $JULIA_CMD --project=. -e 'using Pkg; Pkg.test()'" >&2 + exit 1 + fi + $JULIA_CMD --project=. -e 'using Pkg; Pkg.test()' + +# ----------------------------------------------------------------------- # +# Benchmarks (informational; checksums are hard gates, timing is not) +# ----------------------------------------------------------------------- # + +# Frontend microbenchmarks vs committed baseline (checksum-verified). +bench: + cd frontend && bun run bench + +# Julia FFI-soak / MockRecovery benchmark lane (heavy; needs instantiate). +bench-julia data="": + #!/usr/bin/env bash + if ! timeout 15 $JULIA_CMD --version >/dev/null 2>&1; then + echo "JULIA BENCH UNAVAILABLE: '$JULIA_CMD' not usable." >&2 + echo "Install via juliaup, then: just julia-instantiate" >&2 + exit 1 + fi + avail=$(awk '/MemAvailable/{print $2}' /proc/meminfo) + if [[ $avail -lt {{JULIA_MIN_AVAIL_KB}} ]]; then + echo "JULIA BENCH ENVIRONMENT-BLOCKED: cold compile needs ~2.5 GB free RAM (avail: $((avail/1024)) MB)." >&2 + exit 1 + fi + $JULIA_CMD --project=. -t4 bench/layer1_mock_recovery/runner.jl {{data}} + +# ILR-basis scaling benchmark (issue #20); taxa="100,1000" for a quick run. +bench-ilr taxa="100,1000,10000": + ILR_BENCH_TAXA={{taxa}} $JULIA_CMD --project=. bench/ilr_bases/benchmark.jl + +# The CI CLR/ILR regression gate, locally: this checkout vs `base` (a git ref), +# interleaved base/head/base/head on this machine; fails on >10% (time or allocation). +bench-ilr-gate base="origin/main": + #!/usr/bin/env bash + set -euo pipefail + tmp=$(mktemp -d) + trap 'git worktree remove --force "$tmp/base" >/dev/null 2>&1 || true; rm -rf "$tmp"' EXIT + git worktree add --detach "$tmp/base" "{{base}}" + $JULIA_CMD --project="$tmp/base" -e 'using Pkg; Pkg.instantiate()' + for round in 1 2; do + $JULIA_CMD --project="$tmp/base" bench/ilr_bases/regression_gate.jl measure base "$tmp/base_$round.json" + $JULIA_CMD --project=. bench/ilr_bases/regression_gate.jl measure head "$tmp/head_$round.json" + done + $JULIA_CMD --project=. bench/ilr_bases/regression_gate.jl compare --base "$tmp"/base_*.json --head "$tmp"/head_*.json + +# ----------------------------------------------------------------------- # +# Composites +# ----------------------------------------------------------------------- # + +# The pre-push composite: types + tests + bench (mirrors package.json). +check: + cd frontend && bun run check + +# Every green gate, in CI order. This is the 'am I safe to push?' recipe. +ci: spdx format lint check-kyaml typecheck test bench + @echo "ci: ALL GATES GREEN" + +# Full local CI including the production bundle (sandbox-RAM hostile). +ci-full: spdx format lint typecheck test bench build + @echo "ci-full: ALL GATES GREEN (including build)" + +# Estate-quality composite: format + lint + tests. +quality: format lint test + @echo "quality: OK" + +# Every test category wired in this lane (E2E excluded: needs browsers). +test-all: test-types test-unit test-integration + @echo "test-all: OK (e2e is an opt-in lane: just test-e2e)" + +# ----------------------------------------------------------------------- # +# Build / serve (hostile in low-RAM sandboxes: dev server is fine, +# `vite build` may be OOM-killed under ~1.5 GB — that is the known +# environment limitation, not a code defect; CI runs it fine.) +# ----------------------------------------------------------------------- # + +# Vite dev server (foreground; http://localhost:5173, exposed on all +# interfaces so the sandboxed live preview can reach it). +dev: + cd frontend && bun run dev -- --host + +# Production bundle (tsc + vite build). RAM-hungry; see note above. +build: + cd frontend && bun run build + +# Preview the production bundle (requires 'just build' first; fails loudly +# rather than idling when no bundle exists). +preview: + #!/usr/bin/env bash + if [[ ! -d frontend/dist ]]; then + echo "PREVIEW UNAVAILABLE: frontend/dist does not exist — run 'just build' first." >&2 + exit 1 + fi + cd frontend && bun run preview + +# ----------------------------------------------------------------------- # +# Estate launcher (Julia server; requires instantiated Julia project) +# ----------------------------------------------------------------------- # + +# Start the MetaManifold server via the estate launcher. +start: + "{{LAUNCHER}}" --start + +# Stop it. +stop: + "{{LAUNCHER}}" --stop + +# Restart it. +restart: + "{{LAUNCHER}}" --stop; sleep 1; "{{LAUNCHER}}" --start + +# Server status. +status: + "{{LAUNCHER}}" --status + +# ----------------------------------------------------------------------- # +# Security / audit +# ----------------------------------------------------------------------- # + +# Dependency vulnerability audit (informational report, not a gate). +audit: + cd frontend && bun audit + +# ----------------------------------------------------------------------- # +# Cleanup +# ----------------------------------------------------------------------- # + +# Remove generated outputs (dist, coverage, playwright/test results). +clean: + rm -rf frontend/dist frontend/coverage frontend/playwright-report frontend/test-results + +# Remove generated outputs AND installed dependencies. +clean-all: clean + rm -rf frontend/node_modules + +# ----------------------------------------------------------------------- # +# Integration & environment healing (fork↔upstream) +# +# The fork and upstream share no git ancestor, so a naive merge conflicts on +# every shared path. These recipes expose config/integration.toml as a set of +# trust decisions the maintainer can make incrementally — from "behave exactly +# like upstream" (base) to "everything verified" (full) — without ever +# compromising a running system: the default profile changes no behaviour. +# Engine: scripts/integrate.sh. Guide: docs/integration/README.md. +# ----------------------------------------------------------------------- # + +# Repair the local environment to a known-good state: re-sync repo pins, re-wire +# hooks + merge drivers, regenerate the machine tool map, reinstall frontend +# deps, and (where present) re-instantiate Julia and restore the R lockfile. +# Resilient by design — each lane is attempted and a failure is reported, not +# fatal. Idempotent. The "fix my box" one-shot. +heal: + #!/usr/bin/env bash + set -uo pipefail + echo "heal: re-syncing repo pins..."; just sync-pins || echo "heal: sync-pins skipped" + echo "heal: re-wiring hooks + merge drivers..."; just hooks merge-drivers + echo "heal: regenerating machine tool map..."; just codegen-tools || echo "heal: codegen-tools skipped" + echo "heal: reinstalling frontend deps..."; just install || echo "heal: install skipped" + if timeout 15 $JULIA_CMD --version >/dev/null 2>&1; then + echo "heal: re-instantiating Julia..."; just julia-instantiate || echo "heal: julia-instantiate skipped" + else + echo "heal: Julia absent — provision the pinned toolchain with: just setup-tools" + fi + if command -v Rscript >/dev/null 2>&1; then + echo "heal: restoring R lockfile..."; just renv-restore || echo "heal: renv-restore skipped" + else + echo "heal: R absent — R lane left untouched (documented exception)" + fi + echo "heal: done. Verify with: just doctor" + +# Integration profiles & component toggles (thin wrappers over scripts/integrate.sh). +integrate: integrate-status + +integrate-status: + @{{INTEGRATE}} status + +integrate-profiles: + @{{INTEGRATE}} profiles + +# Switch the active profile: just integrate-profile . +integrate-profile profile="base": + @{{INTEGRATE}} profile "{{profile}}" + +# Recommended staging order (safest → riskiest). +integrate-plan: + @{{INTEGRATE}} plan + +# Classify in-progress merge conflicts (auto / component / human). +integrate-triage: + @{{INTEGRATE}} triage + +# Gates for the active selection; add strict="--strict" to require the tools be present. +integrate-verify strict="": + @{{INTEGRATE}} verify {{strict}} + +# Suspend a component: it stops being active (if runtime-gated, it will refuse). +suspend component: + @{{INTEGRATE}} disable "{{component}}" + +# Augment a component: it becomes active for this checkout. +augment component: + @{{INTEGRATE}} enable "{{component}}" + +# ----------------------------------------------------------------------- # +# Re-anchoring — collapse the one-shot merge into granular per-commit work +# +# The fork and upstream share the root commit but diverged early and developed +# in parallel, so a single merge shows ~159 conflicts at once. `reanchor` replays +# the fork's commits one-by-one onto upstream (git auto-applies the clean ones), +# turning that wall into a handful of small decisions. Measured: the whole fork +# re-anchors with 3 decisions and 0 residual conflicts, producing ~206 granular +# commits the maintainer can review/merge incrementally. Runs in an isolated +# worktree — it never touches your current branch. Engine: scripts/reanchor.sh. +# ----------------------------------------------------------------------- # + +# Read-only plan: classify each fork commit (auto-apply / overlap / delete-risk). +reanchor-plan: + @{{REANCHOR}} plan + +# Perform the re-anchor; leave a reviewable branch `reanchor/onto-upstream`. +reanchor: + @{{REANCHOR}} run --branch reanchor/onto-upstream --keep + +# Re-anchor but STOP at every conflict for hands-on resolution. +reanchor-manual: + @{{REANCHOR}} run --policy manual --keep + +# ----------------------------------------------------------------------- # +# Formal verification — Agda proofs for the validated statistics layer +# +# Issue #1 requires the numeric core to be validated, not merely tested. These +# recipes wrap `proofs/bootstrap.sh`, which pins Agda 2.7.0.1 and agda-stdlib +# 3.0 and refuses to pass when the prover is missing. `just proofs` is the whole +# gate: install if needed, audit for escape hatches, type-check every module. +# See proofs/PROOF-STATUS.md for what is proved and proofs/residue/ for what is +# explicitly not. +# ----------------------------------------------------------------------- # + +# Bootstrap the pinned Agda toolchain into proofs/.vendor (no checking). +proofs-bootstrap: + @proofs/bootstrap.sh --bootstrap + +# The whole proof gate: axiom audit + type-check of MetaManifold.All. +proofs: + @proofs/bootstrap.sh + +# Type-check only; fails loudly if the toolchain has not been bootstrapped. +proofs-check: + @proofs/bootstrap.sh --check + +# Audit for postulates, FFI, unsound flags, holes, and unreachable modules. +proofs-audit: + @proofs/tests/axiom-audit.sh + +# Prove the gate can fail: nine deliberate breakages, each must be rejected. +# A gate whose self-test is skipped is a gate nobody can trust, so `just ci` +# runs this too. +proofs-selftest: + @proofs/tests/gate-selftest.sh + +# Remove the vendored toolchain (proofs/.vendor) and Agda's interface cache. +proofs-clean: + @rm -rf proofs/.vendor proofs/agda/_build proofs/agda/MetaManifold/*.agdai + @echo "proofs: cleaned" diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + 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/LICENSES/AGPL-3.0-only.txt b/LICENSES/AGPL-3.0-only.txt new file mode 100644 index 00000000..be3f7b28 --- /dev/null +++ b/LICENSES/AGPL-3.0-only.txt @@ -0,0 +1,661 @@ + 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/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 00000000..e69451d2 --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,395 @@ +Creative Commons Attribution 4.0 International Public License + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/LICENSES/CC-BY-SA-4.0.txt b/LICENSES/CC-BY-SA-4.0.txt new file mode 100644 index 00000000..2d58298e --- /dev/null +++ b/LICENSES/CC-BY-SA-4.0.txt @@ -0,0 +1,428 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + including for purposes of Section 3(b); and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt new file mode 100644 index 00000000..d0a1fa14 --- /dev/null +++ b/LICENSES/MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + 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 in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/Manifest.toml b/Manifest.toml new file mode 100644 index 00000000..3146ad43 --- /dev/null +++ b/Manifest.toml @@ -0,0 +1,882 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.5" +manifest_format = "2.0" +project_hash = "15c3e14a1821e548099e9b92fd6c3f06c9160794" + +[[deps.AliasTables]] +deps = ["PtrArrays", "Random"] +git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" +uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" +version = "1.1.3" + +[[deps.ArgCheck]] +git-tree-sha1 = "f9e9a66c9b7be1ad7372bbd9b062d9230c30c5ce" +uuid = "dce04be8-c92d-5529-be00-80e4d2c0e197" +version = "2.5.0" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BenchmarkTools]] +deps = ["Compat", "JSON", "Logging", "PrecompileTools", "Printf", "Profile", "Statistics", "UUIDs"] +git-tree-sha1 = "9670d3febc2b6da60a0ae57846ba74670290653f" +uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +version = "1.8.0" + +[[deps.BitFlags]] +git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" +uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" +version = "0.1.9" + +[[deps.BitIntegers]] +deps = ["Random"] +git-tree-sha1 = "091d591a060e43df1dd35faab3ca284925c48e46" +uuid = "c3b6d118-76ef-56ca-8cc7-ebb389d030a1" +version = "0.3.7" + +[[deps.CSV]] +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"] +git-tree-sha1 = "8d8e0b0f350b8e1c91420b5e64e5de774c2f0f4d" +uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +version = "0.10.16" + +[[deps.CategoricalArrays]] +deps = ["Compat", "DataAPI", "Future", "Missings", "Printf", "Requires", "Statistics", "Unicode"] +git-tree-sha1 = "a6f644eb7bbc0171286f0f3ad1ffde8f04be7b83" +uuid = "324d7699-5711-5eae-9e2f-1d82baa6b597" +version = "1.1.0" + + [deps.CategoricalArrays.extensions] + CategoricalArraysArrowExt = "Arrow" + CategoricalArraysJSONExt = "JSON" + CategoricalArraysRecipesBaseExt = "RecipesBase" + CategoricalArraysSentinelArraysExt = "SentinelArrays" + CategoricalArraysStatsBaseExt = "StatsBase" + CategoricalArraysStructTypesExt = "StructTypes" + + [deps.CategoricalArrays.weakdeps] + Arrow = "69666777-d1a9-59fb-9406-91d4454c9d45" + JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" + RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" + SentinelArrays = "91c51154-3ec4-41a3-a24f-3f23e20d615c" + StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" + StructTypes = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" + +[[deps.CodecInflate64]] +deps = ["TranscodingStreams"] +git-tree-sha1 = "d981a6e8656b1e363a2731716f46851a2257deb7" +uuid = "6309b1aa-fc58-479c-8956-599a07234577" +version = "0.1.3" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.8" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.12.1" +weakdeps = ["StyledStrings"] + + [deps.ColorTypes.extensions] + StyledStringsExt = "StyledStrings" + +[[deps.Colors]] +deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] +git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74" +uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" +version = "0.13.1" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "9d8a54ce4b17aa5bdce0ea5c34bc5e7c340d16ad" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.18.1" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.3.0+1" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "21d088c496ea22914fe80906eb5bce65755e5ec8" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.5.1" + +[[deps.Conda]] +deps = ["Downloads", "JSON", "VersionParsing"] +git-tree-sha1 = "8f06b0cfa4c514c7b9546756dbae91fcfbc92dc9" +uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d" +version = "1.10.3" + +[[deps.Crayons]] +git-tree-sha1 = "249fe38abf76d48563e2f4556bebd215aa317e15" +uuid = "a8cc5b0e-0ffa-5ad4-8c14-923d3ee1735f" +version = "4.1.1" + +[[deps.DBInterface]] +git-tree-sha1 = "a444404b3f94deaa43ca2a58e18153a82695282b" +uuid = "a10d1c49-ce27-4219-8d33-6db1a4562965" +version = "2.6.1" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataFrames]] +deps = ["Compat", "DataAPI", "DataStructures", "Future", "InlineStrings", "InvertedIndices", "IteratorInterfaceExtensions", "LinearAlgebra", "Markdown", "Missings", "PooledArrays", "PrecompileTools", "PrettyTables", "Printf", "Random", "Reexport", "SentinelArrays", "SortingAlgorithms", "Statistics", "TableTraits", "Tables", "Unicode"] +git-tree-sha1 = "d8928e9169ff76c6281f39a659f9bca3a573f24c" +uuid = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +version = "1.8.1" + +[[deps.DataStructures]] +deps = ["OrderedCollections"] +git-tree-sha1 = "e86f4a2805f7f19bec5129bc9150c38208e5dc23" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.19.4" + +[[deps.DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.5" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.7.0" + +[[deps.DuckDB]] +deps = ["DBInterface", "Dates", "DuckDB_jll", "FixedPointDecimals", "Tables", "UUIDs", "WeakRefStrings"] +git-tree-sha1 = "b0e167d2ccfd32d59b7990ec1ccca3dcedc1f5da" +uuid = "d2f5444f-75bc-4fdf-ac35-56f514c445e1" +version = "1.5.1" + +[[deps.DuckDB_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0590d0e9f63459e6a25a8d91272bb35e135a0411" +uuid = "2cbbab25-fc8b-58cf-88d4-687a02676033" +version = "1.5.1+0" + +[[deps.ExceptionUnwrapping]] +deps = ["Test"] +git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" +uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +version = "0.1.11" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates"] +git-tree-sha1 = "3bab2c5aa25e7840a4b065805c0cdfc01f3068d2" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.24" +weakdeps = ["Mmap", "Test"] + + [deps.FilePathsBase.extensions] + FilePathsBaseMmapExt = "Mmap" + FilePathsBaseTestExt = "Test" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.FixedPointDecimals]] +deps = ["BitIntegers", "Parsers"] +git-tree-sha1 = "41d3a5de0eab320cc04833a373f0fcb3640073d5" +uuid = "fb4d412d-6eee-574d-9565-ede6634db7b0" +version = "0.6.5" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.5" + +[[deps.Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" +version = "1.11.0" + +[[deps.Glob]] +git-tree-sha1 = "83cb0092e2792b9e3a865b6655e88f5b862607e2" +uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" +version = "1.4.0" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "51059d23c8bb67911a2e6fd5130229113735fc7e" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "1.11.0" + +[[deps.HypergeometricFunctions]] +deps = ["LinearAlgebra", "OpenLibm_jll", "SpecialFunctions"] +git-tree-sha1 = "68c173f4f449de5b438ee67ed0c9c748dc31a2ec" +uuid = "34004b35-14d8-5ef3-9330-4cdb6864b03a" +version = "0.3.28" + +[[deps.InlineStrings]] +git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.4.5" + + [deps.InlineStrings.extensions] + ArrowTypesExt = "ArrowTypes" + ParsersExt = "Parsers" + + [deps.InlineStrings.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + Parsers = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" + +[[deps.InputBuffers]] +git-tree-sha1 = "e5392ea00942566b631e991dd896942189937b2f" +uuid = "0c81fc1b-5583-44fc-8770-48be1e1cca08" +version = "1.1.1" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.InvertedIndices]] +git-tree-sha1 = "6da3c4316095de0f5ee2ebd875df8721e7e0bdbe" +uuid = "41ab1584-1d38-5bbf-9106-f11c6c58b48f" +version = "1.3.1" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "b2d91fe939cae05960e760110b328288867b5758" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.6" + +[[deps.IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.1" + +[[deps.JSON]] +deps = ["Dates", "Logging", "Parsers", "PrecompileTools", "StructUtils", "UUIDs", "Unicode"] +git-tree-sha1 = "b3ad4a0255688dcb895a52fafbaae3023b588a90" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "1.4.0" + + [deps.JSON.extensions] + JSONArrowExt = ["ArrowTypes"] + + [deps.JSON.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + +[[deps.JSON3]] +deps = ["Dates", "Mmap", "Parsers", "PrecompileTools", "StructTypes", "UUIDs"] +git-tree-sha1 = "411eccfe8aba0814ffa0fdf4860913ed09c34975" +uuid = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +version = "1.14.3" + + [deps.JSON3.extensions] + JSON3ArrowExt = ["ArrowTypes"] + + [deps.JSON3.weakdeps] + ArrowTypes = "31f734f8-188a-4ce0-8406-c8a06bd891cd" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.LRUCache]] +git-tree-sha1 = "5519b95a490ff5fe629c4a7aa3b3dfc9160498b3" +uuid = "8ac3fa9e-de4c-5943-b1dc-09c6b5f20637" +version = "1.6.2" +weakdeps = ["Serialization"] + + [deps.LRUCache.extensions] + SerializationExt = ["Serialization"] + +[[deps.LaTeXStrings]] +git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.4.0" + +[[deps.LazyArtifacts]] +deps = ["Artifacts", "Pkg"] +uuid = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +version = "1.11.0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.15.0+0" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.12.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.29" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "f00544d95982ea270145636c181ceda21c4e2575" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.2.0" + +[[deps.MIMEs]] +git-tree-sha1 = "c64d943587f7187e751162b3b84445bbbd79f691" +uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65" +version = "1.1.0" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MD5]] +deps = ["Random", "SHA"] +git-tree-sha1 = "1576f756617d31eb397a4a517b68562fd28dc2b4" +uuid = "6ac74813-4b46-53a4-afec-0b5dc9d7885c" +version = "0.2.3" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] +git-tree-sha1 = "8785729fa736197687541f7053f6d8ab7fc44f92" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.10" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "ff69a2b1330bcb730b9ac1ab7dd680176f5896b8" +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.1010+0" + +[[deps.MetaManifold]] +deps = ["CSV", "DBInterface", "DataFrames", "Dates", "Downloads", "DuckDB", "HTTP", "JSON3", "Logging", "OrderedCollections", "Oxygen", "PackageCompiler", "RCall", "Random", "SHA", "Statistics", "UUIDs", "XLSX", "YAML"] +path = "." +uuid = "ea959a01-5458-4cf1-8f0d-4ed45446c396" +version = "0.0.0" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2025.11.4" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.29+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.7+0" + +[[deps.OpenSSL]] +deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"] +git-tree-sha1 = "1d1aaa7d449b58415f97d2839c318b70ffb525a0" +uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" +version = "1.6.1" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.4+0" + +[[deps.OpenSpecFun_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1346c9208249809840c91b26703912dff463d335" +uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e" +version = "0.5.6+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.8.1" + +[[deps.Oxygen]] +deps = ["DataStructures", "Dates", "HTTP", "JSON", "LRUCache", "MIMEs", "Reexport", "RelocatableFolders", "Sockets", "Statistics", "StructTypes"] +git-tree-sha1 = "294a35cc29803a3e1fdc3da2e0bf0d8826530575" +uuid = "df9a0d86-3283-4920-82dc-4555fc0d1d8b" +version = "1.10.1" + + [deps.Oxygen.extensions] + BonitoExt = "Bonito" + CairoMakieExt = "CairoMakie" + MustacheExt = "Mustache" + OteraEngineExt = "OteraEngine" + ProtoBufExt = "ProtoBuf" + TimeZonesExt = "TimeZones" + WGLMakieExt = ["WGLMakie", "Bonito"] + + [deps.Oxygen.weakdeps] + Bonito = "824d6782-a2ef-11e9-3a09-e5662e0c26f8" + CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0" + Mustache = "ffc61752-8dc7-55ee-8c37-f3e9cdd09e70" + OteraEngine = "b2d7f28f-acd6-4007-8b26-bc27716e5513" + ProtoBuf = "3349acd9-ac6a-5e09-bcdb-63829b23a429" + TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53" + WGLMakie = "276b4fcb-3e11-5398-bf8b-a0c2d153d008" + +[[deps.PackageCompiler]] +deps = ["Artifacts", "Glob", "LazyArtifacts", "Libdl", "Pkg", "Printf", "RelocatableFolders", "TOML", "UUIDs", "p7zip_jll"] +git-tree-sha1 = "7b2dbae3d0eda41dad445b5f36f40588c208a942" +uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" +version = "2.2.5" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.3" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.1" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "36d8b4b899628fb92c2749eb488d884a926614d3" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.3" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.3" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "8b770b60760d4451834fe79dd483e318eee709c4" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.2" + +[[deps.PrettyTables]] +deps = ["Crayons", "LaTeXStrings", "Markdown", "PrecompileTools", "Printf", "REPL", "Reexport", "StringManipulation", "Tables"] +git-tree-sha1 = "624de6279ab7d94fc9f672f0068107eb6619732c" +uuid = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d" +version = "3.3.2" + + [deps.PrettyTables.extensions] + PrettyTablesTypstryExt = "Typstry" + + [deps.PrettyTables.weakdeps] + Typstry = "f0ed7684-a786-439e-b1e3-3b82803b501e" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.Profile]] +deps = ["StyledStrings"] +uuid = "9abbd945-dff8-562f-b5e8-e1ebf5ef1b79" +version = "1.11.0" + +[[deps.PtrArrays]] +git-tree-sha1 = "4fbbafbc6251b883f4d2705356f3641f3652a7fe" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.4.0" + +[[deps.RCall]] +deps = ["CategoricalArrays", "Conda", "DataFrames", "DataStructures", "Dates", "Libdl", "Preferences", "REPL", "Random", "StatsModels", "WinReg"] +git-tree-sha1 = "0ea46f30de5b17d7bd8eaaadb431b0a9ae494a48" +uuid = "6f49c342-dc21-5d91-9882-a32aef131414" +version = "0.14.12" + + [deps.RCall.extensions] + RCallAxisArraysExt = ["AxisArrays"] + + [deps.RCall.weakdeps] + AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9" + +[[deps.REPL]] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.RelocatableFolders]] +deps = ["SHA", "Scratch"] +git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" +uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" +version = "1.0.1" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.1" + +[[deps.Rmath]] +deps = ["Random", "Rmath_jll"] +git-tree-sha1 = "5b3d50eb374cea306873b371d3f8d3915a018f0b" +uuid = "79098fc4-a85e-5d69-aa6a-4863f24498fa" +version = "0.9.0" + +[[deps.Rmath_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "58cdd8fb2201a6267e1db87ff148dd6c1dbd8ad8" +uuid = "f50d1b31-88e8-58de-be2c-1cc44531875f" +version = "0.5.1+0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.3.0" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "ebe7e59b37c400f694f52b58c93d26201387da70" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.4.9" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.ShiftedArrays]] +git-tree-sha1 = "503688b59397b3307443af35cd953a13e8005c16" +uuid = "1277b4bf-5013-50f5-be3d-901d8477a67a" +version = "2.0.0" + +[[deps.SimpleBufferStream]] +git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.2.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.2" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.12.0" + +[[deps.SpecialFunctions]] +deps = ["IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] +git-tree-sha1 = "2700b235561b0335d5bef7097a111dc513b8655e" +uuid = "276daf66-3868-5448-9aa4-cd146d93841b" +version = "2.7.2" + + [deps.SpecialFunctions.extensions] + SpecialFunctionsChainRulesCoreExt = "ChainRulesCore" + + [deps.SpecialFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "178ed29fd5b2a2cfc3bd31c13375ae925623ff36" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.8.0" + +[[deps.StatsBase]] +deps = ["AliasTables", "DataAPI", "DataStructures", "IrrationalConstants", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "aceda6f4e598d331548e04cc6b2124a6148138e3" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.10" + +[[deps.StatsFuns]] +deps = ["HypergeometricFunctions", "IrrationalConstants", "LogExpFunctions", "Reexport", "Rmath", "SpecialFunctions"] +git-tree-sha1 = "91f091a8716a6bb38417a6e6f274602a19aaa685" +uuid = "4c63d2b9-4356-54db-8cca-17b64c39e42c" +version = "1.5.2" + + [deps.StatsFuns.extensions] + StatsFunsChainRulesCoreExt = "ChainRulesCore" + StatsFunsInverseFunctionsExt = "InverseFunctions" + + [deps.StatsFuns.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.StatsModels]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "Printf", "REPL", "ShiftedArrays", "SparseArrays", "StatsAPI", "StatsBase", "StatsFuns", "Tables"] +git-tree-sha1 = "08786db4a1346d17d0a8d952d2e66fd00fa18192" +uuid = "3eaba693-59b7-5ba5-a881-562e759f1c8d" +version = "0.7.9" + +[[deps.StringEncodings]] +deps = ["Libiconv_jll"] +git-tree-sha1 = "b765e46ba27ecf6b44faf70df40c57aa3a547dcb" +uuid = "69024149-9ee7-55f6-a4c4-859efe599b68" +version = "0.3.7" + +[[deps.StringManipulation]] +deps = ["PrecompileTools"] +git-tree-sha1 = "d05693d339e37d6ab134c5ab53c29fce5ee5d7d5" +uuid = "892a3eda-7b42-436c-8928-eab12a02cf0e" +version = "0.4.4" + +[[deps.StructTypes]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "159331b30e94d7b11379037feeb9b690950cace8" +uuid = "856f2bd8-1eba-4b0a-8007-ebc267875bd4" +version = "1.11.0" + +[[deps.StructUtils]] +deps = ["Dates", "UUIDs"] +git-tree-sha1 = "fa95b3b097bcef5845c142ea2e085f1b2591e92c" +uuid = "ec057cc2-7a8d-4b58-b3b3-92acb9f63b42" +version = "2.7.1" + + [deps.StructUtils.extensions] + StructUtilsMeasurementsExt = ["Measurements"] + StructUtilsStaticArraysCoreExt = ["StaticArraysCore"] + StructUtilsTablesExt = ["Tables"] + + [deps.StructUtils.weakdeps] + Measurements = "eff96d63-e80a-5855-80a2-b1b0885c5ab7" + StaticArraysCore = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" + Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.8.3+2" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[deps.Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "OrderedCollections", "TableTraits"] +git-tree-sha1 = "f2c1efbc8f3a609aadf318094f8fc5204bdaf344" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.12.1" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.URIs]] +git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.6.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.VersionParsing]] +git-tree-sha1 = "58d6e80b4ee071f5efd07fda82cb9fbe17200868" +uuid = "81def892-9a0e-5fdd-b105-ffc91e053289" +version = "1.3.0" + +[[deps.WeakRefStrings]] +deps = ["DataAPI", "InlineStrings", "Parsers"] +git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" +uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" +version = "1.4.2" + +[[deps.WinReg]] +git-tree-sha1 = "cd910906b099402bcc50b3eafa9634244e5ec83b" +uuid = "1b915085-20d7-51cf-bf83-8f477d6f5128" +version = "1.0.0" + +[[deps.WorkerUtilities]] +git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7" +uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60" +version = "1.6.1" + +[[deps.XLSX]] +deps = ["Artifacts", "Colors", "Dates", "OrderedCollections", "PrecompileTools", "Printf", "Random", "Tables", "UUIDs", "Unicode", "XML", "ZipArchives"] +git-tree-sha1 = "9a69aaa75b0c29ec0bad391d960157d74c19e5b9" +uuid = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" +version = "0.11.0" +weakdeps = ["StyledStrings"] + + [deps.XLSX.extensions] + StyledStringsSstsExt = "StyledStrings" + +[[deps.XML]] +deps = ["Mmap", "OrderedCollections"] +git-tree-sha1 = "6791d41872eb990faba2b72bd5e8c3d9035a188a" +uuid = "72c71f33-b9b6-44de-8c94-c961784809e2" +version = "0.3.8" + +[[deps.YAML]] +deps = ["Base64", "Dates", "Printf", "StringEncodings"] +git-tree-sha1 = "a1c0c7585346251353cddede21f180b96388c403" +uuid = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" +version = "0.4.16" + +[[deps.ZipArchives]] +deps = ["ArgCheck", "CodecInflate64", "CodecZlib", "InputBuffers", "PrecompileTools", "TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "83f728ecb873c58b794964f8b4bed811814d4b0d" +uuid = "49080126-0e18-4c2a-b176-c102e4b3760c" +version = "2.6.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.15.0+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.64.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.7.0+0" diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..03a2e033 --- /dev/null +++ b/NOTICE @@ -0,0 +1,79 @@ + +# NOTICE — MetaManifold-WebUI + +## Provenance and licensing summary + +MetaManifold-WebUI as a work is **AGPL-3.0-only** (see `LICENSE`). It was +created by **Joshua Benjamin Jewell** (upstream: +`github.com/JoshuaJewell/MetaManifold-WebUI`), and this repository is the +**hyperpolymath fork** (`github.com/hyperpolymath/MetaManifold-WebUI`) +tracking that upstream. + +File-level licensing follows the estate Licence Policy +(`hyperpolymath/standards`, `LICENCE-POLICY.adoc`): + +| Authorship | Code / config / scripts | Prose documentation | +|---|---|---| +| Upstream files (first-committed by JoshuaJewell) | `AGPL-3.0-only` (inherited from `LICENSE`; upstream headers preserved verbatim) | `AGPL-3.0-only` | +| Fork-series files (first-committed by the hyperpolymath engineering series) | `MPL-2.0` (Rule 3a: owner-only components inside an AGPL work stay MPL-2.0) | `CC-BY-SA-4.0` | + +Each source file carries an `SPDX-License-Identifier` comment stating which +row applies. Canonical licence texts: `LICENSE` (AGPL-3.0), and +`LICENSES/MPL-2.0.txt`, `LICENSES/CC-BY-SA-4.0.txt`. The authorship +classification is mechanical (first-commit author in this repository's +history) and exists to keep headers — and the CI licence check — +unambiguous; it is not a claim of copyright ownership either way. + +## Third-party components + +MetaManifold orchestrates, but does not vendor, third-party command-line +tools. Each is fetched from its upstream source by `install.sh` and is +subject to its own licence; no third-party binaries are included in this +repository. + +| Tool | License | Source | +|---|---|---| +| [cutadapt](https://github.com/marcelm/cutadapt) | MIT | PyPI | +| [FastQC](https://github.com/s-andrews/FastQC) | GPL v3 | Babraham Bioinformatics | +| [MultiQC](https://github.com/MultiQC/MultiQC) | GPL v3 | PyPI | +| [DADA2](https://benjjneb.github.io/dada2/) | LGPL v3 | Bioconductor | +| [swarm](https://github.com/frederic-mahe/swarm) | GPL v3 | GitHub Releases | +| [vsearch](https://github.com/torognes/vsearch) | GPL v3 | GitHub Releases | +| [cd-hit](https://github.com/weizhongli/cdhit) | GPL v2+ | GitHub Releases / apt | + +R/vegan and the R runtime are system components under their own licences +(GPL family). Frontend npm dependencies are declared in +`frontend/package.json` / `frontend/bun.lock` under their own licences. + +## Acknowledgements and lineage + +This pipeline draws on the following prior work: + +- **Frédéric Mahé**: [Fred's metabarcoding pipeline](https://github.com/frederic-mahe/swarm/wiki/Fred's-metabarcoding-pipeline) + informed the overall workflow architecture, namely the sequencing of + primer trimming, `swarm.jl`, vsearch-based taxonomy assignment, and the + final table merge/filter stages. +- **Benjamin J. Callahan _et al._**: [DADA2 tutorial](https://benjjneb.github.io/dada2/tutorial.html), + used under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/), on + which `dada2.jl` and its modules are based. + +The following colleagues at the **Department of Parasitology, Charles +University** (Faculty of Science, BIOCEV, Vestec, Czech Republic) +contributed to this work: + +- **Mgr. Jiří Novák** (supervisor): scripts from which several modules and + configurations were adapted. +- **doc. Mgr. Vladimír Hampl**: provided laboratory access and resources. +- **Mgr. Paulína Pristašová**: <3. + +Copyright © 2026 Joshua Benjamin Jewell (origin design) and the +hyperpolymath fork authors. + +## Notices required by AGPL-3.0 + +This work is licensed under the GNU Affero General Public License, version +3 only (AGPL-3.0-only). As a network service, offering modified versions +requires offering the corresponding source, per AGPL §13. The canonical +source location is the repository above. diff --git a/Project.toml b/Project.toml new file mode 100644 index 00000000..1878f08e --- /dev/null +++ b/Project.toml @@ -0,0 +1,37 @@ +name = "MetaManifold" +uuid = "ea959a01-5458-4cf1-8f0d-4ed45446c396" + +[deps] +BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +DuckDB = "d2f5444f-75bc-4fdf-ac35-56f514c445e1" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1" +Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" +MD5 = "6ac74813-4b46-53a4-afec-0b5dc9d7885c" +OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +Oxygen = "df9a0d86-3283-4920-82dc-4555fc0d1d8b" +PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d" +RCall = "6f49c342-dc21-5d91-9882-a32aef131414" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SHA = "ea8e919c-243c-51af-8825-aaa63cd721ce" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +XLSX = "fdbf4ff8-1666-58a4-91e7-1b58723a45e0" +YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6" + +[compat] +BenchmarkTools = "1.8.0" +CSV = "0.10" +DataFrames = "1.8" +MD5 = "0.2.3" +HTTP = "1" +JSON3 = "1" +PackageCompiler = "2.2.5" +RCall = "0.14" +YAML = "0.4" +julia = "1" diff --git a/R/_renv_dependencies.R b/R/_renv_dependencies.R new file mode 100644 index 00000000..f16c04a2 --- /dev/null +++ b/R/_renv_dependencies.R @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Renv dependency discovery file. +# +# renv finds dependencies by parsing every .R file and collecting the +# library()/require() calls it sees, so these four names must appear literally +# in a file that is never actually executed for its effect. +# +# The conventional idiom for that is `if (FALSE) { ... }`, but a condition that +# is a constant is dead code to every static analyser (SonarCloud rdre:S1145), +# and silencing the rule would be hiding a true observation. A function that is +# defined and never called says the same thing without the dead branch: renv +# parses the whole file either way. +# +# Verified 2026-09-21 with renv::dependencies() against both forms: each returns +# exactly dada2, dplyr, tibble, vegan. +.renv_dependencies <- function() { + library(dada2) + library(vegan) + library(dplyr) + library(tibble) +} diff --git a/README.adoc b/README.adoc new file mode 100644 index 00000000..a8fb05bc --- /dev/null +++ b/README.adoc @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: CC-BY-SA-4.0 +// SPDX-FileCopyrightText: 2026 Joshua Benjamin Jewell; 2026 Jonathan D.A. Jewell (hyperpolymath) += MetaManifold +:toc: preamble +:toc-title: Contents +:icons: font +:doctype: article + +// ── Licensing ─────────────────────────────────────────────────────────────────────── +image:https://img.shields.io/badge/Code-AGPL--3.0-blue.svg?logo=gnu[Code licence: AGPL-3.0,link="LICENSE"] +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/"] +// ── Toolchain ─────────────────────────────────────────────────────────────────────── +image:https://img.shields.io/badge/Julia-1.12.5-9558B2?logo=julia[Julia 1.12.5,link="https://julialang.org"] +image:https://img.shields.io/badge/R-%E2%89%A54.0-276DC3?logo=r[R at least 4.0,link="https://www.r-project.org"] +image:https://img.shields.io/badge/Bun-1.3.10-F9F1E1?logo=bun[Bun 1.3.10,link="https://bun.sh"] +// ── Continuous integration ────────────────────────────────────────────────────────── +image:https://github.com/hyperpolymath/MetaManifold-WebUI/actions/workflows/ci.yml/badge.svg[CI,link="https://github.com/hyperpolymath/MetaManifold-WebUI/actions/workflows/ci.yml"] + +Amplicon metabarcoding from raw paired-end Illumina reads to filtered, +taxonomy-annotated ASV/OTU tables — orchestrated by one Julia engine, +driven from one browser workbench, and increasingly explicit about what its +statistics can and cannot claim. + +For researchers who need reproducible microbiome analysis (clinical +parasitology, environmental eDNA, teaching datasets) and for the +professionals who run those analyses as a service. + +[NOTE] +.OpenSSF badges are planned, not claimed +==== +The estate's README standard asks for OpenSSF Best Practices and Scorecard +badges on public repositories. MetaManifold is not enrolled yet; the badges +will appear here on the day enrolment does, not before. +==== + +== The design in three layers + +MetaManifold is three designs stacked honestly on top of each other. Each +layer is still visible in the code; none of them pretends to be the whole +story. (The full lineage, with file-level receipts, is +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Deep-Dives--Design-Progression[in the wiki].) + +=== Layer 1 — the base design: R and Python around DADA2 + +The scientific core is the established open-source pipeline as it is used +*raw*, without us: DADA2 in R for exact sequence variants, and the +swarm/vsearch shell tradition (Fred's metabarcoding pipeline) for OTUs. +Before MetaManifold, a lab ran these as separate scripts and stitched the +tables by hand. + +[source] +---- + R scripts (DADA2 tutorial lineage) Shell / Python (swarm pipeline lineage) + ────────────────────────────────── ──────────────────────────────────────── + filterAndTrim() cutadapt — primer trimming + learnErrors() vsearch --fastq_mergepairs — merge + dada() ── ASVs ── swarm -d 1 ── OTU clusters + mergePairs() vsearch --uchime_denovo — chimera filter + makeSequenceTable() vsearch --usearch_global — taxonomy + removeBimeraDenovo() + assignTaxonomy() + │ │ + └──────── counts + taxonomy ─────────────┘ + spreadsheets, ad-hoc glue, no shared provenance +---- + +*In place since the beginning:* DADA2 denoising and taxonomy (R, pinned by +`renv.lock`), swarm clustering, vsearch alignment. *Never ours to change:* the +science of these tools. This fork tracks application changes; it does not fork +the science. + +=== Layer 2 — the MetaManifold augmentation (JoshuaJewell's design) + +The origin design wraps those raw lanes in one configurable Julia +orchestrator and one web UI. Both lanes run side by side per run; their +tables merge into a per-run DuckDB store the browser can query. + +[source] +---- + data/{study}/[{group}/]{run}/*.fastq.gz + │ + cutadapt · primer trimming + │ + ├─── DADA2 ASV lane ────────────────┬─── SWARM OTU lane ──────────────┐ + │ filter & trim → learn errors → │ merge pairs → dereplicate → │ + │ denoise → merge pairs → │ cluster (swarm) → │ + │ length filter → chimera cull → │ chimera filter → │ + │ taxonomy assign* → cd-hit-est* │ vsearch taxonomy │ + │ → vsearch* │ │ + └───────────────┬───────────────────┴───────────────┬────────────────┘ + │ │ + merge_taxa; join tables; apply filters │ + │ │ + └────────► DuckDB results store ◄───┘ + │ + Oxygen.jl REST + SSE ◄───────┴───────► React SPA workbench + (config cascade, runs & jobs, QC, results explorer, + annotation & curation, composition views) +---- + +*In place (layer 2):* the parallel ASV/OTU pipelines; the configuration +cascade (instance → study → group → run, merged to `run_config.yml` as +provenance); primer/database/composition editors in the UI; the results +explorer with saved filter presets and Excel export; the functional +annotation layer with dual-classifier consensus, contamination tagging and a +FuncDB ledger; live job progress over server-sent events; remote (SSH) +offload of the memory-heavy taxonomy step. *Coming at this layer:* the +Julia-authored Stipple UI migration and standalone offline release archives +(link:docs/migration/STATUS.md[migration status]). + +=== Layer 3 — the steps this fork adds (hyperpolymath, 2026-09) + +The fork's contribution is a discipline of honesty and typing around the +science layer 2 runs: statistics that compute real estimates or refuse; +numbers that never claim more precision than they carry; a typed frontend +estate; and an engineering harness that keeps every claim checkable. + +[source] +---- + JoshuaJewell's MetaManifold (layer 2) + │ + ├── typed stages & a strict TypeScript estate ──── src/core/types.jl, + │ frontend/src/types/ + ├── tests, benchmarks, CI gates ────────────────── test/, frontend/tests/, + │ bench/, .github/workflows/ + ├── AnalysisConfig v1 ──────────────────────────── src/analysis/AnalysisConfig.jl + │ (NB-GLM, CLR/ILR + LM, logistic; BH mandatory; DANGER banner) + ├── real estimation or explicit refusal ────────── src/analysis/estimation.jl + │ (MASS::glm.nb / lm / glm fits with identifiability, + │ convergence and boundary reporting) + ├── exact counts & rationals ───────────────────── src/analysis/numeric_policy.jl, + │ src/analysis/exact_summaries.jl + ├── exact TSS/CSS/RSS offsets, no substitutions ── src/analysis/scaling.jl + └── epistemic receipts & refusals as results ───── src/core/epistemic.jl + ▼ + where this is heading (tracked, not promised as present) + ├── exact tests (Fisher, exact NB, permutation) ──── issue #3 [COMING] + ├── compositional methods (ANCOM-BC, ALDEx2, …) ──── issue #5 [COMING] + ├── occupancy · ordination · PhILR/SBP · zeros ───── issues #17–21 [COMING] + ├── CladeCumulus & Full Evidence Mode ────────────── issues #6–7 [COMING, scaffolded] + ├── Zenodo DOI minting ───────────────────────────── issue #8 [COMING] + └── symbolic formula engine ──────────────────────── issue #2 [BLOCKED + until the numeric layer has passed independent review] +---- + +*In place (layer 3):* placeholder statistics were removed and guarded +against by test; maximum-likelihood fits (or explicit unsuccessful states) +published under method conditions written *before* implementation +(link:docs/statistics[method catalogue]); an exact/approximate/rounded +numeric policy where higher precision is never sold as exactness; exact +TSS/CSS/RSS offsets with refusals instead of silent substitutions; zero-depth +samples healed before transforms; 0 TypeScript errors under strict + +`exactOptionalPropertyTypes`; pinned toolchains (`mise` + Guix lanes) with +sha256-pinned pipeline tools. *Coming and blocked items are listed as exactly +that*, on the +link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Status-and-Roadmap[Status and Roadmap] +page of the wiki. + +The layers nest — that is the whole picture: + +[source] +---- +┌─ Layer 3 · verified statistics, typed estate, engineering gates ─────────────────┐ +│ ┌─ Layer 2 · Julia orchestrator + WebUI: both lanes, DuckDB, config cascade ──┐ │ +│ │ ┌─ Layer 1 · base design: raw DADA2 (R) + swarm/vsearch (shell) pipelines ─┐│ │ +│ │ │ raw FASTQs → denoise or cluster → taxonomy → count tables ││ │ +│ │ └──────────────────────────────────────────────────────────────────────────┘│ │ +│ └─────────────────────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +---- + +== What it does today + +* One run takes raw paired-end FASTQs to chimera-filtered, taxonomy-annotated + ASV **and** OTU tables, with per-stage read accounting and QC (FastQC/MultiQC + plus DADA2 diagnostics) embedded in the UI. +* Analysis on request, per run and across runs: alpha diversity with + significance testing that reports its status rather than degrading quietly, + taxonomic composition bars, organism composition categories (protozoa, + helminths, fungi, host, …), taxon overlap (Euler/UpSet), NMDS and PERMANOVA + via the locked R runtime, pipeline-stage read summaries. +* Analysis configuration is typed and validated (`AnalysisConfig`): negative + binomial GLM, CLR/ILR linear models, logistic models — each with explicit + constraints, convergence and boundary reporting, or a refusal. Benjamini– + Hochberg correction is mandatory wherever several tests are reported. +* Normalisation as honest bookkeeping: none, rarefaction, or exact + TSS/CSS/RSS size-factor **offsets** (depth modelled, response unchanged) — + never silently swapped for relative abundances. +* Functional annotation with dual-classifier consensus (DADA2 bootstrap × + vsearch identity), contamination curation, manual BLAST override, and an + append-only FuncDB ledger that survives re-annotation. +* Everything at a run's configuration is editable in the browser at every + cascade level; stale stages are flagged with the exact keys that changed. + +[NOTE] +.Planned — tracked, not claimed +==== +Exact statistical tests (issue #3) · compositional methods such as ANCOM-BC +and ALDEx2 (issue #5) · occupancy and zero-inflated models, constrained +ordinations, PhILR/SBP ILR bases, advanced zero handling (issues #17–21) · +CladeCumulus phylogenetic explorer and Full Evidence Mode (issues #6–7) · +Zenodo DOI minting (issue #8) · a symbolic formula engine (issue #2, formally +**blocked** pending independent statistical validation of the numeric layer). +The wiki's link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki/Status-and-Roadmap[Status and Roadmap] +page carries the complete board. +==== + +== Quick start + +[source,bash] +---- +git clone https://github.com/hyperpolymath/MetaManifold-WebUI.git +cd MetaManifold-WebUI +bash install.sh # Julia + R deps, sha256-pinned pipeline tools +bash start.sh # builds the frontend on first run, serves on :8080 +---- + +Put paired-end `.fastq.gz` files under `data/MyProject/run_A/` (Illumina +naming), open `http://localhost:8080`, create a study and launch a run. +Bundled sample data: `data/MiSeq_SOP/` (the mothur MiSeq SOP set). + +Toolchain of record: `mise.toml` (Julia 1.12.5, Bun 1.3.10, Node 20.20.2, +just 1.43.1) with a Guix peer lane (`guix.scm` + `channels.scm`); R is a +documented system exception restored byte-pinned by `renv.lock`. From a clean +checkout, `just ci` runs every gate that CI runs. + +Wondering how this works? See link:EXPLAINME.adoc[]. + +== Repository layout + +[cols="1,3", options="header"] +|=== +| Path | What lives there + +| `src/pipeline/` | Stage implementations: cutadapt, DADA2 (R bridge), swarm, vsearch, cd-hit-est, merge_taxa — typed results, freshness-based skipping. +| `src/analysis/` | Diversity, estimation, exact summaries, numeric policy, scaling/offsets, AnalysisConfig, chart builders. +| `src/core/` | Config cascade, DuckDB store, provenance, epistemic statuses, R runtime bridge, validation. +| `src/server/` | Oxygen.jl REST API + SSE job stream (`routes/`). +| `frontend/` | TypeScript + React + Vite SPA (strict-typed estate; tests and benches alongside). +| `ui/` | The opt-in Stipple/Vue UI migration slice (legacy React app remains default). +| `config/` | Defaults, schemas, primers, databases, composition filters, CI fixtures. Per-study/run overrides live under `data/`, `projects/`. +| `docs/` | Statistics method catalogue and conditions, type-system notes, compliance and migration records. +| `docs/wikis/` | Source of the GitHub wiki (BerryWiki page format); synced to `MetaManifold-WebUI.wiki.git`. +| `test/`, `bench/`, `frontend/tests/` | Julia tests, benchmark lanes with baselines, frontend unit/integration/e2e lanes. +| `Justfile`, `install.sh`, `start.sh` | Every task is a `just` recipe; the two shell entry points above. +|=== + +== Documentation + +* link:EXPLAINME.adoc[EXPLAINME] — receipts: every claim here mapped to code, with caveats. +* link:https://github.com/hyperpolymath/MetaManifold-WebUI/wiki[The project wiki] — the long-form documentation, in three audiences: *users* (with separate tracks for academics and lab professionals), *platform maintainers* (operator and repo-steward tracks), and *developers*, plus the deep dives this README deliberately does not attempt. +* link:docs/statistics/method-catalogue-v1.md[method catalogue] and link:docs/statistics/method-conditions/[method conditions] — what each statistical method accepts, computes, and refuses. +* link:docs/reproducibility.md[Reproducibility] — toolchain source of truth; link:docs/compliance/[compliance records] — RSR and standards alignment. +* link:CONTRIBUTING.md[Contributing] · link:SECURITY.md[Security] · link:ROADMAP.md[Roadmap] · link:CHANGELOG.md[Changelog] · link:CITATION.cff[Citing this work] + +== Licence + +Source code is licensed under the GNU Affero General Public License v3.0 — +see link:LICENSE[license]; upstream-authored files carry `AGPL-3.0-only` +headers and fork-authored files `MPL-2.0`/`CC-BY-SA-4.0` per the estate +licence policy, all summarised in link:NOTICE[notice]. +This documentation (README.adoc) is licensed under CC BY-SA 4.0. +Pipeline tools are orchestrated, not vendored; each retains its own licence — +see the third-party note in link:NOTICE[notice]. + +Copyright © 2026 Joshua Benjamin Jewell (origin design) and the +hyperpolymath fork authors. Full attribution, including the DADA2 tutorial +(CC BY 4.0) and Fred's metabarcoding pipeline lineages, is in link:NOTICE[notice]. diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000..4f503abc --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,67 @@ + +# Roadmap + +Status as of 2026-09-17. Reflects **actual** repository state — completed +sections are claims you can verify in `docs/compliance/`, not aspirations. + +## Done (engineering series, 2026-09) + +- [x] Advanced zero handling and the glmGamPoi dispersion port (issue #21): multiplicative and + Bayesian multiplicative replacement with their refusals, provenance and Agda-checked + laws; the pure-Julia dispersion pipeline with the un-ported spline refused by name; + config/Nickel/JSON/frontend surface; tests, fixtures and the 100/1000/10000-taxa bench. + Documentation: `docs/statistics/zero-handling.md`, + `docs/statistics/method-conditions/dispersion-glmGamPoi.md`. +- [x] KYAML pilot (owner ruling 2026-09-26): `just use-kyaml` / `just use-yaml` / `just + check-kyaml`, the drift list, `docs/pilots/kyaml-pilot.md`, and the standalone + deployment of the toolchain and proof lane (`stapeln.toml`, `Containerfile`). +- [x] Bun toolchain migration (1.3.10 pinned; lockfile text format) +- [x] Strict TypeScript foundation (165 → 0 errors, zero suppressions) +- [x] Test & benchmark infrastructure (proven-tests-and-benchmarks patterns) +- [x] Domain type system (`src/types/*`; 20+ type-level assertions) +- [x] Type-driven behavioural tests (67 pass / 5 e2e-lane todos) +- [x] RSR template & standards alignment (this tree) + +## Near term (decision points, not started) + +- **Finish the KYAML migration.** The tool, gate and ruling are in; `just use-kyaml` has not + been run, because it must be run where Julia is (the gate compares bytes against the Julia + emitter, and the authoring sandbox had no Julia). One command plus the CI step that holds + the result — see `docs/pilots/kyaml-pilot.md` §8. +- **Enable the proofs lane in CI.** `proofs` job is written and gated on + `vars.STAPELN_AGDA_IMAGE`; the image (`stapeln.toml`, `Containerfile`) has not been built + yet. Build it, then set the variable. + +- **DOM test lane.** Plotly-chain modules (`PlotlyChart`, `ChartCustomiser`, + `ChartEditorInner`, `AnnotationPanel`, `RunView`) are import-blocked under + the DOM-less bun lane. Decision queued for the e2e lane: playwright + (lane exists, opt-in) vs a DOM harness. Tracked as `TODO(tests/e2e-lane)` + in `frontend/tests/unit/plotly-chain.todo.test.ts`. +- **Coverage gate.** Metrics are reported in CI artefacts (lcov) but not + gated — deliberate; a gate lands with CI maturity, against a recorded + baseline, not an arbitrary number. Baseline: `docs/testing/coverage.md`. +- **`alphaFig` narrowing.** `useAnalysis.alphaFig` is `unknown`; narrowing + to `PlotFigure` threads the chart response type through analysis state. + Listed in `docs/types/architecture.md § Known gaps`. +- **skipLibCheck exception.** Documented exception for react-router 6.30.x + (7 upstream `.d.ts` errors). Retry on `react-router@7` upgrade. + Tracked in `docs/type-system/` and `docs/compliance/fixme-index.md`. + +## Medium term + +- **Upstream PR cadence.** This fork's engineering series is delivered as + patch series; the upstream-review/PR flow is an owner decision (base must + always be `hyperpolymath`, never direct push to `joshuajewell`). +- **e2e smoke set.** `frontend/tests/e2e/app.e2e.ts` exists; grow only + after the DOM-lane decision above lands. +- **Benchmark stability window.** Informational bench comparison already + prints deltas vs `bench/baseline.json`; promotion to a regression + *alert* (still non-gating) waits for baseline data across several weeks. + +## Out of scope here (by portfolio rules) + +- Storage/journal/provenance internals — owned by Lithoglyph/GNPL repos. +- Bioinformatics pipeline semantics — upstream `joshuajewell` domain; this + fork tracks application changes, it does not fork the science. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..1db25e29 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,83 @@ + +# Security Policy + +## Supported versions + +| Version | Supported | +|---|---| +| `main` branch (this fork) | ✅ | +| Fork release tags | ✅ latest only | +| Any older revision | ❌ | + +The fork tracks upstream `main`; security fixes land on `main` first and +are not backported to older revisions. + +## Reporting a vulnerability + +**Preferred:** use GitHub Security Advisories on this repository: + +1. Navigate to *Security → Advisories → Report a vulnerability* on + [hyperpolymath/MetaManifold-WebUI](https://github.com/hyperpolymath/MetaManifold-WebUI/security/advisories/new). +2. Describe the issue privately with reproduction details. +3. You will be credited when the advisory is published, unless you prefer + anonymity. + +**If the issue is in upstream code** (anything also present in +`JoshuaJewell/MetaManifold-WebUI`), please report it there as well — the +fork will coordinate any fix with upstream rather than diverge silently. + +**Alternative:** open a regular issue marked in the title as +`[SECURITY-SENSITIVE — move to advisory]`, with no exploit details; a +maintainer will migrate it to a private advisory. + +> ⚠️ Do not report exploitable vulnerabilities in public issues, pull +> requests, or discussions. + +## What to include + +- Observed behaviour with the exact command/request and its output +- Affected component (route file, frontend module, CI workflow…) +- Affected commit(s) (`git rev-parse --short HEAD`) +- Impact assessment (what an attacker could achieve) +- Suggested remediation, if you have one + +## Response targets + +| Stage | Target | +|---|---| +| Acknowledgement | 7 days | +| Triage and severity assessment | 14 days | +| Fix or documented mitigation | Best effort on `main` | + +This is a research-software project without a security team; targets are +honest effort estimates, not SLAs. + +## Scope + +**In scope:** this repository's Julia server, React frontend, pipeline +orchestration scripts, CI workflows, and container/deployment files. + +**Out of scope:** third-party tools we orchestrate (cutadapt, DADA2, +SWARM, vsearch, cd-hit-est, R/vegan — report to those projects), attacks +requiring local shell access, and denial-of-service against deployments +you do not own. + +## Safe harbour + +Good-faith security research against your own deployment of this software +is expressly authorised. Do not test against deployments you do not +administer. + +## Operational guidance + +This application reads bioinformatics data and writes to databases on the +host machine. Recommended deployment hygiene: + +- Run behind authentication if exposed beyond localhost +- Keep system dependencies (bun, Julia, R) at the versions pinned in + `docs/reproducibility.md` +- Never commit sequencing data, secrets, or environment files + +Last updated: 2026-09-17 · v1.0 diff --git a/bench/analysis_config/benchmark.jl b/bench/analysis_config/benchmark.jl new file mode 100644 index 00000000..563f3f84 --- /dev/null +++ b/bench/analysis_config/benchmark.jl @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: AGPL-3.0-only +""" + Benchmark for AnalysisConfig layer + +Ensures runtime and memory regression <10% vs baseline. + +Measures: +- Config creation + validation +- JSON serialization roundtrip +- DOI bundle creation +- Epistemic validation (present_in_every_admissible_world) +- CladeCumulus tree building + +Fail CI on >10% regression. +""" + +using BenchmarkTools +using OrderedCollections +# `:` form — the dotted form binds the exported `struct AnalysisConfig`, +# not the same-named submodule, so every `AnalysisConfig.x` below would FieldError. +using MetaManifold: AnalysisConfig +using MetaManifold.Epistemic +using MetaManifold.CladeCumulus +using Statistics + +const SUITE = BenchmarkGroup() + +SUITE["config_creation"] = @benchmarkable begin + norm = AnalysisConfig.NormalizationConfig(method="size_factors") + cfg = AnalysisConfig.AnalysisConfigStruct( + method="nb_glm", + formula="~ group + batch", + metadata_columns=["group", "batch", "age"], + normalization=norm, + created_by="benchmark" + ) +end + +SUITE["config_validation"] = @benchmarkable begin + norm = AnalysisConfig.NormalizationConfig(method="clr", pseudocount=0.5) + cfg = AnalysisConfig.AnalysisConfigStruct( + method="clr_lm", + formula="~ group", + metadata_columns=["group"], + normalization=norm, + ) + AnalysisConfig.validate_config(cfg, ["group", "batch"]; strict=false) +end + +SUITE["json_roundtrip"] = @benchmarkable begin + norm = AnalysisConfig.NormalizationConfig(method="size_factors") + cfg = AnalysisConfig.AnalysisConfigStruct( + method="nb_glm", + formula="~ group", + metadata_columns=["group"], + normalization=norm, + ) + json = AnalysisConfig.to_json(cfg) + AnalysisConfig.from_json(json) +end + +SUITE["doi_bundle"] = @benchmarkable begin + norm = AnalysisConfig.NormalizationConfig(method="size_factors") + cfg = AnalysisConfig.AnalysisConfigStruct( + method="nb_glm", + formula="~ group", + metadata_columns=["group"], + normalization=norm, + ) + result = AnalysisConfig.AnalysisResult( + config_id=cfg.id, + config_hash=cfg.hash, + method=cfg.method, + results=OrderedDict{String,Any}("taxon1" => OrderedDict("p" => 0.01)) + ) + mktempdir() do tmp + AnalysisConfig.create_doi_bundle(cfg, result, joinpath(tmp, "bundle"); authors=["Bench"], title="Bench") + end +end + +SUITE["epistemic_present_in_every"] = @benchmarkable begin + c1 = Epistemic.Candidate{Tuple{Int,Int},Int}((1,1), true, true) + c2 = Epistemic.Candidate{Tuple{Int,Int},Int}((2,0), true, true) + case_bounded = Epistemic.Case{Tuple{Int,Int},Int}(c1, [c1, c2]) + Epistemic.present_in_every_admissible_world(case_bounded, world -> world[1] != 0) +end + +SUITE["clade_tree_build"] = @benchmarkable begin + rows = [ + Dict{String,Any}("Domain" => "Bacteria", "Phylum" => "Firmicutes", "Genus" => "Lacto$(i)", "total" => Float64(10+i), "avec_fibre" => true, "epistemic_status" => "present_in_every_admissible_world", "residual_count" => i % 5) + for i in 1:100 + ] + tree = CladeCumulus.build_clade_tree(rows) + CladeCumulus.cumulative_frequencies(tree) +end + +# Run and check regression +function run_benchmarks(; baseline_path::String=joinpath(@__DIR__, "baseline.json")) + results = run(SUITE, verbose=true) + + # Save current as new baseline if no baseline exists + if !isfile(baseline_path) + BenchmarkTools.save(baseline_path, results) + println("No baseline found, saved current as baseline at $baseline_path") + return results + end + + baseline = BenchmarkTools.load(baseline_path)[1] + + # Compare, fail on >10% regression in time or memory + for (key, trial) in results + if haskey(baseline, key) + base_trial = baseline[key] + # Median time comparison + curr_time = BenchmarkTools.prettytime(BenchmarkTools.median(trial).time) + base_time = BenchmarkTools.prettytime(BenchmarkTools.median(base_trial).time) + + # Ratio + time_ratio = BenchmarkTools.median(trial).time / BenchmarkTools.median(base_trial).time + mem_ratio = BenchmarkTools.median(trial).memory / max(1, BenchmarkTools.median(base_trial).memory) + + println("$key: time ratio $(round(time_ratio, digits=3)) (baseline $base_time vs current $curr_time), memory ratio $(round(mem_ratio, digits=3))") + + if time_ratio > 1.10 + error("Benchmark regression >10% in time for $key: ratio $time_ratio (threshold 1.10)") + end + if mem_ratio > 1.10 + error("Benchmark regression >10% in memory for $key: ratio $mem_ratio") + end + end + end + + println("All benchmarks within 10% regression threshold — OK") + return results +end + +if abspath(PROGRAM_FILE) == @__FILE__ + run_benchmarks() +end diff --git a/bench/comprehensive_benchmark.jl b/bench/comprehensive_benchmark.jl new file mode 100644 index 00000000..320b7065 --- /dev/null +++ b/bench/comprehensive_benchmark.jl @@ -0,0 +1,92 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +""" +Comprehensive benchmark runner for Milestone 2 + +Runs all benchmark categories: +- table_loading +- epistemic_parsing +- duckdb_aggregation +- permanova_nmds +- tree_rendering + +Fails on >10% regression vs committed baselines when CI=true +Uploads artifacts via GitHub Actions (see .github/workflows/ci.yml) +""" + +using Logging +using JSON3 + +const BENCH_DIR = @__DIR__ + +function run_category(cat::String) + bench_file = joinpath(BENCH_DIR, cat, "benchmark.jl") + if !isfile(bench_file) + @warn "Benchmark file not found" cat bench_file + return nothing + end + println("\n" * "="^60) + println("Running benchmark category: $cat") + println("="^60) + # Include and run + mod = Module() + Base.include(mod, bench_file) + if isdefined(mod, :run_benchmarks) + return Base.invokelatest(mod.run_benchmarks) + else + @warn "No run_benchmarks defined in $bench_file" + return nothing + end +end + +function main() + categories = [ + "table_loading", + "epistemic_parsing", + "duckdb_aggregation", + "permanova_nmds", + "tree_rendering" + ] + + all_results = Dict{String, Any}() + + for cat in categories + try + results = run_category(cat) + all_results[cat] = results + catch e + @error "Benchmark category failed" cat exception=(e, catch_backtrace()) + all_results[cat] = Dict("error" => string(e)) + if get(ENV, "CI", "false") == "true" + # Don't exit immediately, continue to run others for full report + # But mark failure + println("::error::Benchmark $cat failed: $e") + end + end + end + + # Write combined results + results_path = joinpath(BENCH_DIR, "results", "comprehensive_results.json") + mkpath(dirname(results_path)) + try + open(results_path, "w") do io + JSON3.write(io, all_results) + end + println("\nWrote combined results to $results_path") + catch e + @warn "Failed to write JSON results" exception=e + # Fallback: write simple text + open(results_path * ".txt", "w") do io + println(io, all_results) + end + end + + println("\n" * "="^60) + println("Comprehensive benchmark complete") + println("="^60) + return all_results +end + +if abspath(PROGRAM_FILE) == @__FILE__ + main() +end diff --git a/bench/doi/benchmark.jl b/bench/doi/benchmark.jl new file mode 100644 index 00000000..65f592a8 --- /dev/null +++ b/bench/doi/benchmark.jl @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MPL-2.0 +# Credential-free warmed publication microbenchmarks. No R or live Zenodo calls. +# Run with the pinned isolated environment; stdout is a machine-readable report: +# julia --project=test/doi bench/doi/benchmark.jl > /tmp/doi-performance.json +using JSON3, Test, HTTP, Dates, SHA, MD5 +include(joinpath(@__DIR__, "..", "..", "test", "doi", "bootstrap.jl")) +const S = DOIIsolated.DOIStorage +const B = DOIIsolated.DOIBundles +const Z = DOIIsolated.Zenodo +const P = DOIIsolated.DOIPublications +include(joinpath(@__DIR__, "..", "..", "test", "doi", "fixtures.jl")) + +function measure(operation; samples=31) + for _ in 1:3; operation(); end # exclude first-compilation costs + GC.gc() + trials = [@timed(operation()) for _ in 1:samples] + midpoint = cld(samples, 2) + Dict("median_ns" => sort!([t.time * 1e9 for t in trials])[midpoint], + "median_bytes" => sort!([t.bytes for t in trials])[midpoint]) +end + +report = Dict{String,Any}("schema_version" => 1, "julia_version" => string(VERSION), + "cpu" => Sys.CPU_NAME, "os" => string(Sys.KERNEL), "arch" => string(Sys.ARCH), + "threads" => Threads.nthreads(), "samples" => 31, "payload_bytes" => 8 * 1024 * 1024, + "metrics" => Dict{String,Any}()) +prepared_fixture() do tmp, root, source, fake, client, prepared + path = joinpath(tmp, "bounded-download.bin") + write(path, repeat("0123456789abcdef", div(report["payload_bytes"], 16))) + metadata = B.validate_metadata(metadata_fixture()) + report["metrics"]["metadata_validation"] = measure(() -> B.validate_metadata(metadata_fixture())) + report["metrics"]["snapshot_checksums"] = measure(() -> B.snapshot(source)) + report["metrics"]["archive_sha256_8MiB"] = measure(() -> S.file_sha256(path)) + report["metrics"]["download_8MiB"] = measure(() -> write(devnull, S.FileBody(path))) + before = length(fake.calls) + report["metrics"]["prepared_replay"] = measure(() -> P.prepare!(root, source, metadata, client)) + length(fake.calls) == before || error("A prepared replay must not perform network operations") + publish_fixture(root, prepared, client) + before = length(fake.calls) + report["metrics"]["published_replay"] = measure(() -> publish_fixture(root, prepared, client)) + length(fake.calls) == before || error("A published replay must not perform network operations") + # Resource-safety contract, independent of CPU speed or a historical baseline. + report["metrics"]["download_8MiB"]["median_bytes"] <= 2 * 1024 * 1024 || + error("Download allocation exceeded its bounded-memory budget (2 MiB)") +end +println(S.canonical_json(report)) diff --git a/bench/doi/compare.js b/bench/doi/compare.js new file mode 100644 index 00000000..95eb9d2b --- /dev/null +++ b/bench/doi/compare.js @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 +// Deliberately fail closed: never manufacture a baseline or silently ignore cases. +import { readFileSync } from 'node:fs' + +export function compareReports(baseline, current) { + if (baseline.schema_version !== 1 || current.schema_version !== 1) throw new Error('Unsupported DOI benchmark report version') + for (const key of ['julia_version', 'cpu', 'os', 'arch', 'threads', 'samples', 'payload_bytes']) { + if (baseline[key] === undefined || baseline[key] !== current[key]) throw new Error(`Incomparable benchmark environment/workload: ${key}`) + } + const keys = Object.keys(baseline.metrics ?? {}).sort() + if (!keys.length || JSON.stringify(keys) !== JSON.stringify(Object.keys(current.metrics ?? {}).sort())) throw new Error('Missing or changed benchmark cases') + const failures = [] + for (const key of keys) { + for (const field of ['median_ns', 'median_bytes']) { + const before = baseline.metrics[key][field], after = current.metrics[key][field] + if (![before, after].every(x => typeof x === 'number' && Number.isFinite(x) && x >= 0)) throw new Error(`Invalid benchmark measurement: ${key}.${field}`) + if (after > before * 1.10) failures.push(`${key}.${field}: ${before} -> ${after} (>10% regression)`) + } + } + if (failures.length) throw new Error(failures.join('\n')) + return keys.length +} + +if (import.meta.main) { + if (process.argv.length !== 4) throw new Error('Usage: bun bench/doi/compare.js BASELINE.json CURRENT.json; generate both with bench/doi/benchmark.jl on the same controlled host') + const [baseline, current] = process.argv.slice(2).map(path => JSON.parse(readFileSync(path, 'utf8'))) + console.log(`DOI benchmark gate passed: ${compareReports(baseline, current)} cases, time and allocation <=10% regression`) +} diff --git a/bench/duckdb_aggregation/baseline.json b/bench/duckdb_aggregation/baseline.json new file mode 100644 index 00000000..6c72e0ef --- /dev/null +++ b/bench/duckdb_aggregation/baseline.json @@ -0,0 +1,7 @@ +{ + "aggregate_by_taxon": 0.2, + "venn_taxa_present": 0.15, + "bar_chart": 0.1, + "taxa_bar_chart": 0.1, + "alpha_chart": 0.1 +} diff --git a/bench/duckdb_aggregation/benchmark.jl b/bench/duckdb_aggregation/benchmark.jl new file mode 100644 index 00000000..f752cded --- /dev/null +++ b/bench/duckdb_aggregation/benchmark.jl @@ -0,0 +1,152 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +""" +Benchmark for DuckDB aggregation pathways + +Measures: +- aggregate_by_taxon (SUM COALESCE, Unclassified fallback) +- combined_counts_across_runs +- venn_taxa_present +- bar_chart and taxa_bar_chart generation +- alpha_chart generation +""" + +using DuckDB, DataFrames, DBInterface +using MetaManifold.Analysis: aggregate_by_taxon, venn_taxa_present, alpha_chart, bar_chart, taxa_bar_chart, sample_columns, filtered_counts +using Random +using JSON3 +using Statistics + +function _create_mock_db(n_samples::Int=20, n_features::Int=1000) + db = DuckDB.DB() + con = DBInterface.connect(db) + sample_cols = ["Sample$(i)" for i in 1:n_samples] + df = DataFrame() + df.SeqName = ["ASV$(i)" for i in 1:n_features] + df.Domain = rand(["Bacteria", "Archaea"], n_features) + df.Phylum = rand(["Firmicutes", "Bacteroidetes", "Proteobacteria"], n_features) + df.Genus = rand(["Bacteroides", "Prevotella", "Faecalibacterium", "Escherichia"], n_features) + df.Species = rand(["B. fragilis", "P. copri", "F. prausnitzii", "E. coli"], n_features) + for sc in sample_cols + df[!, sc] = rand(0:1000, n_features) + end + DuckDB.register_data_frame(con, df, "merged_df") + DBInterface.execute(con, "CREATE TABLE merged AS SELECT * FROM merged_df") + return con, sample_cols +end + +function bench_aggregate_by_taxon(con, sample_cols) + # `aggregate_by_taxon(con, table, sample_cols, rank, where_clause, where_params)` + # -- six arguments. This previously passed four, omitting the trailing filter + # pair. `src/analysis/analysis.jl:141` has required all six since the function + # was introduced, and `test/unit/test_analysis_duckdb.jl` calls it that way. + # The call was unreachable until the bench steps were wired into CI, so it + # failed the moment it first ran. An empty filter benchmarks the unfiltered + # aggregation, which is what the header comment says this measures. + @elapsed aggregate_by_taxon(con, "merged", sample_cols, "Genus", "", []) +end + +function bench_venn_taxa_present(con, sample_cols) + # Split samples into 2 groups + g1 = sample_cols[1:div(length(sample_cols),2)] + g2 = sample_cols[div(length(sample_cols),2)+1:end] + # `venn_taxa_present(con, table, sample_cols, rank_col, where_clause, where_params)` + # returns the taxa present in ONE sample set (`src/analysis/analysis.jl:161`). + # It has never accepted a list of groups: the previous call passed `[g1, g2]` + # as a fourth argument in a five-argument form that matches no method. A Venn + # is assembled by calling it once per group, which is what this now measures. + @elapsed begin + venn_taxa_present(con, "merged", g1, "Genus", "", []) + venn_taxa_present(con, "merged", g2, "Genus", "", []) + end +end + +function bench_bar_chart() + # `bar_chart(segment_labels, sample_names, counts; top_n, ...)` -- the counts + # matrix is (segments x samples), as `src/analysis/analysis.jl:403` (column + # totals are per-sample) and `test/unit/test_analysis.jl:35` ("2 taxa x 2 + # samples") both establish. The previous call passed (labels, counts, names), + # putting the matrix in the `sample_names` position, so no method matched. + # The 100x3 matrix means 100 segments (taxa) across 3 samples (groups), so the + # taxon vector is the segment labels and the group vector the sample names. + segment_labels = ["Taxon$i" for i in 1:100] + sample_names = ["GroupA", "GroupB", "GroupC"] + counts = rand(100, 3) * 1000 + @elapsed bar_chart(segment_labels, sample_names, counts, top_n=20) +end + +function bench_taxa_bar_chart() + labels = ["Taxon$i" for i in 1:50] + counts = rand(50, 10) * 100 + sample_names = ["Sample$i" for i in 1:10] + # `taxa_bar_chart(taxon_labels, sample_names, counts; ...)` -- arguments 2 and + # 3 were transposed here. The 50x10 matrix is already (taxa x samples), which + # is the orientation the function wants; only the call order was wrong. + @elapsed taxa_bar_chart(labels, sample_names, counts, top_n=20) +end + +function bench_alpha_chart() + sample_names = ["Sample$i" for i in 1:20] + richness = rand(50:500, 20) + shannon = rand(1.0:0.1:5.0, 20) + simpson = rand(0.5:0.01:0.99, 20) + # `alpha_chart(sample_names, richness, shannon, simpson)` takes exactly four + # arguments (`src/analysis/analysis.jl:312`); it has no grouping parameter, and + # the `groups` vector built here was never consumed by any method. Grouped + # alpha display is `alpha_boxplot`'s job, benchmarked in permanova_nmds. + @elapsed alpha_chart(sample_names, richness, shannon, simpson) +end + +function run_benchmarks(; n_samples=20, n_features=1000, reps=5) + println("=== DuckDB Aggregation Benchmark ===") + con, sample_cols = _create_mock_db(n_samples, n_features) + + results = Dict{String, Vector{Float64}}() + for name in ["aggregate_by_taxon", "venn_taxa_present", "bar_chart", "taxa_bar_chart", "alpha_chart"] + results[name] = Float64[] + end + + for _ in 1:reps + push!(results["aggregate_by_taxon"], bench_aggregate_by_taxon(con, sample_cols)) + push!(results["venn_taxa_present"], bench_venn_taxa_present(con, sample_cols)) + push!(results["bar_chart"], bench_bar_chart()) + push!(results["taxa_bar_chart"], bench_taxa_bar_chart()) + push!(results["alpha_chart"], bench_alpha_chart()) + end + + for (name, times) in results + med = median(times) + println("$name: median $(round(med*1000, digits=2)) ms over $reps reps") + end + + baseline_path = joinpath(@__DIR__, "baseline.json") + if isfile(baseline_path) + baseline = JSON3.read(read(baseline_path, String)) + println("\nBaseline comparison (informational):") + for (name, times) in results + med = median(times) + if haskey(baseline, name) + base_med = baseline[name] + delta = (med - base_med) / base_med * 100 + status = delta > 10 ? "NOTE" : "ok" + println("$status $name: $(round(delta, digits=1))% vs baseline $(round(base_med*1000, digits=2)) ms") + if delta > 10 + # Informational — absolute ns vs a committed baseline measures the host, not the change (see the benchmark step comment in .github/workflows/ci.yml). Never gates in CI. + @warn "Delta >10% vs baseline for $name (informational)" delta + end + end + end + else + println("\nNo baseline.json — saving current as baseline") + baseline = Dict(name => median(times) for (name, times) in results) + open(baseline_path, "w") do io + JSON3.write(io, baseline) + end + end + + return results +end + +if abspath(PROGRAM_FILE) == @__FILE__ + run_benchmarks() +end diff --git a/bench/epistemic_parsing/baseline.json b/bench/epistemic_parsing/baseline.json new file mode 100644 index 00000000..bc443175 --- /dev/null +++ b/bench/epistemic_parsing/baseline.json @@ -0,0 +1,7 @@ +{ + "avec_fibre_parse": 0.05, + "epistemic_colour": 0.05, + "cloud_size": 0.05, + "present_in_every": 0.1, + "warrant_logic": 0.05 +} diff --git a/bench/epistemic_parsing/benchmark.jl b/bench/epistemic_parsing/benchmark.jl new file mode 100644 index 00000000..f1834f42 --- /dev/null +++ b/bench/epistemic_parsing/benchmark.jl @@ -0,0 +1,157 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +""" +Benchmark for epistemic parsing pathways + +Measures (future epistemic layer, currently mocked with categories + avec_fibre): +- avec_fibre column parsing and boolean coercion +- Epistemic colour coding logic +- Cloud sizing by residual count +- present_in_every_admissible_world validation +- Warrant / Candidate / Holds logic (finite model) +- Category materialisation (contamination model) +""" + +using Random +using JSON3 +using Statistics + +# Mock epistemic types (mirrors src/core/epistemic.jl future implementation) +@enum EpistemicStatus present_in_every=1 present_in_some=2 absent=3 unknown=4 sans_fibre=5 + +struct MockCandidate + observation::Int + residual::Int + witness::Int +end + +struct MockCase + candidates::Vector{MockCandidate} +end + +function present_in_every_admissible_world(case_::MockCase, query::Function) + # Returns true if query holds for every candidate + all(c -> query(c.witness), case_.candidates) +end + +function epistemic_colour(status::EpistemicStatus) + if status == present_in_every + return "#2e7d32" # green + elseif status == present_in_some + return "#f9a825" # yellow + elseif status == absent + return "#9e9e9e" # grey + elseif status == sans_fibre + return "#c62828" # red + else + return "#9e9e9e" + end +end + +function cloud_size(residual_count::Int) + return log(1 + residual_count) * 10 + 5 +end + +function avec_fibre_parse(value::Union{Bool, String, Int, Missing}) + if ismissing(value) + return false + elseif value isa Bool + return value + elseif value isa String + return lowercase(value) in ("true", "t", "1", "avec_fibre", "avec") + elseif value isa Int + return value != 0 + else + return false + end +end + +function bench_avec_fibre_parsing(n::Int=10000) + values = rand([true, false, "true", "false", "avec_fibre", "sans_fibre", 1, 0, missing], n) + @elapsed for v in values + avec_fibre_parse(v) + end +end + +function bench_epistemic_colour(n::Int=10000) + statuses = rand([present_in_every, present_in_some, absent, unknown, sans_fibre], n) + @elapsed for s in statuses + epistemic_colour(s) + end +end + +function bench_cloud_size(n::Int=10000) + residuals = rand(0:1000, n) + @elapsed for r in residuals + cloud_size(r) + end +end + +function bench_present_in_every(n_cases::Int=100, n_candidates::Int=50) + cases = [MockCase([MockCandidate(rand(-6:6), rand(-3:3), rand(-6:6)) for _ in 1:n_candidates]) for _ in 1:n_cases] + @elapsed for case_ in cases + present_in_every_admissible_world(case_, w -> w != 0) + end +end + +function bench_warrant_logic(n::Int=10000) + # Mock Warrant: evidence set, no Evidence->A + @elapsed for _ in 1:n + evidence = rand(Bool, 10) + # Warrant holds if any evidence true (simplified) + any(evidence) + end +end + +function run_benchmarks(; reps=5) + println("=== Epistemic Parsing Benchmark ===") + results = Dict{String, Vector{Float64}}() + + for name in ["avec_fibre_parse", "epistemic_colour", "cloud_size", "present_in_every", "warrant_logic"] + results[name] = Float64[] + end + + for _ in 1:reps + push!(results["avec_fibre_parse"], bench_avec_fibre_parsing()) + push!(results["epistemic_colour"], bench_epistemic_colour()) + push!(results["cloud_size"], bench_cloud_size()) + push!(results["present_in_every"], bench_present_in_every()) + push!(results["warrant_logic"], bench_warrant_logic()) + end + + for (name, times) in results + med = median(times) + println("$name: median $(round(med*1000, digits=2)) ms over $reps reps") + end + + baseline_path = joinpath(@__DIR__, "baseline.json") + if isfile(baseline_path) + baseline = JSON3.read(read(baseline_path, String)) + println("\nBaseline comparison (informational):") + for (name, times) in results + med = median(times) + if haskey(baseline, name) + base_med = baseline[name] + delta = (med - base_med) / base_med * 100 + status = delta > 10 ? "NOTE" : "ok" + println("$status $name: $(round(delta, digits=1))% vs baseline $(round(base_med*1000, digits=2)) ms") + if delta > 10 + # Informational — absolute ns vs a committed baseline measures the host, not the change (see the benchmark step comment in .github/workflows/ci.yml). Never gates in CI. + @warn "Delta >10% vs baseline for $name (informational)" delta + end + end + end + else + println("\nNo baseline.json — saving current as baseline") + baseline = Dict(name => median(times) for (name, times) in results) + open(baseline_path, "w") do io + JSON3.write(io, baseline) + end + end + + return results +end + +if abspath(PROGRAM_FILE) == @__FILE__ + run_benchmarks() +end diff --git a/bench/ilr_bases/benchmark.jl b/bench/ilr_bases/benchmark.jl new file mode 100644 index 00000000..b6f680bd --- /dev/null +++ b/bench/ilr_bases/benchmark.jl @@ -0,0 +1,141 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +""" +ILR-basis scaling benchmark (issue #20) — 100, 1 000 and 10 000 taxa. + +Measures, per size, the CLR transform and the default (Helmert) ILR through +`Execution.prepare_analysis_table`, and each new basis through the engine +(`ILRBasis.ilr_transform`, file read and SHA-256 included): + + * phylogenetic — a balanced rooted bifurcating tree; + * sequential_binary_partition — the SBP CSV of the same tree (D x (D-1) cells: the + format the issue specifies is dense, 10^8 cells at 10 000 taxa; the reader streams it); + * balance_dendrogram — ward, complete and average on the variation matrix. + +Reported per workload: wall time, bytes allocated, and growth of the process's peak +resident set. A workload over 5 minutes, or over 1 GiB of allocation or of peak-RSS growth, +is WARNED about (a GitHub `::warning::` annotation in CI), as the issue asks. Like every +other Julia benchmark in this repository the result is informational (cross-host timings +are noise; see the comment above the frontend benchmark step in ci.yml): the hard >10% +CLR/ILR regression gate is the same-runner base-vs-head comparison in +bench/ilr_bases/regression_gate.jl. + +Run: julia --project=. bench/ilr_bases/benchmark.jl + ILR_BENCH_TAXA=100,1000 julia --project=. bench/ilr_bases/benchmark.jl +Writes bench/results/ilr_bases_results.json. +""" + +using MetaManifold +using MetaManifold: AnalysisConfig, Execution +using JSON3 +using Logging +using OrderedCollections + +const ILR = MetaManifold.ILRBasis +const WARN_SECONDS = 300.0 +const WARN_BYTES = 1 << 30 +const SAMPLES = 20 + +sizes() = parse.(Int, split(get(ENV, "ILR_BENCH_TAXA", "100,1000,10000"), ',')) + +# Deterministic strictly positive taxa x samples table (no RNG: identical on every host). +synth(D, n) = [1.5 + mod(i * 7919 + j * 104729, 997) + 0.25 * mod(i * j, 7) for i in 1:D, j in 1:n] + +function balanced_newick(names::Vector{String}, lo::Int, hi::Int) + lo == hi && return names[lo] + mid = (lo + hi) >>> 1 + return "(" * balanced_newick(names, lo, mid) * ":0.1," * balanced_newick(names, mid + 1, hi) * ":0.1)" +end + +function write_sbp(path, taxa, W) + open(path, "w") do io + println(io, "taxon,", join(("b$k" for k in 1:size(W, 2)), ",")) + for i in eachindex(taxa) + print(io, taxa[i]) + for k in 1:size(W, 2) + print(io, ',', W[i, k]) + end + println(io) + end + end +end + +function config(norm::String, method::String) + AnalysisConfig.AnalysisConfig( + method = method, formula = "~ group", metadata_columns = ["group"], + normalization = AnalysisConfig.NormalizationConfig(method = norm, pseudocount = 0.5), + advanced = AnalysisConfig.AdvancedConfig(min_prevalence = 0.0, min_abundance = 0.0), + created_by = "bench/ilr_bases") +end + +function measure(f) + GC.gc() + rss0 = Sys.maxrss() + stats = @timed f() + return (seconds = stats.time, allocated_bytes = stats.bytes, gc_seconds = stats.gctime, + peak_rss_growth_bytes = max(0, Int(Sys.maxrss()) - Int(rss0))) +end + +function workloads(D::Int, dir::String) + X = synth(D, SAMPLES) + taxa = ["t$i" for i in 1:D] + samples = ["s$j" for j in 1:SAMPLES] + tree = joinpath(dir, "tree_$D.nwk") + write(tree, balanced_newick(taxa, 1, D) * ";") + sbp = joinpath(dir, "sbp_$D.csv") + write_sbp(sbp, taxa, ILR.sbp_matrix(first(ILR.phylo_balance_tree(ILR.parse_newick(read(tree, String)), taxa)))) + prep(cfg) = () -> Execution.prepare_analysis_table(cfg, X; sample_ids = samples, taxa_ids = taxa, drop_policy = "drop") + clr, ilr = config("clr", "clr_lm"), config("ilr", "ilr_lm") + w = OrderedDict{String,Any}( + "clr (prepare_analysis_table)" => prep(clr), + "ilr default Helmert (prepare_analysis_table)" => prep(ilr), + "phylogenetic" => () -> ILR.ilr_transform(X, taxa; basis = "phylogenetic", tree_path = tree), + "sequential_binary_partition" => () -> ILR.ilr_transform(X, taxa; basis = "sequential_binary_partition", sbp_path = sbp), + ) + for m in ILR.VALID_DENDROGRAM_METHODS + w["balance_dendrogram $m"] = () -> ILR.ilr_transform(X, taxa; basis = "balance_dendrogram", dendrogram_method = m) + end + return w +end + +function main() + in_ci = haskey(ENV, "GITHUB_ACTIONS") + results = OrderedDict{String,Any}("julia" => string(VERSION), "samples" => SAMPLES, + "warn_seconds" => WARN_SECONDS, "warn_bytes" => WARN_BYTES, + "runs" => Any[]) + warnings = String[] + mktempdir() do dir + with_logger(NullLogger()) do + # compile everything once on a small problem so no size pays for compilation + foreach(f -> f(), values(workloads(20, dir))) + for D in sizes() + for (name, f) in workloads(D, dir) + m = measure(f) + push!(results["runs"], OrderedDict{String,Any}("taxa" => D, "workload" => name, pairs(m)...)) + flags = String[] + m.seconds > WARN_SECONDS && push!(flags, "took $(round(m.seconds; digits = 1)) s (> 5 min)") + m.allocated_bytes > WARN_BYTES && push!(flags, "allocated $(round(m.allocated_bytes / 2^30; digits = 2)) GiB (> 1 GiB)") + m.peak_rss_growth_bytes > WARN_BYTES && push!(flags, "grew peak RSS by $(round(m.peak_rss_growth_bytes / 2^30; digits = 2)) GiB (> 1 GiB)") + isempty(flags) || push!(warnings, "ILR benchmark, $D taxa, $name: " * join(flags, "; ")) + end + end + end + end + println("=== ILR bases: scaling (", SAMPLES, " samples) ===") + println(rpad("taxa", 7), rpad("workload", 48), lpad("time s", 10), lpad("alloc MiB", 12), lpad("ΔpeakRSS MiB", 14)) + for r in results["runs"] + println(rpad(string(r["taxa"]), 7), rpad(r["workload"], 48), lpad(string(round(r["seconds"]; digits = 3)), 10), + lpad(string(round(r["allocated_bytes"] / 2^20; digits = 1)), 12), + lpad(string(round(r["peak_rss_growth_bytes"] / 2^20; digits = 1)), 14)) + end + results["warnings"] = warnings + for w in warnings + println(in_ci ? "::warning::$w" : "WARNING: $w") + end + out = joinpath(@__DIR__, "..", "results", "ilr_bases_results.json") + mkpath(dirname(out)) + open(io -> JSON3.pretty(io, results), out, "w") + println("results: ", normpath(out)) +end + +main() diff --git a/bench/ilr_bases/regression_gate.jl b/bench/ilr_bases/regression_gate.jl new file mode 100644 index 00000000..c1f43205 --- /dev/null +++ b/bench/ilr_bases/regression_gate.jl @@ -0,0 +1,130 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# CLR/ILR performance regression gate (issue #20: "fail CI if CLR/ILR performance regresses +# more than 10%"). +# +# Why this shape. This repository deliberately has no hard timing gate against a committed +# baseline: a baseline recorded on one machine says nothing about a shared CI runner (see the +# frontend benchmark comment in ci.yml). So the gate never compares with a stored number. It +# measures the PR's BASE commit and its HEAD on the SAME runner, in the same job, interleaved +# A B A B so that drift in the runner (thermal, noisy neighbours) hits both sides equally, +# and compares: +# +# * bytes allocated — deterministic for a given Julia version and input, so a >10% growth +# is a real change, not noise; +# * minimum wall time over k repetitions per round, minimum across rounds — the minimum is +# the least noise-sensitive location statistic for "how fast can this code go". +# +# Either ratio above 1.10 fails the job. Both sides run this same script from the HEAD +# checkout, so a change to the gate cannot favour one side; the only difference is which +# MetaManifold `--project` resolves to. Only APIs present on main before #20 are used +# (`prepare_analysis_table` with the CLR and default Helmert ILR configurations), so the base +# side always runs. +# +# Usage: +# julia --project= bench/ilr_bases/regression_gate.jl measure