Pantheon 2.0: version-centric manifest and query-time difficultyTier - #133
Conversation
Expose pantheonVersionIds and pantheonSunsetVersionIds from /manifest. Co-authored-by: Cursor <cursoragent@cursor.com>
Barecheck - Code coverage reportTotal: 91.01%Your code coverage diff: 1.89% ▴ |
There was a problem hiding this comment.
Pull request overview
This PR updates the /manifest API to stop exposing Pantheon “gauntlet” version IDs and instead expose two explicit Pantheon version ID lists (active vs sunset), while also tightening Pantheon activity/version resolution logic in the manifest helper utilities.
Changes:
- Replaces
gauntletVersionIdswithpantheonVersionIdsandpantheonSunsetVersionIdsin/manifestresponse + OpenAPI. - Refactors Pantheon manifest helpers to compute/split Pantheon versions by associated activity +
isSunset. - Updates unit tests to validate the new Pantheon version list behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
src/services/manifest/pantheon.ts |
Refactors Pantheon activity/version selection logic and version lookup query. |
src/services/manifest/pantheon.test.ts |
Updates tests to cover the new getPantheonVersionIds API and modified Pantheon fixtures. |
src/routes/manifest.ts |
Updates /manifest schema + handler to return the new Pantheon version ID fields. |
open-api/openapi.json |
Regenerates OpenAPI schema to reflect the /manifest field changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Sunset Pantheon versions remain associated with activity 101 (path thepantheon) in production, so lookups and manifest splits must accept both paths. Co-authored-by: Cursor <cursoragent@cursor.com>
Both sunset (101) and active (102) Pantheon activities use path pantheon in Services; drop thepantheon legacy handling. Co-authored-by: Cursor <cursoragent@cursor.com>
Align OpenAPI with nullable DB column; null for legacy runs and non-tier activities. Co-authored-by: Cursor <cursoragent@cursor.com>
Look up feat skulls from activity_feat_definition per request instead of reading a stored column or using a startup cache. Co-authored-by: Cursor <cursoragent@cursor.com>
Handle null skull_hashes in difficulty tier resolution, restore pantheon path compatibility for production sunset activity, and update tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Align manifest helpers and OpenAPI examples with Services seeds where activity 101 and 102 both use path pantheon. Co-authored-by: Cursor <cursoragent@cursor.com>
| instance.duration::int AS "duration", | ||
| instance.platform_type AS "platformType", | ||
| pgcr.date_crawled AS "dateResolved" | ||
| FROM instance |
There was a problem hiding this comment.
Bug: The INNER JOIN in getInstanceBasic causes it to return null for valid instances if their activity_version is missing, leading callers to incorrectly assume the instance doesn't exist.
Severity: HIGH
Suggested Fix
Change the INNER JOIN in getInstanceBasic to a LEFT JOIN on the activity_version table. This will ensure that the instance data is always returned, even if the corresponding activity version details are missing. The function should then handle the case where activity version fields are null, preventing silent failures and incorrect 'not found' responses.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: src/services/instance/instance.ts#L228
Potential issue: The `getInstanceBasic` function uses an `INNER JOIN` with the
`activity_version` table. If an instance exists in the database but its hash does not
have a corresponding row in `activity_version` (e.g., due to incomplete data seeding for
a new activity), the query will return no results. Callers interpret the resulting
`null` as the instance not being found, leading to incorrect 404 errors in admin routes
or silent data omission in status endpoints. This failure occurs without logging an
error, masking the underlying data integrity issue.
Co-authored-by: Cursor <cursoragent@cursor.com>
| } | ||
|
|
||
| const rows = await pgReader.queryRows<{ skullHash: number }>( | ||
| `SELECT skull_hash::int AS "skullHash" |
There was a problem hiding this comment.
Bug: The SQL query in loadKnownFeatSkulls incorrectly casts skull_hash to int. This will cause a database error for any hash value exceeding the 32-bit signed integer limit.
Severity: CRITICAL
Suggested Fix
Remove the ::int cast from the SQL query in loadKnownFeatSkulls. Instead, retrieve the bigint value directly and use a transformer like convertUInt32Value, as demonstrated in listFeatDefinitions, to safely convert it to a JavaScript number.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: src/services/difficulty-tier/resolve.ts#L14
Potential issue: The SQL query in `loadKnownFeatSkulls` at
`src/services/difficulty-tier/resolve.ts:14` incorrectly casts the `skull_hash` column
from `bigint` to `int`. Destiny 2 hashes are treated as unsigned 32-bit integers and can
exceed the maximum value for a signed 32-bit integer (2,147,483,647). When a hash value
larger than this is encountered in the `activity_feat_definition` table, PostgreSQL will
raise an "integer out of range" error. This will cause the query to fail, crashing core
instance retrieval endpoints like `getInstances` and `getActivities` when they process
affected activities.
Summary
API layer for Pantheon 2.0 — boss identity is version-centric; difficulty is a separate read-time axis.
gauntletVersionIdsfrom/manifestpantheonVersionIds(active 132–141 on activity 102) andpantheonSunsetVersionIds(sunset 128–131 on activity 101)path: "pantheon"only (activities 101 and 102; nothepantheon, no activity 201)difficultyTier(adventure|standard|custom) on instance responses — computed at query time, not stored oncore.instanceinstance.skull_hashesagainstactivity_feat_definitionper request (batched for list endpoints)Model: Card titles and PGCR version names use boss
versionId/metadata.versionName.difficultyTieris derived from PGCR skulls and does not rename versions.Tier classification (
src/services/difficulty-tier/)adventure845104503,2008962334standardcustomnullRequires
activity_feat_definitionpopulated (manifest-downloader). Nullskull_hasheshandled safely.Related PRs
difficultyTierDeploy order
Test plan
bun test src/services/manifest/pantheon.test.tsbun test src/services/difficulty-tier/classify.test.ts/manifestreturnspantheonVersionIds[132–141] andpantheonSunsetVersionIds[128–131]difficultyTierderived from skulls + feat definitionsbun run docs)