Skip to content

[#52] Fix StorylineCreated ABI + genesis plot indexing#53

Merged
realproject7 merged 2 commits intomainfrom
task/52-fix-storyline-genesis-plot
Mar 13, 2026
Merged

[#52] Fix StorylineCreated ABI + genesis plot indexing#53
realproject7 merged 2 commits intomainfrom
task/52-fix-storyline-genesis-plot

Conversation

@realproject7
Copy link
Copy Markdown
Owner

Summary

  • ABI fix: Added missing openingCID (string) and openingHash (bytes32) fields to storylineCreatedEvent in lib/contracts/abi.ts
  • Genesis plot indexing: Storyline indexer now inserts a plots row for plot_index=0 after creating the storyline record:
    • Fetches content from IPFS via openingCID (10s timeout)
    • Falls back to request body content field
    • Verifies keccak256 hash match against openingHash
    • Upserts to plots table with deduplication
  • createStorylineFunction ABI already had the correct params (no change needed)

Fixes #52

Test plan

  • tsc --noEmit passes
  • vitest run — 22/22 passing
  • Verify StorylineCreated topic0 changed (ABI fields affect signature)
  • Integration test: createStoryline tx → verify both storylines and plots rows created

🤖 Generated with Claude Code

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>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

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 the content column. Silently dropping the content while still writing plot_index = 0 leaves 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.

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>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

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:

  1. The genesis plot's log_index is the StorylineCreated event's log index, not a PlotChained log index.
  2. If a future transaction emits both StorylineCreated and PlotChained in the same tx (unlikely but possible in a batch/multicall), and they happen to share the same logIndex, 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 rowstorylineRow). 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.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

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

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.

@realproject7 realproject7 merged commit 1986411 into main Mar 13, 2026
1 check passed
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.

[BUG] Storyline indexer missing genesis plot + StorylineCreated ABI incomplete

2 participants