Open-source rule processors that power the Carrot dMRV (digital Measurement, Reporting and Verification) pipeline. Each rule processor validates a specific aspect of supply chain data against a Methodology Verification Framework, producing deterministic, auditable verification results that underpin the issuance of environmental credits.
flowchart LR
A[Supply Chain Data] --> B[MassID Documents]
B --> C[Rule Processors]
C --> D{All Rules Pass?}
D -->|Yes| E[Certificate Issued]
D -->|No| F[Verification Failed]
E --> G[Credit Tokenization]
Every methodology in the Carrot Network has three layers:
| Layer | What | Where |
|---|---|---|
| Methodology | Scientific foundation (e.g., UNFCCC CDM) | docs.carrot.eco |
| MvF (Methodology Verification Framework) | Specification translating the methodology into concrete verification rules | docs.carrot.eco |
| MvA (Methodology Verification Application) | Open-source rule processors implementing the MvF | This repository · docs |
Rule processors are the core units of the MvA. Each one evaluates MassID documents — digital records tracking material type, weight, and chain of custody — and returns PASSED, FAILED, or REVIEW_REQUIRED with a traceable explanation.
methodology-rules/
├── apps/methodologies/ # Methodology applications (Lambda deployments)
│ ├── bold-carbon/ # BOLD Carbon CH₄
│ └── bold-recycling/ # BOLD Recycling
├── libs/
│ ├── methodologies/bold/
│ │ └── rule-processors/ # Shared BOLD rule processors
│ │ └── mass-id/ # MassID verification rules
│ └── shared/ # Shared libraries across methodologies
├── tools/ # CLI tooling
│ ├── create-rule.js # Scaffold new rule processors
│ ├── rule-runner-cli/ # Run rules locally against test data
│ ├── document-extractor-cli/ # Extract data from documents
│ ├── mass-id-generator-cli/ # Emit the MassID event data the docs site syncs
│ ├── apply-methodology-rule.js # Apply rules to a methodology
│ └── versioning/ # Rule version management
├── scripts/ # Build & generation scripts
└── docs/ # Internal documentation
pnpm install# Testing
pnpm test <project-name> # Test a single project
pnpm test:affected # Test only changed projects
pnpm test:all # Test all projects
# Linting & type-checking
pnpm lint <project-name> # Lint a single project
pnpm lint:affected # Lint and auto-fix changed projects
pnpm ts <project-name> # Type-check a single project
# Building
pnpm build-lambda <project-name> # Build a Lambda package
pnpm build-lambda:affected # Build only changed Lambdas
# Run a single test file
pnpm nx test <project-name> --testFile=<relative-path>pnpm create-rule # Scaffold a new rule processor
pnpm run-rule <args> # Run a rule locally against test data
pnpm apply-methodology-rule # Apply a rule to a methodology
pnpm generate:event-data # Emit MassID event data to dist/methodology-event-dataUse one processor-path mode per dry run.
Supply AUDIT_URL and SMAUG_API_GATEWAY_ASSUME_ROLE_ARN through shell variables or an operator-managed --env-file. The tracked test configuration is deliberately non-routable.
# Local mode: runs the processor from the supplied path against an explicit dataset.
pnpm run-rule dry-run libs/methodologies/bold/rule-processors/mass-id/privacy-flags --data-set-name TEST --document-id <massid-document-id>
# Registered mode: resolves the processor against the supplied methodology.
pnpm run-rule dry-run libs/methodologies/bold/rule-processors/mass-id/document-manifest-data --methodology-slug bold-carbon-organic --document-id <massid-document-id>tools/mass-id-generator-cli emits the canonical MassID example the docs site publishes: an events manifest, one request payload per event and one attribute dictionary per event other than the create document and the actors, once for bold-recycling and once for bold-carbon.
pnpm generate:event-data # writes dist/methodology-event-data/The example's content comes from one authored table, tools/mass-id-generator-cli/src/mass-id-catalog.ts. Privacy flags are read from the privacy-flags rule's own tables, so they cannot drift from the rule. Output is deterministic: the same source produces the same bytes. The emitter does not remove files a previous run left behind: delete dist/methodology-event-data before re-running.
The test-e2e target runs these rule lambdas against the catalog and expects PASSED: composting-cycle-timeframe, driver-identification, drop-off-at-recycler, hauler-identification, mass-id-qualifications, privacy-flags, processor-identification, project-period-limit, recycler-identification, regional-waste-classification, vehicle-identification, waste-origin-identification. When an intended rule change makes one fail, the published example is out of date: update the catalog. mass-id-qualifications also checks measurementUnit and currentValue, which the published create body does not carry; the test document supplies them. The test document also places every address in Brazil and leaves attachments out, and the suite runs on the real clock because project-period-limit reads it: once the Recycled event's date is before 1 January of the previous UTC year the suite fails, and the remedy is to move every date in the catalog forward by one shared interval.
Rules it does not run, and why:
| Rule | Needs |
|---|---|
geolocation-and-address-precision |
the audit and accreditation document graph |
mass-id-sorting |
the audit and accreditation document graph |
participant-accreditations-and-verifications-requirements |
the audit and accreditation document graph |
prevented-emissions |
the audit and accreditation document graph |
weighing |
the audit and accreditation document graph |
no-conflicting-certificate-or-credit |
certificate, credit order and MassID audit documents |
document-manifest-data |
attachment bytes |
waste-mass-is-unique |
Smaug over the network |
version-and-sync.yaml runs the generator on each push to main (unless the head commit message contains [skip ci]) and uploads the tree as the methodology-event-data artifact of that run. The rules-manifest-updated dispatch to the docs repo carries the run id.
Every rule processor follows a standardized pattern:
loadDocument()— Fetches the documents needed for evaluationgetRuleSubject()— Extracts the specific data elements to evaluateevaluateResult()— Applies verification logic, returnsPASSED,FAILED, orREVIEW_REQUIRED
Complex processors may override process() with custom document loading and evaluation logic.
Every rule processor follows a standardized validation flow:
- Load raw documents
- Build a rule subject
- Validate the rule subject schema (
validateRuleSubjectOrThrow) - Evaluate business logic
Document schemas are organized in 5 layers:
- Envelope (
LoadedDocumentEnvelopeSchema) — wrapper for loaded documents - Inbound (
InboundDocumentSchema) — boundary contract, looseObject - Normalized (
DocumentSchema) —z.object()(default), strips unknown fields on parse - Domain (
BoldDocumentSchema,MassID*Schema) — methodology-specific - Rule (
*RuleSubjectSchema) — exact contract per rule
Enums use const-object + z.enum() pattern with .extract() / .literal() for narrower layers.
Design principles:
- Fidelity — Mirrors the MvF specification exactly
- Traceability — Every result links back to source data
- Determinism — Same input always produces the same output
- Standard compliance — Follows Carrot dMRV Standard requirements
Contributions are welcome. Before starting work:
- Read the MvA Developer Guide to understand the verification model
- Use
pnpm create-ruleto scaffold new rule processors - Ensure 100% test coverage — this is enforced by CI
- Follow Conventional Commits for all commit messages
- Carrot Documentation — Full platform documentation
- Methodology Overview — How methodologies work
- MvA Developer Guide — Guide for rule processor developers
- dMRV Standard — Digital Measurement, Reporting and Verification
This project is licensed under the LGPL-3.0.