[#52] Fix StorylineCreated ABI + genesis plot indexing#53
Conversation
1. Add missing openingCID (string) and openingHash (bytes32) fields to StorylineCreated event ABI 2. Update storyline indexer to insert genesis plot (plot_index=0) into plots table with IPFS content fetch, hash verification, and fallback to request body content Without this fix, opening chapters were invisible to readers because the storyline indexer only created the storylines row but never stored the genesis plot content. Fixes #52 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: REQUEST CHANGES
Summary
The ABI fix is correct, but the storyline indexer still does not satisfy the bug ticket’s acceptance criteria for the genesis plot content path. Right now it can insert a plots row with content: null, which means the opening chapter still is not actually stored for readers.
Findings
- [high] On IPFS fetch failure without fallback, or on hash mismatch after fallback/IPFS fetch, the route still upserts the genesis plot row with
content: null. Issue #52 explicitly requires the genesis plot content to be fetched, hash-verified, and stored in thecontentcolumn. Silently dropping the content while still writingplot_index = 0leaves the bug effectively unresolved.- File:
src/app/api/index/storyline/route.ts:96 - Suggestion: follow the plot indexer behavior exactly: if IPFS fetch fails and no fallback content is provided, return an error; if the computed hash does not match
openingHash, return an error instead of nulling out content and continuing. Only upsert the genesis plot once verified content is available.
- File:
Decision
Request changes because the current implementation can still produce a genesis plot record without readable content, which fails the core bug fix requirement for opening chapter visibility.
Mirror plot indexer behavior: return error if IPFS fetch fails without fallback, or if content hash doesn't match openingHash. Never insert a plots row with null content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
project7-interns
left a comment
There was a problem hiding this comment.
T2b Review — REQUEST CHANGES
Findings
BUG (blocking): Genesis plot uses same tx_hash,log_index as storyline row
File: src/app/api/index/storyline/route.ts, lines ~145-152
The genesis plot row reuses storylineLog.logIndex! for its log_index field:
const plotRow = {
...
tx_hash: txHash.toLowerCase(),
log_index: storylineLog.logIndex!, // <-- same as storylineRow
};The onConflict: "tx_hash,log_index" deduplication key is shared between the storylines and plots tables, so this works at the DB level (separate tables). However, the plot indexer (route.ts in /api/index/plot/) uses the PlotChained log's logIndex for regular plots. The genesis plot has no PlotChained event — it only has a StorylineCreated event. This means:
- The genesis plot's
log_indexis theStorylineCreatedevent's log index, not aPlotChainedlog index. - If a future transaction emits both
StorylineCreatedandPlotChainedin the same tx (unlikely but possible in a batch/multicall), and they happen to share the samelogIndex, the upsert would silently overwrite.
This is acceptable for now since createStoryline only emits StorylineCreated (no PlotChained), but add a code comment explaining this — it's a landmine for future maintainers.
Severity: Low — downgrading from blocking to advisory. The real blocker is below.
BUG (blocking): Silent content loss — no error when genesis content is unavailable
File: src/app/api/index/storyline/route.ts, lines ~98-110
When IPFS fetch fails AND no fallbackContent is provided, genesisContent stays null, and the handler proceeds to insert a plot row with content: null. The API returns { success: true } even though the reader still cannot see the opening chapter.
Compare with the plot indexer, which hard-fails with a 502:
// plot indexer (correct behavior):
if (!fallbackContent) {
return error("IPFS fetch failed and no fallback content provided", 502);
}The storyline indexer should match this pattern: if genesisContent is still null after IPFS + fallback + hash check, return a 502 so the caller knows to retry. A content: null plot row is worse than no row — it looks "indexed" but is actually broken.
Fix: After step 7 (hash verification), add:
if (genesisContent === null) {
return error("Failed to fetch or verify genesis plot content", 502);
}OK: ABI fix
storylineCreatedEvent now correctly includes openingCID (string) and openingHash (bytes32). Matches proposal §4.1. ✅
OK: createStorylineFunction ABI
Already had openingCID and openingHash inputs — no change needed. Confirmed. ✅
OK: Storyline upsert preserved
The storyline upsert logic is unchanged (just renamed row → storylineRow). No regression. ✅
OK: IPFS gateway + timeout
Uses same IPFS_GATEWAY and IPFS_TIMEOUT_MS constants as plot indexer. Same AbortSignal.timeout pattern. ✅
OK: Hash verification
Uses hashContent() from lib/content.ts (keccak256 of UTF-8), same as plot indexer. ✅
OK: Deduplication
onConflict: "tx_hash,log_index" on the plots upsert — idempotent. ✅
OK: Type safety
decoded.args correctly destructures the new openingCID and openingHash fields. as const ABI ensures viem infers the right types. ✅
OK: CI
Lint + typecheck pass. 22/22 tests pass. ✅
Verdict: REQUEST CHANGES
One blocking fix required: hard-fail when genesis content is unavailable (IPFS down + no fallback). A content: null plot row defeats the purpose of this bug fix. Also recommend a comment on the log_index reuse.
project7-interns
left a comment
There was a problem hiding this comment.
Verdict: APPROVE
Summary
The blocker from the previous review is fixed. The storyline indexer now follows the plot indexer pattern: it fails if verified genesis content is unavailable, and it only inserts the genesis plot row once content has been fetched and hash-verified.
Findings
- None.
Decision
Approve because the bug ticket acceptance criteria are now met, the ABI includes the missing fields, and the updated CI check passed.
project7-interns
left a comment
There was a problem hiding this comment.
T2b Review: APPROVE
Fix confirmed — genesis content null guard verified:
- IPFS fetch failure → 502 before any DB write
- Hash mismatch → 400 before any DB write
- Plot row insert only reached with verified non-null content
ABI fix correct (openingCID + openingHash). Storyline upsert unchanged. Deduplication present. CI green.
Previous blocking issue fully resolved.
Summary
openingCID(string) andopeningHash(bytes32) fields tostorylineCreatedEventinlib/contracts/abi.tsplotsrow for plot_index=0 after creating the storyline record:contentfieldcreateStorylineFunctionABI already had the correct params (no change needed)Fixes #52
Test plan
tsc --noEmitpassesvitest run— 22/22 passing🤖 Generated with Claude Code