-
Notifications
You must be signed in to change notification settings - Fork 7
feat: complete M015 reference implementation with KPI, datasets, and edge-case test vectors #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
brawlaphant
wants to merge
2
commits into
regen-network:main
Choose a base branch
from
brawlaphant:pr/m015-reference-impl-complete
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
mechanisms/m015-contribution-weighted-rewards/datasets/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| # m015 datasets (replay fixtures) | ||
|
|
||
| These fixtures are **deterministic inputs** for generating non-zero m015 KPI outputs **without MCP**. | ||
|
|
||
| ## Files | ||
| - `schema.json` -- JSON schema for replay datasets | ||
| - `fixtures/v0_sample.json` -- single distribution period with 4 participants and 1 stability commitment | ||
| - `fixtures/v0_stability_sample.json` -- stability tier scenarios (committed, matured, early exit, cap overflow) | ||
|
|
||
| ## How they are used | ||
| A replay runner (e.g., in `regen-heartbeat`) can read a fixture file and compute: | ||
| - Per-participant activity scores using `computeActivityScore` from `reference-impl/m015_score.js` | ||
| - Stability tier allocation using `computeStabilityAllocation` from `reference-impl/m015_score.js` | ||
| - Pro-rata distribution using `computeDistribution` from `reference-impl/m015_score.js` | ||
| - Aggregated KPIs using `computeM015KPI` from `reference-impl/m015_kpi.js` | ||
|
|
||
| Key metrics produced: | ||
| - `total_distributed_uregen` -- total rewards distributed (stability + activity) | ||
| - `activity_pool_uregen` -- pool available for activity-based distribution | ||
| - `stability_allocation_uregen` -- stability tier payout (capped at 30% of inflow) | ||
| - `participant_count` -- participants with non-zero activity scores | ||
| - `gini_coefficient` -- inequality measure of reward distribution | ||
|
|
||
| ## Units | ||
| All token amounts are in **uregen** (1 REGEN = 1,000,000 uregen) and represented as integers. | ||
|
|
||
| These datasets are **reference-only** and do not imply enforcement or on-chain actions. | ||
231 changes: 231 additions & 0 deletions
231
mechanisms/m015-contribution-weighted-rewards/datasets/schema.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,231 @@ | ||
| { | ||
| "$schema": "http://json-schema.org/draft-07/schema#", | ||
| "title": "m015 replay dataset", | ||
| "type": "object", | ||
| "required": [ | ||
| "community_pool_inflow_uregen", | ||
| "periods_per_year", | ||
| "max_stability_share", | ||
| "stability_commitments", | ||
| "participants" | ||
| ], | ||
| "properties": { | ||
| "description": { | ||
| "type": "string", | ||
| "description": "Human-readable description of the dataset" | ||
| }, | ||
| "epoch": { | ||
| "type": "string", | ||
| "description": "ISO week identifier (e.g. 2026-W07)" | ||
| }, | ||
| "community_pool_inflow_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Community Pool inflow for this period in uregen" | ||
| }, | ||
| "periods_per_year": { | ||
| "type": "integer", | ||
| "minimum": 1, | ||
| "description": "Number of distribution periods per year (default 52 for weekly epochs)" | ||
| }, | ||
| "max_stability_share": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "maximum": 1, | ||
| "description": "Maximum fraction of inflow allocated to stability tier (default 0.30)" | ||
| }, | ||
| "stability_commitments": { | ||
| "type": "array", | ||
| "description": "Active stability tier commitments for the period", | ||
| "items": { | ||
| "type": "object", | ||
| "required": ["holder_address", "committed_amount_uregen", "lock_period_months"], | ||
| "properties": { | ||
| "holder_address": { | ||
| "type": "string", | ||
| "description": "Bech32 address of the commitment holder" | ||
| }, | ||
| "committed_amount_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Amount of uregen locked in stability commitment" | ||
| }, | ||
| "lock_period_months": { | ||
| "type": "integer", | ||
| "minimum": 6, | ||
| "maximum": 24, | ||
| "description": "Lock period in months (min 6, max 24)" | ||
| }, | ||
| "status": { | ||
| "type": "string", | ||
| "enum": ["uncommitted", "committed", "matured", "early_exit"], | ||
| "description": "Lifecycle state of the commitment" | ||
| }, | ||
| "committed_at": { | ||
| "type": "string", | ||
| "format": "date-time", | ||
| "description": "ISO-8601 timestamp when commitment was made" | ||
| }, | ||
| "matured_at": { | ||
| "type": ["string", "null"], | ||
| "format": "date-time", | ||
| "description": "ISO-8601 timestamp when commitment matured (null if not matured)" | ||
| }, | ||
| "exited_at": { | ||
| "type": ["string", "null"], | ||
| "format": "date-time", | ||
| "description": "ISO-8601 timestamp of early exit (null if not exited)" | ||
| }, | ||
| "accrued_rewards_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Total rewards accrued to date in uregen" | ||
| }, | ||
| "forfeited_rewards_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Rewards forfeited due to early exit (50% penalty)" | ||
| }, | ||
| "annual_return_rate": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "description": "Annual return rate (default 0.06)" | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "participants": { | ||
| "type": "array", | ||
| "description": "Participants with activity data for the period", | ||
| "items": { | ||
| "type": "object", | ||
| "required": ["address", "activities"], | ||
| "properties": { | ||
| "address": { | ||
| "type": "string", | ||
| "description": "Bech32 address of the participant" | ||
| }, | ||
| "label": { | ||
| "type": "string", | ||
| "description": "Human-readable label for the participant" | ||
| }, | ||
| "activities": { | ||
| "type": "object", | ||
| "required": [ | ||
| "credit_purchase_value", | ||
| "credit_retirement_value", | ||
| "platform_facilitation_value", | ||
| "governance_votes_cast", | ||
| "proposals" | ||
| ], | ||
| "properties": { | ||
| "credit_purchase_value": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Total credit purchase value in uregen for this period" | ||
| }, | ||
| "credit_retirement_value": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Total credit retirement value in uregen for this period" | ||
| }, | ||
| "platform_facilitation_value": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Total platform facilitation value in uregen for this period" | ||
| }, | ||
| "governance_votes_cast": { | ||
| "type": "integer", | ||
| "minimum": 0, | ||
| "description": "Number of governance votes cast in this period" | ||
| }, | ||
| "proposals": { | ||
| "type": "array", | ||
| "description": "Proposals submitted in this period", | ||
| "items": { | ||
| "type": "object", | ||
| "required": ["passed", "reached_quorum"], | ||
| "properties": { | ||
| "id": { | ||
| "type": "string", | ||
| "description": "Proposal identifier" | ||
| }, | ||
| "passed": { | ||
| "type": "boolean", | ||
| "description": "Whether the proposal passed" | ||
| }, | ||
| "reached_quorum": { | ||
| "type": "boolean", | ||
| "description": "Whether the proposal reached quorum" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "computed": { | ||
| "type": "object", | ||
| "description": "Pre-computed values for validation (optional, present in fixture files)", | ||
| "properties": { | ||
| "total_score": { | ||
| "type": "number", | ||
| "minimum": 0 | ||
| }, | ||
| "activity_reward_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "activity_share": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "maximum": 1 | ||
| }, | ||
| "stability_reward_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "total_reward_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }, | ||
| "summary": { | ||
| "type": "object", | ||
| "description": "Pre-computed summary for validation (optional, present in fixture files)", | ||
| "properties": { | ||
| "stability_allocation_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "activity_pool_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "total_distributed_uregen": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "participant_count": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "stability_commitments_count": { | ||
| "type": "integer", | ||
| "minimum": 0 | ||
| }, | ||
| "stability_cap_utilization": { | ||
| "type": "number", | ||
| "minimum": 0, | ||
| "maximum": 1 | ||
| }, | ||
| "notes": { | ||
| "type": "string" | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
80 changes: 80 additions & 0 deletions
80
mechanisms/m015-contribution-weighted-rewards/reference-impl/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| # m015 reference implementation (v0) | ||
|
|
||
| This folder provides a **canonical computation** for m015 outputs so that different agents/runners | ||
| produce consistent numbers. | ||
|
|
||
| ## Modules | ||
|
|
||
| ### Activity scoring and distribution (`m015_score.js`) | ||
| - `computeActivityScore({ activities })` -- weighted score from five activity types | ||
| - `computeStabilityAllocation({ community_pool_inflow, stability_commitments, ... })` -- stability tier payout with 30% cap | ||
| - `computeDistribution({ activity_pool_amount, participants })` -- pro-rata reward distribution with remainder handling | ||
|
|
||
| ### KPI computation (`m015_kpi.js`) | ||
| - `computeM015KPI({ community_pool_inflow_uregen, stability_commitments, participants, ... })` -- aggregated KPI block | ||
| - `giniCoefficient(values)` -- Gini inequality measure for reward distribution | ||
|
|
||
| ## Inputs | ||
|
|
||
| ### Activity scoring | ||
| - `activities.credit_purchase_value` (number, uregen) -- weight 0.30 | ||
| - `activities.credit_retirement_value` (number, uregen) -- weight 0.30 | ||
| - `activities.platform_facilitation_value` (number, uregen) -- weight 0.20 | ||
| - `activities.governance_votes_cast` (number) -- weight 0.10 | ||
| - `activities.proposals[]` (array of `{ passed, reached_quorum }`) -- weight 0.10, anti-gaming rules apply | ||
|
|
||
| ### Stability allocation | ||
| - `community_pool_inflow` (number, uregen) -- Community Pool inflow for the period | ||
| - `stability_commitments[]` (array of `{ committed_amount_uregen }`) -- active commitments | ||
| - `periods_per_year` (number, default 52) -- weekly epochs | ||
| - `max_stability_share` (number, default 0.30) -- 30% cap on stability tier | ||
|
|
||
| ## Outputs | ||
|
|
||
| ### Activity score | ||
| - `total_score` -- weighted sum of all activity contributions | ||
| - `breakdown` -- per-activity detail (raw_value, weight, weighted_value) | ||
|
|
||
| ### Stability allocation | ||
| - `stability_allocation` -- uregen allocated to stability tier (capped) | ||
| - `activity_pool` -- uregen remaining for activity distribution | ||
|
|
||
| ### Distribution | ||
| - Per participant: `address`, `reward` (uregen), `share` (0-1) | ||
| - Remainder from `Math.floor()` truncation assigned to largest-share participant | ||
|
|
||
| ### KPI block | ||
| - `total_distributed_uregen` -- stability + activity distributions | ||
| - `stability_allocation_uregen`, `activity_pool_uregen` | ||
| - `stability_utilization` -- fraction of 30% cap used | ||
| - `participant_count` -- participants with score > 0 | ||
| - `gini_coefficient` -- inequality measure (0 = equal, 1 = max inequality) | ||
| - `top_earner_share` -- share of rewards going to highest scorer | ||
| - `revenue_constraint_satisfied` -- boolean: total payout <= inflow | ||
| - `stability_cap_satisfied` -- boolean: stability <= 30% cap | ||
|
|
||
| ## Self-test | ||
|
|
||
| ```bash | ||
| node m015_score.js | ||
| node m015_kpi.js | ||
| ``` | ||
|
|
||
| Each script reads all test vectors from `test_vectors/` and validates computed outputs against expected values. | ||
|
|
||
| ## Test vectors | ||
|
|
||
| | Vector | Scenario | | ||
| |--------|----------| | ||
| | `vector_v0_sample` | 4 participants with diverse activity profiles, 1 stability commitment | | ||
| | `vector_v0_early_exit` | Stability tier with early exit penalty (50% forfeiture), 3 participants | | ||
| | `vector_v0_cap_overflow` | Stability obligations exceed 30% cap, demonstrating cap enforcement | | ||
| | `vector_v0_zero_activity` | All participants have zero activity, no stability commitments | | ||
|
|
||
| Each vector has a `.input.json` and `.expected.json` pair. | ||
|
|
||
| ## Design notes | ||
| - All monetary values are integers in **uregen** (1 REGEN = 1,000,000 uregen). | ||
| - `Math.floor()` truncation is intentional for all reward computations, matching on-chain integer arithmetic. | ||
| - The remainder from floor truncation is assigned to the largest-share participant to ensure `sum(rewards) == activity_pool`. | ||
| - Stability allocation uses `Math.min(Math.floor(rawAllocation), Math.floor(cap))` to prevent over-allocation. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line references
fixtures/v0_stability_sample.json, but this file is not included in the pull request. To avoid confusion, please either add the file or update the documentation to indicate that it is planned for a future update.