Skip to content

Resolve vault allocators from Kong assignment logs - #472

Open
rossgalloway wants to merge 6 commits into
mainfrom
codex/allocator-assignment-projection
Open

Resolve vault allocators from Kong assignment logs#472
rossgalloway wants to merge 6 commits into
mainfrom
codex/allocator-assignment-projection

Conversation

@rossgalloway

@rossgalloway rossgalloway commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Kong currently derives vault allocator addresses from factory logs, which misses replacements and misreads shared allocator deployments. Resolve assignments from Kong's own indexed Role Manager and vault events, then serve both GraphQL fields and snapshot ratios from the same saved projection.

Closes #471.

Indexing stays in Kong. Configure the direct Ethereum and Polygon Yearn role managers and the shared allocator factory, discover initial/replacement assigned contracts, and decode shared ratio events through the existing extractor and loader. Factory records identify deployment and ABI provenance; they do not establish assignments. No new indexing service or environment configuration is required.

Failed refreshes retain the last validated allocator and ratios with explicit stale, lastAttemptAt, and lastError metadata. Confirmed replacements and clears still apply atomically. A confirmed manager change invalidates the old assignment even when the new manager has no indexed assignment; delayed jobs cannot restore the old manager at an earlier block. Old ratios never cross assignment boundaries, and delayed refreshes cannot roll back the accepted state.

The allocator GraphQL resolver masks malformed-address and database errors consistently with the vault resolver, keeping diagnostic details in server logs.

How to review

Start with docs/allocator-assignments.md, config/manuals.yaml, and config/abis.yaml. Follow Role Manager and shared factory discovery hooks, the combined allocator ABI, and the local evmlog queries in packages/ingest/abis/yearn/lib/allocators/projection.ts. The refresh CLI and writer live beside that projection. Shared serving and activation rules live in packages/lib/allocator-snapshot.ts.

The refresh CLI supports read-only checks and explicit writes, reporting the committed projection and whether it was applied, skipped, or retained with stale metadata. Required evidence failures exit nonzero even when a write records staleness.

Validation

  • 70 ingest mock tests, 90 web tests, and 11 disposable PostgreSQL tests passed.
  • The web production build and all four package linters passed. Ingest TypeScript retains the same 17 existing diagnostics as main.
  • SQL tests cover unconfigured factories, excluded Katana bound factories, ambiguous deployments, and valid Katana/Polygon shared factories.
  • Tests cover discovery, legacy manager metadata, no-code/zero assignments, two vaults sharing an allocator, outage retention, explicit clears, replacement isolation, delayed recovery, and refresh reporting.
  • Real Ethereum Incorrect V3 allocator discovery and GraphQL resolution after Role Manager updates #471 assignment and deployment logs were loaded into a local Kong database. At block 20,987,762, both GraphQL paths and REST returned the expected shared allocator and ratios.
  • Polygon's real assignment transaction 0x71455263e2a5440fd9a9e1e4d155f237c20f703d6205883df5cedceccee45bb6 and shared deployment were loaded locally. USDC-1 resolved allocator 0x10BC1f4f0dDD8027264E9F10FabE7A751614C207 at block 86,228,011 with on-chain ratios, including real zeros.
  • An outage simulated only in the local review database preserved the accepted address, ratios, revision and observation time across GraphQL and REST, exposed stale metadata, rejected delayed recovery, and recovered successfully.

Risk / rollout

Production backfill and activation have not been performed. Run Kong discovery/fanout for the direct managers and shared factories, replay stored manager events for historical contract discovery, and freshly extract missing shared ratio events where old ABI strides already exist. Stored-log replay cannot recover events that were never indexed. Refresh snapshots and REST caches after validating coverage.

Legacy Gnosis vaults using controllers without getDebtAllocator(address) are intentionally unsupported. Their three vault/controller pairs and null/unavailable behavior are documented in docs/allocator-assignments.md. A legacy role-grant compatibility path is outside this PR; Gnosis factory indexing remains configured for provenance.

Allocator.address is nullable for confirmed clears or missing validated evidence. Custom assignments retain their address with unsupported/unavailable ratios. Full allocation-history APIs remain outside this PR.

Materialize current allocator assignments independently of deployment provenance. Serve both GraphQL paths and snapshot ratios from the same saved revision, preserving arbitrary addresses and explicit unavailable configuration.
@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
kong Ready Ready Preview Sep 10, 2026 8:01pm UTC

Request Review

Keep the last validated allocator block outside the replaceable
projection so failures cannot allow older assignments to reappear.

Cover failed reads, lagging recovery, same-block recovery, explicit
clears, and zero ratios, including both Postgres write paths.
Return the committed projection and whether the candidate was applied.
Report retained state with a skipped outcome when the merge guard rejects
a delayed refresh, and distinguish dry runs from accepted writes.

Cover CLI output and database outcomes, including rejection by observation
time when the revision and block are unchanged. Document the output fields.

Validation: 54 ingest mock tests, 88 web tests, two Postgres persistence
tests, package lint, and actual CLI checks against a disposable database.

@matheus1lva matheus1lva left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixes #471's core regression correctly; the ordered resolver and shared-projection design are sound.

  • Allocator regression on Gnosis and Polygon (high) — V3 vaults on chains 100 and 137 lose vault.allocator and all target/max ratios: the factory-derived path is deleted, and the new projection returns chain_not_configured for anything outside the hardcoded chain list.
    Done when: a Polygon or Gnosis V3 vault with a NewDebtAllocator log still returns its allocator after the snapshot hook runs, or the PR states those chains are intentionally dropped.
    Provenance: 12c83eb

  • Transient outage blanks served data (high) — any Envio or RPC failure during the routine snapshot hook replaces a previously validated projection with an unavailable one, so the vault's allocator and every strategy ratio read null in GraphQL and REST until the next successful run. This is documented as intended, but it is an availability regression versus main, which served the address regardless of Envio.
    Done when: with a validated projection stored, a hook run whose Envio call throws leaves the served allocator and ratios unchanged, at most with a stale marker.
    Provenance: 12c83eb

  • Unmet acceptance criterion (medium)#471 requires shared allocator contracts and their UpdateStrategyDebtRatio events to be indexed in Kong; this PR delegates discovery to Envio and reads ratios as point-in-time RPC calls, without amending the issue scope.
    Done when: Kong indexes shared allocator ratio events, or the issue is explicitly amended to move that out of scope.
    Provenance: 12c83eb

  • Missing shared-allocator multi-vault test (medium)#471 lists "multiple vaults sharing one allocator" as a required test case; every spec uses a single vault constant.
    Done when: a test resolves two vaults against the same shared allocator and asserts each projection's address and ratio calls are scoped to its own vault.
    Provenance: 12c83eb

  • Pagination untested (low) — the keyset cursor, stall detection, and event cap in the Envio query have no coverage; every fixture returns one page under the page size.
    Done when: a test feeds more than one page and asserts the resolved allocator comes from the last page, plus one test each for the stalled-cursor and event-limit throws.
    Provenance: 12c83eb

  • Deployment rejection branches untested (low) — wrong factory address, vault-bound row on Katana, and ambiguous deployments are all swallowed into allocator_configuration_unavailable, so a regression there is invisible.
    Done when: tests cover each rejection branch and one valid Katana shared deployment.
    Provenance: 12c83eb


How This Was Reviewed

Reviewed with the review-pr-workflow skill
5 review lenses, each finding independently verified by claude. 1 candidate finding was refuted and dropped.

@murderteeth murderteeth 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.

Summary

An independent multi-lens pass surfaced the same defects matheus1lva already posted (Polygon regression, outage blanking, unmet #471 criteria, missing shared-allocator and pagination tests), so those are not repeated. Two findings below: the first sharpens matheus1lva's "Unmet acceptance criterion" with replica evidence for why Envio is unnecessary, the second is new.

Issues

  • packages/ingest/allocators.ts:44 - Envio is not needed; this is a Kong config gap (high) — same defect as "Unmet acceptance criterion (medium)" in matheus1lva's review, raised to high because every input the projection fetches from Envio is data Kong already knows how to index. The roleManager ABI carries AddedNewVault, RemovedVault and UpdateDebtAllocator, and the extractor stores every ABI event, not just hooked ones; the vault ABI carries UpdateRoleManager; vault-bound factory deployments are already in evmlog. What Kong is missing on prod is coverage, not capability: the yvUSDC-1 role manager (0xb3bd…) predates the roleManagerFactory, so it is not a thing and Kong holds zero logs from it, which is the entire #471 case; and the shared factory (0x03D4…) is not in abis.yaml. Both close with config: legacy role managers declared in config/manuals.yaml, the shared factory added as a source with its allocator ABI. The ordered resolver this PR adds in lib/allocators.ts can then run over Kong's own evmlog. Instead the PR introduces a second indexer, three env vars, a 30-second external call per vault snapshot, and a projection that goes null whenever that indexer is unreachable.
    • Done when: allocator assignments are resolved from Kong's own evmlog with no ENVIO_* configuration; the legacy role managers and the shared factory are indexed via config; yvUSDC-1 resolves to 0x1e9eB053… from Kong data alone; and UpdateDebtAllocator rows exist in evmlog for chain 1.
    • Provenance: 12c83eb
  • packages/ingest/allocators.ts:1 - Feature code placed at the package root (medium) — the ingest root holds only cross-cutting infrastructure (db, rpcs, prices, things, the service entry), and protocol logic lives under abis/, with abis/yearn/lib/ already serving as the shared home for Yearn modules. This PR puts three Yearn-specific files at the root instead: the projection, whose only consumer is the V3 vault snapshot hook reaching up five directory levels; the store helper, used only by the CLI; and a one-off rollout CLI, for which the repo already has packages/scripts/src/. Anything at the root reads as shared infrastructure to packages/scripts importers, so this placement will be copied.
    • Done when: no new .ts files sit at the ingest package root; the projection lives under abis/yearn/, the CLI and its store helper live under packages/scripts/src/ or beside the feature they serve, the specs follow their modules, and the docs' invocation path is updated to match.
    • Provenance: 12c83eb

Verdict

REQUEST_CHANGES


How This Was Reviewed

Reviewed with the review-pr-workflow skill
5 review lenses, each finding independently verified by claude. 0 candidate findings were refuted and dropped.

Resolve assignments and factory provenance from Kong evmlog records.
Remove the external indexer client, configuration, and chain allowlist.

Register the legacy Yearn role manager and shared allocator factories.
Discover assigned contracts from initial and replacement events, and
decode shared ratio events through Kong's existing allocator ABI.

Cover native log persistence, shared-vault scoping, legacy metadata,
discovery hooks, and refresh behavior. Document Kong backfill steps.
@rossgalloway rossgalloway changed the title Resolve vault allocators from Envio assignments Resolve vault allocators from Kong assignment logs Sep 9, 2026
Keep validated assignments and ratios during outages, with explicit stale
metadata and guards against delayed recovery. Confirmed replacements and
clears still replace old ratios atomically.

Register Polygon's direct Role Manager and shared allocator factory for
native Kong indexing. Document intentionally unsupported legacy Gnosis
vaults. Move the allocator feature under the Yearn ingest directory and
add SQL deployment rejection and stale-state regression coverage.
@rossgalloway

Copy link
Copy Markdown
Collaborator Author

fixes have been applied and the description has been updated.

@matheus1lva

Copy link
Copy Markdown
Collaborator

Verified these three against the branch. All reproduce in the code as written.

1. Manager change keeps serving the old allocator (high)

projectCurrentAllocator returns early at packages/ingest/abis/yearn/lib/allocators/projection.ts:126 for every
unavailable result except vault_removed_from_role_manager — before revision is computed at
projection.ts:164. So the projection carries revision: null.

mergeAllocatorHook classifies revision == null as a failed refresh
(packages/lib/allocator-snapshot.ts:51) and retains the previous state with stale: true
(allocator-snapshot.ts:56). When a vault's role manager moves to an address with no indexed assignment
events (EOA, third-party manager, zero), every subsequent refresh repeats this, so the previous manager's
allocator is served indefinitely through both GraphQL and REST.

The projection-level behaviour is covered at projection.mock.spec.ts:121, but nothing covers the merge
retention for the manager-change case.

Conflating "manager not indexed" with "refresh failed" is defensible — Kong cannot see logs of an unconfigured
manager. But the happy path already calls getDebtAllocator(vault); calling it on this path disambiguates:
a zero return from the new manager is conclusive, not a failure.

Done when: with a validated allocatorState stored, a manager change to an address with no assignment rows
yields allocator: null or an explicit unavailable state, transient RPC/DB failures still retain, and a test
covers the manager-change merge.

2. Every unmaterialized vault answers null on deploy (medium)

allocatorSnapshotFields forces allocator: null when allocatorState is absent
(packages/lib/allocator-snapshot.ts:17), and its SQL twin does the same. mergeSnapshot applies it after the
hook spread, so from the web deploy every V3 vault serves no allocator until its snapshot hook re-runs.

The removed projectDebtAllocator read the latest NewDebtAllocator by vault — a deployment, not an
assignment — which is exactly what this PR is fixing. Still, the interim needs to be a decision, not a
side effect. Combined with (1), vaults whose role manager is never indexed stay null permanently.

Done when: a snapshot with a legacy hook.allocator and no allocatorState still serves that address, or the
rollout sequences backfill before the web deploy and states the interim null as accepted risk.

3. Raw DB and viem errors reach GraphQL clients (low)

packages/web/app/api/gql/resolvers/allocator.ts:7-13 drops the try/catch that the sibling vault resolver
keeps, and the server at packages/web/app/api/gql/route.ts:38 sets no formatError. A malformed vault
argument throws viem's InvalidAddressError and a pg failure serializes its message to unauthenticated
callers. Low impact — the leaked content is an address or a pg message — but the inconsistency with vault is
free to fix.

Done when: a DB failure or malformed address on allocator(chainId, vault) returns the same generic error as
vault, with details only in server logs.

Priority: fix (1), sequence or annotate (2), (3) is a two-line wrap.

@rossgalloway

Copy link
Copy Markdown
Collaborator Author

2. Every unmaterialized vault answers null on deploy (medium)

allocatorSnapshotFields forces allocator: null when allocatorState is absent (packages/lib/allocator-snapshot.ts:17), and its SQL twin does the same. mergeSnapshot applies it after the hook spread, so from the web deploy every V3 vault serves no allocator until its snapshot hook re-runs.

The removed projectDebtAllocator read the latest NewDebtAllocator by vault — a deployment, not an assignment — which is exactly what this PR is fixing. Still, the interim needs to be a decision, not a side effect. Combined with (1), vaults whose role manager is never indexed stay null permanently.

Done when: a snapshot with a legacy hook.allocator and no allocatorState still serves that address, or the rollout sequences backfill before the web deploy and states the interim null as accepted risk.

Codex has a question:
On the deployment/backfill issue, I'd like to clarify how you'd prefer this handled before I make a change.

My concern with adding a legacy fallback is that the old factory-derived allocator isn't simply an older representation of the same data — it can be incorrect, which is one of the things this PR is intended to fix. I'd prefer not to knowingly preserve two conflicting sources of truth.

Would you prefer that we handle this as a staged migration: deploy the new ingestion/indexing path first, backfill and verify allocatorState coverage, and only then switch the API/web readers to the new projection?

If ingestion and web can't practically be deployed/cut over separately, would you instead prefer an explicit activation/feature gate so the new reader isn't enabled until the backfill has been verified?

Or do you specifically want the legacy factory-derived allocator retained as a temporary fallback during the migration?

@rossgalloway

Copy link
Copy Markdown
Collaborator Author

I'll push fixes for the other 2 issues shortly

Preserve successful finalized manager observations across later database
failures. Clear the old assignment on confirmed manager changes and keep
the observed block as a floor for delayed recovery, while retaining data
for transient failures without a confirmed change.

Mask allocator GraphQL address and database errors consistently with the
vault resolver. Add projection, persistence, serving, and error-response
regressions. Leave deployment sequencing for the separate rollout review.
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.

Incorrect V3 allocator discovery and GraphQL resolution after Role Manager updates

3 participants