# Aere Network contracts: continuous verification (GITEA ACTIONS)
#
# ═════════════════════════════════════════════════════════════════════════════
# WHY THIS FILE EXISTS, AND WHY IT IS A SEPARATE FILE
# ═════════════════════════════════════════════════════════════════════════════
#
# The canonical host is our own Gitea instance at git.aere.network. Gitea
# Actions scans BOTH `.gitea/workflows/` and `.github/workflows/`, and prefers
# `.gitea/workflows/` when both are present. GitHub Actions scans
# `.github/workflows/` only. (SPEC: Gitea Actions documentation.)
#
# An earlier version of this repository relied on that overlap to run ONE file
# on both hosts, and justified pinning `actions/upload-artifact` at v3 with the
# note "On GitHub, v3 still runs."
#
# THAT NOTE IS OUT OF DATE AND THE SINGLE-FILE DESIGN IS THEREFORE BROKEN.
# GitHub retired the v3 artifact backend on 2025-01-30; `upload-artifact@v3`
# now hard-fails on GitHub-hosted runners. So the single-file design did not
# actually give portability, it gave a pipeline that fails on GitHub for a
# reason having nothing to do with our code. A CI that goes red for a
# tooling reason is worse than one that goes red for a real reason, because it
# trains people to ignore the colour.
#
# The split is the fix:
#
#   .gitea/workflows/ci.yml   (THIS FILE)  -> runs on git.aere.network.
#                                            upload-artifact pinned at v3,
#                                            which is the protocol the Gitea
#                                            artifact backend implements.
#   .github/workflows/ci.yml               -> runs on GitHub mirrors.
#                                            upload-artifact at v4.
#
# Because Gitea prefers `.gitea/`, each host runs exactly one file and each
# file is correct for its host. Nothing is duplicated silently: the two files
# are identical except for the artifact action version and these header notes.
# Drift between them is checkable in one command:
#
#   diff <(grep -v '^\s*#' .gitea/workflows/ci.yml | grep -v '^$') \
#        <(grep -v '^\s*#' .github/workflows/ci.yml | grep -v '^$')
#
# Note `^\s*#` and not `^#`: indented comments must be stripped too, or the
# diff drowns in commentary and stops being a usable check.
#
# MEASURED 2026-07-20: that command returns exactly THREE differences, all of
# them the `upload-artifact@v3` / `@v4` line. `runs-on:` is currently identical
# in both files (`ubuntu-22.04`); it is called out separately below only because
# it is the line you would change per host if your runner registers a different
# label. If the diff ever returns anything other than those three lines, one of
# the two hosts is no longer running what we claim it runs, and that is a bug.
#
# ═════════════════════════════════════════════════════════════════════════════
# HONESTY STATEMENT: DOES THIS PIPELINE CURRENTLY PASS?
# ═════════════════════════════════════════════════════════════════════════════
#
# NO. Two of the four jobs are RED, deliberately, and will stay red until the
# underlying problems are fixed rather than until the pipeline is edited.
#
#   job: build-and-test .......... RED
#   job: interop ................. RED
#   job: pqc-kat ................. GREEN
#   job: coverage ................ RED
#
# Each status below is recorded from a command someone actually ran, with the
# date, and is labelled MEASURED. Nothing here is assumed.
#
# ── build-and-test: RED ──────────────────────────────────────────────────────
# MEASURED 2026-07-20, Windows 11, Node v24.14.1, npm 11.11.0, 15.78 GB RAM:
#
#   NODE_OPTIONS=--max-old-space-size=6144 npx hardhat test
#
#     1,448 passing
#         9 failing
#     run ABORTED, exit 127
#     last line: memory allocation of 3623878656 bytes failed
#
# Read the counts carefully. The process was killed, so mocha never printed its
# summary. "1,448 passing / 9 failing" are result markers scraped from the log,
# NOT the suite's totals. The suite's true totals are currently UNKNOWN. Quoting
# 1,448 as "the number of passing tests" would be a smaller version of exactly
# the fabrication this pipeline exists to correct.
#
# Two distinct causes, kept apart:
#
#   (1) MEMORY. The full suite does not fit in ~16 GB. A standard Gitea or
#       GitHub runner has 16 GB, so this job is EXPECTED to die the same way
#       until the suite is sharded across jobs. That sharding is NOT done.
#
#   (2) NINE FAILURES.
#       * 6 in `AereEthLightClient (real EIP-2537 BLS, Prague)`. A MISCONFIGURED
#         RUN, not a contract defect: that suite lives in test/interop/ and
#         ships its own config pinning the `prague` hardfork for the EIP-2537
#         BLS12-381 precompiles (0x0b..0x11), while the default config pins
#         `cancun` where those precompiles do not exist. `npx hardhat test`
#         globs test/ recursively and drags it in under the wrong hardfork.
#         MEASURED: under the correct config, one of those six becomes a PASS,
#         and the run then SEGFAULTS in the EVM's EIP-2537 path. The `interop`
#         job below runs it correctly and is red because of that segfault.
#       * 3 undiagnosed (AereCompliancePool fuzz, AereGovernorV2 quorum
#         invariant, AereLendingMarket solvency invariant). We have their titles
#         and no assertion text, because the run died before mocha's summary. We
#         do not know whether they are product defects or run artifacts, and we
#         are not going to guess in public.
#
# ── interop: RED ─────────────────────────────────────────────────────────────
# Segmentation fault in node, inside the EVM implementation's EIP-2537 BLS
# path, on `accepts a valid full-participation update and advances finality`.
# A hard crash in the toolchain. Open and undiagnosed. Giving this suite its own
# config is running it the way it was written, NOT an exemption: it gates like
# every other job. If a green wall were the goal, this job would have been
# deleted instead.
#
# ── pqc-kat: GREEN ───────────────────────────────────────────────────────────
# MEASURED 2026-07-20, and INDEPENDENTLY RE-MEASURED the same day by a second
# session running this job's exact commands, in two parts:
#
#   part 1 (CryptoRegistry x3 + SPHINCS+):   60 passing (6s)   exit 0
#   part 2 (Falcon-512/1024 + PQCMessage):   41 passing (28s)  exit 0
#
# 101 passing, 0 failing, 0 pending. This is the one job that is green, and it
# is green having been run twice rather than assumed once.
#
# ── coverage: RED ────────────────────────────────────────────────────────────
# MEASURED 2026-07-20. The coverage run FAILED at the COMPILE phase:
#
#   Unknown exception during compilation: Dynamic exception type:
#     class std::bad_alloc
#   Error in plugin solidity-coverage: HardhatError: HH600: Compilation failed
#
# exit 1, after roughly 45 minutes, node observed at 16.04 GB private bytes on a
# 15.78 GB box. WE HAVE NO COVERAGE NUMBER FROM TODAY. Not a low one. None.
#
# CORRECTION CARRIED FORWARD, because an earlier copy of this file contradicted
# itself on exactly this point and that is worth naming rather than quietly
# patching. A previous revision's coverage-job comment asserted "The COMPILE
# phase is solved: all contracts instrument and compile", while its own header
# recorded a compile-phase std::bad_alloc. Both cannot be true. The header was
# right and the job comment was a stale note from a 2026-07-19 run that did NOT
# reproduce on 2026-07-20. An internal note that no longer reproduces is the
# same species of problem as an unmeasured coverage percentage, only with a
# shorter half-life.
#
# A SECOND CORRECTION: `--testfiles` does NOT reduce compile cost.
# solidity-coverage instruments and compiles EVERY contract in the tree
# regardless; `--testfiles` only narrows which tests execute AFTERWARDS. So
# scoping by test file cannot rescue a run that dies during compilation. It
# cannot, by construction. We made that mistake and are recording it so nobody
# repeats it.
#
# THE COVERAGE FIGURE THIS PROJECT USED TO CARRY WAS FABRICATED. A "99.8 percent
# statements / 95 percent branches" figure has been associated with Aere
# Network. It was NEVER produced by a coverage tool. It was a target written as
# though it had been achieved. It is not a claim this repository makes.
# The honest replacement, in full:
#
#   * Whole-repository coverage: NOT MEASURED. Not "approximately", not
#     "roughly 90 percent": not measured. The run does not complete.
#   * Best prior floor, dated 2026-07-19 and NOT reproduced on 2026-07-20:
#     26.82% statements (2,585/9,638), 22.74% branches (2,088/9,184),
#     23.25% functions (573/2,464), 27.21% lines (2,487/9,139), from a merge of
#     two completed shards covering 64 of 168 test files against the FULL
#     repository denominator. This is a floor from a minority of the test files.
#     It must never be quoted as the project's coverage figure in either
#     direction.
#   * Scoped economic-core cluster (7 contracts of ~285), same 2026-07-19
#     provenance, also not reproduced: 98.17% statements, 79.82% branches.
#     Seven contracts is not a repository.
#
# 26.82 that reproduces is worth more to this project than 99.8 that evaporates.
#
# ═════════════════════════════════════════════════════════════════════════════
# WHAT THIS PIPELINE IS FORBIDDEN FROM DOING
# ═════════════════════════════════════════════════════════════════════════════
# These are the standard ways a CI file is made green while the software is
# broken. None is used here, and every absence is checkable by reading the file:
#
#   * No `continue-on-error` on any compile, test, KAT or coverage step.
#   * No `|| true` swallowing a non-zero exit in any gating step.
#   * No coverage threshold invented below the real number to manufacture a
#     pass. There is no threshold at all, because there is no real number yet.
#   * No suite skipped for being slow or inconvenient. `.solcover.js` has an
#     EMPTY `skipFiles`, so nothing is removed from the coverage denominator.
#   * `if: always()` appears ONLY on artifact-upload steps, so a failing run
#     still hands you its evidence. It never appears on a step that decides
#     pass or fail. That distinction is the entire difference between a
#     debuggable pipeline and a dishonest one.
#
# Verify the first two yourself, and note the exact patterns. A bare
# `grep secrets.` returns hits because this file DISCUSSES secrets in prose; the
# patterns below match the forms that would actually grant access or actually
# suppress a failure:
#
#   grep -c '\${{ *secrets\.' .gitea/workflows/ci.yml        # expect 0
#   grep -cE '^\s*continue-on-error\s*:' .gitea/workflows/ci.yml  # expect 0
#
# MEASURED 2026-07-20: both return 0.
#
# If the suite goes red, the correct response is to fix the suite. Editing this
# file to restore green is precisely the failure this design prevents.

name: contracts-ci

on:
  push:
  pull_request:
  workflow_dispatch:

# Least privilege. This pipeline reads code and writes artifacts. Nothing else.
permissions:
  contents: read

# ═════════════════════════════════════════════════════════════════════════════
# THE SECRETS BOUNDARY
# ═════════════════════════════════════════════════════════════════════════════
# "Radical open source" invites this question immediately, so it is answered
# here in full rather than as a footnote. Naming the boundary is part of the
# credibility, not a caveat to it. Linux publishes all of its code and none of
# its signing keys.
#
# WHAT THIS PIPELINE DOES NOT HAVE, AND MUST NEVER BE GIVEN:
#
#   * validator signing keys for the 7 chain-2800 validators;
#   * any deploy key, funded account private key, or mnemonic with a balance;
#   * infrastructure credentials: SSH, hosting provider APIs, DNS, TLS private
#     keys;
#   * RPC admin, or any authenticated write endpoint;
#   * explorer, relayer or oracle service credentials.
#
# The `env:` block below is exhaustive and contains no `secrets.*` reference.
# A pull request from a fork runs the identical pipeline against the identical
# (empty) credential surface.
#
# SCOPE OF THAT CLAIM, stated precisely so it cannot be over-read. It covers
# THIS workflow file. It is not a claim that no workflow anywhere in the wider
# Aere monorepo references a secret: the internal `reproducible-besu-build.yml`
# does reference a `WELLKNOWN_DEPLOY_TOKEN` used to publish a build digest to a
# static `.well-known` path. That token publishes a hash to a website. It is not
# a chain key, it cannot sign a transaction, and it is not present in this
# published contracts repository at all. We are naming it because a reader who
# greps only this file and trusts a blanket "no secrets anywhere" claim would
# have been misled, and a boundary that survives only careless reading is not a
# boundary.
#
# WHY A PUBLIC CI STRUCTURALLY CANNOT TOUCH THE LIVE CHAIN, beyond not holding
# keys. Four independent reasons, any one of which is sufficient:
#
#   1. Every job runs against an in-process Hardhat EVM at chain ID 31337. It
#      never dials chain 2800. `AERE_RPC_URL` below is pinned to a dead local
#      address so a stray config cannot reach a real endpoint.
#   2. The only account material anywhere in the test path is the well-known
#      public Hardhat test mnemonic ("test test ... junk"), which holds nothing
#      on any real network.
#   3. Writing to chain 2800 requires a signature from a funded key. No such key
#      exists in this repository, this pipeline, or the runner environment. A
#      compromised workflow file, a malicious dependency, or a hostile pull
#      request still cannot produce a valid mainnet transaction.
#   4. Deployment is a separate, human-gated, offline procedure. It is
#      deliberately NOT automated from CI. Moving it into CI would be a
#      downgrade in safety, not an improvement in convenience.
#
# Aere consensus is CLASSICAL secp256k1 ECDSA QBFT with 7 Foundation-operated
# validators, f=2, quorum 5 of 7, 500 ms blocks (SPEC). Post-quantum
# cryptography exists at the signature, precompile, account and transport layers
# only. Nothing in this pipeline says anything about consensus, and no Aere
# material should ever describe Aere as having post-quantum consensus.

env:
  # Bounds only the V8 old-space heap. The bulk of coverage's growth is
  # Hardhat's in-process EVM state plus per-statement hit maps, which live
  # OUTSIDE this budget. Do not expect this setting alone to make a whole-repo
  # coverage run fit; it does not, and section "coverage: RED" above is why.
  NODE_OPTIONS: --max-old-space-size=6144
  # Never let a stray config or test reach a real endpoint from CI.
  AERE_RPC_URL: http://127.0.0.1:0

jobs:
  # ───────────────────────────────────────────────────────────────────────────
  # 1. THE GATE. Clean-checkout install, compile, full suite. No exceptions.
  # ───────────────────────────────────────────────────────────────────────────
  build-and-test:
    name: Install (lockfile) → compile → full test suite
    # GITEA PORTABILITY: the runner must register a label matching this string.
    # Ours registers `ubuntu-22.04`. If your Gitea runner registers only
    # `ubuntu-latest`, change this line in all four jobs.
    runs-on: ubuntu-22.04
    timeout-minutes: 90

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          # Pinned to the version every number in REPRODUCE.md was measured on.
          node-version: '24.14.1'
          cache: 'npm'

      - name: Record toolchain versions
        # Printed so any run can be compared against any other run. A number is
        # only reproducible if you know what produced it.
        run: |
          echo "node:  $(node --version)"
          echo "npm:   $(npm --version)"
          echo "os:    $(uname -a)"
          echo "mem:   $(free -g | awk '/^Mem:/{print $2" GB"}')"
          echo "cores: $(nproc)"

      - name: Install from the lockfile
        # `npm ci`, NOT `npm install`. ci deletes node_modules and installs the
        # exact lockfile tree, failing outright if package.json and
        # package-lock.json disagree. That is precisely what an outsider cloning
        # this repo does, and it is the step that catches "it works because of
        # what was already on my machine".
        run: npm ci

      - name: Compile contracts
        run: npx hardhat compile

      - name: Full test suite
        # THE GATE. No continue-on-error. A single failing test fails the run.
        # Currently RED: see the header. Leaving it red is the correct state.
        run: npx hardhat test

      - name: Upload compiled artifacts
        if: always()
        # v3, not v4: the Gitea artifact backend implements the v3 upload
        # protocol. The GitHub twin of this file uses v4, because GitHub retired
        # v3 on 2025-01-30. This is the ONLY functional difference between the
        # two files.
        uses: actions/upload-artifact@v3
        with:
          name: build-artifacts
          path: |
            artifacts/
          retention-days: 30

  # ───────────────────────────────────────────────────────────────────────────
  # 2. ETHEREUM INTEROP, run under the config it was actually written for.
  # ───────────────────────────────────────────────────────────────────────────
  interop:
    name: Ethereum interop (EIP-2537 BLS, prague hardfork)
    runs-on: ubuntu-22.04
    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '24.14.1'
          cache: 'npm'

      - name: Install from the lockfile
        run: npm ci

      - name: Interop suite (own config, prague hardfork)
        # No continue-on-error. Expected to fail today, and that failure is the
        # accurate signal.
        run: npx hardhat test --config hardhat.config.interop.js

  # ───────────────────────────────────────────────────────────────────────────
  # 3. PQC KNOWN-ANSWER TESTS, isolated so a crypto regression is legible.
  # ───────────────────────────────────────────────────────────────────────────
  #
  # These also run inside the full suite. They are ALSO run alone, because
  # "do the post-quantum verifiers still match the official NIST vectors" is the
  # question this project is asked most often, and burying its answer in a
  # 1,400-line log is a bad answer.
  #
  # SCOPE HONESTY, because this is the most misread part of the project:
  #
  #   * These KATs exercise the SOLIDITY verifier implementations against the
  #     official NIST reference vectors (falcon512-KAT.rsp, falcon1024-KAT.rsp,
  #     the FIPS 205 SLH-DSA ACVP vectors, the FIPS 202 SHAKE256 vector). The
  #     vectors ship in this repository as test/falcon512_kat0.json and
  #     test/falcon1024_kat0.json so you can diff them against the NIST
  #     originals rather than trusting our copy.
  #   * They assert BOTH directions, which is the part that matters: a genuine
  #     NIST signature is ACCEPTED, and a single-bit-flipped signature, a
  #     single-bit-flipped message, and a genuine signature under the wrong
  #     public key are all REJECTED. An "accepts everything" verifier would pass
  #     the first check and fail the rest.
  #   * They run on the local Hardhat EVM, which has NO Aere precompiles. The
  #     precompile-backed paths are therefore NOT covered by this job. A green
  #     tick here is evidence about the Solidity implementations, not about the
  #     deployed precompiles. Verifying those needs a precompile-bearing devnet,
  #     which a public CI does not have.
  #   * Precompile status is NOT uniform (SPEC): Falcon-512 0x0AE1,
  #     Falcon-1024 0x0AE2, ML-DSA-44 0x0AE3, SLH-DSA-128s 0x0AE4 and
  #     SHAKE256 0x0AE5 are live on mainnet from block 9,189,161. ML-KEM-768
  #     0x0AE6 and Falcon HashToPoint 0x0AE7 are TESTNET-ONLY and are NOT live
  #     on mainnet. P-256 at 0x100 is live.
  #   * The on-chain ZK verifiers are classical BN254, which is breakable by a
  #     sufficiently large quantum computer. Post-quantum signature verification
  #     does not make the ZK stack post-quantum.
  pqc-kat:
    name: PQC known-answer tests (NIST vectors)
    runs-on: ubuntu-22.04
    timeout-minutes: 60

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '24.14.1'
          cache: 'npm'

      - name: Install from the lockfile
        run: npm ci

      - name: Compile contracts
        run: npx hardhat compile

      - name: Falcon / SPHINCS+ / ML-DSA known-answer tests
        # No continue-on-error. A KAT mismatch is a hard failure: it means an
        # implementation stopped agreeing with the NIST reference.
        run: |
          npx hardhat test \
            test/AereFalcon512Verifier.test.js \
            test/AereFalcon1024Verifier.test.js \
            test/AereSphincsVerifier.test.js \
            test/AerePQCMessageVerifier.test.js \
            test/AereCryptoRegistry.test.js \
            test/AereCryptoRegistry.agility.test.js \
            test/AereCryptoRegistry.newprecompiles.test.js

      - name: Upload KAT vectors used
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: pqc-kat-vectors
          path: |
            test/falcon512_kat0.json
            test/falcon1024_kat0.json
          retention-days: 90

  # ───────────────────────────────────────────────────────────────────────────
  # 4. COVERAGE. Read the header's "coverage: RED" section before quoting any
  #    number that comes out of this job.
  # ───────────────────────────────────────────────────────────────────────────
  #
  # WHAT THIS JOB IS FOR: producing a real, checkable coverage number and the
  # raw istanbul JSON alongside it, so anyone can recompute the percentage from
  # the counters rather than take our word for the summary line.
  #
  # WHAT IT CURRENTLY DOES: fails. MEASURED 2026-07-20, it dies at the COMPILE
  # phase with std::bad_alloc out of solc itself, exit 1, on a 15.78 GB box.
  # It is left doing the real thing and failing honestly rather than made green
  # by lowering a threshold, shrinking the denominator via skipFiles, or adding
  # continue-on-error.
  #
  # WHAT IT WILL NOT PRODUCE EVEN WHEN IT RUNS: a whole-repository percentage.
  # A single-process whole-repo run does not fit a 16 GB runner. The route to a
  # real whole-repo number is sharding with --testfiles plus an istanbul merge.
  # That merge is arithmetically valid because instrumentation is deterministic
  # over identical sources, so every shard emits identical statement, branch and
  # function maps and the per-counter arrays sum. Keeping skipFiles empty in
  # every shard is what guarantees those maps match. Until that sharded run is
  # taken to completion end to end, the honest whole-repo figure is NOT
  # MEASURED, and this pipeline says so rather than substituting a scoped number
  # for it.
  coverage:
    name: Coverage (scoped, with artifacts)
    runs-on: ubuntu-22.04
    timeout-minutes: 120

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: '24.14.1'
          cache: 'npm'

      - name: Install from the lockfile
        run: npm ci

      - name: Coverage over the economic-core cluster
        # Scoped by TEST FILE, never by skipFiles. This matters: skipFiles would
        # shrink the DENOMINATOR and inflate the percentage, which is the most
        # common way a coverage number gets quietly flattered. `--testfiles`
        # leaves every contract instrumented and simply runs fewer tests, so the
        # number produced is an honest "what these tests reach", measured
        # against the full instrumented denominator.
        #
        # Note the limitation recorded in the header: --testfiles does NOT
        # reduce COMPILE cost, so this scoping does not rescue the run from its
        # current compile-phase OOM. It is here for when the compile is fixed.
        #
        # No continue-on-error: if a test in this scope fails, coverage fails.
        run: |
          npx hardhat coverage \
            --config hardhat.config.coverage.js \
            --testfiles "{test/aere-fee-burn-vault.test.js,test/aere-sink-branch-coverage.test.js,test/saere-v2-fix.test.js,test/aeresink-conservation-invariant.test.js}"

      - name: Print the measured summary
        run: |
          echo "── Coverage summary (istanbul) ─────────────────────────────"
          cat coverage/coverage-summary.json 2>/dev/null \
            || echo "coverage-summary.json not emitted; see the artifact for coverage.json"

      - name: Upload the raw coverage report
        # `if: always()` is used ONLY on artifact-upload steps, so a FAILED run
        # still hands you its evidence. It is never used on a step that decides
        # pass or fail.
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: coverage-report
          path: |
            coverage/
            coverage.json
          retention-days: 90
