Vortex Dev Log #15 - The Great Bug Hunt Restores Trust

Vortex Dev Log #15 - The Great Bug Hunt Restores Trust

Date: 2026-04-27

  • Rebuilt the DB-backed server test harness so each test file has its own isolated Postgres database.
  • Fixed same-key SS58 identity drift across server and web governance surfaces.
  • Corrected Citizen Veto math so 3 eligible Citizens require 2 vetoes.
  • Hardened concurrent command paths for rate limits, idempotency, era quotas, draft submit, and era snapshots.
  • Aligned docs with the current API routes and governance semantics.
  • Pushed the server and web review branches; the docs were updated on master.

The bug hunt started with an uncomfortable problem: the simulator was producing too much noise to tell which failures mattered.

The server suite was not simply red. It was unreliable in a way that made engineering judgment harder. DB-backed Rstest workers were sharing one Postgres database, migrating it concurrently, and truncating each other's setup data. One worker could seed a proposal while another worker resets the tables underneath it. A migration could start twice. A route could return a 404 for data inserted a few lines earlier.

That kind of failure is worse than a normal bug. It turns the test suite from evidence into static. Real product regressions and harness races look identical, so every failure becomes a debate.

The first job was to make the broad suite trustworthy again. Only after that could the bug hunt move through the server, web, and docs with useful feedback rather than guesswork.

As of the 2026-05-05 update, the local bug hunt is complete, committed, and pushed for review.

First, Fix the Signal

The first repair was the test harness. The server now has a reliable DB-suite path:

TEST_DATABASE_URL= TEST_DATABASE_RUN_ID= yarn test:db

That command makes database-backed tests explicit and isolated. Each DB-backed test file gets its own Postgres database. Migrations run against that file's database. Resets truncate only that target. Set up failures, name the database they were trying to prepare.

This matters because it keeps one test file from deleting another test file's world. It also gives failures a stable address: if a test fails now, we can usually look at product behavior rather than ask whether another worker stole the database.

The harness also needed another hardening step. Long isolated database names were being truncated at Postgres' 63-byte identifier limit after the hash suffix was appended. With long run IDs, that could cut off the part that made the name unique. The helper now preserves the hash suffix inside the identifier limit.

Then, Hunt the Bugs

Once the harness stopped hiding the signal, real bugs started to stand out.

The largest class was identity. The simulator had many places where two SS58 encodings of the same public key were treated as different Humans. That affected votes, vetoes, delegation, CM, feed filters, chamber membership, factions, formation teams, court actions, proposal authorship, drafts, profiles, and My Governance. Those paths now resolve Humanode-canonical and default/generic SS58 forms back to the same person.

The veto system also needed correction. Citizen Veto threshold math now consistently uses snapped eligible Citizens. The important regression is simple: with 3 eligible Citizens, 1 veto is not enough. The system requires 2.

Several lifecycle and projection bugs were also fixed. Feed pagination now returns the last displayed event as the cursor, not the lookahead row. Post-vote veto-window cards become non-actionable once the vote is accepted and are awaiting finalization. Clock ticks now process post-vote veto-window finalization even when generic stage windows are disabled. Empty legacy proposal payloads no longer appear as formation-eligible by default.

Command paths got concurrency fixes, too. Rate limits, idempotency, era quotas, draft submit, and era snapshot bootstrap were made safer under concurrent requests. Draft reads, and draft command flows now tolerate legacy partial payloads before replacing them with complete data.

CM and governance projections were reconciled with the stored facts. CM profile/list, faction, chamber roster, and chamber-CM totals use stored mcmPoints instead of recomputing old awards with current multipliers. Finished-page failed-stage inference no longer uses live denominators where snapshot semantics are needed.

Where This Leaves the Work

The important outcome is that the suite is meaningful again. The server has an isolated DB-backed test path; the web has regressions in the identity gates it depends on; the docs match the current API and governance semantics; and the bug hunt has a written trail of what was found.

The remaining work is process work: review the pushed server and web branches, open or refresh the PRs, and let GitHub CI check the same DB-isolated path as part of the PR flow.