diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 000000000..98b1aa744
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,95 @@
+# AGENTS.md - GovTool agent guide
+
+Cardano GovTool monorepo: lets ada holders use the CIP-1694 governance features -
+register as DRep, delegate voting power, submit and vote on governance actions.
+Production is https://gov.tools.
+
+## Guides
+
+docs/ai/ARCHITECTURE_MAP.md: topology, data flow, env vars, CI gates
+docs/ai/PLAYBOOKS.md: which files to touch, per change type
+govtool/frontend/AGENTS.md: React/TS conventions and traps
+govtool/backend/AGENTS.md: Haskell conventions and traps
+tests/AGENTS.md: E2E, API and load suites
+
+## Packages, and what belongs in each
+
+- govtool/frontend: React 19 + Vite + TS, the gov.tools app. All UI, routes, forms,
+ wallet connection and transaction building. Main surface.
+- govtool/backend: Haskell + Servant, read-only REST over cardano-db-sync. All
+ endpoints, db-sync queries, response shapes.
+- govtool/metadata-validation: NestJS. Rules for what makes CIP-100/108/119 off-chain
+ metadata valid.
+- govtool/analytics-dashboard: Next.js internal usage dashboard. Not part of the
+ gov.tools user flow; touch only when asked about analytics.
+- tests: Playwright E2E, Python API tests, Gatling load tests, test infra.
+- docker: compose for the whole stack. docs: architecture, style guides, ops.
+- gov-action-loader: dev utility to bulk-submit governance actions to a testnet.
+
+A new governance action type is a frontend change. See PLAYBOOKS, "add a new
+governance action type".
+
+Three dependencies cannot be fixed from this repo: @intersect.mbo/pdf-ui and
+@intersect.mbo/govtool-outcomes-pillar-ui, npm packages that render the Proposal
+Discussion and Outcomes pillars inside the frontend, and
+govtool-outcomes-pillar-backend, an image pinned in docker/docker-compose.yaml.
+
+## Workflow rules
+
+From CONTRIBUTING.md, enforced by CI or review:
+
+1. Base branch is develop, never main. Promotion: develop, test, staging, main.
+2. Branch name is type/issue-number-description, e.g. feat/123-add-voting-ui. Types:
+ feat, fix, chore, docs.
+3. Every PR updates CHANGELOG.md under "[Unreleased]", in the right subsection
+ (Added, Fixed, Changed, Removed), with a link to the GitHub issue.
+4. Never hand-bump versions. package.json, vva-be.cabal and the Dockerfiles are
+ bumped together by update-govtool-version.yml on manual dispatch. All at 2.0.29.
+5. Commit subject: imperative, 50 chars or fewer, capitalized, no trailing period,
+ issue number in subject or body.
+
+## Verification
+
+Run the checks for the package you touched. CI runs exactly these.
+
+```bash
+# govtool/frontend - all three gate the PR
+npm run lint && npm run tsc && npx vitest run # NOT npm run test: that is watch mode
+
+# govtool/metadata-validation
+npm run lint && npm test
+
+# govtool/backend - GHC 9.2.8 + cabal, easiest inside nix
+cabal build all && pre-commit run --all-files hlint && pre-commit run --all-files stylish-haskell
+```
+
+The Haskell toolchain is not installed here - no cabal, ghc, stack or nix on PATH -
+and the backend needs a real cardano-db-sync Postgres to run, with no fixture path.
+Edit and reason about backend code freely; never claim a build passed.
+
+## Docs that are wrong
+
+- docs/operations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md: stale paths and line numbers.
+ Corrected in PLAYBOOKS, "add a new governance action type".
+- docs/architecture/README.md: dated 2024-04-30, predates React 19, TanStack Query v5,
+ React Router v8 and the Outcomes pillar. Concepts hold, versions do not.
+
+## Local stack
+
+```bash
+cd docker && cp .env.example .env # fill DBSYNC_POSTGRES_*, IPFS_GATEWAY, PDF_API_URL
+docker compose up -d --build
+```
+
+Frontend on 80, backend on 9999 with Swagger at /swagger-ui, metadata-validation on
+3000, outcomes on 3001. Compose also needs govtool/backend/example-config.json,
+govtool/metadata-validation/.env and docker/.envs to exist. See docker/README.md.
+
+Skip all of it for frontend-only work: point VITE_BASE_URL at a deployed environment
+and run npm run dev.
+
+## Style
+
+Match the surrounding file first. Guides in docs/style-guides for react, css-in-js and
+css-sass, all Airbnb-based. Haskell: .stylish-haskell.yaml plus hlint. Prettier: semi
+true, singleQuote false, trailingComma all, printWidth 80.
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 01009d4aa..42df2b777 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,49 +1,47 @@
# Contributing to the `GovTool` project
-โ ๏ธ This is a work in progress document, more instruction on how-to contribute to come!
-
-Contributing todo:
-
-- [ ] Align with latest OSC policies
-- [ ] Refactor to reflect reality
-- [ ] Make more friendly to open source contributors
-
Thanks for considering contributing and helping us on creating GovTool! ๐
-The best way to contribute right now is to try things out and provide feedback, but we also accept contributions to the documentation and the obviously to the code itself.
+The best way to contribute right now is to try things out and provide feedback, but we
+also accept contributions to the documentation and obviously to the code itself.
-This document contains guidelines to help you get started and how to make sure your contribution gets accepted, making you our newest GovTool contributor!
+This document gets you from "I want to help" to a merged pull request.
## Table of Contents
-- [Contributing to the `GovTool` project](#contributing-to-the-govtool-project)
- - [Table of Contents](#table-of-contents)
- - [Code of Conduct](#code-of-conduct)
- - [Ask for Help](#ask-for-help)
- - [Roles and Responsibilities](#roles-and-responsibilities)
- - [I Want To Contribute](#i-want-to-contribute)
- - [Before Submitting a Bug Report](#before-submitting-a-bug-report)
- - [How Do I Submit a Good Bug Report?](#how-do-i-submit-a-good-bug-report)
- - [Your First Code Contribution](#your-first-code-contribution)
- - [Working Conventions](#working-conventions)
- - [Pull Requests](#pull-requests)
- - [Branch Naming](#branch-naming)
- - [Commit Messages](#commit-messages)
- - [Rationale](#rationale)
- - [Merge Commit PRs and Rebase Branches on top of Main](#merge-commit-prs-and-rebase-branches-on-top-of-main)
- - [Rationale](#rationale-1)
- - [Versioning](#versioning)
- - [Changelog](#changelog)
- - [Style Guides](#style-guides)
- - [React](#react)
- - [CSS in Javascript](#css-in-javascript)
- - [CSS / SASS](#css--sass)
- - [Haskell](#haskell)
- - [Development Processes](#development-processes)
- - [Developer workflow](#developer-workflow)
- - [QA Workflow](#qa-workflow)
- - [PO Workflow](#po-workflow)
- - [Tech Lead Workflow](#tech-lead-workflow)
+- [Code of Conduct](#code-of-conduct)
+- [Ask for Help](#ask-for-help)
+- [Ways to Contribute](#ways-to-contribute)
+- [Reporting Bugs](#reporting-bugs)
+ - [Before Submitting a Bug Report](#before-submitting-a-bug-report)
+ - [How Do I Submit a Good Bug Report?](#how-do-i-submit-a-good-bug-report)
+- [Your First Code Contribution](#your-first-code-contribution)
+ - [1. Set up your environment](#1-set-up-your-environment)
+ - [2. Find an issue to work on](#2-find-an-issue-to-work-on)
+ - [3. Claim the issue](#3-claim-the-issue)
+ - [4. Fork the repository, if needed](#4-fork-the-repository-if-needed)
+ - [5. Create a branch](#5-create-a-branch)
+ - [6. Make your changes](#6-make-your-changes)
+ - [7. Run the checks](#7-run-the-checks)
+ - [8. Commit your changes](#8-commit-your-changes)
+ - [9. Update the changelog](#9-update-the-changelog)
+ - [10. Open a pull request](#10-open-a-pull-request)
+ - [11. Address feedback](#11-address-feedback)
+- [Working Conventions](#working-conventions)
+ - [Branches and Environments](#branches-and-environments)
+ - [Pull Requests](#pull-requests)
+ - [Branch Naming](#branch-naming)
+ - [Commit Messages](#commit-messages)
+ - [Merge Commit PRs and Rebase Branches on top of the Base Branch](#merge-commit-prs-and-rebase-branches-on-top-of-the-base-branch)
+ - [Changelog](#changelog)
+ - [Versioning](#versioning)
+ - [Style Guides](#style-guides)
+- [Roles and Responsibilities](#roles-and-responsibilities)
+- [Development Processes](#development-processes)
+ - [Developer workflow](#developer-workflow)
+ - [QA Workflow](#qa-workflow)
+ - [PO Workflow](#po-workflow)
+ - [Tech Lead Workflow](#tech-lead-workflow)
## Code of Conduct
@@ -52,44 +50,58 @@ By participating, you are expected to uphold this code.
## Ask for Help
-See [`SUPPORT.md`](./SUPPORT.md) should you have any questions or need some help in getting set up.
+See [`SUPPORT.md`](./SUPPORT.md) should you have any questions or need some help in
+getting set up. Questions are best asked in
+[GitHub Discussions](https://github.com/IntersectMBO/govtool/discussions), where others
+can benefit from the answer too.
-## Roles and Responsibilities
+Security vulnerabilities are different: do not open an issue, follow
+[`SECURITY.md`](./SECURITY.md) instead.
-We maintain a [CODEOWNERS file](./CODEOWNERS) which provides information who should review a contributing PR.
-Note that you might need to get approvals from all code owners (even though GitHub doesn't give a way to enforce it).
+## Ways to Contribute
-## I Want To Contribute
+- Use GovTool on [mainnet](https://gov.tools/) or [preview](https://preview.gov.tools/) and report what does not work.
+- Improve documentation, in this repository or on [docs.gov.tools](https://docs.gov.tools/).
+- Fix a bug or implement a feature idea, see [Your First Code Contribution](#your-first-code-contribution).
-#### Before Submitting a Bug Report
+## Reporting Bugs
+
+### Before Submitting a Bug Report
A good bug report shouldn't leave others needing to chase you up for more information.
-Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report.
-Please complete the following steps in advance to help us fix any potential bug as fast as possible.
+Therefore, we ask you to investigate carefully, collect information and describe the
+issue in detail in your report.
+Please complete the following steps in advance to help us fix any potential bug as fast
+as possible.
- Make sure that you are using the latest version.
-- Determine if your bug is really a bug and not an error on your side.
+- Determine if your bug is really a bug and not an error on your side,
e.g. using incompatible environment components/versions.
- If you are looking for support, you might want to check [this section](#i-have-a-question).
-- To see if other users have experienced (and potentially already solved) the same issue you are having.
+ If you are looking for support, see [Ask for Help](#ask-for-help).
+- Search the [open issues](https://github.com/IntersectMBO/govtool/issues) to see if
+ other users have experienced (and potentially already solved) the same issue.
- Also make sure to search the internet (including Stack Overflow)
to see if users outside of the GitHub community have discussed the issue.
- Collect information about the bug:
- Stack trace (Traceback)
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
+ - Browser and wallet extension, with versions, for frontend issues
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
- Possibly your input and the output
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
-#### How Do I Submit a Good Bug Report?
+### How Do I Submit a Good Bug Report?
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
-- Open an [Issue](https://github.com/IntersectMBO/govtool/issues/new).
- (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
+- Open an [Issue](https://github.com/IntersectMBO/govtool/issues/new/choose) using the
+ ๐ Bug report template.
+ (Since we can't be sure at this point whether it is a bug or not, we ask you not to
+ talk about a bug yet and not to label the issue.)
- Explain the behavior you would expect and the actual behavior.
- Please provide as much context as possible.
- Describe the _reproduction steps_ that someone else can follow to recreate the issue on their own.
+ Describe the _reproduction steps_ that someone else can follow to recreate the issue
+ on their own.
This usually includes your code.
For good bug reports you should isolate the problem and create a reduced test case.
- Provide the information you collected in the previous section.
@@ -98,78 +110,134 @@ Once it's filed:
- The project team will label the issue accordingly.
- A team member will try to reproduce the issue with your provided steps.
- If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps.
+ If there are no reproduction steps or no obvious way to reproduce the issue, the team
+ will ask you for those steps.
The issue would then be marked as `needs-repro`.
Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
- If the team is able to reproduce the issue, it will be marked `needs-fix`.
It may possibly be marked with other tags (such as `critical`).
The issue will then be left to be [implemented by someone](#your-first-code-contribution).
-#### Your First Code Contribution
+## Your First Code Contribution
+
+Welcome! Whether you're fixing a bug, adding a feature, or improving documentation,
+we're excited to have you on board. The steps below take you through one full
+contribution.
+
+### 1. Set up your environment
+
+Follow [Getting started](./README.md#-getting-started) in the README and set up only the
+package you intend to change:
+
+- UI work: [`govtool/frontend`](./govtool/frontend/README.md), Node is pinned by
+ `.nvmrc` and you can run against a deployed backend, so no local chain data is needed.
+- API work: [`govtool/backend`](./govtool/backend/README.md), needs access to a
+ `cardano-db-sync` Postgres database.
+- Metadata rules: [`govtool/metadata-validation`](./govtool/metadata-validation/README.md).
+- Everything at once: [`docker/`](./docker/README.md).
+
+Make sure you're working from the latest `develop` to avoid potential conflicts.
+
+### 2. Find an issue to work on
+
+- Browse open issues on the [GovTool GitHub Issues page](https://github.com/IntersectMBO/govtool/issues).
+- Look for issues labeled [`๐ Bug`](https://github.com/IntersectMBO/govtool/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%90%9B+Bug%22)
+ or [`๐ก Feature idea`](https://github.com/IntersectMBO/govtool/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%92%A1+Feature+idea%22).
+- For existing feature idea tasks, comment on the issue to express interest or share thoughts.
+- No issue for what you want to change? Open one first, so the approach can be agreed
+ before you write code.
+
+### 3. Claim the issue
+
+- Comment on the issue to let maintainers know you're working on it. This helps avoid
+ duplicate efforts.
+- Move the issue from `todo` to `in progress` on the project board (if you have permissions).
+
+### 4. Fork the repository, if needed
+
+This step applies only if you lack permission to create branches in the `govtool`
+repository. Fork it on GitHub to create a copy under your account.
+
+### 5. Create a branch
+
+Create a new branch from `develop`, never from `main`, with a descriptive name
+(see [Branch Naming](#branch-naming)).
-Welcome to contributing to `GovTool`! Whether you're fixing a bug, adding a feature, or improving documentation, weโre excited to have you on board. This section guides you through your first code contribution to make the process smooth and rewarding.
+```bash
+git checkout develop
+git pull
+git checkout -b feat/123-add-voting-ui
+```
-#### Getting Started
+### 6. Make your changes
-1. **Set Up Your Environment**:
- - Follow the instructions in [`README.md`](./README.md) file and navigate to the specific folder corresponding to the form you want to fix or enhance.
- - Follow the setup instructions to clone the repository and install the necessary dependencies.
- - Make sure you're using the latest version of the project to avoid potential conflicts.
+- Write clean, well-documented code following the [Style Guides](#style-guides) for
+ React, Haskell, CSS, or other relevant technologies.
+- Match the conventions of the files around you.
+- Add or update tests to ensure your changes are robust.
+- Keep your changes focused and aligned with the issue's scope.
-2. **Find an Issue to Work On**:
- - Browse open issues on the [GovTool GitHub Issues page](https://github.com/IntersectMBO/govtool/issues).
- - Look for issues labeled `๐ Bug` or `๐ก Feature idea`.
- - For existing feature idea tasks, comment on the issue to express interest or share thoughts.
+### 7. Run the checks
-3. **Claim an Issue**:
- - Comment on the issue to let maintainers know youโre working on it. This helps avoid duplicate efforts.
- - Move the issue from `todo` to `in progress` on the project board (if you have permissions).
-
+Run the checks for the package you touched before pushing, CI runs the same ones.
-4. **Fork the Repository**:
- - This step applies only if you lack permission to create branches or perform actions in the `govtool` repository.
- - Fork the `govtool` repository on GitHub to create a copy under your account.
+```bash
+# govtool/frontend
+npm run lint && npm run tsc && npx vitest run # note: npm run test is watch mode
+# govtool/metadata-validation
+npm run lint && npm test
-4. **Create a Branch**:
- - Create a new branch from the `develop` branch with a descriptive name (see [Branch Naming](#branch-naming)).
- - Example: `feat/123-add-voting-ui` or `fix/456-update-api-endpoint`.
+# govtool/backend (inside the nix shell)
+cabal build all && pre-commit run --all-files hlint && pre-commit run --all-files stylish-haskell
+```
-5. **Make Your Changes**:
- - Write clean, well-documented code following the [Style Guides](#style-guides) for React, Haskell, CSS, or other relevant technologies.
- - Add or update tests to ensure your changes are robust.
- - Keep your changes focused and aligned with the issueโs scope.
+### 8. Commit your changes
-6. **Commit Your Changes**:
- - Write clear, concise commit messages following the [Commit Messages](#commit-messages) guidelines.
+Write clear, concise commit messages following the [Commit Messages](#commit-messages)
+guidelines, and reference the issue number.
-7. **Submit a Pull Request**:
+### 9. Update the changelog
- `If You Have Push Permissions to the Original Repository`
- - Push your branch to the repository and create a pull request (PR) to the `develop` branch.
+Add a line to [`CHANGELOG.md`](./CHANGELOG.md) under `[Unreleased]`, in the matching
+`Added` / `Fixed` / `Changed` / `Removed` subsection, with a link to the issue. See
+[Changelog](#changelog).
- `If You Donโt Have Push Permissions (Using a Forked Repository)`
- - Push your branch to the forked repository and create a pull request (PR) in the `govtool` main repository, using the `govtool` `develop` branch as the base and your forkโs branch name as the head repository.
+### 10. Open a pull request
- - Use the PR template provided in the repository and link the related issue (e.g., `issues #123`).
- - Describe your changes clearly, including why theyโre needed and how they were tested.
- - If your PR isnโt ready for review, mark it as a draft.
+- If you have push permissions: push your branch to this repository and open a pull
+ request against `develop`.
+- If you work from a fork: push your branch to your fork and open a pull request in the
+ `IntersectMBO/govtool` repository, with `develop` as the base branch and your fork's
+ branch as the head.
+- Fill in the PR template and link the related issue (e.g. `issues #123`).
+- Describe your changes clearly, including why they're needed and how they were tested.
+- If your PR isn't ready for review, open it as a draft.
-8. **Address Feedback**:
- - Expect reviews from maintainers or other contributors (see [CODEOWNERS](./CODEOWNERS)).
- - Respond to feedback promptly and make requested changes.
- - Once approved, your PR will be merged into `develop` by the author after passing tests.
+### 11. Address feedback
-9. **Celebrate Your Contribution!**:
- - Once merged, your changes will move through the [Development Processes](#development-processes) (QA, staging, etc.).
- - Youโre now a `GovTool` contributor! Share your achievement and consider tackling another issue.
+- Expect reviews from maintainers or other contributors (see [CODEOWNERS](./CODEOWNERS)).
+- Respond to feedback promptly and make requested changes.
+- Once approved and green, the PR is merged into `develop` by the author.
+๐ Once merged, your changes move through the [Development Processes](#development-processes)
+(QA, staging, release). You're now a `GovTool` contributor, consider tackling another issue!
## Working Conventions
+### Branches and Environments
+
+Changes flow in one direction through four long-lived branches:
+
+`develop` โ `test` โ `staging` โ `main`
+
+- `develop` is where all contributions land, and it is the base branch for every PR.
+- `test` and `staging` are promotion branches driven by QA and the tech lead.
+- `main` tracks what has been released, never branch off it and never PR into it directly.
+
### Pull Requests
-Thank you for contributing your changes by opening a pull requests!
+Thank you for contributing your changes by opening a pull request!
To get something merged we usually require:
@@ -185,7 +253,7 @@ Please reuse the branch naming for the pull request naming.
### Branch Naming
- When creating your branches please create informative names.
-- Using prefixes such as `feat/`, `fix/`, `chore/`, `docs/` for branch names are a good start.
+- Use prefixes such as `feat/`, `fix/`, `chore/`, `docs/`.
- Using the related issue number after the prefix is required.
Examples:
@@ -198,14 +266,18 @@ Examples:
### Commit Messages
Please make informative commit messages!
-It makes it much easier to work out why things are the way they are when youโre debugging things later.
+It makes it much easier to work out why things are the way they are when you're
+debugging things later.
-A commit message is communication, so as usual, put yourself in the position of the reader: what does a reviewer, or someone reading the commit message later need to do their job?
+A commit message is communication, so as usual, put yourself in the position of the
+reader: what does a reviewer, or someone reading the commit message later need to do their job?
Write it down!
-It is even better to include this information in the code itself, but sometimes it doesnโt belong there (e.g. ticket info).
+It is even better to include this information in the code itself, but sometimes it
+doesn't belong there (e.g. ticket info).
Also, include any relevant meta-information, such as issue numbers.
-If a commit completely addresses a issue, you can put that in the headline if you want, but itโs fine to just put it in the body.
+If a commit completely addresses an issue, you can put that in the headline if you want,
+but it's fine to just put it in the body.
Here are seven rules for great git commit messages:
@@ -217,71 +289,89 @@ Here are seven rules for great git commit messages:
6. Wrap the body at 72 characters (hard limit)
7. Use the body to explain what and why vs. how
-There is plenty to say on this topic, but broadly the guidelines in [this post](https://cbea.ms/git-commit/) are good.
-
-#### Rationale
+There is plenty to say on this topic, but broadly the guidelines in
+[this post](https://cbea.ms/git-commit/) are good.
-Git commit messages are our only source of why something was changed the way it was changed.
-So we better make the readable, concise and detailed (when required).
+**Rationale:** git commit messages are our only source of why something was changed the
+way it was changed. So we better make them readable, concise and detailed (when required).
-### Merge Commit PRs and Rebase Branches on top of Main
+### Merge Commit PRs and Rebase Branches on top of the Base Branch
-When closing branches / PRs use merge commits, so we have a history of PRs also in the git history.
-Do not merge main into side branches, instead rebase them on top of main.
-Try to keep branches up-to-date with main (not strict requirement though).
-Once merged to main, please delete the branch.
+When closing branches / PRs use merge commits, so we have a history of PRs also in the
+git history.
+Do not merge the base branch (usually `develop`) into side branches, instead rebase them
+on top of it.
+Try to keep branches up-to-date with the base branch (not a strict requirement though).
+Once merged, please delete the branch.
-**Tip:** Use Github's merge button in PRs to merge with commit.
-This strategy helps us operate on the commits you've delivered: it's easier to [cherry-pick a merge commit](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--mltparent-numbergt) than a series of commits, and it's also easier to [revert changes using a merge commit](https://git-scm.com/docs/git-revert#Documentation/git-revert.txt--mparent-number) instead of a series of reverts.
-If a branch is outdated, use the rebase button in PRs to rebase feature branches (NOT update via merge).
-
-#### Rationale
-
-Keeping branches ahead of main not only make the git history a lot nicer to process, it also makes conflict resolutions easier.
-Merging main into a branch repeatedly is a good recipe to introduce invalid conflict resolutions and loose track of the actual changes brought by a the branch.
-
-### Versioning
+**Tip:** Use GitHub's merge button in PRs to merge with commit.
+This strategy helps us operate on the commits you've delivered: it's easier to
+[cherry-pick a merge commit](https://git-scm.com/docs/git-cherry-pick#Documentation/git-cherry-pick.txt--mltparent-numbergt)
+than a series of commits, and it's also easier to
+[revert changes using a merge commit](https://git-scm.com/docs/git-revert#Documentation/git-revert.txt--mparent-number)
+instead of a series of reverts.
+If a branch is outdated, use the rebase button in PRs to rebase feature branches (NOT
+update via merge).
-Not all releases are declared stable.
-Releases that aren't stable will be released as pre-releases and will append a -pre tag indicating it is not ready for running on production networks.
+**Rationale:** keeping branches ahead of the base branch not only makes the git history
+a lot nicer to process, it also makes conflict resolutions easier.
+Merging the base branch into a branch repeatedly is a good recipe to introduce invalid
+conflict resolutions and lose track of the actual changes brought by the branch.
### Changelog
-During development, on every PR;
+On every PR, keep [`CHANGELOG.md`](./CHANGELOG.md) up-to-date with a high-level,
+technical, but user-focused list of changes, according to
+[keepachangelog](https://keepachangelog.com/en/1.0.0/).
-- Make sure `CHANGELOG.md` is kept up-to-date with high-level, technical, but user-focused list of changes according to [keepachangelog](https://keepachangelog.com/en/1.0.0/).
-- Bump `UNRELEASED` version in `CHANGELOG.md` according to [semver](https://semver.org/).
+Add your line under `[Unreleased]`, in the `Added`, `Fixed`, `Changed` or `Removed`
+subsection, and link the issue:
-### Style Guides
+```markdown
+### Fixed
-#### React
+- Fix disappearing proposals in the governance actions list for the same tx hashes [Issue 3918](https://github.com/IntersectMBO/govtool/issues/3918)
+```
-Please see [React Style Guide](./docs/style-guides/react/).
+### Versioning
-#### CSS in Javascript
+Do not hand-bump version numbers. `package.json`, `vva-be.cabal` and the Dockerfiles are
+bumped together by the `Update GovTool Version and Changelog` workflow on manual
+dispatch, which also turns the `[Unreleased]` changelog section into a released one.
-Please see [CSS in Javascript Style Guide](./docs/style-guides/css-in-js/).
+Releases follow [semver](https://semver.org/). Not all releases are declared stable:
+releases that aren't stable are published as pre-releases and append a `-pre` tag
+indicating they are not ready for running on production networks.
-#### CSS / SASS
+### Style Guides
-Please see [CSS / SASS Style Guide](./docs/style-guides/css-sass/).
+- React: [React Style Guide](./docs/style-guides/react/)
+- CSS in JavaScript: [CSS in Javascript Style Guide](./docs/style-guides/css-in-js/)
+- CSS / SASS: [CSS / SASS Style Guide](./docs/style-guides/css-sass/)
+- Haskell: [stylish-haskell configuration](./govtool/backend/.stylish-haskell.yaml) plus `hlint`
-#### Haskell
+## Roles and Responsibilities
-Please see [stylish-haskell configuration](./govtool/backend/.stylish-haskell.yaml).
+We maintain a [CODEOWNERS file](./CODEOWNERS) which provides information who should
+review a contributing PR.
+Note that you might need to get approvals from all code owners (even though GitHub
+doesn't give a way to enforce it).
## Development Processes
+These describe how the core team moves a ticket from idea to release. As an external
+contributor you are only involved in the developer workflow.
+
### Developer workflow
- Choose ticket/issue to work on from the project, move ticket from `todo` to `in progress`.
-- Create [well named](#branch-naming) branch from `develop` add changes, then make a pull request back to the `develop` branch.
+- Create [well named](#branch-naming) branch from `develop`, add changes, then make a pull request back to the `develop` branch.
- If the changes are not ready for review then feel free to create a draft PR, and link this to the ticket/issue.
- When the PR is ready for review move the ticket from `in progress` to `in review`. Remember to change the state of the PR from draft to actual PR.
- Developers should review each other's pull requests, and should be requested via [CODEOWNERS](./CODEOWNERS).
- Unit tests are run on each pull request to `develop`.
-- After a review remember to address all the requests of changes since they are blocking PR from being merged.
-- Once tests pass and peer review is done the branch can be merged into `develop` by author and then deployed to the dev environment (manually).
+- After a review remember to address all the requests of changes since they are blocking the PR from being merged.
+- Once tests pass and peer review is done the branch can be merged into `develop` by the author and then deployed to the dev environment (manually).
- The ticket status can then be moved to `in QA` making sure that the PR/branch has been added to the ticket/issue as a comment.
### QA Workflow
@@ -306,3 +396,10 @@ Please see [stylish-haskell configuration](./govtool/backend/.stylish-haskell.ya
- Merge `staging` branch into the `main` branch.
- Deploy tagged build to `beta` environment.
- Move tickets from staging status to done status.
+
+---
+
+This document is still evolving. Known gaps we intend to close:
+
+- [ ] Align with latest OSC policies
+- [ ] Keep the development processes in sync with how the team actually works
diff --git a/README.md b/README.md
index 3ccb1ea55..748a9f81e 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,137 @@
-
-
-
-
-
- Monorepo containing Cardano GovTool and supporting utilities
-
-
-
-
-[](https://www.npmjs.com/package/npm) [](http://makeapullrequest.com) [](https://opensource.org/licenses/Apache-2.0)
-
-[](https://sonarcloud.io/summary/overall?id=intersect-govtool) [](https://sonarcloud.io/summary/overall?id=intersect-govtool) [](https://sonarcloud.io/summary/overall?id=intersect-govtool)
-
-
-
-
-
-## ๐ Purpose
-
-The Cardano GovTool enables ada holders to experience the governance features described in [CIP-1694](https://github.com/cardano-foundation/CIPs/blob/master/CIP-1694/README.md).
-
-### Instances
-
-#### Mainnet
-
-- [gov.tools](https://gov.tools/)
-
-#### Preview Testnet
-
-- [preview.gov.tools](https://preview.gov.tools/)
-
-### Documentation
-
-Learn more; [docs.gov.tools](https://docs.gov.tools/cardano-govtool/using-govtool).
-
-## ๐ Navigation
-
-- [Backend](./govtool/backend/README.md)
-- [Frontend](./govtool/frontend/README.md)
-- [In repo documentation](./docs/)
-- [Tests](./tests/)
-
-### Utilities
-
-- [Governance Action Loader](./gov-action-loader/)
-
-### Backend
-
-GovTool backend implements an API wrapper around an instance of [DB-Sync](https://github.com/IntersectMBO/cardano-db-sync) which interfaces with a [Cardano Node](https://github.com/IntersectMBO/cardano-node).
-The API exposes endpoints making the querying of governance related data from DB-Sync straight forward.
-
-### Frontend
-
-GovTool frontend web app communicates with the backend over a REST interface, reading and displaying on-chain governance data.
-Frontend is able to connect to Cardano wallets over the [CIP-30](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0030/README.md) and [CIP-95](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0095/README.md) standards.
-
-## ๐ณ Running locally with docker
-
-This repository includes a Docker Compose setup for running GovTool services locally.
-For local setup instructions, see the [Docker Compose README](docker/README.md).
-
-## ๐ค Contributing
-
-Thanks for considering contributing and helping us on creating GovTool! ๐
-
-Please checkout our [Contributing Documentation](./CONTRIBUTING.md).
\ No newline at end of file
+
+
+
+
+
+ Monorepo containing Cardano GovTool and supporting utilities
+
+
+
+
+[](https://www.npmjs.com/package/npm) [](http://makeapullrequest.com) [](https://opensource.org/licenses/Apache-2.0)
+
+[](https://sonarcloud.io/summary/overall?id=intersect-govtool) [](https://sonarcloud.io/summary/overall?id=intersect-govtool) [](https://sonarcloud.io/summary/overall?id=intersect-govtool)
+
+
+
+
+
+## ๐ Purpose
+
+The Cardano GovTool enables ada holders to use the governance features described in
+[CIP-1694](https://github.com/cardano-foundation/CIPs/blob/master/CIP-1694/README.md):
+register as a DRep, delegate voting power, submit governance actions and vote on them.
+
+### Instances
+
+- Mainnet: [gov.tools](https://gov.tools/)
+- Preview testnet: [preview.gov.tools](https://preview.gov.tools/)
+
+### Documentation
+
+- User documentation: [docs.gov.tools](https://docs.gov.tools/cardano-govtool/using-govtool)
+- In-repo documentation: [`docs/`](./docs/) - [architecture](./docs/architecture/), [style guides](./docs/style-guides/), [operations](./docs/operations/)
+- AI agent guide: [`AGENTS.md`](./AGENTS.md) - orientation, playbooks and conventions for AI coding agents
+
+## ๐๏ธ Repository layout
+
+| Path | What lives there |
+| --- | --- |
+| [`govtool/frontend`](./govtool/frontend/) | React + TypeScript + Vite web app behind gov.tools. All UI, routes, forms, wallet connection and transaction building. |
+| [`govtool/backend`](./govtool/backend/) | Haskell + Servant read-only REST API over [cardano-db-sync](https://github.com/IntersectMBO/cardano-db-sync). |
+| [`govtool/metadata-validation`](./govtool/metadata-validation/) | NestJS service that validates CIP-100/108/119 off-chain metadata. |
+| [`govtool/analytics-dashboard`](./govtool/analytics-dashboard/) | Next.js internal usage dashboard, not part of the gov.tools user flow. |
+| [`tests`](./tests/) | Playwright end-to-end tests, Python API tests, Gatling load tests, test infrastructure. |
+| [`docker`](./docker/) | Docker Compose setup for running the whole stack locally. |
+| [`gov-action-loader`](./gov-action-loader/) | Dev utility for bulk-submitting governance actions to a testnet. |
+
+The frontend talks to the backend over REST and to Cardano wallets over the
+[CIP-30](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0030/README.md) and
+[CIP-95](https://github.com/cardano-foundation/CIPs/blob/master/CIP-0095/README.md) standards.
+Every on-chain write is a transaction the frontend builds and the wallet signs; the
+backend only reads.
+
+## ๐ Getting started
+
+Pick the smallest setup that covers the change you want to make.
+
+### Frontend only - the fastest path
+
+No Cardano node and no database needed: run the app locally against an already
+deployed backend. This covers most UI work.
+
+```bash
+cd govtool/frontend
+nvm use # Node 22.22.0, pinned by .nvmrc
+npm ci
+cp .env.example .env
+# then set VITE_BASE_URL in .env to a deployed backend,
+# e.g. https://govtool.cardanoapi.io/api
+npm run dev # http://localhost:5173
+```
+
+To connect a wallet you need a browser extension that supports CIP-95, see the
+[compatible wallets list](https://docs.gov.tools/cardano-govtool/using-govtool/getting-started/compatible-wallets).
+More detail in the [frontend README](./govtool/frontend/README.md).
+
+### ๐ณ Whole stack with Docker
+
+Requires Docker and a reachable `cardano-db-sync` Postgres instance.
+
+```bash
+cd docker
+cp .env.example .env # fill in DBSYNC_POSTGRES_*, IPFS_GATEWAY, PDF_API_URL
+docker compose up -d --build
+```
+
+Frontend on port 80, backend on 9999 (Swagger UI at `/swagger-ui`),
+metadata-validation on 3000, outcomes on 3001.
+See the [Docker Compose README](./docker/README.md) for the config files the stack
+expects.
+
+### Backend on its own
+
+Haskell, GHC 9.2.8, needs access to a `cardano-db-sync` Postgres database; a Nix shell
+is the supported setup. See the [backend README](./govtool/backend/README.md).
+
+## โ
Verifying a change
+
+Run the checks for the package you touched, CI runs the same ones.
+
+```bash
+# govtool/frontend
+npm run lint && npm run tsc && npx vitest run # note: npm run test is watch mode
+
+# govtool/metadata-validation
+npm run lint && npm test
+
+# govtool/backend (inside the nix shell)
+cabal build all && pre-commit run --all-files hlint && pre-commit run --all-files stylish-haskell
+```
+
+End-to-end, API and load suites live in [`tests/`](./tests/) and run against a deployed
+environment rather than your working tree.
+
+## ๐ค Contributing
+
+Thanks for considering contributing and helping us on creating GovTool! ๐
+
+Start with the [Contributing Documentation](./CONTRIBUTING.md). The short version:
+
+- Branch off `develop`, never `main`, and name the branch `type/issue-or-feature-description`.
+- Add an entry to [`CHANGELOG.md`](./CHANGELOG.md) under `[Unreleased]` with a link to the issue.
+- Open the PR against `develop` and fill in the PR template.
+
+Good first issues are labelled [`๐ Bug`](https://github.com/IntersectMBO/govtool/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%90%9B+Bug%22)
+or [`๐ก Feature idea`](https://github.com/IntersectMBO/govtool/issues?q=is%3Aissue+is%3Aopen+label%3A%22%F0%9F%92%A1+Feature+idea%22).
+
+## ๐ฌ Support
+
+Questions and setup help: [`SUPPORT.md`](./SUPPORT.md) and
+[GitHub Discussions](https://github.com/IntersectMBO/govtool/discussions).
+Security vulnerabilities: [`SECURITY.md`](./SECURITY.md).
+Everyone participating is expected to follow the [Code of Conduct](./CODE-OF-CONDUCT.md).
+
+## ๐ License
+
+[Apache 2.0](./LICENSE)
diff --git a/docs/ai/ARCHITECTURE_MAP.md b/docs/ai/ARCHITECTURE_MAP.md
new file mode 100644
index 000000000..45c9145a2
--- /dev/null
+++ b/docs/ai/ARCHITECTURE_MAP.md
@@ -0,0 +1,206 @@
+# Architecture map
+
+Orientation only. Conventions and traps are in the per-package AGENTS.md; recipes in
+docs/ai/PLAYBOOKS.md. Paths whose purpose is obvious from the name are omitted.
+
+## Topology
+
+frontend to wallet extension, CIP-30/95: all chain writes happen here
+frontend to backend, REST /api/*: backend is read-only
+frontend to metadata-validation: POST /validate
+frontend to outcomes-pillar-backend and pdf/budget API: external, other repos
+backend to cardano-db-sync Postgres: read-only SQL, TTL-cached
+backend to Pinata: IPFS pin, the one backend write
+cardano-node to cardano-db-sync: follows the chain, not run by us
+
+The backend never writes to chain. Every state change is a transaction the frontend
+builds with cardano-serialization-lib and the wallet signs.
+
+## govtool/frontend
+
+Vite aliases: @ for src, plus @pages @consts @context @hooks @models @services @utils
+@mock, and @atoms @molecules @organisms for src/components/*. There is no @types or
+@config alias, so use @/types/... and @/config/env. Many feature directories expose files through index.ts barrels. When a directory already uses a barrel, add new public exports to it; otherwise follow the surrounding direct-import pattern.
+
+- App.tsx: the route table. Every route registers here.
+- config/env.ts: the only env accessor. Reads window.__ENV__, then import.meta.env.
+- consts/paths.ts: PATHS, PDF_PATHS, OUTCOMES_PATHS, USER_PATHS,
+ BUDGET_DISCUSSION_PATHS
+- consts/queryKeys.ts: QUERY_KEYS and MUTATION_KEYS. Every new key registers here.
+- consts/governanceAction/fields.ts: GA form schemas, driving both rendering and
+ hashing
+- services/API.ts:is the shared Axios instance for the GovTool backend and redirects backend HTTP 500 responses to the error
+ page.Metadata validation uses a separate Axios client because it has a separate base URL.
+- i18n/locales/en.json: is the only locale and should be the source of new user-facing copy. Some existing hardcoded strings
+ remain.
+- components: atomic design. atoms have no GovTool dependencies, molecules compose
+ atoms without fetching, organisms are feature blocks that may use contexts and hooks.
+
+Contexts in src/context:
+
+- wallet.tsx: about 1600 lines. CIP-30/95 connection plus every buildXCert and
+ buildXGovernanceAction, funnelling into buildSignSubmitConwayCertTx. The chain-write
+ boundary.
+- appContext.tsx: protocol phase. isInBootstrapPhase, isFullGovernance,
+ isAppInitializing.
+- featureFlag.tsx: the VITE_IS_X_ENABLED flags, and phase-derived visibility for which
+ vote totals show for which GA type
+- pendingTransaction: in-flight txs, so the UI can poll /transaction/status/:txId
+- governanceAction.tsx: GA list and detail state
+- contextProviders.tsx: composes the main application providers. ChatwootProvider is mounted separately in main.tsx, and
+ pending-transaction state is integrated through the wallet context.
+
+## govtool/backend
+
+Layers, outermost first: route in src/VVA/API.hs, handler in the same file, the
+domain function in the matching src/VVA module using withPool plus a query, then
+sql/*.sql.
+
+Handlers return API types from API/Types.hs; domain modules return internal types from
+Types.hs. The explicit mapping between them keeps the public OpenAPI schema decoupled
+from db-sync's shape. Do not shortcut it.
+
+- app/Main.hs: config, connection pool, CacheEnv, CORS, Sentry, error-to-status map,
+ Swagger mount
+- src/VVA/API.hs: the VVAApi route type and every handler
+- src/VVA/Types.hs: internal types, the App monad, AppError, CacheEnv
+- src/VVA/Pool.hs: withPool, borrow a connection
+- src/VVA/Cache.hs: cacheRequest, the only caching primitive
+- src/VVA/Ipfs.hs: Pinata upload, the only write path
+- sql/views.sql: shared views the other queries build on
+- Domain modules: DRep, AdaHolder, Proposal, Epoch, Transaction, Network, Account
+- Config.hs uses Conferer; CommandLine.hs defines -c/--config, start-app, show-config
+
+Route groups. Read src/VVA/API.hs line 59, or /swagger-ui, for the authoritative list:
+/drep/* for list, info, getVotes, get-voting-power and voting-power-list;
+/ada-holder/* for get-current-delegation and get-voting-power; /proposal/* for list,
+get and enacted-details; /epoch/params; /transaction/status;
+/network/metrics, /network/info, /network/total-stake; /account/:stakeKey;
+/ipfs/upload; and /throw500 as a deliberate error probe.
+
+Error to status, in handleErrors in app/Main.hs: ValidationError 400, NotFoundError
+404, CriticalError and InternalError 500, AppIpfsError OtherIpfsError 400, other
+AppIpfsError 503.
+
+CacheEnv is built once in app/Main.hs, each field a Data.Cache with a TTL from config.
+cachedurationseconds defaults to 20s, dreplistcachedurationseconds to 600s.
+
+Pool is 2 stripes of 60 connections with a 1s idle timeout; Warp has a 300s request
+timeout and 60s graceful shutdown. Long queries are a real failure mode, so push work
+into SQL and cache the result rather than holding a connection.
+
+## govtool/metadata-validation
+
+A NestJS service that validates off-chain metadata. It fetches metadata from a URL,
+parses the JSON, accepts a supplied CIP standard or attempts to identify CIP-108 or
+CIP-119, validates the required fields for a recognized standard, and compares a
+Blake2b-256 hash of the exact fetched content with the submitted hash.
+
+POST /validate is defined in app.controller.ts, orchestration is handled by
+app.service.ts, and validation logic lives in utils/getStandard.ts,
+utils/validateCIP108body.ts, and utils/validateMetadataStandard.ts.
+enums/ValidationError.ts defines the validation statuses consumed by the frontend,
+so check frontend usage before renaming or removing one.
+
+Both metadata validation and the frontend contain utils/canonizeJSON.ts. The
+metadata-validation copy is currently unused by the production validation flow. The
+frontend uses canonicalization for signature verification, not for the metadata hash submitted to POST /validate.
+
+## Environment variables
+
+### Frontend application configuration
+
+The frontend reads configuration from window.__ENV__ when running in a container,
+falling back to Vite's import.meta.env values for local development and build-time
+configuration.
+
+When adding a frontend runtime variable, update all applicable configuration points:
+
+1. govtool/frontend/.env.example
+2. govtool/frontend/src/config/env.ts
+3. govtool/frontend/docker-entrypoint.sh
+4. The govtool-frontend service in docker/docker-compose.yaml
+
+Active frontend variables:
+
+- VITE_BASE_URL: GovTool backend REST API base URL.
+- VITE_METADATA_API_URL: metadata-validation service base URL.
+- VITE_PDF_API_URL: Proposal Discussion and Budget Discussion API base URL.
+- VITE_OUTCOMES_API_URL: Governance Outcomes API base URL.
+- VITE_IPFS_GATEWAY: gateway used to resolve ipfs:// resources.
+- VITE_IPFS_PROJECT_ID: optional project identifier sent when accessing the
+ configured IPFS gateway.
+- VITE_NETWORK_FLAG: Cardano network ID; 0 selects a test network and 1
+ selects mainnet.
+- VITE_APP_ENV: deployment environment name supplied to services such as Sentry.
+- VITE_IS_DEV: enables development behavior, including React Query devtools and
+ bypassing production maintenance checks.
+- VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED: enables the Proposal Discussion
+ pillar.
+- VITE_IS_GOVERNANCE_OUTCOMES_PILLAR_ENABLED: enables the Governance Outcomes
+ pillar.
+- VITE_SENTRY_DSN: optional Sentry data source name.
+- VITE_CHATWOOT_URL: base URL from which the Chatwoot SDK is loaded.
+- VITE_CHATWOOT_WEBSITE_TOKEN: Chatwoot website token used to initialize the
+ feedback widget.
+
+### Frontend container and analytics configuration
+The frontend container also accepts variables that are handled by
+docker-entrypoint.sh and Nginx rather than exposed to React through window.__ENV__:
+
+- UMAMI_URL: base URL of the Umami analytics service.
+- UMAMI_WEBSITE_ID: Umami website identifier.
+- UMAMI_SSL_VERIFY: controls TLS certificate verification for the Umami proxy;
+ defaults to true.
+- TRUSTED_PROXY_CIDRS: trusted proxy address ranges used when resolving the
+ original client IP. Set it to none to disable real-IP resolution.
+- REAL_IP_HEADER: header used to obtain the original client IP; defaults to
+ X-Forwarded-For.
+
+Umami is enabled only when both UMAMI_URL and UMAMI_WEBSITE_ID are configured.
+
+### Metadata-validation service
+
+The metadata-validation service reads these variables directly from process.env:
+
+- PORT: HTTP port used by the NestJS service.
+- IPFS_GATEWAY: gateway used to resolve ipfs:// metadata URLs.
+- IPFS_PROJECT_ID: optional project identifier sent to the configured IPFS
+ gateway.
+
+The local template is govtool/metadata-validation/.env.example.
+
+### Backend configuration
+
+The Haskell backend loads configuration from example-config.json, or from the file
+supplied with --config / -c. Conferer also supports environment-variable overrides
+prefixed with VVA_.
+
+Backend configuration keys:
+
+- dbsyncconfig.host: cardano-db-sync PostgreSQL host.
+- dbsyncconfig.dbname: database name.
+- dbsyncconfig.user: database user.
+- dbsyncconfig.password: database password.
+- dbsyncconfig.port: PostgreSQL port.
+- port: backend HTTP port; example-config.json uses 9999.
+- host: backend bind address.
+- cachedurationseconds: default endpoint-cache lifetime in seconds.
+- dreplistcachedurationseconds: DRep-list cache lifetime in seconds.
+- pinataapijwt: optional Pinata API JWT used by the IPFS upload endpoint.
+- sentrydsn: backend Sentry data source name.
+- sentryenv: backend Sentry environment name.
+
+## CI gates, in .github/workflows
+
+- code_check_frontend.yml: on push touching govtool/frontend. Runs npm run test,
+ npm run lint and npm run tsc as parallel jobs.
+- code_check_backend.yml: on push touching govtool/backend. hlint then
+ stylish-haskell via pre-commit.
+- pr.yaml: on PR to develop, test, staging or main. hadolint per Dockerfile, optional
+ lint.sh and unit-test.sh per package, docker build, Dockle image scan.
+- test_storybook.yml: Storybook must build
+- test_integration_playwright.yml and test_backend.yml: E2E and Python API tests
+ against a deployment
+- frontend_sonar_scan.yml, lighthouse.yml: SonarCloud, Lighthouse
+- update-govtool-version.yml: manual dispatch, bumps every version. Never by hand.
diff --git a/docs/ai/PLAYBOOKS.md b/docs/ai/PLAYBOOKS.md
new file mode 100644
index 000000000..aa2dee788
--- /dev/null
+++ b/docs/ai/PLAYBOOKS.md
@@ -0,0 +1,219 @@
+# Playbooks
+
+Which files to touch, per change type. Conventions are in the per-package AGENTS.md;
+this is the sequence. Where a file already shows the pattern, this points at it
+rather than reproducing it, so the exemplar cannot go stale.
+
+## Frontend: consume a new backend endpoint
+
+Exemplar: getNetworkMetrics and useGetNetworkMetrics. Four files:
+
+1. src/models/api.ts: response type, fields snake_case, mirroring backend JSON
+2. src/services/requests/getThing.ts: via the shared API instance, never axios
+3. src/consts/queryKeys.ts: add to QUERY_KEYS
+4. src/hooks/queries/useGetThingQuery.ts
+
+Plus each folder's index.ts. TanStack Query v5 takes object syntax only:
+useQuery({ queryKey, queryFn }). The positional v3/v4 form will not compile. Every
+value the query depends on goes in the queryKey, not just the closure: see
+useGetProposalQuery, keyed on [key, dRepID, proposalId]. Return a named object like
+{ networkMetrics, fetchNetworkMetrics }, not raw { data, refetch }.
+
+## Frontend: add a page or route
+
+1. src/consts/paths.ts: camelCase key, snake_case URL, :param for params. PATHS for
+ the core app; PDF_PATHS, OUTCOMES_PATHS, USER_PATHS, BUDGET_DISCUSSION_PATHS for
+ pillars.
+2. src/pages/MyPage.tsx
+3. src/App.tsx: where you register decides access. Top level is public, inside the
+ Dashboard element route gets connected chrome, wrapped in PublicRoute redirects
+ when a wallet is connected.
+
+Route placement controls layout and connected variants, but does not by itself define
+all access requirements:
+
+- Public browsing routes are registered at the top level.
+- Routes nested under Dashboard receive the connected dashboard layout.
+- PublicRoute currently redirects public DRep-directory routes to their
+ /connected equivalents when a wallet is connected.
+- Wallet-dependent transaction pages, including registration and governance-action
+ creation, are currently top-level routes and enforce their requirements through
+ their page and wallet logic.
+Keep the page thin. It should wire hooks and state to feature components; follow the
+surrounding page and organism structure.
+
+## Frontend: add a component
+
+Choose the existing component layer whose responsibilities most closely match the new
+component. The atoms, molecules and organisms directories express increasing feature
+composition, but the boundaries are not strict: existing atoms and molecules may use
+shared hooks or contexts. Match nearby components before introducing a new layering
+rule.
+For a reusable component, add or update a Storybook story under src/stories, using
+.stories.ts or .stories.tsx depending on whether the story contains JSX.
+Storybook build and interaction tests run in CI.
+
+## Frontend: add user-facing copy
+
+src/i18n/locales/en.json, read with t("key"), or I18n.t("key") outside components as
+in consts/governanceAction/fields.ts. Interpolation is {{name}} with
+t("key", { name }). No hardcoded strings in JSX.
+
+## Frontend: add an environment variable
+
+A new React-visible `VITE_` runtime variable is complete only after updating all four
+applicable configuration points:
+
+1. .env.example
+2. src/config/env.ts, as MY_VAR: getEnv("VITE_MY_VAR")
+3. docker-entrypoint.sh, into the window.__ENV__ block.
+4. docker/docker-compose.yaml, passed into the govtool-frontend service
+
+The entrypoint is easy to miss: without runtime injection, a variable may work through
+import.meta.env during local development but be undefined in a deployed container.
+
+Container-only variables such as Umami and Nginx proxy configuration do not belong in
+src/config/env.ts or window.__ENV__; document and wire those through
+docker-entrypoint.sh and Compose instead.
+
+## Frontend: add a feature flag
+
+In src/context/featureFlag.tsx: add to FeatureFlagContextType, the createContext
+default, and the useMemo value. A deploy toggle is a VITE_IS_X_ENABLED var, so do the
+env-var recipe first, and compare against both the string "true" and boolean true
+since runtime injection can give either. A protocol-phase toggle derives from
+appContext's isInBootstrapPhase or isFullGovernance as a useCallback returning a
+predicate; exemplar areDRepVoteTotalsDisplayed.
+
+## Frontend: build a new transaction or certificate
+
+Shared Cardano serialization, wallet interaction and transaction submission live in
+src/context/wallet.tsx. Form-specific validation and selection of the appropriate
+builder remain in the calling form hook or page.
+
+Add the appropriate builder beside its siblings:
+- buildXCert for certificates
+- buildXGovernanceAction for governance-action proposal builders
+- A voting builder for votes
+
+Expose the builder through CardanoContextType and the provider value. Pass the
+result to buildSignSubmitConwayCertTx through the matching argument:
+certBuilder, govActionBuilder or votingBuilder.
+
+buildSignSubmitConwayCertTx owns maintenance checks, UTxO selection, change,
+signing, submission and pending-transaction registration. Supply the correct
+transaction type and, when applicable, resourceId; it records the submitted hash
+so usePendingTransaction can poll /transaction/status/:txId.
+
+Extend an existing builder rather than adding a parallel path, and verify on a
+testnet. This code moves real ada and has no unit tests.
+
+## Frontend: write a unit test
+
+Utils go in src/utils/tests/.test.ts and normally import the
+utility from the parent barrel with from "..". Other tests are generally colocated
+with the code under test, for example context/featureFlag.test.tsx.
+
+Run a focused test with npx vitest run .Coverage comes from components, consts, context, hooks,
+services and utils; pages and models are excluded, so do not chase coverage there.
+
+## Backend: add a REST endpoint
+
+Copy the shape of sql/get-network-total-stake.sql, then Network.networkTotalStake,
+then getNetworkTotalStake in API.hs. Six edits, all required:
+
+1. sql/get-my-thing.sql, positional ? placeholders
+2. vva-be.cabal, the new file under extra-source-files
+3. src/VVA/Types.hs, internal type
+4. the matching src/VVA module, domain function. Copy the header of src/VVA/Network.hs
+ TemplateHaskell pragma and local sqlFrom. A new module also needs exposed-modules.
+5. src/VVA/API/Types.hs, response type with fields prefixed by the type name and
+ ToJSON, FromJSON and ToSchema instances. ToSchema is what puts it in Swagger.
+6. src/VVA/API.hs, route on VVAApi and handler, in matching positional order
+
+Consequences of getting 2 or 6 wrong are in govtool/backend/AGENTS.md under "rules
+that will bite you". Then mirror it on the frontend and add a case to
+tests/govtool-backend/test_cases.
+
+## Backend: add caching to an endpoint
+
+Add a field to CacheEnv in src/VVA/Types.hs, initialize it in the cacheEnv
+block in `app/Main.hs`, and use it at the handler call site.
+
+Use newCache for the standard configured TTL. Use the DRep-list TTL only when the
+endpoint is deliberately intended to share that longer cache duration; rename or add
+a dedicated constructor binding when introducing another cache category.
+
+```haskell
+CacheEnv {myThingCache} <- asks vvaCache
+cacheRequest myThingCache cacheKey $ do ...
+```
+
+## Backend: change a db-sync query
+
+Change a SELECT list and you must change the domain function's
+case result of [(a, b, c)] pattern in lockstep. Decoding is positional, so the same
+arity in the wrong order compiles and fails at runtime. sql/views.sql holds shared
+views the other queries build on. There is no local fixture: validate against a
+db-sync instance, and say so if you could not.
+
+## Add a new governance action type
+
+docs/operations/HANDLE_NEW_GOVERNANCE_ACTION_TYPE.md covers this but its paths and
+line numbers are stale.A genuinely new governance-action type can affect both frontend and backend.
+
+Frontend locations:
+
+1. src/types/governanceAction.ts: add the GovernanceActionType member, field
+ types, schema extending SharedGovernanceActionFieldSchema, and the schema union
+ member.
+2. src/consts/governanceAction/fields.ts: define the fields, meaning components,
+ i18n keys and validation rules.
+3. src/consts/governanceAction/filters.ts: add the type when it should appear in
+ governance-action filters.
+4. src/i18n/locales/en.json: add every label, placeholder, tip, error and display
+ key used by the new type.
+5. src/utils: add any custom validation and update exhaustive mappings such as
+ getGovActionVotingThresholdKey.ts.
+6. src/context/featureFlag.tsx: define voting and vote-total behavior for bootstrap
+ and full-governance phases.
+7. src/context/wallet.tsx: add the Cardano serialization builder and expose it
+ through the wallet context.
+8. src/hooks/forms/useCreateGovernanceActionForm.ts: add the new type to the
+ buildTransaction switch and construct the builder arguments.
+9. Update details rendering, tests and Storybook fixtures wherever behavior differs
+ by governance-action type.
+
+Backend locations:
+
+1. src/VVA/API/Types.hs: add the type to GovernanceActionType. This affects JSON,
+ query-parameter parsing and the OpenAPI enum.
+2. Review src/VVA/API.hs filtering, response conversion and enacted-details logic
+ for type-specific mappings.
+3. Update backend API tests and response examples that enumerate or assume the
+ existing action types.
+
+Verify creation at /create_governance_action, backend filtering and response
+decoding, voting behavior, details rendering and protocol phase visibility.Add
+frontend unit tests and backend/E2E coverage where applicable.
+Actions must comply with CIP-100 and CIP-108.
+Afterward, update the stale operations document rather than preserving conflicting
+paths.
+
+## Change metadata validation rules
+
+File layout and the two cross-package constraints are in docs/ai/ARCHITECTURE_MAP.md
+under govtool/metadata-validation. Verify with npm run lint && npm test.
+
+## Add E2E coverage
+
+tests/govtool-frontend/playwright, against a deployed environment. The spec goes in
+the numbered feature folder that matches; reuse lib/pages page objects over raw
+selectors and lib/datafactory for data. Setup and the wallet-bootstrap dependency
+chain are in tests/AGENTS.md.
+
+## Finishing any change
+
+Run the checks for what you touched, listed in AGENTS.md under Verification. Frontend
+needs lint, tsc and vitest run all green. Add the CHANGELOG.md entry under
+"[Unreleased]" with an issue link. Update any doc your change contradicts.
diff --git a/govtool/backend/AGENTS.md b/govtool/backend/AGENTS.md
new file mode 100644
index 000000000..38edf8193
--- /dev/null
+++ b/govtool/backend/AGENTS.md
@@ -0,0 +1,50 @@
+# AGENTS.md - govtool/backend
+
+A read-only Haskell REST API over cardano-db-sync. Servant + warp, postgresql-simple,
+Conferer config, Data.Cache TTL caching, Sentry. Its only write is pinning to IPFS via
+Pinata.
+
+The executable is still vva-be, the historical Voltaire Voting App name. Renaming it
+to govtool-backend is a known outstanding chore; see README.md.
+
+## Commands
+
+```bash
+direnv allow # from this directory, after filling .envrc
+cabal update && cabal build all
+cabal run vva-be -- --config CONFIG.json start-app # or show-config
+pre-commit run --all-files hlint # <- CI gate
+pre-commit run --all-files stylish-haskell # <- CI gate
+```
+
+GHC 9.2.8 and cabal 3.8.1.0, which is what CI uses. Plain cabal outside nix
+historically fails on openapi3, hence nix plus direnv. stylish-haskell reads
+.stylish-haskell.yaml here and produces the aligned import and record blocks you see
+everywhere, so match the surrounding formatting: a reformat-everything diff will fail
+review.
+
+CONFIG.json above is a file you create by copying example-config.json. Conferer also accepts
+environment variable overrides prefixed with VVA_. Swagger UI is at /swagger-ui, generated
+from the VVAApi type, declaring /api and / as server bases.
+
+## Rules that will bite you
+
+1. Every .sql file used with embedFile must be listed in vva-be.cabal under extra-source-files. The
+ sqlFrom embedFile call is a compile-time splice, so an unlisted file breaks the
+ build and the release tarball even when a local build works.
+2. A new module must be added to exposed-modules in the cabal file.
+3. The :<|> order in server must match the VVAApi type positionally. A mismatch
+ produces a wall of Servant types, so check ordering before anything else.
+4. Postgres decoding is positional. Change a SELECT list and you must change the
+ case result of [(a, b, c)] pattern in lockstep; the same arity in the wrong order
+ compiles and fails at runtime.
+5. A cache key must include every parameter that affects the result, or one user's
+ response is served to another.
+6. A new cache is two edits: a CacheEnv field in Types.hs,initialisation in
+ app/Main.hs and use at the relevant handler call site.
+7. Throw the right AppError. The HTTP status is derived from the constructor.
+8. CORS allows only GET, HEAD, POST and OPTIONS, set in vvaCorsResourcePolicy in
+ app/Main.hs. A PUT or DELETE route needs that widened too.
+9. Keep API types and internal types separate. Handlers return API/Types.hs types,
+ domain modules return Types.hs types. That mapping is what keeps the OpenAPI schema
+ decoupled from db-sync's shape.
diff --git a/govtool/backend/CLAUDE.md b/govtool/backend/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/govtool/backend/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/govtool/frontend/AGENTS.md b/govtool/frontend/AGENTS.md
new file mode 100644
index 000000000..864e0f321
--- /dev/null
+++ b/govtool/frontend/AGENTS.md
@@ -0,0 +1,67 @@
+# AGENTS.md - govtool/frontend
+
+The gov.tools web app. React 19 + TS + Vite 6, MUI 5 + Emotion, TanStack Query v5,
+React Router v8, react-hook-form + yup, i18next, cardano-serialization-lib.
+
+## Commands
+
+```bash
+npm ci # run this first if anything looks mysteriously missing
+npm run dev # vite, port 5173
+npm run lint # eslint --max-warnings 0 <- CI gate
+npm run tsc # tsc --noEmit --skipLibCheck <- CI gate
+npx vitest run [path] # one-shot tests <- CI gate
+npm run format # prettier --write src
+npm run storybook # port 6006
+```
+
+npm run test is watch mode and will not exit, so use npx vitest run. Coverage is
+enabled in vite.config.ts, so every run emits coverage; that is expected.
+
+Node is pinned to 22.22.0 by .nvmrc. Setup is cp .env.example .env plus VITE_BASE_URL,
+which can be any deployed backend, and VITE_METADATA_API_URL. No local backend is
+needed for UI work. postinstall runs patch-package against patches, so add a patch
+rather than editing node_modules.
+
+## Conventions
+
+Import through the Vite aliases, and add every new file to its folder's index.ts
+barrel or it will not resolve through them. Components are arrow functions, one per
+file, PascalCase, layered atoms then molecules then organisms by composition; pages
+stay thin. Data fetching goes only through hooks/queries, then services/requests, then
+services/API.ts: never axios directly, never a fetch inside a component. Copy comes
+only from i18n/locales/en.json, env only from the env object in config/env.ts. Style
+with MUI sx or Emotion using theme.ts and consts/colors.ts; no new stylesheets.
+
+## Lint
+
+Airbnb plus TS, react-hooks, jest and storybook. Turned off locally:
+react-hooks/exhaustive-deps, no-nested-ternary, no-param-reassign, no-plusplus,
+import/prefer-default-export. Still errors, and easy to trip:
+
+- no-console: only console.warn and console.error
+- @typescript-eslint/no-unused-vars, no-shadow, no-redeclare
+- react/jsx-filename-extension: JSX only in .tsx
+- react/function-component-definition: arrow functions only, so a function declaration
+ for a component is an error
+- import/no-extraneous-dependencies: devDependencies only in test and stories files
+- semi always, linebreak-style unix
+
+## Traps
+
+- src/context/wallet.tsx is the chain-write boundary: about 1600 lines, no tests, and
+ it moves real ada. Extend an existing buildX function and verify on a testnet.
+- Recent migrations changed import paths. Use react-router, not react-router-dom, for
+ v8, and @tanstack/react-query, not react-query, for v5 with object-syntax useQuery
+ only. The old forms do not compile.
+- The pillars are npm packages, @intersect.mbo/pdf-ui and
+ @intersect.mbo/govtool-outcomes-pillar-ui, mounted as routes. A bug inside them
+ cannot be fixed from this repo.
+- src/utils/canonizeJSON.ts is duplicated at
+ govtool/metadata-validation/src/utils/canonizeJSON.ts and must behave identically,
+ or metadata hash checks diverge. Change one, change both.The metadata-validation copy is currently unused. The frontend copy is used for signature verification.
+- Stale local node_modules is common. If npm run tsc cannot find a module that is in
+ package.json, run npm ci before debugging further.
+- Adding a VITE_ variable touches .env.example, src/config/env.ts,
+ docker-entrypoint.sh, and docker/docker-compose.yaml. Missing the runtime
+ wiring can make it work locally but remain undefined in containers.
diff --git a/govtool/frontend/CLAUDE.md b/govtool/frontend/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/govtool/frontend/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/tests/AGENTS.md b/tests/AGENTS.md
new file mode 100644
index 000000000..fa19a2c8b
--- /dev/null
+++ b/tests/AGENTS.md
@@ -0,0 +1,77 @@
+# AGENTS.md - tests
+
+Four suites, none of which is the frontend unit-test suite. That one lives in
+govtool/frontend, runs with npx vitest run, and is what gates frontend PRs.
+
+- govtool-frontend/playwright: Playwright + TS E2E against a deployed GovTool
+- govtool-backend: Python + pytest against a deployed backend /api
+- load-testing: Gatling + Maven, JDK 17, against a deployed /api
+- test-metadata-api: Node support service that hosts JSON metadata during tests
+- test-infrastructure: compose files and scripts that deploy the test stack
+
+Every suite here needs a running deployment, a real testnet, funded wallets and API
+keys. None runs offline, so do not claim one did.
+
+## Which suite gets my change
+
+- A util, hook, component, context or service: govtool/frontend vitest, not here
+- A backend endpoint or response shape: govtool-backend
+- A user-visible flow such as registration, delegation, voting or submission: the
+ matching numbered folder in govtool-frontend/playwright/tests
+- Metadata validation rules: govtool/metadata-validation jest tests
+
+## Playwright E2E
+
+```bash
+cp .env.example .env && npm install && npx playwright install
+npm run test # all, UI mode; npm run test:headless for CI-style
+npm run test:delegation-pillar / :voting-pillar / :proposal-pillar / :outcomes
+npm run allure:serve # report
+```
+
+lib/constants/environments.ts is the source of truth for env. Required: HOST_URL, the
+deployed frontend under test, with the README listing dev, QA, preview, pre-prod and
+mainnet URLs; NETWORK, preview by default, which derives the Blockfrost, faucet and
+Kuber URLs and networkId; BLOCKFROST_API_KEY for the matching network; KUBER_API_KEY;
+and FAUCET_ADDRESS, FAUCET_PAYMENT_PRIVATE and FAUCET_STAKE_PRIVATE from
+npm run generate-faucet-wallet. Optional: TX_TIMEOUT default 240000, TEST_WORKERS,
+CARDANOAPI_METADATA_URL, DOCS_URL, CI.
+
+Fund the faucet wallet. An unfunded one produces wallet-bootstrap failures that read
+as flaky tests. Timeout is 90s per test, 180s on preview, with zero retries, so a
+failure is real - but so is an empty wallet.
+
+lib/pages holds page objects; use those over raw selectors. lib/_mock holds JSON
+fixtures. The fake CIP-30/95 wallet is not local: it is the npm dependency
+@cardanoapi/cardano-test-wallet, injected by lib/fixtures/loadExtension.ts from
+node_modules/@cardanoapi/cardano-test-wallet/script.js, so wallet behaviour changes
+mean bumping that package, not editing this repo. The package-wallet script in
+package.json is dead - it points at lib/_mock/cardano-test-wallet/types.ts, which no
+longer exists. playwright.config.ts wires the setup and teardown
+files as Playwright projects with dependencies: wallet bootstrap, then auth setups,
+then feature suites, then teardowns. A new suite needing an authenticated wallet
+declares that dependency instead of duplicating login. Details in
+govtool-frontend/playwright/DEVELOPERS_GUIDE.md and
+govtool-frontend/playwright/README.md.
+
+## Backend API tests
+
+```bash
+python -m venv venv && source venv/bin/activate && pip install -r requirements.txt
+export KUBER_API_URL=โฆ KUBER_API_KEY=โฆ && python3 ./setup.py # writes test_data.json
+export BASE_URL=โฆ/api METRICS_URL=โฆ METRICS_API_SECRET=โฆ && pytest -v
+```
+
+setup.py registers the DReps and stakes the tests assume, and needs its main wallet
+funded; the address is in govtool-backend/README.md. Add a case to test_cases whenever
+you add or change a public backend endpoint.
+
+## Load tests and infrastructure
+
+load-testing needs JDK 17: export API_URL then run via mvnw. Not in PR CI.
+
+test-infrastructure stands up cardano-node, db-sync, govtool and gov-action-loader.
+gen-configs.sh renders configs_template and secrets_template into the rendered yml
+files. Env files here hold secrets, so check .gitignore before adding one. An
+untracked .env-mainet currently exists in this working tree and should not be
+committed.
diff --git a/tests/CLAUDE.md b/tests/CLAUDE.md
new file mode 100644
index 000000000..43c994c2d
--- /dev/null
+++ b/tests/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md