Skip to content

ci(registry): PR validation, preview comments, and tightened triggers#807

Merged
gpmayorga merged 12 commits intomainfrom
ci/registry-pr-validation
Apr 9, 2026
Merged

ci(registry): PR validation, preview comments, and tightened triggers#807
gpmayorga merged 12 commits intomainfrom
ci/registry-pr-validation

Conversation

@gpmayorga
Copy link
Copy Markdown
Collaborator

@gpmayorga gpmayorga commented Mar 31, 2026

Summary

  • Validate env file schemas on PRs — a fast-fail gate (validate-env-schema.js) catches broken JSON, missing network.chainId, invalid addresses, and structural renames before registry generation even starts.
  • Validate generated registry against indexer requirements — a post-generation gate (validate-registry.js) checks the 6 hard requirements the Ponder indexer needs: version, previousRegistry.ipfsHash, startBlock, address, blockNumber, and ABI completeness. Errors block the PR; warnings are shown but don't block.
  • PR preview comment — every PR touching env/** gets a comment with the registry summary (version, chains, contracts, ABIs) plus any validation errors/warnings, so reviewers can see exactly what would be published.
  • Tightened triggers — the workflow now only runs when env/**, script/registry/**, or the workflow file itself changes. The release trigger is removed (env file merges to main are the canonical signal). This keeps the GHA log clean.

Pipeline flow

flowchart TD
    A[env file changed] --> B[Validate env schemas]
    B -->|fail| X1[PR blocked]
    B -->|pass| C[Detect changed environments]
    C --> D[Generate registry]
    D --> E[Validate registry output]
    E -->|errors| X2[PR blocked]
    E -->|pass/warnings| F[Post PR comment]
    F --> G{Merged to main?}
    G -->|no| H[Done]
    G -->|yes| I[Pin to IPFS]
    I --> J[Update Cloudflare DNS]
Loading

Indexer hard requirements (enforced by validate-registry.js)

Field Rule
version Non-empty string
previousRegistry.ipfsHash Non-null if a live registry exists
chains.<chainId>.deployment.startBlock Must be a number
chains.<chainId>.contracts.<name>.address Non-empty string
chains.<chainId>.contracts.<name>.blockNumber Must be a number
abis.<ContractName> Must exist for every contract in chains

Add two zero-dependency validation scripts:

- validate-env-schema.js: validates all env/*.json files against the
  expected schema (required network.chainId, valid addresses, known
  top-level keys). Fast-fail gate before registry generation.

- validate-registry.js: validates generated registry JSON against
  indexer hard requirements (version, previousRegistry.ipfsHash,
  startBlock, blockNumber, address, ABI completeness). Produces a
  sidecar .validation.json report for CI consumption.
- Add env/** and script/registry/** to pull_request paths trigger so
  PRs touching env files run the registry pipeline before merge
- Add paths filter to push trigger (only run on env/registry changes)
- Remove release trigger (env file merges are the canonical signal)
- Add validate-env-schema.js step before generation (fast-fail gate)
- Add validate-registry.js step after generation (indexer requirements)
- Add PR preview comment with registry summary, errors, and warnings
- Update pin-to-ipfs condition to remove release event
- Update README with pipeline diagram, validation docs, and indexer
  hard/soft requirements tables
Use dnslink TXT record lookup (_dnslink.registry.centrifuge.io) to
resolve the IPFS CID of the currently live registry at generation time.
This is the source of truth — the DNS record points to whatever CID
Cloudflare is actually serving.

Eliminates the need for Pinata API key during generation and removes
the fragile metadata-based lookup. SOURCE_IPFS env var still works
as an explicit override.

Also reverts previousRegistry.ipfsHash back to an error in the
validator since it's now filled at generation time.
…to-ipfs

Update pin-to-ipfs.js to use DNS dnslink lookup as the primary method
for resolving the previous registry CID, with Pinata as a fallback.
This aligns with abi-registry.js which now fills previousRegistry.ipfsHash
via DNS at generation time.

Both the pinning flow and --update-previous mode now follow the same
resolution order: DNS first, Pinata if DNS fails.
Version extraction now falls back to contract-level version fields
when deploymentInfo entries don't have a version (which is the case
for all current env files).

ABI name mapping now handles mismatches between env file contract keys
and Forge artifact names:
- Hook contracts: freezeOnlyHook → FreezeOnly (not FreezeOnlyHook)
- NAV manager: navManager → NAVManager (not NavManager)
- Token factory base: TokenFactory → ShareToken (not Token)

Both abi-registry.js and validate-registry.js share the same override
maps to stay in sync.

Tested locally: 0 errors, 0 warnings, 53 ABIs packed.
tokenBridge is not a protocol contract — it has no Solidity source in
this repo and no Forge build artifact. It was likely an artifact from
a deployment session that leaked into the env files. It's not present
in any mainnet env file.
Version extraction now picks the highest version across all contracts
in a chain (e.g. v3.1 wins over 3), instead of the first one found.
This handles chains where root kept its original v3 version while all
other contracts were upgraded to v3.1.

Remove warnings from validate-registry.js for fields the indexer
doesn't use (txHash, deployedAt, gitCommit, previousRegistry.version).
The validator now only checks what actually matters for the indexer.
Comment thread script/registry/validate-env-schema.js
Enhance the GitHub Actions workflow by introducing an optional input for the `workflow_dispatch` event, allowing users to specify which registry to regenerate (mainnet, testnet, or both). Update the environment detection logic to accommodate this input, ensuring correct handling of changed environments based on user selection or automatic detection.
@gpmayorga gpmayorga marked this pull request as ready for review April 7, 2026 10:34
@gpmayorga gpmayorga enabled auto-merge (squash) April 7, 2026 10:34
@centrifuge centrifuge deleted a comment from github-actions bot Apr 7, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

Registry Preview

mainnet

Metric Value
Version v3.1
Chains with changes 10
Contracts in delta 443
ABIs included 53

✅ Passes all indexer requirements

testnet

Metric Value
Version v3.1
Chains with changes 0
Contracts in delta 0
ABIs included 0
⚠️ 1 warning(s)
Path Message
chains Delta registry has zero chains — nothing changed?

✅ Passes all indexer hard requirements


Preview only — IPFS upload happens on merge to main.

Removed the optional input for `workflow_dispatch` to streamline the environment detection process. The logic now directly detects changed environments without user input, ensuring consistent behavior during CI runs. Updated conditions for pinning registry files to IPFS to enhance clarity and maintain functionality.
@gpmayorga gpmayorga self-assigned this Apr 7, 2026
@gpmayorga gpmayorga requested a review from filo87 April 7, 2026 12:42
@wischli wischli added the scope:infra CI, GitHub Actions, foundry config label Apr 8, 2026
Copy link
Copy Markdown
Contributor

@lemunozm lemunozm left a comment

Choose a reason for hiding this comment

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

Just two questions. Everything else looks good to me!

Comment thread .github/workflows/registry.yml
Comment thread script/registry/README.md
Comment on lines +120 to +122
# Validate a generated registry against indexer hard requirements
node script/registry/validate-registry.js registry/registry-mainnet.json
node script/registry/validate-registry.js registry/registry-testnet.json
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.

Q. In which cases a generated registry will make fail the validation? What's the use case of this validate-registry.js script?

Copy link
Copy Markdown
Collaborator Author

@gpmayorga gpmayorga Apr 9, 2026

Choose a reason for hiding this comment

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

That was my way of coding in @filo87 requirements (the must-have for the indexer) to make sure whatever changes, usually as a result of changes to the registry generation code, or the env files does not break the indexer when the next deployment files are merged to main

- Require deploymentInfo.*.startBlock when any contract has blockNumber
- Reject large gap vs min active contract block (chain-level indexer listeners)
- Document in README; validate-registry docblock defers gap check to env step
- Fix stale deployment startBlock on Arbitrum mainnet and Arbitrum Sepolia
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 9, 2026

Coverage after merging ci/registry-pr-validation into main will be

97.10%

Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
src/adapters
   AxelarAdapter.sol97.67%90%100%100%118
   ChainlinkAdapter.sol98.11%91.67%100%100%95
   LayerZeroAdapter.sol98.41%90%100%100%124
   RecoveryAdapter.sol100%100%100%100%
   WormholeAdapter.sol97.67%90%100%100%108
src/admin
   OpsGuardian.sol100%100%100%100%
   ProtocolGuardian.sol100%100%100%100%
   Root.sol100%100%100%100%
   TokenRecoverer.sol100%100%100%100%
src/core/hub
   Accounting.sol97.92%96%100%98.31%134, 137
   Holdings.sol97.71%88.89%100%100%118, 243, 82
   Hub.sol93.98%81.13%93.02%97.54%269, 287, 305, 339, 343, 374–375, 413, 498–499, 544, 549, 586, 601, 87
   HubHandler.sol98%90.91%100%100%71
   HubRegistry.sol92.39%76.67%100%100%118, 124, 130, 35, 46, 79, 99
   ShareClassManager.sol98.81%95.45%100%100%42
src/core/libraries
   PricingLib.sol100%100%100%100%
src/core/messaging
   GasService.sol96.55%100%87.50%96.20%106, 135, 145
   Gateway.sol100%100%100%100%
   MessageDispatcher.sol99.60%98.55%100%100%733
   MessageProcessor.sol82.42%60.26%100%99.01%101, 103, 109, 112, 114, 125, 130, 139, 142, 145, 157, 160, 163, 168, 178, 181, 190, 193, 196, 209, 220, 225, 228, 232, 83–84, 87–88, 91–92, 97, 99
   MultiAdapter.sol100%100%100%100%
src/core/messaging/libraries
   MessageLib.sol100%100%100%100%
src/core/spoke
   BalanceSheet.sol97.47%96.97%92.59%98.55%291, 348, 57
   PoolEscrow.sol100%100%100%100%
   ShareToken.sol92.41%60%94.44%98.04%100, 112, 144, 146, 32
   Spoke.sol97.46%89.71%100%100%132, 132–133, 133, 135, 92–93
   VaultRegistry.sol93.88%85.71%100%98.18%54, 60–62, 98–99
src/core/spoke/factories
   PoolEscrowFactory.sol100%100%100%100%
   TokenFactory.sol100%100%100%100%
src/core/utils
   BatchedMulticall.sol100%100%100%100%
   ContractUpdater.sol100%100%100%100%
src/deployment
   ActionBatchers.sol84.80%62.50%80%86.46%438, 440–441, 443, 443–444, 446, 448, 448–449, 453, 453–454, 456, 459, 459–460, 462, 465, 465–466, 469, 472, 472, 474, 476, 508–512, 517–518, 520–521, 523–524
src/hooks
   BaseTransferHook.sol100%100%100%100%
   FreelyTransferable.sol92.31%80%100%100%37
   FreezeOnly.sol100%100%100%100%
   FullRestrictions.sol95.24%88.89%100%100%47
   RedemptionRestrictions.sol85.71%50%100%100%37
src/hooks/libraries
   UpdateRestrictionMessageLib.sol90%50%100%100%40, 61, 82
src/managers/hub
   NAVManager.sol100%100%100%100%
   SimplePriceManager.sol100%100%100%100%
src/managers/spoke
   MerkleProofManager.sol97.01%87.50%100%100%103, 110
   OnOfframpManager.sol100%100%100%100%
   QueueManager.sol100%100%100%100%
src/managers/spoke/decoders
   BaseDecoder.sol100%100%100%100%
   CircleDecoder.sol83.33%100%100%75%22
   VaultDecoder.sol100%100%100%100%
src/misc
   Auth.sol100%100%100%100%
   ERC20.sol100%100%100%100%
   Escrow.sol56.25%33.33%100%66.67%17, 19, 23–24, 24, 24, 26
   Multicall.sol91.67%66.67%100%100%19
   Recoverable.sol100%100%100%100%
   ReentrancyProtection.sol90%75%100%100%24
src/misc/libraries
   ArrayLib.sol100%100%100%100%
   BitmapLib.sol100%100%100%100%
   BytesLib.sol90.27%56%100%100%109, 120, 131, 14, 142, 153, 16, 164, 175, 186, 87
   CastLib.sol95.24%66.67%100%100%10, 34
   EIP712Lib.sol100%100%100%100%
   ExcessivelySafeCallLib.sol100%100%100%100%
   MathLib.sol93.46%76.19%100%97.33%34–35, 44, 46, 48, 50, 52
   MerkleProofLib.sol100%100%100%100%
   SafeTransferLib.sol96.97%92.86%100%100%75
   SignatureLib.sol95.24%80%100%100%17
   StringLib.sol100%100%100%100%
   TransientArrayLib.sol100%100%100%100%
   TransientBytesLib.sol100%100%100%100%
   TransientStorageLib.sol100%100%100%100%
src/spell
   V2CleaningsSpell.sol0%0%0%0%100–103, 103–106, 111–112, 112–114, 114, 114–116, 116, 116–118, 118, 118–119, 121, 124, 126–127, 129, 133–137, 46–47, 47, 47–48, 50–53, 55, 55–56, 58, 61, 63, 63, 65, 65–66, 66–69, 71, 75, 75–76, 81, 81–82, 82–84, 86–87, 91–94, 96–97
src/utils
   RefundEscrow.sol100%100%100%100%
   RefundEscrowFactory.sol100%100%100%100%
   SubsidyManager.sol100%100%100%100%
src/valuations
   IdentityValuation.sol100%100%100%100%
   OracleValuation.sol100%100%100%100%
src/vaults
   AsyncRequestManager.sol96.85%86.36%100%99.64%195, 198, 201, 204, 215, 227, 295, 328, 455, 460, 505, 573, 580
   AsyncVault.sol96.25%83.33%95%98.15%146, 47
   BaseVaults.sol93.50%80.77%95.24%95.45%124, 137, 239, 309–310, 85–86, 86, 86–88
   BatchRequestManager.sol100%100%100%100%
   SyncDepositVault.sol100%100%100%100%
   SyncManager.sol98.58%92.59%100%100%67, 72
   VaultRouter.sol87.37%58.82%100%91.53%107, 107, 109–110, 110, 112, 149, 70, 73–74, 86–87
src/vaults/factories
   AsyncVaultFactory.sol93.75%50%100%100%32
   SyncDepositVaultFactory.sol95%50%100%100%40
src/vaults/libraries
   RequestCallbackMessageLib.sol89.58%50%100%100%104, 139, 38, 57, 77
   RequestMessageLib.sol89.74%50%100%100%37, 55, 72, 89

Copy link
Copy Markdown
Contributor

@lemunozm lemunozm left a comment

Choose a reason for hiding this comment

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

LGTM

@gpmayorga gpmayorga merged commit 839b9d0 into main Apr 9, 2026
16 checks passed
@gpmayorga gpmayorga deleted the ci/registry-pr-validation branch April 9, 2026 16:15
gpmayorga added a commit that referenced this pull request Apr 9, 2026
Fixes unintended registry publish triggered by PR #807 merge to main.
Non-contract changes (CI scripts, docs) now no longer pin to IPFS or
update Cloudflare — only registries with actual contract changes do.

- validate-registry.js: add `publishable` flag to summary (errors==0 && chains>0)
- pin-to-ipfs.js: zero-chains guard skips pinning empty delta registries;
  fix FORCE_PIN="both" not bypassing the guard for all envs
- registry.yml: structured workflow_dispatch input (mainnet/testnet/both);
  detect-changed-environments respects dispatch input; new "Validate and gate
  publish" step in pin-to-ipfs job re-runs validation and drops unpublishable
  files before pinning; Update Cloudflare step gated to push events only —
  workflow_dispatch pins to IPFS but never touches Cloudflare (avoids
  unauthorized DNSLink overwrites)
gpmayorga added a commit that referenced this pull request Apr 14, 2026
* feat(registry): add env schema and registry output validators

Add two zero-dependency validation scripts:

- validate-env-schema.js: validates all env/*.json files against the
  expected schema (required network.chainId, valid addresses, known
  top-level keys). Fast-fail gate before registry generation.

- validate-registry.js: validates generated registry JSON against
  indexer hard requirements (version, previousRegistry.ipfsHash,
  startBlock, blockNumber, address, ABI completeness). Produces a
  sidecar .validation.json report for CI consumption.

* ci(registry): add PR validation, preview comments, and tighten triggers

- Add env/** and script/registry/** to pull_request paths trigger so
  PRs touching env files run the registry pipeline before merge
- Add paths filter to push trigger (only run on env/registry changes)
- Remove release trigger (env file merges are the canonical signal)
- Add validate-env-schema.js step before generation (fast-fail gate)
- Add validate-registry.js step after generation (indexer requirements)
- Add PR preview comment with registry summary, errors, and warnings
- Update pin-to-ipfs condition to remove release event
- Update README with pipeline diagram, validation docs, and indexer
  hard/soft requirements tables

* feat(registry): resolve previousRegistry CID via DNS instead of Pinata

Use dnslink TXT record lookup (_dnslink.registry.centrifuge.io) to
resolve the IPFS CID of the currently live registry at generation time.
This is the source of truth — the DNS record points to whatever CID
Cloudflare is actually serving.

Eliminates the need for Pinata API key during generation and removes
the fragile metadata-based lookup. SOURCE_IPFS env var still works
as an explicit override.

Also reverts previousRegistry.ipfsHash back to an error in the
validator since it's now filled at generation time.

* refactor(registry): use DNS dnslink as primary CID resolution in pin-to-ipfs

Update pin-to-ipfs.js to use DNS dnslink lookup as the primary method
for resolving the previous registry CID, with Pinata as a fallback.
This aligns with abi-registry.js which now fills previousRegistry.ipfsHash
via DNS at generation time.

Both the pinning flow and --update-previous mode now follow the same
resolution order: DNS first, Pinata if DNS fails.

* fix(registry): fix version extraction and ABI name mapping

Version extraction now falls back to contract-level version fields
when deploymentInfo entries don't have a version (which is the case
for all current env files).

ABI name mapping now handles mismatches between env file contract keys
and Forge artifact names:
- Hook contracts: freezeOnlyHook → FreezeOnly (not FreezeOnlyHook)
- NAV manager: navManager → NAVManager (not NavManager)
- Token factory base: TokenFactory → ShareToken (not Token)

Both abi-registry.js and validate-registry.js share the same override
maps to stay in sync.

Tested locally: 0 errors, 0 warnings, 53 ABIs packed.

* fix(env): remove tokenBridge from testnet env files

tokenBridge is not a protocol contract — it has no Solidity source in
this repo and no Forge build artifact. It was likely an artifact from
a deployment session that leaked into the env files. It's not present
in any mainnet env file.

* refactor(registry): use highest version and trim non-indexer warnings

Version extraction now picks the highest version across all contracts
in a chain (e.g. v3.1 wins over 3), instead of the first one found.
This handles chains where root kept its original v3 version while all
other contracts were upgraded to v3.1.

Remove warnings from validate-registry.js for fields the indexer
doesn't use (txHash, deployedAt, gitCommit, previousRegistry.version).
The validator now only checks what actually matters for the indexer.

* feat(registry): add workflow_dispatch input for environment selection

Enhance the GitHub Actions workflow by introducing an optional input for the `workflow_dispatch` event, allowing users to specify which registry to regenerate (mainnet, testnet, or both). Update the environment detection logic to accommodate this input, ensuring correct handling of changed environments based on user selection or automatic detection.

* refactor(registry): simplify environment detection logic in CI workflow

Removed the optional input for `workflow_dispatch` to streamline the environment detection process. The logic now directly detects changed environments without user input, ensuring consistent behavior during CI runs. Updated conditions for pinning registry files to IPFS to enhance clarity and maintain functionality.

* feat(registry): deprecations in delta and per-tag ABI cache

- Emit contracts removed from env vs previous registry as address null
- Skip deprecated entries in packAbis; document schema in README
- Build ABIs per contract version: resolve git tag (v-prefix, .0 patch)
- Cache Forge out/ under cache/abi-registry/<tag>/ via worktrees
- Preserve contract version through processContracts for tag resolution
- Map env names to Forge artifacts (ShareToken, NAVManager, hooks)

* docs(registry): example delta JSON and consumer note for deprecations

* ci(registry): align workflow with per-tag ABI cache

- Remove single-commit worktree forge + out/ copy (abi-registry builds per tag)
- Fetch git tags before generation for version-tag worktrees
- Keep DEPLOYMENT_COMMIT for registry metadata only
- Update README pipeline and detect-deployment-commit descriptions

* feat(registry): validate env versions and git tags before registry work

- validateEnvContractVersionTags: require version, cross-file consistency, resolvable tags
- Run at start of main() before live registry fetch
- Run registry workflow on PRs touching env/ or script/registry/
- Document early validation in README

* ci(registry): fail early when env contract version has no git tag

- Extract tag-resolution.js for shared version→tag logic
- Add validate-env-contract-version-tags.js; run after fetch-tags in registry.yml
- Expand PR path filters to env/, script/registry/, ci-scripts/
- Document validation in README

* refactor(registry): move tag-resolution and env tag validator to utils

Colocate shared tag helpers with other registry utilities. CI runs the validator from script/registry/utils; abi-registry imports ./utils/tag-resolution.js. README and workflow paths updated.

* fix(registry): skip contracts already deprecated in live registry for deltas

When the live registry is a delta that already carries address:null deprecations, do not re-emit those keys each run. Avoids growing redundant null entries, spurious chain inclusion, and inflated deprecation counts.

* refactor(registry): remove contract version from output for smaller registries

- Introduced `stripContractVersionsForRegistryOutput` to omit `version` fields from chains before JSON output, reducing size for published registries.
- Updated README to reflect changes in contract data structure.

* docs: add Claude Code registry rule and CLAUDE.md pointer

Path-scoped .claude/rules/registry.md for script/registry, env, and registry CI. Root CLAUDE.md links README and rule; notes Cursor vs Claude rule layout.

* docs: drop registry subsection from CLAUDE.md

Registry workflow remains documented in script/registry/README.md and .claude/rules/registry.md for Claude Code path rules.

* fix(registry): treat top-level version as optional in validate-registry

Omitting registry.version is valid when provenance uses deploymentInfo.gitCommit;
pass validation instead of failing when the field is absent.

* refactor(registry): extract per-tag ABI cache into utils module

- Add utils/abi-cache.js (ensureAbiCache, collectContractTags, findAbiInOutput, aliases)
- Add build-abi-cache.js CLI to warm cache/abi-registry without full registry run
- Thin abi-registry packAbis around shared module; document in .claude/rules/registry.md

* docs(registry): document ABI cache on-disk layout

- README: tree, lifecycle, programmatic reads, build-abi-cache CLI; table links
- abi-cache.js: module header mirrors layout + README pointer
- Claude registry rule: point AI/humans to README ABI cache section

* feat(registry): validate deployment startBlock in env schema

- Require deploymentInfo.*.startBlock when any contract has blockNumber
- Reject large gap vs min active contract block (chain-level indexer listeners)
- Document in README; validate-registry docblock defers gap check to env step
- Fix stale deployment startBlock on Arbitrum mainnet and Arbitrum Sepolia

* add branch main to registry job

* docs(registry): explain how to find SOURCE_IPFS value locally

* refactor(registry): remove dead ABI_NAME_OVERRIDES and FACTORY_BASE_OVERRIDES

These constants were superseded by ABI_NAME_ALIASES in utils/abi-cache.js
but survived the merge from main. packAbis now uses resolveArtifactName()
from the shared module instead.

* ci(registry): smart auto-publish with validation gate

Fixes unintended registry publish triggered by PR #807 merge to main.
Non-contract changes (CI scripts, docs) now no longer pin to IPFS or
update Cloudflare — only registries with actual contract changes do.

- validate-registry.js: add `publishable` flag to summary (errors==0 && chains>0)
- pin-to-ipfs.js: zero-chains guard skips pinning empty delta registries;
  fix FORCE_PIN="both" not bypassing the guard for all envs
- registry.yml: structured workflow_dispatch input (mainnet/testnet/both);
  detect-changed-environments respects dispatch input; new "Validate and gate
  publish" step in pin-to-ipfs job re-runs validation and drops unpublishable
  files before pinning; Update Cloudflare step gated to push events only —
  workflow_dispatch pins to IPFS but never touches Cloudflare (avoids
  unauthorized DNSLink overwrites)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:infra CI, GitHub Actions, foundry config

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants