Skip to content

governance: RFC-0001 — M013 fee split (28/25/45/2 → 15/30/50/5)#108

Open
brawlaphant wants to merge 8 commits into
regen-network:mainfrom
brawlaphant:feat/rfc-0001-m013-fee-split
Open

governance: RFC-0001 — M013 fee split (28/25/45/2 → 15/30/50/5)#108
brawlaphant wants to merge 8 commits into
regen-network:mainfrom
brawlaphant:feat/rfc-0001-m013-fee-split

Conversation

@brawlaphant

Copy link
Copy Markdown
Contributor

First real test of the RFC governance pipeline proposed in #107 (deliberation chamber: Spec → RFC → on-chain proposal).

What this is

Evidence cited in the RFC

  • cadCAD simulation sweep + equilibrium analysis (220.42M supply at 15% burn)
  • docs/governance/needs-governance-proposals.md Proposal A (existing WG risk matrix + voting guidance)
  • M012/M013 mechanism specs

Status

draft — sponsor required before this moves to the next lifecycle stage. I'm tagged as the accountable party (stewarding the Tokenomics WG as of 2026-06-24). This is a proposal for the WG to react to, not a ratified decision — open questions are tracked at the bottom of the RFC (sponsor confirmation, community preference validation, review cadence).

Built by an AI agent working from the existing merged mechanism contracts + your own governance docs as source of truth; I reviewed it before opening this PR. Flagging that origin clearly so reviewers weight it accordingly.

brawlaphant and others added 8 commits April 11, 2026 11:13
Mirrors the AGENT-002 Governance Analyst structure to give AGENT-003
Market Monitor the same full standalone TypeScript implementation that
AGENT-002 has:

- `agent-003-market-monitor/` — standalone Node.js process
- `src/index.ts` — banner, cycle runner, polling vs single-run mode
- `src/config.ts` — config + thresholds mirrored from the character
- `src/types.ts` — ecocredit marketplace types + per-workflow shapes
- `src/ledger.ts` — LCD client for ecocredit + marketplace endpoints
- `src/store.ts` — SQLite state (trade obs, anomaly dedupe, liquidity
  snapshots, retirement summaries, workflow executions)
- `src/ooda.ts` — generic OODA executor (same shape as agent-002)
- `src/monitor.ts` — Claude narrative layer, one function per workflow
- `src/output.ts` — console + optional Discord webhook dispatcher
- `src/workflows/price-anomaly-detection.ts` — WF-MM-01
- `src/workflows/liquidity-monitor.ts` — WF-MM-02
- `src/workflows/retirement-tracking.ts` — WF-MM-03

Design decisions documented in the agent README:
1. Deterministic numbers, narrative-only LLM calls. All severity
   classification, z-score computation, liquidity health scoring, and
   demand index derivation happen locally. Claude only writes the
   report from those numbers. Keeps the agent cheap, reproducible,
   and auditable.
2. Sell-order-as-trade proxy for the MVP until Ledger MCP exposes
   filled-trade events. The code path swaps cleanly once real trade
   events are available.
3. Batch-supply delta as the retirement source for the MVP, capped
   at 100 most recent batches per cycle to protect the LCD. A
   follow-up PR will plug in a real MsgRetire tx-stream client.
4. Alert dedupe by (trade_id, severity) — a trade that escalates from
   WARNING to CRITICAL still fires a new alert; a same-severity
   re-alert does not.

Thresholds (`config.market.*`) mirror the character definition at
`agents/packages/agents/src/characters/market-monitor.ts` so downstream
tooling has a single source of truth. Anomaly severity boundaries
(WARNING >= 2.0, CRITICAL >= 3.5 z-score) and the liquidity depth
floor live in `config.ts` and are referenced from the system prompt.

CI: the new agent is added to the `agents` job so `npx tsc --noEmit`
runs against it on every PR, matching how agent-002-governance-analyst
is wired.

- Lands in: `agent-003-market-monitor/`, `.github/workflows/ci.yml`
- Changes: new standalone AGENT-003 process with 3 workflows (WF-MM-01/02/03)
- Validate: `cd agent-003-market-monitor && npm ci && npx tsc --noEmit`

Refs phase-2/2.2-agentic-workflows.md §WF-MM-01, §WF-MM-02, §WF-MM-03
Refs agents/packages/agents/src/characters/market-monitor.ts (regen-network#64)
Follow-up to PR regen-network#80 — promised in the PR description as "Unit
tests for median, stddev, classifyAnomaly, scoreHealth,
computeDemandIndex, etc., planned as a separate test-only PR so
the original PR stays a single-concern 'add the agent' change."

Adds 52 unit tests across 3 test files covering every
deterministic helper function in the AGENT-003 workflows. The
helpers compute severity, liquidity health, and demand signals
— they're the parts most likely to silently drift in a future
refactor, so pinning their exact behavior prevents regressions
that a typecheck cannot catch.

## Changes

### Helper exports

Eight previously module-private helpers are now exported so the
test file can import them:

  price-anomaly-detection.ts:
    median, stddev, classifyAnomaly, classIdFromBatchDenom,
    isUsdStableDenom

  liquidity-monitor.ts:
    askUsd, scoreHealth

  retirement-tracking.ts:
    computeDemandIndex

The export is the only production-code change — no behavior
change, no API rename. Module consumers unchanged.

### Test files

  src/workflows/price-anomaly-detection.test.ts   (30 tests)
    median — empty, one-element, odd length, even length,
      non-mutation, negatives, floats
    stddev — empty, single, two-element, known five-element,
      n-1 denominator (sample, not population)
    classifyAnomaly — INFO / WARNING / CRITICAL boundaries,
      max of two inputs, symmetry for negative z-scores
    classIdFromBatchDenom — canonical regen format, single-letter,
      no dash, empty, degenerate leading dash
    isUsdStableDenom — USDC/USDT/DAI case-insensitive, ibc/*
      wrappers, non-stable rejects, empty string

  src/workflows/liquidity-monitor.test.ts          (13 tests)
    askUsd — normal, zero quantity, negative quantity, non-finite
      ask, non-finite quantity, fractional
    scoreHealth — CRITICAL at sub-half depth, DEGRADED between
      half and full, HEALTHY at 3x floor with strong count,
      score-cap when depth saturates, count-cap at 20, zero case

  src/workflows/retirement-tracking.test.ts        (9 tests)
    computeDemandIndex — zero, volume cap (60), count cap (20 at
      10 retirements), breadth cap (20 at 5 retirees), total cap
      (100), moderate activity, log10 scaling, sub-1 floor,
      rounding

### Vitest setup

  vitest.config.ts (8 lines) — standard config, node env
  package.json — adds "test" and "test:watch" scripts + vitest
    ^2.1.0 devDep
  tsconfig.json — excludes *.test.ts from the production
    typecheck path (tests still typecheck via vitest itself)
  .gitignore — adds *.db-shm and *.db-wal (SQLite WAL side files
    from running tests locally; the base *.db pattern doesn't
    catch them)

## Validation

  $ cd agent-003-market-monitor && npm test
  Test Files  3 passed (3)
       Tests  52 passed (52)

  $ cd agent-003-market-monitor && npx tsc --noEmit
  (exit 0)

Both CI checks continue to pass after this PR.

## Scope

Does NOT touch the workflow OODA loops themselves, the Claude
narrative layer, the LCD client, the SQLite store, or any output
formatting. The tests cover the pure functions only — the
surrounding integration machinery is tested implicitly when
AGENT-003 runs a live cycle against the Regen LCD.

- Lands in: `agent-003-market-monitor/`
- Changes: 52 unit tests + vitest setup + 8 helper exports
- Validate: `cd agent-003-market-monitor && npm test`

## PR relationship

This PR is based on PR regen-network#80's branch (the AGENT-003 implementation)
because the helpers don't exist on upstream/main. If regen-network#80 merges
first, this PR rebases cleanly.
Replaces the batch-supply-delta MVP proxy in WF-MM-03 with a real
tx-search client that reads recent MsgRetire events from the
Cosmos LCD. This closes the follow-up documented in PR regen-network#80's
design-decision regen-network#3: "Batch supply delta as retirement source
(MVP). A follow-up PR will plug in a real tx-stream client for
MsgRetire once the LCD event endpoint is available."

## What changes in the workflow

The observe phase no longer walks batches → supplies. Instead:

  const retirements = await ledger.getRecentRetirementTxs(200);

The orient phase no longer synthesizes retirement records from
supply deltas. Instead it calls a new pure function
`aggregateRetirementsByClass` that groups a list of real
Retirement records by class id, computes the top retiree by
cumulative quantity (not count), counts unique retirees via a
Set, and measures the jurisdiction-metadata coverage percentage.

The act phase is unchanged — it still persists, outputs, and
alerts on demand-index jumps.

## What changes in the ledger client

Adds two new methods to `LedgerClient`:

  - `getRecentRetirementTxs(limit)` — queries the LCD tx-search
    endpoint with `events=message.action='/regen.ecocredit.v1.MsgRetire'`,
    reverse-ordered, limited. Parses each tx response into zero
    or more Retirement records. Returns [] on error (transient
    LCD failures degrade to "no recent retirements" rather than
    crashing the cycle).

  - `parseRetirementsFromTx(tx)` — public parser exposed so unit
    tests can feed synthetic tx responses. Walks `logs[].events[]`
    AND top-level `events[]` for cross-SDK compatibility, filters
    on the Regen v1 and v1beta1 EventRetire type URLs, and
    extracts the owner / batch_denom / amount / jurisdiction /
    reason attributes. Skips malformed events (missing
    batch_denom, non-finite amount, non-positive amount).

The parser is a pure function — no side effects, no LCD calls —
so it is fully unit-testable with synthetic inputs.

## New tests (16 total across 2 files)

**src/ledger.test.ts** (9 tests)
- empty tx
- single EventRetire with all attributes
- v1beta1 fallback event type
- non-retirement events ignored
- missing batch_denom ignored
- non-finite, zero, and negative amounts ignored
- multiple retirements in one tx (batched)
- events read from tx.events[] as well as logs[].events[]
- tx hash and timestamp carried through to Retirement

**src/workflows/retirement-tracking.test.ts** (7 new tests for
aggregateRetirementsByClass, on top of the existing 9 for
computeDemandIndex)
- empty retirements → empty map
- single retirement → one-entry summary
- multiple retirements grouped by class id
- top retiree identified by cumulative quantity, not count
- jurisdiction metadata percentage
- classes with zero total quantity skipped
- empty retiree string handled without crashing

Full test suite: 68 passed across 4 files (up from 52 in PR regen-network#99).

## What this unlocks

The old MVP proxy produced RetirementSummary records with empty
`topRetiree` / `topRetireeQuantity` / `pctWithJurisdiction`
fields — the supply-delta source couldn't carry retiree identity
or compliance metadata. The new implementation populates every
field on the Retirement type. Downstream:

- Demand index calculations now distinguish "5 unique retirees
  each retiring 200" from "1 whale retiring 1000".
- Jurisdiction-metadata coverage surfaces compliance-driven demand
  (per the SPEC note: ">50% jurisdiction coverage typically implies
  compliance-driven demand").
- Top-retiree identification feeds the narrative layer with
  concrete context instead of a placeholder.
- Each Retirement carries its real tx hash and timestamp, so
  downstream auditing can link back to the exact on-chain event.

## Scope

Does NOT touch WF-MM-01 (price anomaly detection) or WF-MM-02
(liquidity monitor). The price oracle integration noted elsewhere
in the README is still future work. The LCD-level error handling
returns empty arrays on failure — a future follow-up might
distinguish transient failures from genuine "no recent retirements".

- Lands in: `agent-003-market-monitor/`
- Changes: new tx-search client methods + rewritten WF-MM-03 + 16 new tests
- Validate: `cd agent-003-market-monitor && npm test && npx tsc --noEmit`

## PR relationship

Based on PR regen-network#99 (AGENT-003 unit tests) which is based on PR regen-network#80
(AGENT-003 initial implementation). If both land first, this PR
rebases cleanly. Sibling PR to a forthcoming AGENT-004 real
delegation tx-stream follow-up.

Refs phase-2/2.2-agentic-workflows.md §WF-MM-03
Refs PR regen-network#80's design decision regen-network#3 (MVP proxy → real tx-stream)
Addresses Gemini review feedback on PR regen-network#103:

ledger.getRecentRetirementTxs:
* Query both v1 and v1beta1 MsgRetire type URLs instead of only v1.
  Older forwarders still emit v1beta1 and the parser already handles
  both event shapes — the tx-search filter was the only thing
  restricting the stream. Dedupe by tx hash so a tx appearing under
  both queries is only processed once.
* Replace the empty catch with console.error so network issues are
  visible instead of silently degrading to "no recent retirements".

parseRetirementsFromTx:
* Prefer the flattened `tx.events[]` list over walking
  `tx.logs[i].events[]`. In modern Cosmos SDK versions the flat list
  is a superset, so walking both double-counted every event. Fall
  back to the per-log list only when the flat list is empty (very
  old LCD builds).
* Parse the raw integer amount via BigInt first so higher-precision
  credit types (18-decimal biodiversity tokens on the roadmap) do
  not lose precision at the boundary. The downstream Retirement
  record still uses `number` for ergonomics — a safe downcast at
  current 6-decimal precision — with a comment noting the boundary.

aggregateRetirementsByClass / WF-MM-03 orient:
* USD value for retirement volume now uses the most recent WF-MM-02
  median ask price per class (via a new store.getLatestMedianAsk
  helper and a `classPriceUsd` map threaded into the aggregator).
  Falls back to 1:1 USD when no snapshot exists yet. The
  aggregator's new optional `classPriceUsd` parameter keeps the
  unit tests' 1:1 assumption backwards-compatible.

Full vitest suite: 68/68 passing. Typecheck clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ator

Z-score numerator has to pair with its denominator's center. stddev is
measured around the mean, so subtracting the median while dividing by
stddev silently biases every score by (mean − median). On a skewed
price distribution that bias is large enough to flip anomaly severity.

Keep the median on the anomaly record as the human-readable reference
price — it reads more intuitively than mean for thin markets — but
compute the statistical test with the mean.

Addresses Gemini review on feat/agent-003-real-retire-tx-stream:
  - price-anomaly-detection.ts:169 (class Z-score)
  - price-anomaly-detection.ts:178 (batch Z-score)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…k#80 onto regen-network#103

The real MsgRetire tx-stream branch (this PR) was cut from agent-003's
pre-fixup state, so it was missing the four non-z-score fixes from
regen-network#80's gemini-review commit (5d04ae6). Bringing them forward so regen-network#80
can be closed as fully superseded:

- `LedgerClient.getCreditClasses` — walks every pagination page up to
  a 25-page safety cap (was silently truncating to oldest 200 classes;
  new classes were invisible to every workflow).
- `liquidity-monitor` — top-10 depth sort pre-computes askUsd once per
  order instead of recomputing it in each comparator call. Also filters
  non-USD-stablecoin asks so arbitrary-denominated orders don't pollute
  class/batch baselines.
- `index.ts` main loop — `setInterval` → recursive `setTimeout` so a
  slow cycle can't overlap with the next tick (no race conditions or
  database contention when the LCD/LLM is slow).
- Z-score comment refreshed; the `-- mean not median --` fix from
  8c75610 is kept as the authoritative version.

The regen-network#80 retirement-delta work (batch-supply-delta + per-batch
cumulative state) is intentionally **not** ported — this PR's
tx-stream approach supersedes it wholesale and is exact where the
delta method was approximate. The now-unused `batch_retirement_state`
table and its `getPreviousRetiredAmount`/`upsertRetiredAmount` helpers
are removed in the same commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…f/concurrency

Addresses Gemini review feedback on PR regen-network#99:

* Extract the duplicated helpers (median, stddev, classIdFromBatchDenom,
  isUsdStableDenom, computeDemandIndex) into a shared src/utils.ts.
  Workflow files now import from utils and re-export for
  backwards-compatible test imports.
* ledger.ts: every empty catch block now logs via console.error with the
  method name + failing argument so a network hiccup or 5xx no longer
  silently degrades the agent's data.
* liquidity-monitor.ts: the top-10 depth sort pre-computes askUsd once
  per order instead of recomputing the ratio in every comparator call.
* retirement-tracking.ts: batch supply fan-out is chunked at
  concurrency 5 instead of firing 100 parallel LCD requests that would
  trip rate limits on public endpoints.
* price-anomaly-detection.ts: z-score now uses mean/stddev instead of
  mixing median numerator with stddev denominator, and sellOrderToTrade
  skips non-USD-stablecoin asks instead of treating them as 1:1 USD.
* retirement-tracking.test.ts now imports computeDemandIndex directly
  from utils.ts so the test does not transitively construct the
  SQLite-backed Store singleton — previously the test was failing with
  "database is locked" when run alongside the other workflow tests.

Full vitest suite: 52/52 passing. Typecheck clean.

Note: the PR regen-network#99 Gemini review flagged `claude-sonnet-4-5-20250929` as a
"future date typo" in the config default — that is a false positive,
the 2025-09-29 sonnet release is real and ships today, so no change is
needed there.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RFC-0001 packages the M013 fee distribution decision (15/30/50/5 split)
derived from Phase 2 open-questions resolution. Marks agent authorship
(Claude Code) with brawlaphant accountable; human sponsor required
before graduation per RFC graduation rules (GRADUATION_RULES.md §2.1).

This RFC unblocks Economic Reboot Proposal 1 (M013 activation) and
Proposal 3 (M012 hard-cap setting, which depends on burn pool size).
Gregory Landua recommended this as the first real test of the RFC
governance pipeline (mentioned in PR regen-network#107 context).

Evidence:
- E1: M012 cadCAD equilibrium validation at burn_share=0.15
- E2: M012 equilibrium analysis (supply/validator compensation)
- E3: Economic Reboot Proposals governance doc (Proposal A)
- E4: M013 SPEC (parameter bounds)
- E5: M012 SPEC (hard-cap burn interaction)

All foundational infrastructure files (GRADUATION_RULES.md, _template.md,
rfc-review-schema.yaml) also committed from PR regen-network#107 to unblock the RFC
pipeline.

Status: draft (awaiting human sponsor assignment)
Next step: human sponsor posts forum thread, manages community
deliberation (min 7 days), shepherds to ratified (requires ratifier
sign-off).

No code changes; governance doc only.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces AGENT-003: Regen Market Monitor, a standalone Node.js agent that monitors the Regen ecocredit marketplace using three deterministic OODA workflows: price anomaly detection, liquidity monitoring, and retirement tracking. It includes direct Cosmos LCD integration, SQLite storage, and Claude-powered narrative reporting. Feedback on the implementation suggests correcting a typo in the default Anthropic model name, removing a redundant nullish coalescing check for batchDenom in the ledger client, and importing utility helpers directly in test files rather than re-exporting them from workflow files to reduce unnecessary coupling.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.


// Anthropic
anthropicApiKey: process.env.ANTHROPIC_API_KEY || "",
model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The default model name claude-sonnet-4-5-20250929 appears to contain a future date, which is likely a typo. This could cause runtime errors if the specified model does not exist. Please use a known valid model name, for example, claude-3-5-sonnet-20240620.

Suggested change
model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-5-20250929",
model: process.env.ANTHROPIC_MODEL || "claude-3-5-sonnet-20240620",

return hit ? hit.value : null;
};

const batchDenom = attr("batch_denom") ?? attr("batchDenom") ?? "";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The nullish coalescing operator is used with two very similar keys, batch_denom and batchDenom. It seems batchDenom is a typo and this is redundant. The correct attribute key for the batch denom in both v1 and v1beta1 EventRetire events is batch_denom.

Suggested change
const batchDenom = attr("batch_denom") ?? attr("batchDenom") ?? "";
const batchDenom = attr("batch_denom") ?? "";


// Re-exported so the existing unit-test suite can import helpers from
// this workflow file (the tests pre-date the utils.ts extraction).
export { median, stddev, classIdFromBatchDenom, isUsdStableDenom };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Re-exporting helper functions from utils.ts in this workflow file solely for the benefit of the test file is not ideal for maintainability as it creates an unnecessary coupling. The test file price-anomaly-detection.test.ts should be updated to import these utilities directly from ../utils.js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant