Skip to content

refactor(task-catalog): add Task catalog and move Task listing onto it - #224

Open
jackchuma wants to merge 3 commits into
mainfrom
toshi/chain-6506-task-catalog-core-with-task-listing-moved-onto-it-6d34417e
Open

jackchuma wants to merge 3 commits into
mainfrom
toshi/chain-6506-task-catalog-core-with-task-listing-moved-onto-it-6d34417e

Conversation

@jackchuma

@jackchuma jackchuma commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Linear: https://linear.app/coinbase/issue/CHAIN-6506/task-catalog-core-with-task-listing-moved-onto-it

What changed? Why?

Task folder layout and path-safety logic is copied across the Task listing, profile-listing route, install-deps route, and validation service. This PR adds one Task catalog module that owns it, and moves the Task listing onto it. Later PRs add profile listing/loading and migrate the other callers.

  • New src/lib/task-catalog.ts:
    • createTaskCatalog(root) → { root, listTasks(network), taskDirs(taskId, network) }
    • listTasks replaces getUpgradeOptions with identical output (README title, description, execution status; newest-first).
    • taskDirs returns { evmDir, taskDir, signaturesDir, taskOriginDir } (same paths the validation service builds today). It does not require the dirs to exist.
    • defaultTaskCatalog() discovers the contract-deployments root on first use and caches the catalog.
    • TaskCatalogError with kind: 'invalid-id' | 'outside-root' | 'not-found' | 'invalid-profile'. The last two are reserved for the profile slices.
  • /api/upgrades uses defaultTaskCatalog().listTasks; response unchanged.
  • src/lib/deployments.ts now only re-exports root discovery (shared cache) for callers not yet migrated.
  • GLOSSARY.md defines Task and Validation profile.

Notes to reviewers

Decisions made where the ticket left room:

  • taskDirs validates both taskId and network against ^[a-zA-Z0-9_-]+$ (invalid-id).
  • outside-root uses a lexical check plus a symlink-aware check: the root and the deepest existing ancestor of each returned path are realpathed, so a Task dir symlinked outside the root is rejected. Symlinks that stay inside the root are allowed. Returned paths stay lexical joins. With valid ids a lexical escape is impossible, so this is the only way to reach outside-root, and it matches the parent ticket's "never read outside the checkout" story.
  • listTasks validates network too, and applies the same containment check: a Task whose config/<network> or validations dir resolves outside the root is omitted, and a README candidate that resolves outside the root is skipped in favor of the next one (both log a warning). One bad Task does not fail the whole list; trees with no escaping paths list exactly as before.
  • Known limit: containment is a point-in-time check, not atomic against concurrent symlink swaps (same as today's assertWithinDir).

How has it been tested?

  • src/lib/__tests__/task-catalog.test.ts (real temp-dir fixture trees, no mocks): Task listing tests moved from deployments.test.ts, plus README precedence, taskDirs layout, invalid-id for taskId and network, outside-root for symlinks at each layout level and a dangling symlink, internal symlinks allowed, and default root discovery and caching.
  • src/app/api/upgrades/__tests__/route.test.ts runs the route against the real catalog on a fixture root.
  • npm test (199 passed), npm run lint, npx tsc --noEmit, prettier --check on changed files.

Co-authored-by: Toshi <toshi-noreply@coinbase.com>
J-Engine-Session: toshi:cd9e56ba-97a8-4763-a2a7-447db8a125e0
@cb-heimdall

Copy link
Copy Markdown
Collaborator

🟡 Heimdall Review Status

Requirement Status More Info
Reviews 🟡 0/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

Co-authored-by: Toshi <toshi-noreply@coinbase.com>
J-Engine-Session: toshi:cd9e56ba-97a8-4763-a2a7-447db8a125e0
@jackchuma

Copy link
Copy Markdown
Collaborator Author

Review round 1

Reviewers: cbhq-openai/gpt-6.1-sol, anthropic/claude-opus-5-5

  • Fixed: [P2] Enforce root containment during Task listing — src/lib/task-catalog.ts:195-199 (ee1ad6c). Escaping layout dirs omit only that Task; escaping README candidates fall through to the next one; both log a warning. Trees with no escaping paths list unchanged.
  • Fixed: [P2] Validate network ids at the listing entry point — src/lib/task-catalog.ts:96 (ee1ad6c)
    Synced with main: bbc06f3 (already current)
    Validation: npm test (192 passed), npm run lint, npx tsc --noEmit, prettier on changed files

🤖 Reply generated with Toshi

Co-authored-by: Toshi <toshi-noreply@coinbase.com>
J-Engine-Session: toshi:cd9e56ba-97a8-4763-a2a7-447db8a125e0
@jackchuma

Copy link
Copy Markdown
Collaborator Author

Review round 2

Reviewers: cbhq-openai/gpt-6.1-sol, anthropic/claude-opus-5-5

  • Fixed: [P2] Keep malformed or unreadable Task paths from hiding valid siblings — src/lib/task-catalog.ts:79-81, :104-112, :220-223, :274 (fed45e7). Raised by both reviewers. Listing now treats non-outside-root errors from the containment check (ENOTDIR, ELOOP, EACCES) as an unusable path, as the old existsSync/isDirectory checks did.
  • Fixed: [P3] Pin the new listing branch for an escaping tasks root — src/lib/task-catalog.ts:274 (fed45e7)
    Synced with main: bbc06f3 (already current)
    Validation: npm test (199 passed), npm run lint, npx tsc --noEmit, prettier on changed files

🤖 Reply generated with Toshi

@jackchuma

Copy link
Copy Markdown
Collaborator Author

Review round 3

Reviewers: cbhq-openai/gpt-6.1-sol, anthropic/claude-opus-5-5

  • Dropped in triage: [P3] Add a test for the unreadable-README fallback — the catch branch moved verbatim; the rewritten README-selection outcomes (escaping, looping, missing, invalid network) are pinned, and a chmod 000 test depends on the runner's user.
  • Dropped in triage (spec check): criterion 6 untested — the deployments.ts re-export is imported by three production modules, so CI's tsc --noEmit and next build fail if it breaks; a Jest test would only pin an import.
    Synced with main: bbc06f3 (already current)
    Validation: not run (no fixes)

🤖 Reply generated with Toshi

@jackchuma
jackchuma marked this pull request as ready for review October 30, 2026 19:43
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.

2 participants