fix: resolve create-path action/objective ids from chain, not DB serial#51
Merged
Conversation
DB ids for actions and objectives must equal their on-chain ids: the frontend signs upsertaction(action_id = db id) / upsertobjctv(objective_id = db id) against the chain, and claimaction payloads carry action_id too. The create paths (action_id/objective_id = 0) previously let the Postgres serial assign the id; historical duplicate inserts drifted the serials past the chain counters, producing phantom rows and un-editable actions (prod incident, remediated 2026-07-02 — DB and chain realigned, serials reset). Make the create paths recover the real id from the chain, mirroring how claimAction/resolveClaimId already do it for claims: - chain.js: add resolveCreatedActionId (action table, byobj secondary index, scoped to the contract) and resolveCreatedObjectiveId (objective table, per-community symbol-raw scope) plus a private symbolRaw helper (BigInt — 7-char codes exceed 2^53). The created row is the smallest chain id not yet in the DB, valid because blocks are processed in order. Both throw on truncated result sets or when no id is missing, so a bad read retries the block instead of writing a wrong id. - community.js: upsertObjective/upsertAction create paths resolve the id before inserting (and before opening the action transaction, so the chain round-trip never holds a DB connection). Any resolution failure logs and falls back to the serial — a throw here would become an unhandledRejection → process exit → pm2 crash-loop. Create paths use insert() (save() with an explicit novel pk emits a no-op UPDATE); the update paths keep save()'s upsert-by-id. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
DB ids for actions and objectives must equal the on-chain ids: the frontend signs
upsertaction(action_id = db id)/upsertobjctv(objective_id = db id), andclaimactioncarries the action id too. The create paths let the DB serial assign the id, so any drift (historical duplicate inserts pushed the actions serial to 407 while the chain counter was at 399) makes new rows permanently un-editable — and claims signed against a drifted id can hit the wrong on-chain action.Prod incident (2026-07-02): phantom DB actions 399–406 + objective 94 rendered as duplicate UI entries whose edits the contract rejected ("Can't find action") — the adminmuda "can't edit" report. Data remediated (phantoms deleted, serials realigned; DB=chain id-for-id at actions 1–398 / objectives 1–93). This PR keeps the drift from re-opening.
Fix — mirror of the existing
resolveClaimIdpatternchain.js:resolveCreatedActionId(chain actions for the objective via thebyobjsecondary index) andresolveCreatedObjectiveId(per-community scope via the raw symbol value, BigInt-encoded). Created row id = smallest on-chain id not yet in the DB for that scope — valid because blocks are processed in order. Both throw on truncated results (more) or when no id is missing, so a wrong id is never written.upsertObjective/upsertActioncreate paths: after the created_tx idempotency guard, resolve the id from chain andinsert()with it explicitly; on ANY resolution failure, log to Sentry and fall back to the (realigned) serial — a throw escaping an updater would crash-loop pm2. Resolution happens beforedb.withTransactionso chain HTTP never holds a DB connection.insert()notsave()for creates: massive'ssave()with a pk present emits an UPDATE (matching nothing for a novel id — verified in massive 6.6.0 source);claimActionalready inserts the same way.Notes
contracts/community.cpp(objectiveper-community scope,actionglobal scope +byobjindex, both counters fromget_available_id).🤖 Generated with Claude Code