Skip to content

Harden ENSJobPages: Prime-fallback hooks, explicit ENS-side repair, inspector ABI-awareness, and operator tooling - #156

Open
MontrealAI wants to merge 2 commits into
mainfrom
codex/complete-ens-authority-refactor
Open

Harden ENSJobPages: Prime-fallback hooks, explicit ENS-side repair, inspector ABI-awareness, and operator tooling#156
MontrealAI wants to merge 2 commits into
mainfrom
codex/complete-ens-authority-refactor

Conversation

@MontrealAI

Copy link
Copy Markdown
Owner

Motivation

  • Make the ENS job-page subsystem production-ready for an unchanged AGIJobManagerPrime by ensuring handleHook(uint8,uint256) works when the manager does not expose V1 view getters and by exposing operator-safe repair flows.
  • Eliminate unsafe implicit authority binding across multiple root versions and avoid issued/ready overclaiming by depending on observed chain state where possible.
  • Improve read-side diagnostics so resolver auth-read absence is distinct from an explicit false, and update operator scripts/runbooks to be log-driven and mainnet-accurate.

Description

  • Added runtime manager capability detection and a Prime-fallback path in ENSJobPages.handleHook(...) so hooks use IAGIJobManagerPrimeViewV1 when available and fall back to jobEmployerOf(jobId) / jobAssignedAgentOf(jobId) when not, emitting precise best-effort failure reasons for missing URIs.
  • Introduced explicit ENS-side, owner-callable repair/replay functions that do NOT require V1 getters: repairAuthoritySnapshotExplicit, repairResolver, repairSpecTextExplicit, repairCompletionTextExplicit, repairTextsExplicit, repairAuthorisationsExplicit, replayCreateExplicit, replayAssignExplicit, replayCompletionExplicit, replayRevokeExplicit, and replayLockExplicit in contracts/ens/ENSJobPages.sol.
  • Hardened authority snapshot semantics with _establishAuthorityForRootVersion(...) and made repairAuthoritySnapshot(...) refuse ambiguous multi-root repairs unless repairAuthoritySnapshotExplicit(...) is used.
  • Rebased compatibility booleans: jobEnsIssued now checks observed on-chain node existence and jobEnsReady requires node + resolver + base metadata presence, not sticky write-attempt flags.
  • Made ENSJobPagesInspector resolver-auth checks ABI-aware: it reports authReadSupported, authObservationIncomplete, and separates unknown vs false when isAuthorised(bytes32,address) is absent.
  • Updated and added operator scripts that are log-driven and tolerant of the unchanged Prime ABI: scripts/ens/phase0-mainnet-snapshot.mjs, scripts/ens/inventory-job-pages.ts, scripts/ens/repair-job-page.ts, and new scripts/ens/repair-from-logs.ts; they emit machine-readable JSON under scripts/ens/output/.
  • Updated docs and runbooks to reflect preview vs effective semantics, explicit repair paths, and current mainnet Prime/ENS defaults (AGIJobManagerPrime default addressed in deploy scripts and runbooks).
  • Added test doubles and unit tests to cover fallback manager behavior and inspector auth-read absence handling: contracts/test/MockAGIJobManagerPrimeFallback.sol, contracts/test/MockPublicResolverNoAuthRead.sol, and extended test/ensAuthoritySnapshot.test.js.

Testing

  • Syntax and script validation: ran Node-based syntax checks for scripts/ens/* and repair-from-logs.ts; all ENS operator scripts passed syntax evaluation (ok), and new scripts emit JSON payloads as designed. (succeeded)
  • Local contract compile checks: compiled patched ENS contracts and new test mocks using a local/bundled solc invocation as a syntax/bytecode sanity check; compilation succeeded for the targeted contracts in this environment. (succeeded)
  • Unit tests: extended test/ensAuthoritySnapshot.test.js with cases demonstrating preview/effective separation, multi-root explicit repair requirement, unchanged-Prime fallback hook behavior, and inspector auth-read absence handling; these tests are added and runnable in CI, and were exercised via Truffle artifacts verification where possible. (tests added; full Truffle execution not completed in this environment)
  • Hardhat/phase-0 and RPC-dependent flows: attempted npm run ens:phase0:mainnet and full Hardhat compile in this sandbox but remote RPC and external compiler download failed due to network restrictions (ENETUNREACH), so live chain re-audits and canonical 0.8.23 full compile/size remeasure must be run from an operator workstation before mainnet cutover. (blocked by network in this run)

Codex Task

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f85ca5406d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread contracts/ens/ENSJobPages.sol Outdated
Comment on lines +1319 to +1322
function _managerSupportsViewV1(address manager) internal view returns (bool ok) {
uint256 version;
(ok, version) = _staticcallWord(manager, abi.encodeWithSelector(PRIME_VIEW_VERSION_SELECTOR));
ok = ok && version == 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Detect V1 managers without requiring the version getter

_managerSupportsViewV1() now classifies a manager as “rich” only if ensJobManagerViewInterfaceVersion() exists and returns 1, but the repo’s AGIJobManager exposes getJobCore/getJobSpecURI/getJobCompletionURI (contracts/AGIJobManager.sol:1186-1244) without that version getter. When ENSJobPages is wired to AGIJobManager, handleHook() therefore takes the fallback path, and that path expects jobEmployerOf/jobAssignedAgentOf selectors that only exist on AGIJobManagerPrime (contracts/AGIJobManagerPrime.sol:1095-1100), so create hooks start reverting and the later hooks silently lose their metadata/auth updates.

Useful? React with 👍 / 👎.

Comment thread scripts/ens/inventory-job-pages.ts Outdated
} : { label: '', name: '', uri: '', node: ethers.ZeroHash };

const node = authorityReady ? effective.node : (labelProbeNode !== ethers.ZeroHash ? labelProbeNode : preview.node);
const node = authority[0] ? effective.node : preview.node;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the snapshotted label when authority is still unset

For jobs that already have an imported exact label but do not yet have authorityEstablished, jobLabelSnapshot() can legitimately differ from previewJobEnsLabel(). This now always inspects preview.node whenever authority[0] is false, so the inventory probes <current-prefix><jobId> instead of the snapshotted legacy node. In that pre-authority repair state, ownership/resolver/text status is reported for the wrong subdomain and operators can be told that a legacy page is missing or unmanaged even when the real imported node already exists.

Useful? React with 👍 / 👎.

Comment thread scripts/ens/inventory-job-pages.ts Outdated
authReadSupported,
employerAuthorisedObserved: employerAuth,
agentAuthorisedObserved: agentAuth,
authorisationsAsExpected: authReadSupported ? ((employer === ethers.ZeroAddress || employerAuth === true) && (agent === ethers.ZeroAddress || agentAuth === true || terminalSet.has(jobId))) : false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Treat terminal jobs as expecting revoked employer auth

terminalSet only relaxes the agent side of this predicate, but ENSJobPages._revokePermissions() and _lockJobENS() revoke both employer and agent authorisations (contracts/ens/ENSJobPages.sol:908-925). As a result, any correctly finalized/refunded/expired job with readable resolver auth state will still fail authorisationsAsExpected because employerAuth is required to remain true, so inventory-job-pages will classify healthy terminal jobs as permissions-drift and can drive unnecessary repair actions.

Useful? React with 👍 / 👎.

…nspector ABI-awareness, and operator tooling

### Motivation

- Make the ENS job-page subsystem production-ready for an unchanged `AGIJobManagerPrime` by ensuring `handleHook(uint8,uint256)` works when the manager does not expose V1 view getters and by exposing operator-safe repair flows.
- Eliminate unsafe implicit authority binding across multiple root versions and avoid `issued/ready` overclaiming by depending on observed chain state where possible.
- Improve read-side diagnostics so resolver auth-read absence is distinct from an explicit false, and update operator scripts/runbooks to be log-driven and mainnet-accurate.

### Description

- Added runtime manager capability detection and a Prime-fallback path in `ENSJobPages.handleHook(...)` so hooks use `IAGIJobManagerPrimeViewV1` when available and fall back to `jobEmployerOf(jobId)` / `jobAssignedAgentOf(jobId)` when not, emitting precise best-effort failure reasons for missing URIs.
- Introduced explicit ENS-side, owner-callable repair/replay functions that do NOT require V1 getters: `repairAuthoritySnapshotExplicit`, `repairResolver`, `repairSpecTextExplicit`, `repairCompletionTextExplicit`, `repairTextsExplicit`, `repairAuthorisationsExplicit`, `replayCreateExplicit`, `replayAssignExplicit`, `replayCompletionExplicit`, `replayRevokeExplicit`, and `replayLockExplicit` in `contracts/ens/ENSJobPages.sol`.
- Hardened authority snapshot semantics with `_establishAuthorityForRootVersion(...)` and made `repairAuthoritySnapshot(...)` refuse ambiguous multi-root repairs unless `repairAuthoritySnapshotExplicit(...)` is used.
- Rebased compatibility booleans: `jobEnsIssued` now checks observed on-chain node existence and `jobEnsReady` requires node + resolver + base metadata presence, not sticky write-attempt flags.
- Made `ENSJobPagesInspector` resolver-auth checks ABI-aware: it reports `authReadSupported`, `authObservationIncomplete`, and separates unknown vs false when `isAuthorised(bytes32,address)` is absent.
- Updated and added operator scripts that are log-driven and tolerant of the unchanged Prime ABI: `scripts/ens/phase0-mainnet-snapshot.mjs`, `scripts/ens/inventory-job-pages.ts`, `scripts/ens/repair-job-page.ts`, and new `scripts/ens/repair-from-logs.ts`; they emit machine-readable JSON under `scripts/ens/output/`.
- Updated docs and runbooks to reflect preview vs effective semantics, explicit repair paths, and current mainnet Prime/ENS defaults (`AGIJobManagerPrime` default addressed in deploy scripts and runbooks).
- Added test doubles and unit tests to cover fallback manager behavior and inspector auth-read absence handling: `contracts/test/MockAGIJobManagerPrimeFallback.sol`, `contracts/test/MockPublicResolverNoAuthRead.sol`, and extended `test/ensAuthoritySnapshot.test.js`.

### Testing

- Syntax and script validation: ran Node-based syntax checks for `scripts/ens/*` and `repair-from-logs.ts`; all ENS operator scripts passed syntax evaluation (`ok`), and new scripts emit JSON payloads as designed. (succeeded)
- Local contract compile checks: compiled patched ENS contracts and new test mocks using a local/bundled `solc` invocation as a syntax/bytecode sanity check; compilation succeeded for the targeted contracts in this environment. (succeeded)
- Unit tests: extended `test/ensAuthoritySnapshot.test.js` with cases demonstrating preview/effective separation, multi-root explicit repair requirement, unchanged-Prime fallback hook behavior, and inspector auth-read absence handling; these tests are added and runnable in CI, and were exercised via Truffle artifacts verification where possible. (tests added; full Truffle execution not completed in this environment)
- Hardhat/phase-0 and RPC-dependent flows: attempted `npm run ens:phase0:mainnet` and full Hardhat compile in this sandbox but remote RPC and external compiler download failed due to network restrictions (`ENETUNREACH`), so live chain re-audits and canonical 0.8.23 full compile/size remeasure must be run from an operator workstation before mainnet cutover. (blocked by network in this run)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3b31331c81

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1330 to +1332
(coreOk, ) = manager.staticcall(abi.encodeWithSelector(PRIME_GET_JOB_CORE_SELECTOR, uint256(0)));
(specOk, ) = manager.staticcall(abi.encodeWithSelector(PRIME_GET_JOB_SPEC_URI_SELECTOR, uint256(0)));
(completionOk, ) = manager.staticcall(abi.encodeWithSelector(PRIME_GET_JOB_COMPLETION_URI_SELECTOR, uint256(0)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid probing V1 getters on hard-coded job 0

If a legacy AGIJobManager has ever cancelled/deleted job 0, its getJobCore/getJobSpecURI/getJobCompletionURI views all revert for jobId=0 (contracts/AGIJobManager.sol:651-656,1186-1243). In that state _managerSupportsViewV1() returns false even though the V1 surface is present, so handleHook() and the owner replay*() helpers drop into the Prime-only fallback and start calling jobEmployerOf/jobAssignedAgentOf, which legacy managers do not implement.

Useful? React with 👍 / 👎.

Comment on lines 96 to +98
const resolvedNode = authority[0]
? authority[5]
: (resolvedLabel && jobsRootNode !== ethers.ZeroHash
? ethers.solidityPackedKeccak256(['bytes32', 'bytes32'], [jobsRootNode, ethers.id(resolvedLabel)])
: ethers.ZeroHash);
: (resolvedLabel && jobsRootNode !== ethers.ZeroHash ? ethers.solidityPackedKeccak256(['bytes32', 'bytes32'], [jobsRootNode, ethers.id(resolvedLabel)]) : ethers.ZeroHash);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve the repair node from the selected root version

When operators pass ROOT_VERSION_ID to repair a job under an older root, this planner still hashes resolvedLabel under jobsRootNode() (the current root). That makes the ownership/existence probe look at the wrong ENS name, so an already-existing historical page can be reported as missing or contract-managed incorrectly, and EXECUTE=1 can queue the wrong follow-up action after repairAuthoritySnapshotExplicit() switches authority to the requested root.

Useful? React with 👍 / 👎.

Comment on lines +53 to +58
for (const log of [...completedLogs, ...refundedLogs, ...expiredLogs]) {
const jobId = Number(ethers.getBigInt(log.topics[1]));
if (JOB_ID !== null && jobId !== JOB_ID) continue;
const job = jobs.get(jobId) || { jobId, employer: ethers.ZeroAddress, specURI: '', completionURI: '', terminalObserved: false };
job.terminalObserved = true;
jobs.set(jobId, job);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mark cancelled jobs as terminal in log-driven repairs

For cancelled Prime jobs, cancelJob() emits JobCancelled, then REVOKE+LOCK hooks, and deletes the job (contracts/AGIJobManagerPrime.sol:1064-1067). This log-derived terminal set ignores that event, so cancelled jobs keep terminalObserved=false, allowAuth=true, and the generated repair calls re-authorize participants instead of replaying revoke/lock.

Useful? React with 👍 / 👎.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant