Skip to content

fix: resolve create-path action/objective ids from chain, not DB serial#51

Merged
lucca65 merged 1 commit into
masterfrom
fix/create-ids-from-chain
Jul 2, 2026
Merged

fix: resolve create-path action/objective ids from chain, not DB serial#51
lucca65 merged 1 commit into
masterfrom
fix/create-ids-from-chain

Conversation

@lucca65

@lucca65 lucca65 commented Jul 2, 2026

Copy link
Copy Markdown
Member

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), and claimaction carries 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 resolveClaimId pattern

  • chain.js: resolveCreatedActionId (chain actions for the objective via the byobj secondary index) and resolveCreatedObjectiveId (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 / upsertAction create paths: after the created_tx idempotency guard, resolve the id from chain and insert() 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 before db.withTransaction so chain HTTP never holds a DB connection.
  • insert() not save() for creates: massive's save() with a pk present emits an UPDATE (matching nothing for a novel id — verified in massive 6.6.0 source); claimAction already inserts the same way.

Notes

  • Known edge (shared with resolveClaimId): a pre-existing DB gap in the same scope would mis-assign one create; DB and chain align today, so this only bites after some new unrelated discrepancy.
  • StandardJS clean. No test suite in this repo; table-shape assumptions verified against contracts/community.cpp (objective per-community scope, action global scope + byobj index, both counters from get_available_id).

🤖 Generated with Claude Code

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>
@lucca65
lucca65 merged commit bb64138 into master Jul 2, 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.

1 participant