From e8bf20cc23c03513032943a43904187ba1bc77b6 Mon Sep 17 00:00:00 2001 From: Emmy6654 <160542482+Emmy6654@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:56:40 +0000 Subject: [PATCH 1/4] docs: document which app tree to contribute to Add a short CONTRIBUTING.md section that helps contributors pick between the canonical COMEBACKHERE-{contracts,backend,frontend} trees and the older mirrored top-level {contracts,backend,frontend} trees. The canonical trees are preferred because they have dedicated CI workflows; the mirrored trees are still valid PR targets but lack their own independent cargo/npm test runs and are referenced from docs like docs/error-codes.md. Link out to a new ARCHITECTURE.md for the full directory layout, the CI checkout behaviour, and the gap in coverage on the mirrored trees. Closes #285 --- ARCHITECTURE.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 28 +++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..f8a7413 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,72 @@ +# Architecture + +This repository sits at the centre of the COMEBACKHERE protocol and orchestrates three sibling projects: + +- **Smart contracts** — Soroban contracts (`invoice`, `treasury`, `compliance`). +- **Backend API** — Node/Express service that fronts the contracts. +- **Frontend UI** — React/Vite app used by merchants, payers, and admins. + +Each layer has **two source trees in this repository**, plus CI jobs that pull a third copy from upstream. This document explains which tree to target when contributing. + +## Canonical trees (where changes go) + +The active source of truth for each layer lives in a tree with the `COMEBACKHERE-` prefix inside this repository: + +| Layer | Canonical tree | Why | +| --- | --- | --- | +| Contracts (Rust) | `COMEBACKHERE-contracts/` | `cargo test --manifest-path COMEBACKHERE-contracts/Cargo.toml` is run by `make test` and by `.github/workflows/ci-contracts.yml`. ABI snapshots in `abis/` are generated from this workspace. | +| Backend (Node) | `comebackhere-backend/` | Referenced by `.github/workflows/ci.yml` for `npm ci`, `tsc --noEmit`, `npm run lint`, `npm run build`. | +| Frontend (React) | `comebackhere-frontend/` | Same as backend — the `ci.yml` `working-directory` points here. | + +New feature work, bug fixes, and contract changes should target these three directories by default — they're the only ones with a dedicated, contract-specific CI workflow. + +## Mirrored trees (older in-tree copies) + +The repository also contains a parallel set of directories without the prefix: + +- `contracts/` and `backend/` and `frontend/` + +These are in-tree copies of the canonical sources at earlier points in time. They are kept because: + +- Process docs reference them by path. `docs/error-codes.md` calls out `contracts/invoice/src/lib.rs` as the source of `InvoiceError`; that file still lives here. +- They give reviewers a familiar path while the migration to the `COMEBACKHERE-*` trees completes. + +These trees are valid PR targets, but they have a **gap in coverage**: only the `ci-*.yml` workflows that match their files run on a PR touching them. There is no independent `cargo test` for `contracts/Cargo.toml` or `backend/Cargo.toml`, and no independent frontend build for `frontend/`. So a green PR here will not, by itself, prove the change works against the toolchain pinned in the canonical workspace. + +If your change is brand-new work, target the `COMEBACKHERE-*` tree to inherit full CI coverage. If your change is intentionally narrow (a doc tweak, a small Rust fix in a file the CI happens to cover), the mirrored tree is fine. + +## CI checkout behaviour + +Some CI workflows re-check the canonical trees out from upstream rather than using the in-tree copies: + +- `ci-contracts.yml`, `ci-abi-snapshots.yml`, `ci-abi-metadata.yml`, `ci-post-deploy-verify.yml`, and `ci-coverage.yml` do `actions/checkout` of `WHEELBACK/COMEBACKHERE-contracts` into the local `COMEBACKHERE-contracts/` path. +- The local `COMEBACKHERE-contracts/` checkout in this repository exists so that `make update-abi-snapshots` and `scripts/check_abi_snapshot_hygiene.sh` work locally without a separate clone. +- A consumer running on a developer's machine can equivalently clone `COMEBACKHERE-contracts` as a sibling directory; `scripts/generate_abi_metadata.sh` looks for both locations. + +## Other top-level paths + +- `abis/` — committed ABI metadata (`invoice.json`, `treasury.json`, `compliance.json`). Generated from `COMEBACKHERE-contracts/` via `make update-abi-snapshots`. +- `scripts/` — Bash + Python helpers for ABI generation, snapshot hygiene, deployment, and backend env validation. +- `docs/` — error codes, API reference, deployment guides, the ABI snapshot workflow, and other process docs. +- `.github/workflows/` — CI jobs. + +## Local development layout + +A working clone for end-to-end development looks like this (see `docs/dev-environment.md` for full setup): + +```text +~/comebackhere/ + ├── COMEBACKHERE-contracts/ # canonical contracts + ├── COMEBACKHERE/ # this repository + ├── comebackhere-backend/ # canonical backend + └── comebackhere-frontend/ # canonical frontend +``` + +When working inside this repository alone, the in-tree `COMEBACKHERE-contracts/` checkout acts as the canonical contracts tree; the sibling-clone step is optional for backend/frontend contributors. + +## Further reading + +- [docs/dev-environment.md](docs/dev-environment.md) — full local setup. +- [docs/abi-snapshot-workflow.md](docs/abi-snapshot-workflow.md) — when and how to regenerate `abis/`. +- [docs/error-codes.md](docs/error-codes.md) — contract error enums and their meanings. +- [SECURITY.md](SECURITY.md) — which paths handle fund-safety-critical code. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a6faa0..94184e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,33 @@ # Contributing +## Which stack do I work on? + +The repository contains two parallel source trees for each layer — Rust +contracts, Node backend, and React frontend. From directory names alone it is +not obvious which tree receives a given change. + +The short rule: + +- Prefer `COMEBACKHERE-contracts/` for contract changes +- Prefer `comebackhere-backend/` for backend changes +- Prefer `comebackhere-frontend/` for frontend changes + +These are the trees built and tested by CI (`make test`, +`.github/workflows/ci-contracts.yml`, `ci.yml`). They are the canonical +source of truth and the only path that gets the full required-status-check +matrix on every PR. + +The sibling-less top-level `contracts/`, `backend/`, and `frontend/` +directories are also valid PR targets — they contain older in-tree copies of +the same sources and are still referenced from documentation (for example, +`docs/error-codes.md` cites `contracts/invoice/src/lib.rs` as the source of +`InvoiceError`). They do not, however, have a dedicated CI workflow of their +own, so changes there rely on whichever `ci-*.yml` job happens to match the +files. When in doubt, target the canonical `COMEBACKHERE-*` tree. + +See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full reasoning, the exact +directory layout, and how the mirrors are kept in step. + ## Local hooks Install [pre-commit](https://pre-commit.com/) and enable the repository hooks: From 7478c30bf5d96608eb774877ee9a245ffea0d259 Mon Sep 17 00:00:00 2001 From: Emmy6654 <160542482+Emmy6654@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:53:00 +0000 Subject: [PATCH 2/4] chore: add ci check for error code documentation drift Add a small bash/awk grep-based script that walks every contract source in contracts/ and COMEBACKHERE-contracts/, finds every `*Error` enum block, and checks each variant against docs/error-codes.md. The script is deliberately not a Rust AST parser; it just needs to catch a variant added in code with no matching row in any error table. An ignore file (.check-error-codes-ignore) lets teams mark intentionally-undocumented test-only internals (default: the in-test StubError). Wired into a new CI workflow that runs on every PR that touches contract sources or this documentation. Also extend docs/error-codes.md with the three missing sections (TreasuryError, ContractError (invoice), ContractError (compliance)) so the check passes on the current state. Closes #286 --- .github/workflows/ci-error-docs.yml | 25 ++++ docs/error-codes.md | 57 +++++++++- scripts/check_error_docs_sync.sh | 171 ++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/ci-error-docs.yml create mode 100755 scripts/check_error_docs_sync.sh diff --git a/.github/workflows/ci-error-docs.yml b/.github/workflows/ci-error-docs.yml new file mode 100644 index 0000000..c926709 --- /dev/null +++ b/.github/workflows/ci-error-docs.yml @@ -0,0 +1,25 @@ +name: CI - Error Codes Documentation + +on: + pull_request: + branches: [main] + paths: + - 'contracts/**' + - 'COMEBACKHERE-contracts/**' + - 'docs/error-codes.md' + - 'scripts/check_error_docs_sync.sh' + - '.github/workflows/ci-error-docs.yml' + +permissions: + contents: read + +jobs: + check-error-docs-sync: + name: check-error-codes-docs-sync + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify contract error variants are documented + run: ./scripts/check_error_docs_sync.sh diff --git a/docs/error-codes.md b/docs/error-codes.md index ab4200c..9bfdf89 100644 --- a/docs/error-codes.md +++ b/docs/error-codes.md @@ -1,6 +1,6 @@ # Error Codes -This document maps every `InvoiceError` variant (and other contract error codes) to its numeric value, the condition that triggers it, and the recommended remediation steps for integrators. +This document maps every `InvoiceError`, `ContractError`, `SettlementError`, and `TreasuryError` variant (and other contract error codes) to its numeric value, the condition that triggers it, and the recommended remediation steps for integrators. > Cross-reference: see [docs/api-reference.md](./api-reference.md) for HTTP-level error shapes returned by the backend. @@ -8,7 +8,7 @@ This document maps every `InvoiceError` variant (and other contract error codes) ## InvoiceError -Defined in `contracts/invoice/src/lib.rs` and `COMEBACKHERE-contracts/contracts/invoice/src/lib.rs`. +Defined in `contracts/invoice/src/lib.rs`. The `COMEBACKHERE-contracts/contracts/invoice` crate declares a related enum called `ContractError (invoice)` (see below) — variant names overlap but numeric codes are independent. | Code | Name | Trigger condition | Remediation | | ------ | ------ | ------------------- | ------------- | @@ -27,6 +27,42 @@ Defined in `contracts/invoice/src/lib.rs` and `COMEBACKHERE-contracts/contracts/ --- +## ContractError (invoice) + +Defined in `COMEBACKHERE-contracts/contracts/invoice/src/lib.rs`. Shares some variant names with `InvoiceError` above, but numeric codes and semantic triggers are independent — branch on the enum name when integrating. + +| Code | Name | Trigger condition | Remediation | +| ------ | ------ | ------------------- | ------------- | +| 1 | `Unauthorized` | Caller is neither the merchant nor the customer for the operation. | Sign with the merchant key for cancellation flows or with the customer key for refund flows. The admin key is required for `pause`, `unpause`, `set_treasury`, and `set_grace_window`. | +| 2 | `ContractPaused` | A state-changing call was made while the contract is in a paused state. | Check contract status before submitting. Contact the admin to unpause. Do not retry until the contract is unpaused. | +| 3 | `AlreadyInitialized` | `initialize` was called on a contract that is already set up. | Deployment-time error. Remove the extra initialize call; the contract can only be initialised once. | +| 4 | `InvoiceNotFound` | `get_invoice`, `cancel_invoiced`, `mark_paids`, `request_refund`, `release_escrow`, or `raise_dispute` was called with an ID that does not exist. | Confirm the invoice ID returned by `create_invoice`. | +| 5 | `InvoiceAlreadyPaid` | `mark_paids` was called on an invoice that is no longer in `Pending` status. | Inspect invoice status with `get_invoice_status` before marking paid. Already-paid invoices cannot transition again. | +| 6 | `InvoiceExpired` | `mark_paids` was called after `env.ledger().timestamp() >= invoice.expires_at`. | Pay invoices before the configured expiry. Use `batch_expire` to sweep stale invoices off the books. | +| 7 | `InvoiceCancelled` | `cancel_invoiced` was called on a non-Pending invoice. | Invoices can only be cancelled while `Pending`. Confirm status before cancelling. | +| 8 | `NotMerchant` | `release_escrow` was called by a non-merchant caller. | Sign with the merchant key associated with the invoice to release escrow. | +| 9 | `NotCustomer` | `request_refund` was called by a non-customer caller. | Sign with the customer key associated with the invoice to request a refund. | +| 10 | `RefundNotRequested` | `release_escrow` was called before `request_refund` moved the invoice to `RefundRequested`. | Call `request_refund` first, then wait for the grace window to elapse before `release_escrow`. | +| 11 | `AlreadyRefundRequested` | `request_refund` was called on an invoice that is already in `RefundRequested` status. | Each invoice may transition to `RefundRequested` only once. Inspect status before re-requesting. | +| 12 | `GraceWindowNotExpired` | `release_escrow` was called before `created_at + grace_window`. | Wait until `ledger.timestamp() >= created_at + grace_window`. Admin may reduce `GraceWindow` via `set_grace_window` (default 86 400 seconds). | +| 13 | `DuplicateNonce` | The (merchant, nonce) pair has already been used by a previous invoice. | Generate a fresh nonce for each invoice. Different merchants may reuse the same nonce value without collision. | +| 14 | `TreasuryNotConfigured` | `raise_dispute` was called before the admin ran `set_treasury`. | Admin must call `set_treasury` once before disputes can be raised. | + +--- + +## ContractError (compliance) + +Defined in `COMEBACKHERE-contracts/contracts/compliance/src/lib.rs`. + +| Code | Name | Trigger condition | Remediation | +| ------ | ------ | ------------------- | ------------- | +| 1 | `Unauthorized` | Caller is not the admin configured in `initialize`. | Sign with the admin key for state-changing calls (`set_status`, `pause`, `unpause`, admin rotation). | +| 2 | `ContractPaused` | A state-changing call was made while the compliance contract is paused. | Compliance check calls return early on pause; defer the user action or have the admin unpause. | +| 3 | `AlreadyInitialized` | `initialize` was called on a contract that is already set up. | Deployment-time error. The compliance contract can only be initialised once. | +| 4 | `AddressNotFound` | A status query (or block/unblock flow) referenced an address that has not been recorded in `Status(Address)`. | Register the address via `set_status` first, or use the `Cleared` default if no entry exists. | + +--- + ## SettlementError Defined in `contracts/settlement/src/lib.rs`. @@ -40,6 +76,21 @@ Defined in `contracts/settlement/src/lib.rs`. --- +## TreasuryError + +Defined in `COMEBACKHERE-contracts/contracts/treasury/src/lib.rs`. + +| Code | Name | Trigger condition | Remediation | +| ------ | ------ | ------------------- | ------------- | +| 1 | `ContractPaused` | A state-changing call was made while the treasury is in a paused state. | Defer transactions until the admin runs `unpause`. | +| 2 | `NotPending` | `approve_settlement` or `execute_settlement` was called on a settlement that is not in `Pending` status. | Confirm pending status with `get_pending_settlements` before approving or executing. | +| 3 | `InsufficientApprovals` | `execute_settlement` was called before accumulated signer weight reached the configured threshold. | Continue gathering approvals until `approval_weight ≥ threshold`, then call `execute_settlement`. | +| 4 | `TokenNotAllowed` | `propose_settlement` was called with a token not present in the allowlist (when the allowlist is non-empty). | Admin must call `add_token_to_allowlist` for the token before settlements may be proposed against it. | +| 5 | `Unauthorized` | Caller is not registered as a signer (for `propose_settlement`/`approve_settlement`) or not the admin (for `set_signer`, `pause`, etc). | Use a key registered via `initialize` or `set_signer`; admin-only operations require the admin key. | +| 6 | `InvalidThreshold` | `update_threshold` was called with a threshold of 0. | Pass a positive `u32` threshold; the multi-sig cannot function with zero required weight. | + +--- + ## Error shape in API responses Backend endpoints return errors as JSON: @@ -61,7 +112,7 @@ Backend endpoints return errors as JSON: | ------------- | -------------------------- | --------- | | 400 | — | Invalid request body (validation failed before hitting the contract). | | 403 | 1 (`Unauthorized`) | Caller is not authorised for the operation. | -| 404 | 6 (`NotFound`), Settlement 1 | Resource does not exist. | +| 404 | 6 (`NotFound`), 4 (`InvoiceNotFound`/`AddressNotFound`), Settlement 1 | Resource does not exist. | | 422 | 3, 4, 5, 8, 9, 10, 12, 13 | Contract rejected the transaction. | | 503 | — | Backend misconfiguration (missing env vars). | | 504 | — | Transaction confirmation timeout waiting for Soroban. | diff --git a/scripts/check_error_docs_sync.sh b/scripts/check_error_docs_sync.sh new file mode 100755 index 0000000..44dafe0 --- /dev/null +++ b/scripts/check_error_docs_sync.sh @@ -0,0 +1,171 @@ +#!/usr/bin/env bash +# scripts/check_error_docs_sync.sh +# +# Verify that every error variant declared in `contracts/` and +# `COMEBACKHERE-contracts/` is documented in `docs/error-codes.md`. +# +# This is a deliberately small grep/regex-based check; it intentionally +# avoids a full Rust AST parser. It emits one CI failure category: a +# variant name in code that has no matching row in any error table of +# docs/error-codes.md. +# +# Ignore list (used in addition to the always-ignored test stub enum): +# +# - Environment variable $CHECK_ERROR_DOCS_IGNORE may point to a file +# - Default file is $ROOT/.check-error-codes-ignore +# +# Each non-empty, non-comment line of the ignore file is prefixed: +# enum: # skip all variants of this enum (e.g. test stubs) +# var: # skip this variant by name (any enum) +# +# Pass: exits 0 with a success message. +# Fail: exits 1 and lists undocumented variant names with the enum(s) that +# declare them. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +DOCS="$ROOT/docs/error-codes.md" +IGNORE_FILE="${CHECK_ERROR_DOCS_IGNORE:-$ROOT/.check-error-codes-ignore}" + +# ----------------------------------------------------------------------------- +# 1. Parse ignore list. +# ----------------------------------------------------------------------------- +declare -A IGNORE_ENUMS=() +declare -A IGNORE_VARIANTS=() + +# Default: always skip the in-test stub enum. +IGNORE_ENUMS[StubError]=1 + +if [[ -f "$IGNORE_FILE" ]]; then + while IFS= read -r raw || [[ -n "$raw" ]]; do + line="${raw%%#*}" + line="${line## }"; line="${line%% }" + [[ -z "$line" ]] && continue + case "$line" in + enum:*) IGNORE_ENUMS["${line#enum:}"]=1 ;; + var:*) IGNORE_VARIANTS["${line#var:}"]=1 ;; + *) echo "WARN: unrecognized ignore entry '$line' (expected enum:Name or var:Name)" >&2 ;; + esac + done < "$IGNORE_FILE" +fi + +# ----------------------------------------------------------------------------- +# 2. Scan contract sources for enum/variant tuples. +# ----------------------------------------------------------------------------- +CODE_DIRS=( + "$ROOT/contracts/invoice/src" + "$ROOT/contracts/settlement/src" + "$ROOT/COMEBACKHERE-contracts/contracts/invoice/src" + "$ROOT/COMEBACKHERE-contracts/contracts/treasury/src" + "$ROOT/COMEBACKHERE-contracts/contracts/compliance/src" +) + +# Emit `\t` per line. Each Rust file is processed by awk. +TMP_CODE="$(mktemp)" +DOC_VARIANTS_TMP="$(mktemp)" +trap 'rm -f "$TMP_CODE" "$DOC_VARIANTS_TMP"' EXIT + +for d in "${CODE_DIRS[@]}"; do + [[ -d "$d" ]] || continue + while IFS= read -r -d '' f; do + awk ' + function emit(line, n, parts, i, s, m, tok) { + if (!in_enum) return + n = split(line, parts, ",") + for (i = 1; i <= n; i++) { + s = parts[i] + # Strip an enum-header token that could leak onto the opening line. + sub(/^pub[[:space:]]+enum[[:space:]]+[A-Za-z][A-Za-z0-9_]*Error[[:space:]]*\{?[[:space:]]*/, "", s) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", s) + if (s == "" || s == "}") continue + # First whitespace-delimited token must be an UpperCamelCase + # identifier for it to be a variant name (e.g. "Foo", "Bar = 5"). + m = split(s, toks, /[[:space:]]+/) + if (m >= 1 && toks[1] ~ /^[A-Z][A-Za-z0-9_]*$/) { + printf("%s\t%s\n", enum_name, toks[1]) + } + } + } + + BEGIN { in_enum = 0; depth = 0; enum_name = "" } + + { + if (!in_enum) { + # Match `enum XError {`, optionally preceded by `pub`. + if (match($0, /enum[[:space:]]+[A-Za-z][A-Za-z0-9_]*Error[[:space:]]*\{/)) { + s = substr($0, RSTART, RLENGTH) + sub(/^enum[[:space:]]+/, "", s) + sub(/[[:space:]]*\{.*$/, "", s) + enum_name = s + in_enum = 1 + # Count braces on the opening line for cross-line tracking. + n_open = gsub(/\{/, "&", $0) + n_close = gsub(/\}/, "&", $0) + depth = n_open - n_close + emit($0) + if (depth <= 0) in_enum = 0 + } + } else { + emit($0) + n_open = gsub(/\{/, "&", $0) + n_close = gsub(/\}/, "&", $0) + depth += n_open - n_close + if (depth <= 0) in_enum = 0 + } + } + ' "$f" >> "$TMP_CODE" + done < <(find "$d" -maxdepth 1 -name '*.rs' -type f -print0) +done + +# ----------------------------------------------------------------------------- +# 3. Parse docs/error-codes.md for documented variant names. +# ----------------------------------------------------------------------------- +# Rows like: | 1 | `Unauthorized` | ...; capture the backticked identifier. +grep -oE '\|[[:space:]]*[0-9]+[[:space:]]*\|[[:space:]]*`[A-Z][A-Za-z0-9_]*`' "$DOCS" \ + | grep -oE '`[A-Z][A-Za-z0-9_]*`' \ + | tr -d '`' \ + | sort -u > "$DOC_VARIANTS_TMP" + +declare -A DOC_SET=() +while IFS= read -r v; do + [[ -z "$v" ]] && continue + DOC_SET["$v"]=1 +done < "$DOC_VARIANTS_TMP" + +# ----------------------------------------------------------------------------- +# 4. Compare code vs docs and report drift. +# ----------------------------------------------------------------------------- +declare -A SEEN=() +declare -A SOURCE_ENUMS=() # variant -> "EnumName, ..." (for error messages) + +while IFS=$'\t' read -r enum variant || [[ -n "$enum$variant" ]]; do + [[ -z "$variant" ]] && continue + [[ -n "${IGNORE_ENUMS[$enum]:-}" ]] && continue + [[ -n "${IGNORE_VARIANTS[$variant]:-}" ]] && continue + # Only emit one entry per (variant) for visibility, but record all enums that use it. + if [[ -z "${SEEN[$variant]:-}" ]]; then + SEEN[$variant]=1 + if [[ -z "${DOC_SET[$variant]:-}" ]]; then + SOURCE_ENUMS["$variant"]="$enum" + fi + elif [[ -z "${DOC_SET[$variant]:-}" ]]; then + SOURCE_ENUMS["$variant"]="${SOURCE_ENUMS[$variant]}, $enum" + fi +done < "$TMP_CODE" + +if [[ "${#SOURCE_ENUMS[@]}" -gt 0 ]]; then + { + echo "ERROR: contract error variants in code are not documented in $DOCS:" + # Sort by variant name so output is deterministic. + for v in $(printf '%s\n' "${!SOURCE_ENUMS[@]}" | sort); do + printf " - %s (used by: %s)\n" "$v" "${SOURCE_ENUMS[$v]}" + done + echo "" + echo "Fix: add a row to a table in $DOCS for each missing variant, or" + echo "extend $IGNORE_FILE with 'enum:' or 'var:' entries for" + echo "intentionally-undocumented variants." + } >&2 + exit 1 +fi + +echo "OK: all contract error variants from contracts/ and COMEBACKHERE-contracts/ are documented in $DOCS" From d301e6c8c0e1c4044dee64de9ceeebf7bec6d9e7 Mon Sep 17 00:00:00 2001 From: Emmy6654 <160542482+Emmy6654@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:53:50 +0000 Subject: [PATCH 3/4] chore: add codeowners rules for contract directories Add explicit @dreamgeneX ownership for COMEBACKHERE-contracts/ and abis/ so PRs touching fund-safety-critical contract code or the ABI metadata that drives the backend auto-request a review from the contract maintainer. Existing rules for contracts/, scripts/, docs/ are unchanged. Closes #287 --- .github/CODEOWNERS | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4c181cf..1c5330c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,9 +1,18 @@ # CODEOWNERS — critical paths require review from designated owners before merge. # See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners -contracts/ @dreamgeneX -scripts/ @dreamgeneX -docs/ @dreamgeneX +# Smart-contract code — fund-safety-critical; review must go through the +# contract maintainer before any change ships. +contracts/ @dreamgeneX +COMEBACKHERE-contracts/ @dreamgeneX + +# ABI metadata generated from contract sources; any change must be +# reviewed alongside the contract source that produced it. +abis/ @dreamgeneX + +# Tooling, scripts, and process docs. +scripts/ @dreamgeneX +docs/ @dreamgeneX # Frontend — assign a frontend team owner when one is established. # comebackhere-frontend/ @frontend-team From 3623d25db5e6c459ed019cdd214e45b167413cdd Mon Sep 17 00:00:00 2001 From: Emmy6654 <160542482+Emmy6654@users.noreply.github.com> Date: Sat, 25 Jul 2026 13:53:50 +0000 Subject: [PATCH 4/4] chore: add pre commit hook for abi snapshot check Add a new pre-commit hook (id: check-abi-snapshots) that runs `make check-abi-snapshots` whenever a staged file is inside COMEBACKHERE-contracts/contracts/ or abis/. Unlike the existing `abi-snapshot-hygiene` staging-pairing hook, this one actually rebuilds the contracts and diffs the metadata to ensure abis/ is content-fresh, not just that the right files moved together. A small wrapper script formats the failure message to point directly at `make update-abi-snapshots` so the remediation is unambiguous; the same script self-skips when the COMEBACKHERE-contracts sibling is not present locally so unrelated commits are not blocked. Closes #288 --- .pre-commit-config.yaml | 8 +++- scripts/check_abi_snapshots_precommit.sh | 52 ++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100755 scripts/check_abi_snapshots_precommit.sh diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b689b88..3c6d282 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,10 +12,16 @@ repos: - repo: local hooks: - id: abi-snapshot-hygiene - name: ABI snapshot hygiene + name: ABI snapshot staging hygiene entry: scripts/check_abi_snapshot_hygiene.sh language: system pass_filenames: false + - id: check-abi-snapshots + name: ABI snapshot content check + entry: scripts/check_abi_snapshots_precommit.sh + language: system + pass_filenames: false + files: '^(COMEBACKHERE-contracts/contracts/|abis/)' - id: lint-docs name: Markdown lint entry: scripts/lint-docs.sh diff --git a/scripts/check_abi_snapshots_precommit.sh b/scripts/check_abi_snapshots_precommit.sh new file mode 100755 index 0000000..7a89755 --- /dev/null +++ b/scripts/check_abi_snapshots_precommit.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# scripts/check_abi_snapshots_precommit.sh +# +# Pre-commit wrapper around `make check-abi-snapshots`. +# +# Runs the same check CI runs to verify that the committed ABI metadata +# under abis/ matches what `COMEBACKHERE-contracts/` currently produces. +# On failure, prints a clear remediation message pointing the developer +# at `make update-abi-snapshots` instead of dumping a noisy diff. +# +# Skipped gracefully if the COMEBACKHERE-contracts sibling workspace is +# not present locally, since pre-commit hooks should not block unrelated +# commits when the environment lacks the prerequisites. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [[ ! -d "$ROOT/COMEBACKHERE-contracts" && ! -d "$ROOT/../COMEBACKHERE-contracts" ]]; then + echo "check-abi-snapshots: skipping (COMEBACKHERE-contracts/ sibling not found locally)." + exit 0 +fi + +if ! command -v make >/dev/null 2>&1; then + echo "check-abi-snapshots: skipping (make not installed in this environment)." + exit 0 +fi + +if make -C "$ROOT" check-abi-snapshots; then + exit 0 +fi + +cat >&2 <<'EOF' +ERROR: ABI snapshots in abis/ are out of sync with COMEBACKHERE-contracts/. + +To fix this, abort the current commit attempt and re-commit after +regenerating the snapshots: + + # 1. Abort the in-flight commit attempt (this script is running as a + # pre-commit hook, so `git commit --amend` would not work here). + git commit --no-verify + + # 2. Regenerate the snapshots. + make update-abi-snapshots + + # 3. Re-stage and commit the regenerated files. + git add abis/ + git commit -m '' + +(Or, if you do not have the COMEBACKHERE-contracts sibling cloned, clone +it first: `git clone https://github.com/WHEELBACK/COMEBACKHERE-contracts ../COMEBACKHERE-contracts`.) +EOF +exit 1