Skip to content

feat: add canonical bot invocation source revisions - #1817

Open
kristofferremback wants to merge 6 commits into
mainfrom
feat/invocation-source-mutations-01-canonical-state
Open

feat: add canonical bot invocation source revisions#1817
kristofferremback wants to merge 6 commits into
mainfrom
feat/invocation-source-mutations-01-canonical-state

Conversation

@kristofferremback

@kristofferremback kristofferremback commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

Remote bot invocations cache prompt_markdown when a source message is first routed. An edit or deletion can therefore race the outbox and claim paths, hand a runtime stale work, or let a stale completion persist a reply. Bot-backed agent_sessions also entered persona mutation handling, which could rerun or delete output under the wrong lifecycle owner.

Solution

Make the current message row authoritative at every work boundary:

  • Add monotonic messages.revision and source/claimed revision fields on bot_invocations; backfill existing rows and replace the global source/actor uniqueness constraint with active-route uniqueness.
  • Resolve mention and active-scratchpad routes from locked canonical content_json, then reuse that resolver for ordered reconciliation and claim validation.
  • Serialize source mutation, terminal transition, and replacement insertion as source advisory lock → actor advisory lock → invocation row, with the message row share-locked through completion/failure/parking fences (INV-20).
  • Refresh pending work, preserve the running claim’s pinned prompt while resetting its retry budget after an edit, cancel deleted/retargeted work, and reject stale plaintext or sealed completions before any reply/trace write.
  • Keep bot-backed sessions out of persona reruns/deletion cascades; bot cancellation owns their terminal session transition without deleting already-posted bot messages.

This is the backend safety floor only. Durable input_updated / cancelled runtime events and capability negotiation are the next stack layer.

Files

File Change
apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql Adds/backfills message and invocation revisions (including historical tombstones), cancellation metadata, availability time, and active-route uniqueness.
apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts New: canonical mention/active-scratchpad routing and external prompt construction.
apps/backend/src/features/bot-runtimes/repository.ts Adds revision-aware upsert/claim/terminal fences and canonical source→actor advisory locking.
apps/backend/src/features/bot-runtimes/service.ts Reconciles one locked source snapshot, validates claims, and owns bot-session cancellation lifecycle.
apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts Routes create/edit/delete events through canonical source reconciliation instead of event snapshots.
apps/backend/src/features/messaging/{repository,version-repository}.ts Persists monotonic revisions for edits and one-time soft deletion.
apps/backend/src/features/public-api/handlers.ts Pins claims and rejects stale plaintext/sealed terminal writes before side effects.
apps/backend/src/features/agents/message-mutation-outbox-handler.ts Excludes workspace-scoped bot invocation sessions from persona mutation behavior.
packages/prosemirror/src/extractors.ts Promotes mention-slug extraction so routing does not duplicate a tree walker.
apps/backend/tests/integration/bot-invocation-*.test.ts Covers migration backfill, claim/edit/delete ordering, route changes, terminal races, sealed fences, and workspace scope against PostgreSQL.

Test plan

  • Backend unit suite — 3,983 passed, 0 failed.
  • Source-mutation, migration-artifact, and claim-scope integration suites — 19 passed, 57 assertions.
  • @threa/prosemirror typecheck and tests — 154 passed, 211 assertions.
  • Backend typecheck and lint.
  • Migration invariants — 260 migrations clean.
  • git diff --check.
📋 Full implementation plan

Goal

Support source-message edits and deletions across remote runtimes without trusting creation-time prompt snapshots. The stack establishes canonical backend state first, adds a generic durable control protocol second, then teaches the shared runtime client and adapters to consume it.

Plan reference: https://seer.build/ws_vbyzjvdg6g/b/invocation-source-mutations-plan-4a104e/

Stack

  1. Canonical revisions and backend safety floor — this PR.
  2. Generic backend control protocol — durable input-update/cancellation events, capability negotiation, supersession, and reconnect state.
  3. Shared runtime client — transport-neutral onInputUpdated / onCancelled callbacks and bootstrap recovery.
  4. Generic remote-session adapter — apply the protocol to Claude/remote-session runtimes.
  5. Pi adapter — map generic updates to Pi steering/cancellation behavior.

What Was Built

Canonical message revisions

  • messages.revision starts at 1, increments atomically on each content edit, and increments once on soft deletion.
  • The migration reconstructs existing revisions from message_versions.
  • bot_invocations.source_message_revision records the latest reconciled source revision.
  • claimed_source_message_revision pins the exact input accepted by a runtime claim.
  • Existing live claims are backfilled; deleted-source work is cancelled, and historical tombstones consume the same revision they would after deployment.

Canonical route reconciliation

  • resolveCanonicalInvocationRoutes reads resolved mention IDs from content_json; markdown slugs remain display metadata only (INV-35/58/64).
  • It preserves mentionability, bot access/delivery verdicts, root/thread response routing, active-bot suppression, sealed blindness, session-link policy, and target instance/session selection.
  • The outbox handler passes only workspace/source identity. The service locks the current source, resolves all desired routes, cancels obsolete routes, inserts eligible routes, and emits availability in one transaction.
  • Missing/deleted canonical sources are cancellation/no-op outcomes, so an ordered listener cursor cannot be poisoned by a stale create event.

Claim and terminal fences

  • Claim is the work-start linearization boundary: claim reads and share-locks the canonical source, rejects obsolete actor/trigger routes, and pins current revision/prompt.
  • Invalid claim cancellation is candidate-scoped; sibling claims are never set-updated from a claimant.
  • Source/actor advisory locks serialize insertion against completed, failed, parked, and expired same-actor work across trigger spelling.
  • Message share locks remain held through completion, failure, and parking, so a mutation either commits first and rejects the terminal write or commits after it.
  • Claimed edits keep the old claimed prompt/revision for stale-output rejection while advancing the reconciled revision and resetting retry budget for a later canonical reclaim.

Lifecycle ownership

  • Bot-backed agent_sessions are recognized by workspace-scoped invocation identity, not ID-prefix inference.
  • Persona mutation handling skips bot-backed trigger edits, referenced edits, and deletions.
  • Bot source deletion transitions a running bot session to deleted and emits the existing lifecycle event/outbox atomically; startup repair covers migration-cancelled rows, and old-replica agent_session:started events trigger targeted repair during rolling deploys.
  • Route removal transitions a running bot session to superseded; already-posted bot messages are retained.
  • Claim-time session insertion re-locks the claim generation, closing cancellation/session-insert races.

Plaintext and sealed safety

  • Plaintext reply/no-response completion validates the locked source before creating a message, synthesized trace floor, or completed lifecycle event.
  • Sealed completion applies the same fence before ciphertext persistence.
  • Sealed claim context rejects soft-deleted trigger rows; ciphertext/envelope remain canonical payloads the backend cannot inspect.

Verification

  • Real migration-artifact test seeds the pre-migration schema, applies the shipped SQL, and verifies revision/claim backfill.
  • Two-client barriers cover edit-before-claim, completion-before-edit, edit-before-completion, route-change/completion lock ordering, stale failure/parking, and sibling invalid claims.
  • Handler tests assert stale plaintext/sealed completion produces no message or trace side effect.
  • Mutation-handler tests exercise bot-owned trigger, referenced-message, and deletion branches.

Design Decisions

Claim as the processing boundary

Chose: Pending work may be rewritten/cancelled; claimed work is pinned and every terminal write is revision-fenced.

Why: Outbox delivery is asynchronous, so event order alone cannot prove the runtime received current input.

Canonical row over mutation-event payload

Chose: Resolve from the current locked message row.

Why: A delayed revision-N event can arrive after revision N+1 is already canonical. Stamping N routing with N+1 prompt state is unsafe.

Source then actor advisory locks

Chose: One source lock followed by sorted actor locks before invocation rows.

Why: Actor-only locking allowed a route-changing reconciliation to hold an old invocation row while completion held the actor lock, creating a deadlock cycle.

No automatic terminal reruns

Chose: Completed/failed/parked/expired same-actor work suppresses edit redispatch; cancelled predecessors are replaceable and newly added actors remain eligible.

Why: Re-running a completed remote turn can repeat filesystem, network, or PR-creation side effects.

Design Evolution

  • Event-payload routing → one locked canonical reconciliation transaction, closing delayed-edit stamping races.
  • Partial uniqueness alone → terminal same-actor exclusion plus source/actor advisory locking, closing completion-vs-insert races.
  • Completion-only fencing → completion, failure, and parking source locks, preventing a stale terminal status from suppressing edited work.
  • Set-wide invalid-claim cancellation → candidate-scoped CAS, avoiding sibling-claim deadlocks.

Schema Changes

20260808073545_bot_invocation_source_revisions.sql:

  • Adds messages.revision INTEGER NOT NULL DEFAULT 1 and backfills from version counts.
  • Adds source_message_revision, claimed_source_message_revision, claimed_input_update_mode, cancellation_reason, and available_at to bot_invocations.
  • Backfills canonical and claimed revisions; cancels deleted-source active rows.
  • Replaces the old source/actor unique constraint with a partial unique index over active (workspace, source, actor type, actor id, trigger) routes.

What's NOT Included

  • No bot_invocation:input_updated or bot_invocation:cancelled wire events yet.
  • No runtime capability declaration or live-update/restart fallback yet.
  • No shared runtime-client callbacks or adapter changes.
  • No automatic rerun/rewrite of completed output.
  • No compensation for external side effects.
  • No new public API/OpenAPI fields in this layer.

Status

  • Canonical revisions and migration backfill.
  • Pending edit/delete reconciliation.
  • Claim-time canonical routing and input pinning.
  • Plaintext/sealed stale terminal rejection.
  • Bot-session lifecycle isolation.
  • Deterministic migration and concurrency coverage.
  • Generic backend runtime-control protocol (next PR).
  • Shared runtime-client callbacks and recovery.
  • Claude/remote-session adapter.
  • Pi adapter.

🤖 PR by Codex


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 59 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9f0f1233-15fd-43fd-a22f-c92f2b3da71c

📥 Commits

Reviewing files that changed from the base of the PR and between f380555 and 16c7c9d.

📒 Files selected for processing (12)
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
  • apps/backend/src/features/agents/message-mutation-outbox-handler.test.ts
  • apps/backend/src/features/agents/message-mutation-outbox-handler.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts
  • apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added reliable tracking of message revisions for bot-triggered actions.
    • Bot responses now stay synchronized when source messages are edited or deleted.
    • Improved mention and scratchpad-based bot routing, including clearer handling of unavailable or invalid destinations.
  • Bug Fixes
    • Prevented outdated bot actions from running or overwriting newer message state.
    • Avoided duplicate invocations and safely cancelled obsolete work.
    • Blocked completion when the original trigger message has changed or been removed.
  • Tests
    • Expanded coverage for routing, edits, deletions, retries, and concurrent actions.

Walkthrough

Changes

The PR adds message revision tracking and migration backfills. It introduces canonical bot-invocation route resolution, revision-aware reconciliation, transactional claim validation, stale-source cancellation, and public API guards. Bot-owned sessions are excluded from message mutation cascades.

Invocation revision and storage

Layer / File(s) Summary
Message revision storage and migration
apps/backend/src/features/messaging/..., apps/backend/src/db/migrations/..., apps/backend/evals/..., apps/backend/src/features/agents/..., apps/backend/tests/integration/...
Messages now store and increment revision. Invocation source snapshots expose locked message state. The migration backfills revisions and invocation metadata.
Canonical route resolution and outbox reconciliation
apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts, apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts, packages/prosemirror/...
Mention and scratchpad routes are resolved centrally. Create, edit, and delete events use source reconciliation.
Transactional invocation claims and lifecycle
apps/backend/src/features/bot-runtimes/repository.ts, apps/backend/src/features/bot-runtimes/service.ts, apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
Invocation insertion, claiming, cancellation, parking, failure, and completion use source revisions and ordered advisory locks.
Public API validation and session guards
apps/backend/src/features/public-api/..., apps/backend/src/features/agents/message-mutation-outbox-handler.ts
Claim and completion paths reject deleted or stale sources. Bot-owned sessions skip persona reruns and message cascades.

Possibly related PRs

  • threahq/threa#547: Introduced the bot invocation repository and runtime service extended by this PR.
  • threahq/threa#550: Refactored the invocation outbox flow changed here.
  • threahq/threa#553: Related mention-invocation routing changes in the same runtime flow.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description clearly explains the canonical source revision changes and their purpose, including implementation details and verification.
Title check ✅ Passed The title clearly identifies the main change: adding canonical bot invocation source revisions.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql`:
- Around line 43-48: Retain the unconditional unique constraint in this
migration; remove the DROP CONSTRAINT statement while preserving the active-row
unique index creation. Defer removing
bot_invocations_workspace_id_source_message_id_actor_type_a_key until all
existing replicas no longer write using the unconditional ON CONFLICT target.

In `@apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts`:
- Around line 315-325: Remove the duplicate test around processMessageCreated,
or revise it to explicitly configure createdPayload and its reconciliation
inputs to represent canonical source deletion. Keep only a test whose setup
distinguishes the deletion scenario from the preceding live-source no-op test,
while preserving coverage through the existing deletion-route test if no
distinct setup is needed.

In `@apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts`:
- Around line 113-124: Rename processMessageCreated to a name that reflects
handling both created and edited message events, and update every call site or
dispatch reference to use the new method name.

In `@apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts`:
- Around line 128-159: Refactor the active-scratchpad route construction in the
resolver so the shared fields are defined once and reused by both outcomes.
Colocate the variant values for targetInstanceId, targetRuntimeSessionId, and
missingLinkNotice, using nulls when missingLinkNotice is absent and the existing
link-derived values otherwise. Preserve the current return behavior for
missingLinkNotice and routes.push behavior for the normal path.

In `@apps/backend/src/features/bot-runtimes/repository.ts`:
- Around line 960-975: Update the cancellation predicate in
reconcileInvocationSource so rows at the current source message revision can be
cancelled when they are no longer in the desired routes. Change the strict
source_message_revision comparison in the UPDATE query to allow equality, while
preserving the existing workspace, message, status, and NOT EXISTS route
filters.

In `@apps/backend/src/features/public-api/handlers.ts`:
- Around line 1353-1361: Update the claimBotInvocation flow to track when
currentClaim is missing or has a different claimedSourceMessageRevision, then
return data: null after the transaction instead of continuing with claim
details. Preserve the existing audit behavior by setting res.locals.auditSkip
before the early response, allowing the runtime to poll again without starting a
session for a stale claim.

In `@apps/backend/tests/integration/bot-invocation-source-mutations.test.ts`:
- Around line 78-88: Replace the Date.now()-based sequence in the source helper
with a module-scoped deterministic counter, incrementing it for each source()
call so every inserted message receives a unique stream sequence across tests.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 013f1ab0-e1dd-4e5a-951f-7c685a8b67c2

📥 Commits

Reviewing files that changed from the base of the PR and between 2818d57 and f380555.

📒 Files selected for processing (38)
  • apps/backend/evals/suites/boundary-extraction/suite.ts
  • apps/backend/evals/suites/memorizer/suite.ts
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
  • apps/backend/src/features/agents/conversation-summary-service.test.ts
  • apps/backend/src/features/agents/episode-summary-service.test.ts
  • apps/backend/src/features/agents/message-mutation-outbox-handler.test.ts
  • apps/backend/src/features/agents/message-mutation-outbox-handler.ts
  • apps/backend/src/features/agents/quote-resolver.test.ts
  • apps/backend/src/features/agents/reflective-capture-service.test.ts
  • apps/backend/src/features/agents/session-digest.test.ts
  • apps/backend/src/features/agents/thread-anchor-context.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts
  • apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/conversations/boundary-extraction/llm-extractor.test.ts
  • apps/backend/src/features/labels/label-message-service.test.ts
  • apps/backend/src/features/messaging/index.ts
  • apps/backend/src/features/messaging/repository.ts
  • apps/backend/src/features/messaging/steered-message-service.ts
  • apps/backend/src/features/messaging/version-repository.ts
  • apps/backend/src/features/public-api/claim-context.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/e2e-stream-gate.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/public-api/sealed-claim.test.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/saved-messages/service.test.ts
  • apps/backend/src/lib/ai/message-formatter.test.ts
  • apps/backend/src/lib/outbox/payload-parsers.test.ts
  • apps/backend/tests/integration/bot-invocation-claim-scope.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • packages/prosemirror/src/extractors.ts
  • packages/prosemirror/src/index.ts

Comment on lines +43 to +48
ALTER TABLE bot_invocations
DROP CONSTRAINT bot_invocations_workspace_id_source_message_id_actor_type_a_key;

CREATE UNIQUE INDEX idx_bot_invocations_active_source_actor_trigger
ON bot_invocations (workspace_id, source_message_id, actor_type, actor_id, trigger)
WHERE status IN ('pending', 'claimed');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the dropped constraint name and the ON CONFLICT inference target for bot_invocations.
set -euo pipefail

# 1. Find the migration that created the original unique constraint.
rg -n -C 4 'bot_invocations' --glob '*.sql' | rg -n -C 4 -i 'UNIQUE|CONSTRAINT' || true

# 2. Show the exact dropped name usage.
rg -n 'bot_invocations_workspace_id_source_message_id_actor_type_a_key' --glob '*.sql'

# 3. Inspect insertIdempotent and every ON CONFLICT target on bot_invocations.
fd -t f 'repository.ts' apps/backend/src/features/bot-runtimes --exec rg -n -C 12 'insertIdempotent|ON CONFLICT' {}

Repository: threahq/threa

Length of output: 151


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- candidate migration files ---'
git ls-files 'apps/backend/src/db/migrations/*bot*' '*migration*' | head -200

printf '%s\n' '--- bot_invocations schema and constraint definitions ---'
rg -n -C 8 'CREATE TABLE.*bot_invocations|ALTER TABLE bot_invocations|UNIQUE.*bot_invocations|CONSTRAINT.*bot_invocations|source_message_id|insertIdempotent|ON CONFLICT' \
  apps/backend --glob '*.sql' --glob '*.ts' --glob '*.tsx' | head -500

printf '%s\n' '--- repository files near bot runtime feature ---'
git ls-files apps/backend/src | rg 'bot|invocation|repository' | head -300

Repository: threahq/threa

Length of output: 14901


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- migration directory inventory ---'
git ls-files apps/backend/src/db/migrations | tail -100

printf '%s\n' '--- all references to the original constraint and index ---'
rg -n -C 6 'bot_invocations_workspace_id_source_message_id_actor_type_a_key|idx_bot_invocations_active_source_actor_trigger' . \
  --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**'

printf '%s\n' '--- all bot invocation conflict clauses and insertIdempotent definitions ---'
rg -n -C 15 'insertIdempotent|ON CONFLICT|bot_invocations' apps/backend/src \
  --glob '*.ts' --glob '*.sql' | head -800

Repository: threahq/threa

Length of output: 50369


🏁 Script executed:

set -euo pipefail

printf '%s\n' '--- original bot invocation migration ---'
cat -n apps/backend/src/db/migrations/20260516190000_bot_runtime_invocations.sql

printf '%s\n' '--- revision migration ---'
cat -n apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql

printf '%s\n' '--- bot invocation source files ---'
git ls-files apps/backend/src | rg -i 'bot.*(invocation|runtime)|invocation.*(bot|repository)|repository.*bot'

printf '%s\n' '--- insertIdempotent and conflict clauses in bot-related TypeScript files ---'
while IFS= read -r file; do
  rg -n -C 18 'insertIdempotent|ON CONFLICT|bot_invocations' "$file" || true
done < <(git ls-files apps/backend/src | rg -i 'bot.*(invocation|runtime)|invocation.*(bot|repository)|repository.*bot')

Repository: threahq/threa

Length of output: 8155


🏁 Script executed:

set -euo pipefail

repo=apps/backend/src/features/bot-runtimes/repository.ts

printf '%s\n' '--- current repository implementation ---'
rg -n -C 25 'insertIdempotent|ON CONFLICT|bot_invocations' "$repo"

printf '%s\n' '--- repository outline ---'
ast-grep outline "$repo" | head -120

printf '%s\n' '--- recent file history ---'
git log -8 --oneline -- "$repo" apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql

printf '%s\n' '--- previous revision of repository implementation ---'
git show HEAD^:"$repo" 2>/dev/null | rg -n -C 25 'insertIdempotent|ON CONFLICT|bot_invocations' || true

printf '%s\n' '--- integration migration setup and assertions ---'
cat -n apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts | sed -n '1,220p'

Repository: threahq/threa

Length of output: 50369


🏁 Script executed:

set -euo pipefail

repo=apps/backend/src/features/bot-runtimes/repository.ts
old=589db96d

printf '%s\n' '--- current conflict clause only ---'
sed -n '1048,1070p' "$repo"

printf '%s\n' '--- previous conflict clause only ---'
git show "$old:$repo" | sed -n '930,1010p'

printf '%s\n' '--- exact generated-name length ---'
python3 - <<'PY'
name = "bot_invocations_workspace_id_source_message_id_actor_type_a_key"
print(len(name), name[:63], len(name[:63]))
PY

printf '%s\n' '--- migration integration test conflict behavior ---'
rg -n -C 12 'ON CONFLICT|constraint|index|pending|claimed|cancelled|completed' \
  apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts

Repository: threahq/threa

Length of output: 10148


🏁 Script executed:

set -euo pipefail

repo=apps/backend/src/features/bot-runtimes/repository.ts
old=589db96d

printf '%s\n' '--- previous insertIdempotent location and conflict clause ---'
git show "$old:$repo" | rg -n -C 30 'insertIdempotent|ON CONFLICT \(workspace_id, source_message_id, actor_type, actor_id, trigger\)' | head -180

printf '%s\n' '--- commits containing the repository transition ---'
git log --format='%H %s' --all -- "$repo" | head -12

printf '%s\n' '--- current and previous occurrences of the exact conflict target ---'
printf '%s\n' 'current:'
rg -n 'ON CONFLICT \(workspace_id, source_message_id, actor_type, actor_id, trigger\)' "$repo"
printf '%s\n' 'previous:'
git show "$old:$repo" | rg -n 'ON CONFLICT \(workspace_id, source_message_id, actor_type, actor_id, trigger\)' || true

Repository: threahq/threa

Length of output: 6349


Defer dropping the unconditional unique constraint.

The constraint name is deterministic because the original migration defines it. However, previous replicas use the unconditional ON CONFLICT target. Drop the constraint only after those replicas stop writing.

🧰 Tools
🪛 Squawk (2.61.0)

[warning] 46-48: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql`
around lines 43 - 48, Retain the unconditional unique constraint in this
migration; remove the DROP CONSTRAINT statement while preserving the active-row
unique index creation. Defer removing
bot_invocations_workspace_id_source_message_id_actor_type_a_key until all
existing replicas no longer write using the unconditional ON CONFLICT target.

Comment thread apps/backend/src/features/bot-runtimes/invocation-outbox-handler.ts
Comment thread apps/backend/src/features/bot-runtimes/invocation-route-resolver.ts Outdated
Comment thread apps/backend/src/features/bot-runtimes/repository.ts
Comment thread apps/backend/src/features/public-api/handlers.ts
kristofferremback and others added 6 commits August 10, 2026 09:05
- version message mutations and reconcile bot routes from locked source state
- fence claims, failures, parking, and completions against stale input
- add migration, lifecycle isolation, and deterministic race coverage

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
- count historical deletion in canonical message revisions
- close migration-cancelled running sessions through lifecycle outbox repair

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
- allow full migration setup to complete under concurrent integration files
- keep teardown safe when setup fails before assigning the pool

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
- consume old-replica session-start events after the startup repair scan
- target repair by workspace and invocation-backed session identity

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
- preserve embedded session controls while reconciling message edits
- skip bot-backed sessions when selecting persona rerun owners
- reject claims that become stale before session creation

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
- page past multiple newer bot sessions on trigger edits
- select the latest persona session for referenced-context edits

🤖 Generated with [Codex](https://github.com/openai/codex)

Co-authored-by: codex <codex@users.noreply.github.com>
@kristofferremback
kristofferremback force-pushed the feat/invocation-source-mutations-01-canonical-state branch from 923317a to 16c7c9d Compare August 10, 2026 10:51
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