Skip to Content

Changelog

A reverse-chronological record of every shipped change that touches business-logic code or user-facing behaviour. Each entry is authored by the developer (with the docs-update skill’s help) and reviewed in the same PR as the code change.

See the docs-automation spec  for how this page is maintained, and the covers: / owns: convention for how /business-logic/ pages declare which code they document.

2026-06-29 — 2FA passcode SMS uses domain-bound codes for reliable autofill

The 2FA passcode SMS now appends an Apple domain-bound code line (@www.tunnelflight.com #<code>) as its final line, and the website serves the matching Apple App Site Association (AASA) file. Combined with the mobile app’s new Associated Domains entitlement, this pins iOS QuickType autofill to the exact current passcode so it can no longer surface a stale code from an earlier message in the thread — the root cause of members entering the wrong code.

  • Code generation, storage, expiry, and validation are unchanged — only the SMS body gains the trailing domain-bound line.

  • Uses www.tunnelflight.com, not the apex: the apex 301-redirects to www and Apple does not follow redirects when fetching the association file.

  • GET /.well-known/apple-app-site-association now returns {"webcredentials":{"apps":["QV957DP83B.com.app.iba"]}} as application/json.

  • Ship order matters: the app build carrying the Associated Domains entitlement must be live before this deploys, or domain-bound codes are offered to an app that isn’t yet associated. (The 1.0.7 build is already on TestFlight/Play.)

  • Jira: TNBUGS-1732 

2026-06-01 — 2FA SMS passcode uses the stored dialing code directly

The 2FA passcode SMS path previously looked up a member’s dialing code by passing members.country (the numeric dialing code) into a query that matches on countries.iso_code (an ISO string). That lookup never matched, so the recipient number fell back to members.country anyway. The dead lookup is removed and members.country is now used directly to build the +<dialing-code><phone> recipient.

  • No change to the number sent for correctly-configured members — only a redundant per-send database query was removed.

  • Related production incident: members whose members.country was 0 received an invalid +0… number and silently got no SMS. That bad data was corrected separately in production (see the Jira ticket); this change hardens the code path.

  • Jira: TNBUGS-1729 

  • PR: #664 

2026-05-31 — Parent-skill auto-approval: race fix + reconciliation

Members who passed every child skill under a parent could be left without the parent skill — permanently, with no self-heal — when concurrent child approvals raced the synchronous auto-approval. Fixed on three layers:

  • Serialised the live path — a per-member named lock on a single pooled connection (the previous lock leaked across pool connections and never actually serialised). That lock is the real serialisation guarantee: the logbook keeps every skill re-attempt, so there’s no unique key to lean on. The logbook-create duplicate guard was also folded into a single INSERT … WHERE NOT EXISTS, which narrows the race window for the routine’s other (unlocked) callers.
  • Serialised the UIs — admin bulk approvals run sequentially (not Promise.all) and per-row actions are disabled while one is in flight; the member portal blocks a second submit until the first resolves.
  • Reconciliation safety net — a one-shot back-fill of currently-stuck members plus an hourly cron (APP_RECONCILE_PARENT_SKILLS) that re-derives and assigns any missed parent skills, cascading up the tree.

Ships the API’s first forward-only SQL migrations (api/sql/migrations/), applied by the runner from #649/#650. The runner tracks the API’s migrations in a dedicated api_schema_migrations table so it can’t collide with the assistant’s schema_migrations in the shared IBA schema.

See member lifecycle → parent-skill auto-approval.

  • Jira: TNBUGS-1728
  • PR: #648 

2026-05-31 — Forward-only DB migrations + deploy safety

The API now applies schema/data changes through a tracked, forward-only migration runner instead of by hand, with CI and deploy-time guardrails:

  • Forward-only runner (api/scripts/migrate.js, npm run migrate / migrate:pretend) — applies api/sql/migrations/*.sql in filename order, records each in schema_migrations with a SHA-256 checksum, and aborts if an applied file is later edited. See Database migrations.
  • Deploy-time safety — migrations run as a DO App Platform Pre-Deploy command before traffic cuts over; a failure keeps the current version live and fires the Slack failure alert.
  • Pre-merge validation — PRs labelled deploy-preview-with-db dry-run the branch’s migrations against an isolated preview DB (real-schema copy from the latest S3 backup), failing the PR check before a bad migration can reach production.

Runs as a dedicated DB_MIGRATE_USER with DDL privileges, kept separate from the app’s least-privilege runtime user; credentials live in Infisical per environment.

2026-05-21 — Docs automation shipped: docs-update skill + safety nets

Three pieces of tooling that keep /business-logic/ pages in sync with the code they document, plus a deterministic safety net so drift can’t slip through:

  • docs-update Claude Code skill (invoke via /docs-update or npm run docs:update) — reads your branch diff, classifies touched files via covers: / owns: frontmatter, proposes doc edits and a changelog entry into your working tree for review. Never commits, never writes outside docs/.
  • covers: / owns: frontmatter convention on every /business-logic/ page + npm run docs:lint-covers running on every CI PR. See the convention page for details.
  • Pre-push git hook + GitHub Actions safety nets: a deterministic path-overlap check fires both locally and on PRs. Business-logic changes without doc updates are flagged. The PR-title check posts a non-blocking sticky comment if no Jira ref is found.

Recognises Jira refs from both TUN- (main IBA backend) and TMA- (mobile app) projects by default; adding a third project is a one-line config change in docs/.docs-update.config.json.

  • Jira: [ticket?]
  • PR: [#?]

Last updated on