Skip to content

feat: add bot invocation runtime control protocol - #1823

Open
kristofferremback wants to merge 5 commits into
feat/invocation-source-mutations-01-canonical-statefrom
feat/invocation-source-mutations-02-runtime-protocol
Open

feat: add bot invocation runtime control protocol#1823
kristofferremback wants to merge 5 commits into
feat/invocation-source-mutations-01-canonical-statefrom
feat/invocation-source-mutations-02-runtime-protocol

Conversation

@kristofferremback

@kristofferremback kristofferremback commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Problem

The canonical-state layer fences stale bot invocation work, but runtimes still cannot learn that a claimed source was edited or deleted. Claim renewal exposes only lease state; manifests cannot declare live/restart input handling; reconnect bootstrap omits durable cancellations; and plaintext/sealed completion requests cannot identify the source revision they processed.

Solution

Add the backend runtime-control protocol on top of PR #1817:

  • Extend shared manifest and wire contracts with live / restart input modes, source revisions, typed cancellation reasons, and authoritative renewal control state.
  • Reconcile claimed edits by pinned manifest mode: emit metadata-only input_updated hints for live claims; cancel and replace restart claims; keep legacy claims fenced without assuming callback support.
  • Return current plaintext or sealed input through HTTP renewal; sealed deltas atomically rotate the session’s accepted reply generation, and every renewal cancels revoked grants with key_grant_lost rather than downgrading.
  • Persist the runtime session that actually wins each claim, route controls to that owner through the outbox (INV-4/7), and recover only its claims/cancellations from one repeatable-read bootstrap snapshot.
  • Require the applied source revision at plaintext and sealed completion boundaries; stale output returns structured 409 INVOCATION_INPUT_STALE before reply or trace persistence.

This PR is backend protocol only. Shared-client callbacks and runtime adapter behavior remain in PRs 3–5.

Files

Group Files Change
Shared contracts packages/types/src/{constants,domain,index}.ts Adds input-update modes, control-state/update unions, cancellation reasons, source revisions, and outbox payload types.
Runtime state apps/backend/src/features/bot-runtimes/{manifest-schema,index,runtime-write-ops}.ts Derives manifest validation from shared constants and extends presence/renew contracts.
Claim ownership apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql; apps/backend/src/features/agents/session-repository.ts Persists the claiming runtime session and rotates the sealed callback generation transactionally.
Reconciliation apps/backend/src/features/bot-runtimes/{repository,service}.ts Pins claim mode/revision/session; handles live, restart, legacy, route changes, cancellation recovery, and sequential bootstrap reads.
WebSocket boundary apps/backend/src/features/bot-runtimes/{socket-handler,ws-http-schema-parity.test}.ts Accepts manifests and keeps WS/HTTP presence schemas aligned.
HTTP boundary apps/backend/src/features/public-api/{schemas,routes,handlers,runtime-write-ops}.ts Exposes authoritative renew control sync and revision-aware plaintext/sealed completion.
Sealed delivery apps/backend/src/features/public-api/sealed-turn-context.ts Builds encrypted input deltas with generation-correct wraps; fails closed on lost grants.
Durable hints apps/backend/src/lib/outbox/{repository,broadcast-handler}.ts Persists update/cancellation hints with state changes and targets the owning runtime room.
API snapshots docs/public-api/{openapi.json,versions/*.json} Regenerates current and maintained OpenAPI snapshots for additive protocol fields.
Verification Colocated *.test.ts; apps/backend/tests/integration/bot-invocation-{control-protocol,source-mutations}.test.ts Covers schemas, routing, reconciliation, revision fences, sealed updates, bootstrap, and real PostgreSQL behavior.

Test plan

  • Backend unit suite — 3,997 passed, 0 failed.
  • Backend integration suite — 916 passed, 0 failed.
  • Targeted bot-runtime WebSocket E2E — 2/2 passed.
  • Full backend E2E — 370 passed; one unrelated sidebar WebSocket/auth flake failed, then its isolated test passed 5/5. No protocol E2E failed.
  • Root monorepo typecheck.
  • Root lint, Dockerfile checks, and initial 260 migration checks.
  • Post-review affected gates — 80 unit and 18 real-Postgres integration tests; backend typecheck, changed-file lint, and 261 migration checks.
  • Generated OpenAPI snapshot check.
  • git diff --check.
📋 Full implementation plan

Goal

Make source-message edits and deletions observable and enforceable for already-claimed bot invocations. This second layer supplies a generic backend control protocol while preserving the canonical revision and locking floor from PR #1817. Runtime-side observation and actuation intentionally land in the next three stack layers.

Five-Layer Stack

  1. Canonical state — PR feat: add canonical bot invocation source revisions #1817 / layer 1
    • Makes messages.revision authoritative.
    • Reconciles routes from canonical contentJson.
    • Pins claim input and fences stale terminal writes.
    • Excludes bot-owned sessions from persona mutation handling.
  2. Backend runtime protocol — this PR / layer 2
    • Defines manifest negotiation and control wire types.
    • Produces update/cancellation hints and authoritative HTTP synchronization.
    • Supports plaintext and sealed control payloads.
    • Requires source revisions at completion.
  3. Shared runtime client — planned layer 3
    • Observes claims, renews leases, handles WebSocket hints, and resynchronizes over HTTP.
    • Serializes onInputUpdated / onCancelled callbacks.
    • Opens sealed updates locally and deduplicates revisions.
  4. Remote-session adapter — planned layer 4
    • Maps generic callbacks to Claude and transport-neutral remote-session steering/interruption.
    • Advertises live only with native steering; otherwise advertises restart.
  5. Pi adapter — planned layer 5
    • Maps live updates to Pi steer messages and cancellations to abort/recovery.
    • Persists observed source revisions and removes duplicate renewal ownership.

What Was Built

Shared protocol contracts

  • BOT_INPUT_UPDATE_MODES defines live and restart once; Zod and TypeScript derive from that source (INV-31/33).
  • BotRuntimeManifest.input.updates is optional. Absence remains legacy behavior.
  • Claim and owned-claim wire shapes carry source and claimed source revisions.
  • Renewal returns an InvocationControlState union:
    • active with lease expiry, current revision, and optional update;
    • cancelled with revision and typed reason.
  • Completion requests carry the exact applied source revision.
  • Cancellation reasons include source deletion, routing change, input restart/staleness, and key-grant loss.

Files:

  • packages/types/src/constants.ts
  • packages/types/src/domain.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts

Manifest persistence and claim pinning

  • bot:hello validates and stores the declared manifest.
  • Explicit hello omission restores the legacy profile rather than retaining an obsolete declaration.
  • Internal heartbeat/session-link writes retain the existing manifest because they do not carry declaration state.
  • Claim atomically pins the winning instance's input-update mode with token, owner, lease, and source revision.
  • Later presence changes cannot rewrite behavior for an in-flight claim.

Files:

  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/service.ts

Edit, route, and deletion reconciliation

  • Pending edits update canonical prompt/routing projection and emit a new availability hint when destination metadata changes.
  • Claimed live edits advance the source projection and emit bot_invocation:input_updated without ending the claim.
  • Claimed restart edits cancel the old claim/session, emit cancellation, and insert one current replacement.
  • Legacy edits never assume callback support; stale completion remains fenced by canonical revision.
  • Actor, trigger, stream, response stream, target instance, and runtime-session changes are routing identity changes.
  • Deletion transitions active work once; retries repair running sessions without replaying durable cancellation hints.
  • Source and actor advisory locks retain canonical order across reconciliation, renew, completion, and replacement (INV-20).

Files:

  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts

Authoritative renewal control sync

  • Renewal accepts optional knownSourceRevision and restartRequiredRevision.
  • The service locks claim control state before source reconciliation.
  • A live runtime receives the newest canonical input only when its known revision is behind.
  • A runtime that cannot apply a live update can request restart for that exact revision.
  • Cancellation remains recoverable after a missed WebSocket hint.
  • Claim-token, instance, bot, workspace, and revision guards prevent another claim generation from controlling the row.

Files:

  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/handlers.ts

Plaintext and sealed update delivery

  • Plaintext renewal returns current prompt markdown and mention slugs at the authoritative revision.
  • Sealed renewal returns ciphertext and metadata only; server-side plaintext fallback is forbidden.
  • Sealed deltas include older source-history wraps plus the current reply-generation wrap needed across key rotation.
  • Source/key/reply-generation material is read from the same locked snapshot used for renewal.
  • Lost E2E grant cancels with key_grant_lost rather than exposing plaintext.

Files:

  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts

Durable, narrowly routed hints

  • Invocation mutation and outbox insertion share one transaction (INV-4/7).
  • WebSocket update hints contain invocation identity and revision, never source content.
  • Cancellation hints add a typed reason.
  • Broadcast delivery targets the claimed instance and runtime session when known.
  • Pending retargeting emits availability for the new destination.

Files:

  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts

Reconnect and bootstrap recovery

  • Bootstrap returns available work, currently owned claims, and recent cancellations.
  • claimed_runtime_session_id records the actual claim winner independently of route targeting and is overwritten on reclaim.
  • Owned claims, control hints, and cancellations are scoped to that runtime session; sessionless claims remain instance-scoped without leaking to named sibling sessions.
  • Availability, owned claims, cancellations, active actors, and session links use one repeatable-read snapshot.
  • Queries execute sequentially on one PoolClient; no concurrent pg operations share a transaction client.
  • Bootstrap remains a recovery hint; renewal is authoritative.

Files:

  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • Colocated repository/service tests

Revision-aware completion

  • Plaintext and sealed terminal requests identify the applied source revision.
  • Completion accepts the current revision for a live claim even when it advanced after initial claim.
  • Legacy omission remains compatible only while pinned and canonical revisions still agree.
  • A stale terminal attempt cancels/reconciles current routing and returns 409 INVOCATION_INPUT_STALE.
  • Reply, trace floor, and lifecycle writes remain outside the commit path when the fence fails.

Files:

  • apps/backend/src/features/public-api/{routes,handlers,runtime-write-ops}.ts
  • apps/backend/src/features/bot-runtimes/{repository,service}.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts

Design Decisions

Metadata hints plus HTTP authority

Chose: WebSocket events only nudge; authenticated renewal returns control content and state.

Why: Hints can be missed or duplicated. HTTP synchronization supports reconnect, polling fallback, claim-token authorization, and sealed payload construction on one path.

Pin behavior at claim

Chose: Store claimed_input_update_mode when work starts.

Why: Presence changes after claim must not reinterpret the contract under which the runtime accepted work.

Live, restart, and legacy are distinct

Chose: live updates in place; restart cancels/replaces immediately; absent mode remains legacy and fenced.

Why: Treating absence as live risks stale execution; treating all capable runtimes as restart discards safe native steering.

Fail closed for sealed updates

Chose: Recheck grant state on every renewal, cancel on loss, and atomically rotate the session callback fence to the reply generation advertised by a sealed delta.

Why: Delivery capability cannot override the stream's encryption boundary, and an update must not prescribe ciphertext that its own callback authorization rejects.

Full routing identity

Chose: Compare active/response streams and target instance/session as well as actor/trigger.

Why: Same actor and trigger can still point at an obsolete runtime destination.

Design Evolution

  • Output-only manifest → input-update declaration persisted through HTTP and WebSocket parity.
  • Lease-only renewal → authoritative control synchronization and restart disposition.
  • Creation-time route identity → full current destination identity.
  • Single-generation sealed claim assumptions → distinct full-claim and delta wrap sets.
  • Parallel bootstrap reads → sequential queries inside one repeatable-read transaction.
  • Historical cancellation replay → transition-only durable hints plus separate session-repair candidates.
  • Route target as reconnect owner → separately persisted claiming runtime session, preventing sibling-session recovery leaks.
  • Source-change-only grant checks → grant validation on every authoritative renewal.

Schema Changes

20260808180131_add_bot_invocation_claimed_runtime_session.sql adds nullable bot_invocations.claimed_runtime_session_id. Each claim/reclaim overwrites it with the actual requesting runtime session; bootstrap and control-event routing use it independently of target_runtime_session_id, which remains route intent.

Explicit Exclusions

  • No shared runtime-client observeClaim implementation.
  • No onInputUpdated or onCancelled adapter callbacks.
  • No Claude, generic remote-session, Hermes, OpenClaw, hosted-loop, custom-runtime, or Pi behavior changes.
  • No E2EE message-edit UI or new public sealed-edit producer.
  • No server-side parsing of encrypted mentions.
  • No published protocol version number.
  • No automatic rerun or output rewrite for completed/failed/parked same-actor work.
  • No deletion of already-posted bot interim or final messages.
  • No compensation for filesystem, shell, network, tool, or other external side effects.
  • No runtime-kind-specific backend branches.
  • No new persistent mutation-log table.

Status

  • Layer 1 canonical-state dependency implemented in PR feat: add canonical bot invocation source revisions #1817.
  • Shared backend control contracts and manifest validation.
  • Live/restart/legacy reconciliation semantics.
  • Plaintext and sealed renewal synchronization.
  • Durable, instance/session-targeted update and cancellation hints.
  • Reconnect bootstrap control recovery scoped to actual claim-session ownership.
  • Sealed reply-generation rotation and renewal-time grant revocation.
  • Revision-aware plaintext and sealed completion fences.
  • OpenAPI snapshots and backend verification.
  • Layer 3 shared runtime client.
  • Layer 4 generic remote-session/Claude adapter.
  • Layer 5 Pi adapter and recovery.

🤖 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

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added bot runtime capability settings for reply, trace, source output, and live or restart-based input updates.
    • Added source revision tracking to invocation claims, renewals, and completions.
    • Added typed cancellation states and notifications for routing changes, stale inputs, deleted sources, and other interruptions.
    • Added support for plaintext and encrypted invocation updates.
  • Bug Fixes
    • Stale invocation completions now return a clear conflict response instead of appearing missing.
    • Improved recovery for interrupted or reassigned runtime sessions.
  • Documentation
    • Updated public API schemas and contracts.

Walkthrough

Changes

Bot invocation control protocol

Layer / File(s) Summary
Protocol types and API contracts
packages/types/*, apps/backend/src/features/bot-runtimes/manifest-schema.ts, apps/backend/src/features/public-api/{schemas.ts,routes.ts}, docs/public-api/*
Added runtime manifests, update modes, cancellation reasons, source revisions, and active/cancelled renewal contracts.
Repository and persistence lifecycle
apps/backend/src/features/bot-runtimes/repository.ts, apps/backend/src/db/migrations/*, apps/backend/src/features/agents/session-repository.ts
Added session-aware claims, revision-aware source validation, cancellation and repair queries, manifest retention, route reconciliation, and bootstrap cancellation reporting.
Service renewal and completion
apps/backend/src/features/bot-runtimes/service.ts, apps/backend/src/features/public-api/{handlers.ts,runtime-write-ops.ts,sealed-turn-context.ts}
Added transactional renewal, plaintext and sealed input updates, stale-completion reconciliation, key-grant cancellation, and source-revision fencing.
Socket bootstrap and control events
apps/backend/src/features/bot-runtimes/socket-handler.ts, apps/backend/src/lib/outbox/*
Validated manifests and control payloads, serialized revision metadata, returned recent cancellations, and routed input-update or cancellation events.
Validation and integration coverage
apps/backend/tests/integration/*, apps/backend/src/features/**/*.test.ts
Added coverage for manifests, session ownership, revisions, sealed delivery, cancellation races, route changes, migrations, and runtime fixtures.

Possibly related PRs

  • threahq/threa#1825: Consumes the backend invocation-control protocol from the client side.
  • threahq/threa#1828: Extends related runtime invocation revision, manifest, and cancellation behavior.
  • threahq/threa#1829: Implements corresponding source-revision-aware control behavior in the remote runtime.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a backend bot invocation runtime control protocol.
Description check ✅ Passed The description directly explains the runtime control protocol, its design, implementation scope, and verification results.
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.

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: 16

🤖 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/features/bot-runtimes/repository.ts`:
- Around line 1658-1668: Ensure the cancellation lookup used during runtime
bootstrap and reconnect has a supporting partial index on bot_invocations
covering workspace_id, actor_id, claimed_by_instance_id, and updated_at in
descending order, restricted to rows with status = 'cancelled' and a non-null
cancellation_reason. Add or update the relevant database migration/index
definition without changing the query behavior.
- Around line 1262-1272: Rename the repository method
cancelClaimedForInputRestart to cancelClaimedWithReason, and update all three
corresponding call sites in service.ts. Keep the existing reason parameter and
cancellation behavior unchanged for input_restart, adapter_restart_required, and
key_grant_lost.
- Line 1078: Update the `reason` field in the parameter type used by the
affected repository method to `BotInvocationCancellationReason`, matching
`cancelClaimedForInputRestart` and preventing arbitrary strings from reaching
`cancellation_reason`; leave the remaining parameter types and behavior
unchanged.

In `@apps/backend/src/features/bot-runtimes/service.ts`:
- Line 967: Import BotInvocationCancellation through the existing named import
type declaration from ./repository, then replace the inline
import("./repository").BotInvocationCancellation reference in the public return
type with the imported symbol.
- Line 866: Update createInvocation and createInvocationInTransaction so
didAdvanceSourceRevision is not declared without being populated: either
propagate the field returned by BotInvocationRepository.insertIdempotent through
the destructuring and return object, or remove it from both return types. Keep
the signatures and returned values consistent so callers cannot observe an
always-undefined flag.

In `@apps/backend/src/features/bot-runtimes/socket-handler.test.ts`:
- Line 72: Update the fixture override containing recentCancellations to use the
bootstrap element type returned by the service instead of never[]. Add the
corresponding type import from ./repository alongside BotInvocation, matching
the type pattern used by the other overrides and allowing tests to seed
cancellation entries.

In `@apps/backend/src/features/public-api/complete-invocation-floor.test.ts`:
- Around line 92-95: Update
apps/backend/src/features/public-api/complete-invocation-floor.test.ts lines
92-95 so arrangeCompletion returns reconcileStaleCompletionInTransaction, then
assert it is called once in the stale plaintext completion test. Update
apps/backend/src/features/public-api/sealed-complete.test.ts line 102 so arrange
returns the same mock, then assert one call in the stale sealed completion test.

In `@apps/backend/src/features/public-api/runtime-write-ops.test.ts`:
- Around line 49-56: Update the tests using the captured queries from setup to
assert transaction lifecycle statements: in the sealed-delta test, verify the
renewal path commits, and in the retry-conflict test around the retry scenario,
verify failure rolls back. Ensure the assertions cover the expected query
sequence without changing the mock client behavior.

In `@apps/backend/src/features/public-api/runtime-write-ops.ts`:
- Around line 151-155: Update renewClaim to retry the entire REPEATABLE READ
transaction when PostgreSQL returns serialization failure SQLSTATE 40001,
including acquiring a new client and rerunning BEGIN through commit/rollback; do
not rely solely on withClient’s connection-error retries. Alternatively, change
the transaction isolation to READ COMMITTED only if snapshot isolation is not
required, while preserving existing claim-renewal behavior.

In `@apps/backend/src/features/public-api/schemas.ts`:
- Line 271: Make sourceRevision required for both plaintext and sealed
completion request schemas by removing .optional() from the sourceRevision
fields in apps/backend/src/features/public-api/schemas.ts at lines 271-271 and
367-367; update both sites consistently while preserving the existing integer
and non-negative validation.
- Line 9: Update the botRuntimeManifestSchema import in the public API schemas
module to use the bot-runtimes feature barrel at ../bot-runtimes, relying on its
existing export instead of the internal manifest-schema path.

In `@apps/backend/src/features/public-api/sealed-turn-context.ts`:
- Around line 82-84: Add a brief comment immediately above chosenWraps
explaining that, unlike update.wraps, it retains every bot wrap for bikKeyId
because history access may span older key generations.
- Around line 51-52: Change the return type of buildSealedInputUpdate to the
sealed member of InvocationInputUpdateWire union (or that member | null), then
remove the redundant update.delivery !== "sealed" guard in its caller while
preserving null handling and sealed update behavior. Ensure renewClaim’s
InvocationInputUpdateWire assignment remains valid.

In `@apps/backend/src/lib/outbox/broadcast-handler.ts`:
- Around line 324-338: Update the control-event handling in the broadcast event
processor around botInvocationCancelledPayloadSchema and
botInvocationControlPayloadSchema to use safeParse instead of parse; when
validation fails, log the invalid payload and return without emitting, allowing
the cursor to advance. Add coverage proving a malformed control event is dropped
and a subsequent valid event is still processed.

In `@docs/public-api/versions/2026-07-12.json`:
- Around line 1942-2052: The renew response union containing the active and
cancelled shapes must not be exposed in existing API versions. In
docs/public-api/versions/2026-07-12.json lines 1942-2052 and
docs/public-api/versions/2026-07-22.json lines 1942-2052, add the appropriate
VERSION_CHANGES downgrade or restore the prior response schema requiring
nullable data.claimExpiresAt; expose the new cancelled union only starting with
version 2026-07-24.

In `@docs/public-api/versions/2026-07-24.json`:
- Line 2830: Update the request-body schemas for both /sealed-complete at
docs/public-api/versions/2026-07-24.json lines 2830-2830 and /complete at lines
2971-2971 to include sourceRevision in each required array, and document the 409
INVOCATION_INPUT_STALE response on both completion endpoints.
🪄 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: 455637c3-0fb7-4388-9c54-68764d93e19b

📥 Commits

Reviewing files that changed from the base of the PR and between 165bae3 and 293c03f.

📒 Files selected for processing (32)
  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • docs/public-api/openapi.json
  • docs/public-api/versions/2026-07-12.json
  • docs/public-api/versions/2026-07-22.json
  • docs/public-api/versions/2026-07-24.json
  • packages/types/src/constants.ts
  • packages/types/src/domain.ts
  • packages/types/src/index.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: browser-tests (2)
  • GitHub Check: browser-tests (1)
  • GitHub Check: browser-tests (4)
  • GitHub Check: browser-tests (3)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names that clearly indicate purpose and avoid single letters except for loop counters
Add JSDoc comments for all public functions and exported classes to document purpose, parameters, return types, and usage examples
Use const by default, let when variable reassignment is needed, avoid var
Use environment variables for configuration instead of hardcoded values
Format code with Prettier and lint with ESLint according to project configuration

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use TypeScript interfaces for type definitions and avoid type assertions when possible

**/*.{ts,tsx}: Do not use language-specific heuristics or English-only literals and regexes for semantic decisions; use model-based decisions for language-dependent behavior (INV-54).
Keep comments absent by default; retain only durable explanations of ordering, concurrency, non-obvious constraints, or load-bearing values. Do not add speculative TODOs or change narration (INV-25, INV-36).
Limit nested ternaries to one level and colocate variant configuration while keeping shared behavior on one path (INV-29, INV-43, INV-47).

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Bun commands and runtime (bun <file>, bun run test, bun install, bun build), not Node or dotenv.

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
apps/backend/src/features/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/backend/src/features/**/*.ts: Backend feature logic belongs in colocated feature folders; keep lib/ limited to cross-cutting infrastructure and use index.ts barrels for cross-feature imports (INV-51, INV-52).
Keep AI component configuration beside its component in config.ts; evaluations must call production entry points (INV-44, INV-45).

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
apps/backend/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/backend/src/**/*.ts: Handlers and workers stay thin; services own orchestration, transactions, and domain logic; repositories provide data access (INV-5, INV-6, INV-34).
Validate request body, query, and params with Zod; throw HttpError classes and derive types from schemas or constants (INV-31, INV-32, INV-55).
Every workspace-scoped domain query and mutation filters by workspace_id; global infrastructure and authentication tables are exempt (INV-8).
Use prefixed ULIDs, no foreign keys, and no database enums; represent enum-like values as TEXT with code validation (INV-1, INV-2, INV-3).
Migrations are append-only; never edit an existing migration file (INV-17).
Race-safe writes must not use select-then-update without locking; prefer upserts, pin check-then-act guards to row identity or generation, and use integer versions rather than timestamp equality (INV-20, INV-66).
Use set-based or batch operations instead of per-row loops; pass pool for single queries; do not hold database connections during slow AI or network work (INV-30, INV-41, INV-56).
Real-time delivery must use the outbox pattern; write outbox events in the same transaction as domain writes, and commit event-source updates with read projections (INV-4, INV-7).
Do not use hidden singletons except the logger and web-push bootstrap; pass constructed dependencies and construct long-lived collaborators once (INV-9, INV-12, INV-13).
Use createAI for every AI call, only use current-generation models from docs/model-reference.md, and always include telemetry metadata (INV-16, INV-19, INV-28).

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**

⚙️ CodeRabbit configuration file

**: Architecture, invariants, and the full app inventory live in CLAUDE.md and
docs/system-overview.md. Treat those as the source of truth.

What NOT to flag:

  • Pre-existing issues not introduced by this PR
  • Issues that TypeScript compilation or ESLint would catch (types, imports, lint)
  • Stylistic preferences without a concrete rule violation in CLAUDE.md
  • Theoretical risks or hypothetical edge cases without evidence of exploitability
  • General best-practice suggestions that don't map to a specific project rule

Security calibration:

  • React JSX is safe from XSS unless dangerouslySetInnerHTML is used
  • ULIDs/UUIDs are cryptographically unguessable — do not flag as enumeration risks
  • Environment variables are trusted — do not flag as hardcoded secrets
  • DoS, rate limiting, log spoofing, regex complexity, missing audit logs, and
    outdated dependency warnings are out of scope

Plan adherence:
The implementation plan is in the PR description, inside the collapsible
"📋 Full implementation plan" details block (plans are not committed to the repo).
If that block exists, check that PR changes align with the plan.
Flag missing corresponding changes: API change without frontend/backoffice update,
type change without usage update, schema change without migration.

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • docs/public-api/versions/2026-07-12.json
  • docs/public-api/versions/2026-07-24.json
  • docs/public-api/versions/2026-07-22.json
  • docs/public-api/openapi.json
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
apps/backend/src/**

⚙️ CodeRabbit configuration file

apps/backend/src/**: Real-time event delivery must go through the outbox pattern. Do not publish events
directly via socket.io emit or Redis pub/sub outside of the outbox dispatcher. (INV-4)
All AI/LLM usage must go through the project AI wrapper (createAI), not raw SDK
imports from @anthropic-ai/sdk or openai. (INV-28)
Do not keep database connections open during slow AI or network calls. Release the
connection first, then do the slow work. (INV-41)
Never do select-then-update without locking or concurrency control. Use ON CONFLICT,
advisory locks, or transactions with row locks for write paths. (INV-20)
Check-then-act guards must pin the identity or generation observed at read (row id,
integer version, or external key), not just a status flag — status-only guards let
stale work clobber a row that was replaced in between. (INV-20)
Optimistic concurrency must CAS on an integer version column, never on timestamp
equality: PostgreSQL stores microseconds while a JS Date round-trips at millisecond
precision, so a timestamp CAS fails on virtually every uncontended write. Tests for
version- or timestamp-gated predicates must produce the compared value through the
repository's own NOW()-writing code path, never hand-crafted fixture timestamps.
(INV-66)
Avoid withClient for single-query paths. Pass pool directly instead of acquiring a
dedicated client. (INV-30)
Validate API inputs (body, query, params) with Zod schemas, not manual typeof
checks. (INV-55)
Stream access is inherited through root_stream_id: threads never carry their own
access, and public root streams grant read access without a stream_members row.
Any new query or filter gating rows on stream membership/visibility must reuse
checkStreamAccess / listAccessibleStreamIds (features/streams/access.ts) or
replicate the thread-to-root rule. Flag audience/visibility predicates built on
direct stream_members rows alone — they drop thread content for root-stream
members. (INV-62)
SQL correctness is verified against a...

Files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write unit tests for all utility functions and business logic with at least 80% code coverage
Use descriptive test names that explain what is being tested and expected outcome

Files:

  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{ts,tsx}: Never ship unexecuted tests, .skip(), or .todo(); fix failing tests rather than dismissing them as pre-existing (INV-22, INV-26).
Assert specific event presence and content rather than counts; prefer one object comparison over chains of narrow assertions (INV-23, INV-24).
Do not mock shared modules with mock.module() or vi.mock(); use scoped spyOn against namespace imports. Frontend integration tests mount real components and test observable behavior (INV-39, INV-48).

Files:

  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
apps/backend/tests/integration/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Verify SQL against a real schema by seeding rows, executing statements, and asserting returned data; do not use query-text assertions as SQL correctness tests (INV-68).

Files:

  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
🧠 Learnings (13)
📚 Learning: 2026-05-12T07:31:56.525Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/trace.ts:3-10
Timestamp: 2026-05-12T07:31:56.525Z
Learning: In this repo’s TypeScript code under apps/backend/src, avoid recommending or requiring JSDoc comments for exported/public functions solely for documentation purposes. The team considers such suggestions unnecessary; only ask for JSDoc if there is a concrete technical requirement (for example, an enforced documentation generation/lint rule or an existing documented convention that the code must follow).

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-19T08:40:01.120Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 572
File: apps/backend/src/features/memos/repository.ts:662-664
Timestamp: 2026-05-19T08:40:01.120Z
Learning: In the threahq/threa codebase, the PostgreSQL Full-Text Search (FTS) dictionary configuration for the memo search subsystem (e.g., `MemoRepository.hybridSearch`, `MemoRepository.fullTextSearch`, `MemoRepository.exactSearch`, and the message-search layer) is intentionally set to `'english'` as a subsystem-wide convention (INV-35/37). During code review, do not flag `'english'` usage as a language-neutrality problem and do not recommend swapping to `'simple'` (or another dictionary) for any single method/path. Only treat dictionary strategy changes as valid if they are part of a deliberate, repo-wide decision that updates the entire search subsystem consistently (with the corresponding coordinated change), rather than an isolated modification.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-23T13:57:24.350Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 605
File: apps/backend/src/features/conversations/boundary-extraction-service.ts:200-203
Timestamp: 2026-05-23T13:57:24.350Z
Learning: In threahq/threa (apps/backend), treat the current absence of `workspaceId` in calls to `AttachmentRepository.findByMessageId` and `AttachmentRepository.findByMessageIdsWithExtractions` (INV-8) as a known, intentionally unaddressed gap. During code review, do not flag individual call sites as new violations for missing `workspaceId` until the planned follow-up PR lands that updates all `AttachmentRepository.findByMessage*` signatures and updates the affected ~9 call sites in one pass. After that follow-up is merged, start enforcing that `workspaceId` is provided in these calls.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-11T16:46:01.779Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 832
File: apps/backend/src/routes.ts:514-514
Timestamp: 2026-06-11T16:46:01.779Z
Learning: In the threahq/threa repository, do not raise code review findings for missing explicit per-endpoint rate limiting on any HTTP endpoint. The project’s CodeRabbit calibration / coding guidelines treat rate limiting (and related DoS concerns) as out of scope for review flags, assuming the global/baseline rate limiting is already in place. If you identify any need for rate-limit tuning, defer it to a follow-up PR until real usage patterns are available.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:32:39.480Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/issues.ts:59-91
Timestamp: 2026-05-12T07:32:39.480Z
Learning: In the threahq/threa repository, do not recommend adding JSDoc comments for exported/public functions during code review. Treat such documentation suggestions as unnecessary unless there is a concrete, enforceable requirement—e.g., a specific lint rule or a documented convention explicitly requiring JSDoc for public/exported APIs. Only raise documentation concerns when that requirement can be verified from the repo’s tooling or docs.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:44.564Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/format.ts:1-31
Timestamp: 2026-05-12T07:33:44.564Z
Learning: In the threahq/threa repository, do not raise any code review comments (of any severity) about missing, insufficient, or requested JSDoc/documentation comments. This applies to all exported/public TypeScript entities (e.g., exported functions, classes, interfaces, constants) across the repo—missing documentation should never be flagged as a review issue under any circumstances.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:11.118Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/projects.ts:45-76
Timestamp: 2026-05-12T07:33:11.118Z
Learning: In the threahq/threa repository, do not flag missing JSDoc on exported functions during code review. The team does not require JSDoc for exported APIs and considers it review noise; only raise review issues for other concerns (e.g., correctness, types, tests, security), not for absent JSDoc comments on exports.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-12T14:06:33.911Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 882
File: packages/agent-runtime/src/runtime/negotiate-capabilities.ts:4-15
Timestamp: 2026-06-12T14:06:33.911Z
Learning: In this repo, code comments may include invariant/finding anchors (e.g., E2EE-11, INV-E1, E2EE-9, C-1, Phase 2.4) as stable references that map to docs/audits and docs/plans, following the documented convention in CLAUDE.md to “reference it in nearby code comments if the constraint is non-obvious.” When you see such anchor references, do not treat them as violations of INV-25 or INV-36. Also preserve “rollout-phase” notes that describe *current* tolerated behavior (e.g., “absent token tolerated today”)—these are load-bearing context for reviewers and should not be removed as if they were generic change-history narration.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:41.940Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/deps.ts:3-10
Timestamp: 2026-05-12T07:33:41.940Z
Learning: In the threahq/threa repository, do not suggest adding JSDoc comments anywhere during code review (not for interfaces, exported functions, types, or other constructs). Treat missing JSDoc as intentional and acceptable; avoid any review comments recommending JSDoc additions across the codebase.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-11T10:44:53.003Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 825
File: apps/backend/src/features/bot-runtimes/repository.ts:26-34
Timestamp: 2026-06-11T10:44:53.003Z
Learning: In the bot-runtimes feature (apps/backend/src/features/bot-runtimes/), treat operational tuning knobs like BOT_CLAIM_MAX_ATTEMPTS, BOT_RUNTIME_BIK_STALENESS_MS, and ENCLAVE_RUNTIME_STALENESS_MS as intentionally hardcoded, module-level constants. Do not flag them in code review with “should use env vars” guidance unless there is already a dedicated bot-runtime feature-config surface. If/when runtime tuning becomes necessary, add a single cohesive feature-config surface that covers all these knobs together (avoid speculative per-knob env/plumbing).

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-07-13T19:46:31.849Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1324
File: apps/backend/src/features/agents/persona-config-service.ts:1116-1231
Timestamp: 2026-07-13T19:46:31.849Z
Learning: When implementing attachment cleanup/deletion logic (e.g., persona-context-attachments or persona-related cleanup), call `AttachmentService.deleteIfUnbound` rather than doing a select-then-delete. `deleteIfUnbound` should enforce the unbound condition (e.g., `message_id IS NULL`) directly in the `DELETE` statement, making the operation race-safe against concurrent attachment claiming (e.g., `attachToMessage()` claiming the file between a check and a delete). If the attachment has become bound in the meantime, the DELETE should be skipped (log as appropriate) and the underlying file/extraction/S3 object should survive.

Applied to files:

  • apps/backend/src/features/bot-runtimes/index.ts
  • apps/backend/src/features/public-api/sealed-complete.test.ts
  • apps/backend/src/features/public-api/complete-invocation-floor.test.ts
  • apps/backend/src/features/public-api/renew-heartbeat.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/sealed-turn-context.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/runtime-write-ops.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/bot-runtimes/manifest-schema.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-05T14:24:15.849Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 776
File: packages/types/src/prosemirror.ts:446-452
Timestamp: 2026-06-05T14:24:15.849Z
Learning: In this repo, prevent circular dependencies: files under `packages/types` must not import anything from `packages/prosemirror` (e.g., avoid imports from `packages/prosemirror` into `packages/types`). Where host-validation logic is needed (such as the `*.giphy.com`-style check used in `packages/types/src/prosemirror.ts`), it should remain intentionally duplicated inside `packages/types` as an inline helper rather than being shared with `packages/prosemirror`, because `packages/prosemirror` depends on `packages/types`. If a refactor is proposed, ensure it removes the circular dependency (e.g., via a third shared package with clear dependency direction) before changing this pattern.

Applied to files:

  • packages/types/src/domain.ts
  • packages/types/src/constants.ts
  • packages/types/src/index.ts
📚 Learning: 2026-07-16T19:45:30.012Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1365
File: apps/backend/src/features/bot-runtimes/repository.test.ts:328-360
Timestamp: 2026-07-16T19:45:30.012Z
Learning: In bot-runtimes repository unit tests, mocked `Querier` instances should be used intentionally, and assertions should verify the generated SQL structure (shape) rather than relying on real DB execution. Also ensure the runtime-session archived/retired invariant is covered by a real-database integration/e2e test: once an identity/session is retired, a later unarchive must not allow reclaiming that retired identity.

Applied to files:

  • apps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/socket-handler.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
🪛 ast-grep (0.45.0)
apps/backend/tests/integration/bot-invocation-control-protocol.test.ts

[error] 43-48: Avoid SQL injection
Context: pool.query(
INSERT INTO bot_runtime_instances (id, workspace_id, bot_id, instance_id, runtime_kind, status, accepting_invocations, manifest) VALUES ($1, $2, $3, $4, 'openclaw', 'available', TRUE, NULL),
[bri_${crypto.randomUUID().replaceAll("-", "")}, workspace, bot, id]
)
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)


[error] 464-469: Avoid SQL injection
Context: pool.query(
INSERT INTO bot_runtime_instances (id, workspace_id, bot_id, instance_id, runtime_kind, status, accepting_invocations, manifest) VALUES ($1, $2, $3, $4, 'openclaw', 'available', TRUE, NULL),
[bri_${crypto.randomUUID().replaceAll("-", "")}, workspace, replacementBot, ${instance}-replacement]
)
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)

Comment thread apps/backend/src/features/bot-runtimes/repository.ts
Comment on lines +1262 to +1272
async cancelClaimedForInputRestart(
db: Querier,
params: {
workspaceId: string
invocationId: string
sourceMessageRevision: number
reason: BotInvocationCancellationReason
instanceId?: string
claimToken?: string
}
): Promise<BotInvocation | null> {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename cancelClaimedForInputRestart to match its three call sites.

Callers pass input_restart, adapter_restart_required, and key_grant_lost. The last one is not a restart. The reason parameter already carries the intent, so a neutral name such as cancelClaimedWithReason describes the behaviour. Rename the method and its three call sites in service.ts.

🤖 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/features/bot-runtimes/repository.ts` around lines 1262 -
1272, Rename the repository method cancelClaimedForInputRestart to
cancelClaimedWithReason, and update all three corresponding call sites in
service.ts. Keep the existing reason parameter and cancellation behavior
unchanged for input_restart, adapter_restart_required, and key_grant_lost.

Comment on lines +1658 to +1668
const cancellationResult = await db.query<BotInvocationRow>(sql`SELECT * FROM bot_invocations
WHERE workspace_id = ${params.workspaceId}
AND actor_type = 'bot'
AND actor_id = ${params.botId}
AND status = 'cancelled'
AND cancellation_reason IS NOT NULL
AND claimed_by_instance_id = ${params.instanceId}
AND (target_runtime_session_id IS NULL OR target_runtime_session_id = ${params.runtimeSessionId})
AND (${params.since}::timestamptz IS NULL OR updated_at >= ${params.since})
ORDER BY updated_at DESC, id DESC
LIMIT 200`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🔵 Trivial

Confirm an index supports the new cancellation lookup.

This query filters bot_invocations on workspace_id, actor_id, status = 'cancelled', claimed_by_instance_id, and updated_at, and it runs on every runtime bootstrap and reconnect. Cancelled rows accumulate and are never pruned by this path. Without a covering index the planner falls back to a filtered scan over the bot's whole cancelled history, even though since is clamped to 24 hours.

Consider a partial index such as (workspace_id, actor_id, claimed_by_instance_id, updated_at DESC) WHERE status = 'cancelled' AND cancellation_reason IS NOT NULL.

🤖 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/features/bot-runtimes/repository.ts` around lines 1658 -
1668, Ensure the cancellation lookup used during runtime bootstrap and reconnect
has a supporting partial index on bot_invocations covering workspace_id,
actor_id, claimed_by_instance_id, and updated_at in descending order, restricted
to rows with status = 'cancelled' and a non-null cancellation_reason. Add or
update the relevant database migration/index definition without changing the
query behavior.

Comment thread apps/backend/src/features/bot-runtimes/service.ts Outdated
serverGeneratedAt: Date
available: BotInvocation[]
ownedClaims: BotInvocation[]
recentCancellations: import("./repository").BotInvocationCancellation[]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import BotInvocationCancellation at the top of the file.

The inline import("./repository").BotInvocationCancellation in the public return type is harder to read than the file's existing named imports from ./repository. Move it into the existing import type statement.

🤖 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/features/bot-runtimes/service.ts` at line 967, Import
BotInvocationCancellation through the existing named import type declaration
from ./repository, then replace the inline
import("./repository").BotInvocationCancellation reference in the public return
type with the imported symbol.

Comment thread apps/backend/src/features/public-api/sealed-turn-context.ts Outdated
Comment on lines +82 to +84
const chosenWraps = wraps
.filter((wrap) => wrap.recipientKind === "bot" && wrap.recipientKeyId === bikKeyId)
.map((wrap) => ({ keyGeneration: wrap.keyGeneration, wrapEnc: wrap.wrapEnc, wrapCt: wrap.wrapCt }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document why chosenWraps is wider than update.wraps.

update.wraps covers only the trigger and current generations. chosenWraps keeps every bot wrap for bikKeyId because the history in this context can span older generations. After the extraction the two wrap sets sit side by side and the difference is no longer obvious. Add one short comment stating that history access needs the wider set.

🤖 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/features/public-api/sealed-turn-context.ts` around lines 82
- 84, Add a brief comment immediately above chosenWraps explaining that, unlike
update.wraps, it retains every bot wrap for bikKeyId because history access may
span older key generations.

Source: Coding guidelines

Comment on lines +324 to +338
if (
isOutboxEventType(event, "bot_invocation:input_updated") ||
isOutboxEventType(event, "bot_invocation:cancelled")
) {
const payload = (
isOutboxEventType(event, "bot_invocation:cancelled")
? botInvocationCancelledPayloadSchema
: botInvocationControlPayloadSchema
).parse(event.payload) as BotInvocationControlOutboxPayload
let room = `bot:${workspaceId}:bot:${payload.botId}`
if (payload.targetInstanceId) room = `${room}:instance:${payload.targetInstanceId}`
if (payload.targetRuntimeSessionId)
room = `bot:${workspaceId}:bot:${payload.botId}:session:${payload.targetRuntimeSessionId}`
botNs.to(room).emit(event.eventType, payload)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not retry malformed control events indefinitely.

Line 332 calls .parse(), which throws for a malformed outbox row. processEvents() then leaves that row unprocessed. The cursor retries it and blocks every later outbox event.

Use safeParse(). Log and drop invalid payloads so the cursor can advance. Add a test with a valid event after an invalid control event.

Proposed change
-      ).parse(event.payload) as BotInvocationControlOutboxPayload
+      ).safeParse(event.payload)
+      if (!parsed.success) {
+        logger.error(
+          { eventId: event.id, eventType: event.eventType, issues: parsed.error.issues },
+          "dispatchBotEvent: invalid bot invocation control payload"
+        )
+        return
+      }
+      const payload = parsed.data
🤖 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/lib/outbox/broadcast-handler.ts` around lines 324 - 338,
Update the control-event handling in the broadcast event processor around
botInvocationCancelledPayloadSchema and botInvocationControlPayloadSchema to use
safeParse instead of parse; when validation fails, log the invalid payload and
return without emitting, allowing the cursor to advance. Add coverage proving a
malformed control event is dropped and a subsequent valid event is still
processed.

Comment on lines +1942 to +2052
"oneOf": [
{
"type": "object",
"properties": {
"invocationId": { "type": "string" },
"status": { "type": "string", "const": "active" },
"claimExpiresAt": { "type": "string", "format": "date-time" },
"sourceRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"update": {
"oneOf": [
{
"type": "object",
"properties": {
"delivery": { "type": "string", "const": "plaintext" },
"sourceRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"promptMarkdown": { "type": "string" },
"mentionedActorSlugs": { "type": "array", "items": { "type": "string" } }
},
"required": ["delivery", "sourceRevision", "promptMarkdown", "mentionedActorSlugs"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"delivery": { "type": "string", "const": "sealed" },
"sourceRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"prompt": {
"type": "object",
"properties": {
"ciphertext": { "type": "string" },
"envelope": {
"type": "object",
"properties": {
"v": { "type": "number" },
"keyGeneration": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"iv": { "type": "string" },
"aad": { "type": "string" }
},
"required": ["v", "keyGeneration", "iv", "aad"],
"additionalProperties": false
}
},
"required": ["ciphertext", "envelope"],
"additionalProperties": false
},
"wraps": {
"type": "array",
"items": {
"type": "object",
"properties": {
"keyGeneration": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"wrapEnc": { "type": "string" },
"wrapCt": { "type": "string" }
},
"required": ["keyGeneration", "wrapEnc", "wrapCt"],
"additionalProperties": false
}
},
"reply": {
"type": "object",
"properties": {
"keyGeneration": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"senderId": { "type": "string" }
},
"required": ["keyGeneration", "senderId"],
"additionalProperties": false
}
},
"required": ["delivery", "sourceRevision", "prompt", "wraps", "reply"],
"additionalProperties": false
}
]
}
},
"required": ["invocationId", "status", "claimExpiresAt", "sourceRevision"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"invocationId": { "type": "string" },
"status": { "type": "string", "const": "cancelled" },
"sourceRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },
"reason": {
"type": "string",
"enum": [
"routing_changed",
"source_deleted",
"input_restart",
"adapter_restart_required",
"key_grant_lost",
"input_stale"
]
}
},
"required": ["invocationId", "status", "sourceRevision", "reason"],
"additionalProperties": false
}
]

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 | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Determine whether dated API versions are frozen contracts or regenerated snapshots.
set -euo pipefail
fd -t f . docs/public-api
rg -n -C 6 'API_VERSIONS|CURRENT_API_VERSION|resolveApiVersion|SUPPORTED_VERSIONS' --type=ts apps/backend/src
rg -n -C 8 'versions/2026-' --type=ts apps/backend/src packages

Repository: threahq/threa

Length of output: 44419


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- project guidance ---'
sed -n '1,240p' CLAUDE.md
sed -n '1,220p' docs/system-overview.md

printf '%s\n' '--- version implementation map ---'
ast-grep outline apps/backend/src/features/public-api/versions
wc -l apps/backend/src/features/public-api/versions/index.ts \
  apps/backend/src/features/public-api/versions/types.ts \
  docs/public-api/CHANGELOG.md

printf '%s\n' '--- version implementation ---'
sed -n '1,230p' apps/backend/src/features/public-api/versions/index.ts
sed -n '1,180p' apps/backend/src/features/public-api/versions/types.ts

printf '%s\n' '--- public API version documentation ---'
sed -n '1,240p' docs/public-api/CHANGELOG.md
rg -n -C 8 'Threa-Version|version|2026-07-12|2026-07-22|2026-07-24|renew' docs/public-api/openapi.json docs/public-api/versions/*.json

printf '%s\n' '--- repository state ---'
git status --short
git diff --stat
git diff -- docs/public-api/versions/2026-07-12.json docs/public-api/versions/2026-07-22.json docs/public-api/versions/2026-07-24.json

Repository: threahq/threa

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path

files = sorted(Path("docs/public-api/versions").glob("2026-*.json"))
for path in files:
    doc = json.loads(path.read_text())
    print(f"\n== {path} ==")
    for route, methods in doc.get("paths", {}).items():
        for method, operation in methods.items():
            if not isinstance(operation, dict):
                continue
            text = json.dumps(operation).lower()
            if "renew" in route.lower() or "renew" in text or "invocation" in text:
                print(f"{method.upper()} {route} operationId={operation.get('operationId')}")
                print(json.dumps(operation.get("responses"), indent=2, sort_keys=True))

    descriptions = []
    def walk(value, location="$"):
        if isinstance(value, dict):
            for key, child in value.items():
                if key == "description" and isinstance(child, str) and "Threa-Version" in child:
                    descriptions.append((location, child))
                walk(child, f"{location}.{key}")
        elif isinstance(value, list):
            for i, child in enumerate(value):
                walk(child, f"{location}[{i}]")
    walk(doc)
    for location, description in descriptions:
        print(f"{location}: {description}")
PY

printf '%s\n' '--- source and generated-document references ---'
rg -n -C 10 'renew|claimExpiresAt|cancelled|completeBotInvocation|deriveVersionSpec|versions/' \
  apps/backend/src docs/public-api scripts package.json

printf '%s\n' '--- repository state summary ---'
git status --short
git diff --stat

Repository: threahq/threa

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import hashlib
import json
from pathlib import Path

def digest(value):
    raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
    return hashlib.sha256(raw).hexdigest()[:16]

docs = {}
for path in sorted(Path("docs/public-api/versions").glob("2026-*.json")):
    docs[path.name] = json.loads(path.read_text())

for name, doc in docs.items():
    print(f"\n== {name} ==")
    for route, methods in doc.get("paths", {}).items():
        for method, operation in methods.items():
            if not isinstance(operation, dict):
                continue
            encoded = json.dumps(operation, sort_keys=True)
            if "claimExpiresAt" in encoded:
                responses = operation.get("responses", {})
                print(
                    method.upper(),
                    route,
                    "operationId=", operation.get("operationId"),
                    "responseHash=", digest(responses),
                    "requestHash=", digest(operation.get("requestBody")),
                )

print("\n== cross-version response hashes ==")
keys = set()
for doc in docs.values():
    for route, methods in doc.get("paths", {}).items():
        for method, operation in methods.items():
            if isinstance(operation, dict) and "claimExpiresAt" in json.dumps(operation):
                keys.add((route, method))
for route, method in sorted(keys):
    print(f"{method.upper()} {route}")
    for name, doc in docs.items():
        op = doc["paths"][route][method]
        print(f"  {name}: {digest(op.get('responses'))}")

print("\n== generated-file line locations ==")
for path in sorted(Path("docs/public-api/versions").glob("2026-*.json")):
    for number, line in enumerate(path.read_text().splitlines(), 1):
        if "claimExpiresAt" in line or '"operationId":' in line and number > 1800:
            print(f"{path}:{number}:{line.strip()}")
PY

printf '%s\n' '--- focused source references ---'
rg -n -C 4 'claimExpiresAt|completeBotInvocation|renew.*invocation|invocation.*renew|Threa-Version' \
  apps/backend/src/features/public-api apps/backend/src/middleware docs/public-api/CHANGELOG.md

Repository: threahq/threa

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import hashlib
import json
from pathlib import Path

def digest(value):
    raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
    return hashlib.sha256(raw).hexdigest()[:16]

docs = {}
for path in sorted(Path("docs/public-api/versions").glob("2026-*.json")):
    docs[path.name] = json.loads(path.read_text())

for name, doc in docs.items():
    print(f"\n== {name} ==")
    for route, methods in doc.get("paths", {}).items():
        for method, operation in methods.items():
            if not isinstance(operation, dict):
                continue
            encoded = json.dumps(operation, sort_keys=True)
            if "claimExpiresAt" in encoded:
                responses = operation.get("responses", {})
                print(
                    method.upper(),
                    route,
                    "operationId=", operation.get("operationId"),
                    "responseHash=", digest(responses),
                    "requestHash=", digest(operation.get("requestBody")),
                )

print("\n== cross-version response hashes ==")
keys = set()
for doc in docs.values():
    for route, methods in doc.get("paths", {}).items():
        for method, operation in methods.items():
            if isinstance(operation, dict) and "claimExpiresAt" in json.dumps(operation):
                keys.add((route, method))
for route, method in sorted(keys):
    print(f"{method.upper()} {route}")
    for name, doc in docs.items():
        op = doc["paths"][route][method]
        print(f"  {name}: {digest(op.get('responses'))}")

print("\n== generated-file line locations ==")
for path in sorted(Path("docs/public-api/versions").glob("2026-*.json")):
    for number, line in enumerate(path.read_text().splitlines(), 1):
        if "claimExpiresAt" in line or ('"operationId":' in line and number > 1800):
            print(f"{path}:{number}:{line.strip()}")
PY

printf '%s\n' '--- focused source references ---'
rg -n -C 4 'claimExpiresAt|completeBotInvocation|renew.*invocation|invocation.*renew|Threa-Version' \
  apps/backend/src/features/public-api apps/backend/src/middleware docs/public-api/CHANGELOG.md

Repository: threahq/threa

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- commit and file changes ---'
git rev-parse --short HEAD
git diff --numstat HEAD^ HEAD -- docs/public-api/versions/2026-07-12.json \
  docs/public-api/versions/2026-07-22.json docs/public-api/versions/2026-07-24.json \
  apps/backend/src/features/public-api/routes.ts
git diff --unified=3 HEAD^ HEAD -- \
  docs/public-api/versions/2026-07-12.json \
  docs/public-api/versions/2026-07-22.json \
  docs/public-api/versions/2026-07-24.json \
  apps/backend/src/features/public-api/routes.ts |
  rg -n -C 12 'renewBotInvocationClaim|claimExpiresAt|status.*active|status.*cancelled|oneOf|discriminatedUnion'

printf '%s\n' '--- parent renew schemas ---'
for file in docs/public-api/versions/2026-07-12.json \
            docs/public-api/versions/2026-07-22.json \
            docs/public-api/versions/2026-07-24.json; do
  echo "== $file =="
  git show "HEAD^:$file" 2>/dev/null |
    rg -n -C 12 'operationId.*renewBotInvocationClaim|claimExpiresAt|status.*active|status.*cancelled|oneOf' |
    head -n 100
done

printf '%s\n' '--- version list and generation references ---'
rg -n -C 8 'API_VERSIONS|CURRENT_API_VERSION|generate.*openapi|openapi.*generate|CHANGELOG.md|versions/2026' \
  packages/types apps/backend scripts package.json

Repository: threahq/threa

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- renew route and handler ---'
sed -n '1028,1075p' apps/backend/src/features/public-api/routes.ts
rg -n -C 24 'renewBotInvocationClaim|renewInvocationClaimInTransaction|cancelOwnedClaimForKeyGrantLossInTransaction' \
  apps/backend/src/features/public-api apps/backend/src/features/bot-runtimes

printf '%s\n' '--- version middleware response transforms ---'
sed -n '1,180p' apps/backend/src/middleware/api-version.ts

printf '%s\n' '--- version changes affecting invocation renewal ---'
rg -n -C 12 'VERSION_CHANGES|renewBotInvocationClaim|bot-invocation|invocation' \
  apps/backend/src/features/public-api/versions apps/backend/src/features/public-api/versions.test.ts 2>/dev/null || true

Repository: threahq/threa

Length of output: 50369


Preserve the renew response for existing API versions. 2026-07-12 and 2026-07-22 previously required nullable data.claimExpiresAt; the new cancelled response omits it, and runtime can return that branch. Add a VERSION_CHANGES downgrade for these versions, or retain the previous response shape, and expose the union only from 2026-07-24.

📍 Affects 2 files
  • docs/public-api/versions/2026-07-12.json#L1942-L2052 (this comment)
  • docs/public-api/versions/2026-07-22.json#L1942-L2052
🤖 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 `@docs/public-api/versions/2026-07-12.json` around lines 1942 - 2052, The renew
response union containing the active and cancelled shapes must not be exposed in
existing API versions. In docs/public-api/versions/2026-07-12.json lines
1942-2052 and docs/public-api/versions/2026-07-22.json lines 1942-2052, add the
appropriate VERSION_CHANGES downgrade or restore the prior response schema
requiring nullable data.claimExpiresAt; expose the new cancelled union only
starting with version 2026-07-24.

Comment thread docs/public-api/versions/2026-07-24.json
@kristofferremback

Copy link
Copy Markdown
Collaborator Author

Code review

Confidence: 2/7 — Significant Concerns
Review models: Orchestrator: gpt-5.6-sol | Reviewers: gpt-5.6-sol/high x1 (combined plan/spec/design/correctness/data-flow/security)

Found 3 issues:

  1. apps/backend/src/features/public-api/sealed-turn-context.ts:54-68 — A renewed sealed input can advertise a reply generation that the session rejects. After a claim binds agent_sessions.reply_key_generation to generation N, key rotation plus an edit makes renewal return generation N+1; sealed callbacks still call assertReplyKeyGeneration against N, so output sealed according to the renewed contract fails with E2E_WRONG_KEY_GENERATION. (round-trip: the reply-generation contract does not round-trip from renewal into callback authorization.)

    const triggerGeneration = (trigger.envelope as EnclaveStreamEnvelope).keyGeneration
    const requiredGenerations = new Set([triggerGeneration, e2e.currentKeyGeneration])
    const chosen = wraps.filter(
    (wrap) =>
    wrap.recipientKind === "bot" && wrap.recipientKeyId === bikKeyId && requiredGenerations.has(wrap.keyGeneration)
    )
    if ([...requiredGenerations].some((generation) => !chosen.some((wrap) => wrap.keyGeneration === generation)))
    return null
    return {
    delivery: "sealed",
    sourceRevision: inputs.sourceRevision,
    prompt: { ciphertext: trigger.ciphertext.toString("base64"), envelope: trigger.envelope as EnclaveStreamEnvelope },
    wraps: chosen.map((wrap) => ({ keyGeneration: wrap.keyGeneration, wrapEnc: wrap.wrapEnc, wrapCt: wrap.wrapCt })),
    reply: { keyGeneration: e2e.currentKeyGeneration, senderId: inputs.replySenderId },
    }

  2. apps/backend/src/features/public-api/runtime-write-ops.ts:167-174 — Key-grant validation only runs when knownSourceRevision trails the source. Revoking an E2E bot grant without editing the source lets renewal at the current revision keep extending the claim; sealed callbacks authorize from the existing callback token/session and do not recheck the grant, so the revoked runtime can continue interim writes and completion. (security: authorization revocation is not enforced on the authoritative renewal path.)

    if (params.knownSourceRevision != null && params.knownSourceRevision < renewed.sourceMessageRevision) {
    const sealing = await resolveSealingContext(db, {
    workspaceId: renewed.workspaceId,
    streamId: renewed.activeStreamId,
    actor: { kind: "bot", botId: renewed.actorId },
    })
    const verdict = resolveDeliveryVerdict({ trust: TrustTiers.THIRD_PARTY, sealing })
    switch (verdict.delivery) {

  3. apps/backend/src/features/bot-runtimes/repository.ts:1341-1350 — Claim ownership records the instance but not the claiming runtime session. Bootstrap later scopes owned claims and cancellations using target_runtime_session_id, where NULL matches every reconnecting session. For an untargeted invocation claimed by session A, session B on the same instance therefore receives A's owned claim and cancellation recovery state. (state-lifecycle: ownership loses runtime-session identity between claim and reconnect bootstrap.)

    SET status = 'claimed', claimed_by_instance_id = ${params.instanceId}, claim_token = ${params.claimToken}, claim_expires_at = NOW() + (${params.claimTtlSeconds} || ' seconds')::interval, attempts = attempts + 1, updated_at = NOW(),
    claimed_source_message_revision = CASE WHEN i.trigger = 'session-control' THEN 0 ELSE NULL END,
    claimed_input_update_mode = (
    SELECT r.manifest -> 'input' ->> 'updates'
    FROM bot_runtime_instances r
    WHERE r.workspace_id = i.workspace_id AND r.bot_id = i.actor_id
    AND r.instance_id = ${params.instanceId} AND r.runtime_kind = ${params.runtimeKind}
    )
    FROM candidate
    WHERE i.id = candidate.id


📐 Plan Adherence — 1 issueReconnect recovery is specified as runtime-session scoped, but untargeted claims do not preserve claiming-session identity.
🔍 Bugs — 1 issueSealed renewal can instruct the runtime to use a reply generation rejected by the session callback fence.
🔁 Data Flow — 2 issuesReply generation is not propagated into callback state; runtime-session ownership is dropped between claim and bootstrap.
📋 CLAUDE.md Compliance — CLEANNo clear invariant violations survived the ≥80 threshold.
🏗️ Design — CLEANNo additional design concerns survived the ≥80 threshold.
🔒 Security — 1 issueGrant revocation is skipped when source revision is already current.

🤖 Generated with unified-review automation
If this review was useful, react with 👍. Otherwise, react with 👎.

@kristofferremback

Copy link
Copy Markdown
Collaborator Author

Addressed all three findings in 7bfec4ff:

  1. Sealed renewal now updates the RUNNING invocation session’s accepted reply generation in the same transaction as the advertised delta.
  2. Every authoritative renewal rechecks delivery/grant state; denial cancels the exact claim generation with key_grant_lost even at the current source revision.
  3. A new claimed_runtime_session_id records the actual claim winner, scopes owned/cancelled bootstrap recovery, and targets claimed control events independently of route intent.

Post-fix affected gates: 80 unit tests and 18 real-Postgres integration tests passed; backend typecheck, changed-file lint, 261 migration checks, and git diff --check also passed.

kristofferremback and others added 5 commits August 10, 2026 09:05
- publish authoritative input-update and cancellation controls
- reconcile live, restart-required, and legacy runtime capabilities
- fence stale plaintext and sealed completion against canonical input
- recover owner-scoped controls through renew and websocket bootstrap

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

Co-authored-by: codex <codex@users.noreply.github.com>
- rotate sealed callback reply generations with input updates
- enforce E2E grant revocation on every authoritative renewal
- persist claiming runtime sessions for scoped reconnect recovery

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

Co-authored-by: codex <codex@users.noreply.github.com>
- reconcile canonical source and routing state before extending claims
- retry serialization conflicts and preserve legacy cancellation shape
- index active source cancellation lookups

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

Co-authored-by: codex <codex@users.noreply.github.com>
Removes the available_at column, which no production code ever wrote —
its only writer was a test for a feature that does not exist, and two
dead predicates sat on the claim path. The migration is unmerged, so
this is the last point at which the column can be removed (INV-17).

Also collapses an inlined outbox insert onto emitAvailabilityHint,
reduces a two-lock-key comparator to the one field that varies, derives
the outbox payload types from their zod schemas (INV-31), and deletes
type aliases, re-exports and struct members with no consumer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pKNVFpWu4VGPCh3GQ5hrB
The canonical-source gate was spelled five times, three single-claim
cancel methods differed only in which guards they applied, and the claim
lookup existed twice for the pre- and post-lock cases. Each collapses to
one parameterised form; every guard stays conditional, since a guard that
silently defaulted would widen a fence (INV-20).

Tests carried the same duplication: fixture literals pasted up to nine
times, and two advisory-lock tests asserting literal key strings against
a call-index fake — a property now proven against real Postgres through
pg_blocking_pids. Also drops an index-text assertion that evaded the
INV-68 ratchet, and folds the seed fixture shared by three integration
suites into one helper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013pKNVFpWu4VGPCh3GQ5hrB
@kristofferremback
kristofferremback force-pushed the feat/invocation-source-mutations-02-runtime-protocol branch from 88eb613 to 08ca561 Compare August 10, 2026 10:51

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/backend/src/features/bot-runtimes/service.ts (1)

694-707: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

repairDeletedSourceSessions can loop forever if a source is not cleared.

The for (;;) loop re-queries findDeletedSourcesWithRunningSessions and only exits when the query returns an empty batch. Progress depends entirely on cancelInvocationsForDeletedSource clearing the RUNNING session for every source in the batch.

terminalizeCancelledSessions skips any invocation whose status !== "cancelled", and AgentSessionRepository.updateStatus is gated on onlyIfStatus: SessionStatuses.RUNNING. If a source has a RUNNING session whose invocation is neither in cancelled.transitioned nor in cancelled.sessionRepairCandidates — for example an invocation already in a terminal status with a stuck RUNNING session — nothing changes for that source. The next iteration returns the same batch, and the loop never terminates. repairedSources also keeps incrementing. This runs on backend startup, so the failure mode is a hung boot that burns CPU and database connections.

Add a termination guard: track the source ids seen in the previous iteration and stop when a batch makes no progress, or bound the total iterations.

🛡️ Proposed guard
   async repairDeletedSourceSessions(): Promise<number> {
     let repairedSources = 0
+    const attempted = new Set<string>()
     for (;;) {
       const sources = await BotInvocationRepository.findDeletedSourcesWithRunningSessions(
         this.pool,
         DELETED_SOURCE_SESSION_REPAIR_BATCH_SIZE
       )
       if (sources.length === 0) return repairedSources
+      const unseen = sources.filter((source) => !attempted.has(source.sourceMessageId))
+      if (unseen.length === 0) {
+        logger.warn(
+          { pendingSources: sources.length },
+          "Deleted-source session repair made no progress; stopping to avoid an unbounded loop"
+        )
+        return repairedSources
+      }
-      for (const source of sources) {
+      for (const source of unseen) {
+        attempted.add(source.sourceMessageId)
         await this.cancelInvocationsForDeletedSource(source)
         repairedSources += 1
       }
     }
   }

Verify whether the no-progress case is reachable against the current repository predicates.

#!/bin/bash
# Check whether every source returned by the repair query is guaranteed to be cleared.
set -euo pipefail

echo '--- findDeletedSourcesWithRunningSessions ---'
ast-grep run --pattern 'async findDeletedSourcesWithRunningSessions($$$) { $$$ }' \
  --lang typescript apps/backend/src/features/bot-runtimes/repository.ts

echo '--- cancelActiveBySource (which statuses land in transitioned vs sessionRepairCandidates) ---'
ast-grep run --pattern 'async cancelActiveBySource($$$) { $$$ }' \
  --lang typescript apps/backend/src/features/bot-runtimes/repository.ts

echo '--- AgentSessionRepository.updateStatus onlyIfStatus semantics ---'
rg -n -C 20 'async updateStatus' apps/backend/src/features/agents/session-repository.ts
🤖 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/features/bot-runtimes/service.ts` around lines 694 - 707,
Update repairDeletedSourceSessions to detect no progress between batches by
tracking the source identifiers returned in the previous iteration and
terminating when the next batch repeats them, while preserving normal processing
and repairedSources counting. Verify the repository predicates and
cancelInvocationsForDeletedSource behavior so the guard covers sources whose
RUNNING sessions remain uncleared without changing the existing repair logic.
apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql (1)

1-18: 🩺 Stability & Availability | 🔵 Trivial

Plan the lock window for the messages backfill.

ADD COLUMN ... NOT NULL DEFAULT 1 is metadata-only on PostgreSQL 11 and later, so the column add is cheap. The two UPDATE messages statements are not: they rewrite every row that has a message_versions row and every soft-deleted row, inside the migration's implicit transaction. On a large messages table this holds row locks and produces significant WAL and bloat for the duration.

Confirm the expected row counts for the target environment before deploy. If the counts are large, schedule the deploy in a low-traffic window and plan a VACUUM afterwards.

🤖 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 1 - 18, Assess the expected counts of messages with
message_versions rows and soft-deleted messages before deploying this migration,
and plan the lock duration accordingly. If either backfill affects a large
number of rows, schedule the migration during a low-traffic window and arrange a
post-migration VACUUM; keep the revision backfill behavior unchanged.
🤖 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/20260808180131_add_bot_invocation_claimed_runtime_session.sql`:
- Around line 2-3: Remove the IF NOT EXISTS clause from the ALTER TABLE
statement adding claimed_runtime_session_id in the migration, leaving a plain
ADD COLUMN so existing-column schema drift causes the migration to fail loudly.

In `@apps/backend/src/features/bot-runtimes/service.ts`:
- Around line 641-656: Extract the shared target routing calculation from
emitCancellationHints and the bot_invocation:input_updated emitter in
insertCanonicalRoute into one helper, then use that helper in both
OutboxRepository.insert payloads. Preserve the existing claimed-instance,
claimed-session, and original-target fallback rule exactly so both control
events resolve the same target pair.

In `@apps/backend/src/features/public-api/routes.ts`:
- Around line 639-646: Extract the repeated wrap object schema into a shared
sealedWrapSchema, then replace both the inline wrap definition in
sealedTurnContextSchema and the wraps item schema in the shown route definition
with that shared symbol, preserving the existing validation rules and wire
shape.

In `@apps/backend/tests/integration/setup.ts`:
- Around line 142-151: Remove all table-specific DELETE queries from the cleanup
function and leave isolated.cleanup() as the cleanup operation so database
teardown cannot be skipped by a query failure. Update the cleanup doc comment
near the fixture setup to describe closing the pool and dropping the isolated
database, rather than draining fixture tables.

In `@docs/public-api/versions/2026-07-12.json`:
- Line 2710: Add the documented 409 INVOCATION_INPUT_STALE response to both
completeBotInvocationSealed and completeBotInvocation in
docs/public-api/versions/2026-07-12.json (including line 2851) and
docs/public-api/versions/2026-07-22.json (including line 2851), using the same
response schema and description for each operation.

---

Outside diff comments:
In
`@apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql`:
- Around line 1-18: Assess the expected counts of messages with message_versions
rows and soft-deleted messages before deploying this migration, and plan the
lock duration accordingly. If either backfill affects a large number of rows,
schedule the migration during a low-traffic window and arrange a post-migration
VACUUM; keep the revision backfill behavior unchanged.

In `@apps/backend/src/features/bot-runtimes/service.ts`:
- Around line 694-707: Update repairDeletedSourceSessions to detect no progress
between batches by tracking the source identifiers returned in the previous
iteration and terminating when the next batch repeats them, while preserving
normal processing and repairedSources counting. Verify the repository predicates
and cancelInvocationsForDeletedSource behavior so the guard covers sources whose
RUNNING sessions remain uncleared without changing the existing repair logic.
🪄 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: a986b975-4d32-4ff9-ac71-1ed05d25b7fe

📥 Commits

Reviewing files that changed from the base of the PR and between 293c03f and 08ca561.

📒 Files selected for processing (27)
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • 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/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/public-api/handlers.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/tests/integration/setup.ts
  • docs/public-api/openapi.json
  • docs/public-api/versions/2026-07-12.json
  • docs/public-api/versions/2026-07-22.json
  • docs/public-api/versions/2026-07-24.json
  • packages/types/src/constants.ts
  • packages/types/src/domain.ts
  • packages/types/src/index.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Tests
  • GitHub Check: Frontend Tests (1/4)
  • GitHub Check: Frontend Tests (3/4)
  • GitHub Check: Typecheck
  • GitHub Check: Frontend Tests (2/4)
  • GitHub Check: Frontend Tests (4/4)
🧰 Additional context used
📓 Path-based instructions (12)
**/*.sql

📄 CodeRabbit inference engine (CLAUDE.md)

Migrations must be append-only; never modify an existing migration file (INV-17).

Files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
**

⚙️ CodeRabbit configuration file

**: Architecture, invariants, and the full app inventory live in AGENTS.md and
docs/system-overview.md. Treat those as the source of truth.

What NOT to flag:

  • Pre-existing issues not introduced by this PR
  • Issues that TypeScript compilation or ESLint would catch (types, imports, lint)
  • Stylistic preferences without a concrete rule violation in AGENTS.md
  • Theoretical risks or hypothetical edge cases without evidence of exploitability
  • General best-practice suggestions that don't map to a specific project rule

Security calibration:

  • React JSX is safe from XSS unless dangerouslySetInnerHTML is used
  • ULIDs/UUIDs are cryptographically unguessable — do not flag as enumeration risks
  • Environment variables are trusted — do not flag as hardcoded secrets
  • DoS, rate limiting, log spoofing, regex complexity, missing audit logs, and
    outdated dependency warnings are out of scope

Plan adherence:
The implementation plan is in the PR description, inside the collapsible
"📋 Full implementation plan" details block (plans are not committed to the repo).
If that block exists, check that PR changes align with the plan.
Flag missing corresponding changes: API change without frontend/backoffice update,
type change without usage update, schema change without migration.

Files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • docs/public-api/versions/2026-07-12.json
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • docs/public-api/openapi.json
  • docs/public-api/versions/2026-07-22.json
  • apps/backend/src/features/public-api/handlers.ts
  • docs/public-api/versions/2026-07-24.json
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/migrations/**

⚙️ CodeRabbit configuration file

**/migrations/**: No foreign key constraints in migrations. Referential integrity is enforced in
application code, not PostgreSQL schema. (INV-1)
No DB enums. Use TEXT columns and validate in application code. (INV-3)
Migrations are append-only. Never edit or delete existing migration files. Only
add new ones. (INV-17)
A migration that enqueues backfill.plan jobs must delay them: process_after must be
NOW() + an interval of at least 10 minutes, so old-code replicas in a rolling
deploy cannot claim the job before the new code (with the backfill definition)
boots. A backfill definition registered in code is inert without its enqueue
migration. (INV-67)

Files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
apps/backend/src/**

⚙️ CodeRabbit configuration file

apps/backend/src/**: Real-time event delivery must go through the outbox pattern. Do not publish events
directly via socket.io emit or Redis pub/sub outside of the outbox dispatcher. (INV-4)
All AI/LLM usage must go through the project AI wrapper (createAI), not raw SDK
imports from @anthropic-ai/sdk or openai. (INV-28)
Do not keep database connections open during slow AI or network calls. Release the
connection first, then do the slow work. (INV-41)
Never do select-then-update without locking or concurrency control. Use ON CONFLICT,
advisory locks, or transactions with row locks for write paths. (INV-20)
Check-then-act guards must pin the identity or generation observed at read (row id,
integer version, or external key), not just a status flag — status-only guards let
stale work clobber a row that was replaced in between. (INV-20)
Optimistic concurrency must CAS on an integer version column, never on timestamp
equality: PostgreSQL stores microseconds while a JS Date round-trips at millisecond
precision, so a timestamp CAS fails on virtually every uncontended write. Tests for
version- or timestamp-gated predicates must produce the compared value through the
repository's own NOW()-writing code path, never hand-crafted fixture timestamps.
(INV-66)
Avoid withClient for single-query paths. Pass pool directly instead of acquiring a
dedicated client. (INV-30)
Validate API inputs (body, query, params) with Zod schemas, not manual typeof
checks. (INV-55)
Stream access is inherited through root_stream_id: threads never carry their own
access, and public root streams grant read access without a stream_members row.
Any new query or filter gating rows on stream membership/visibility must reuse
checkStreamAccess / listAccessibleStreamIds (features/streams/access.ts) or
replicate the thread-to-root rule. Flag audience/visibility predicates built on
direct stream_members rows alone — they drop thread content for root-stream
members. (INV-62)
SQL correctness is verified against a...

Files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names that clearly indicate purpose and avoid single letters except for loop counters
Add JSDoc comments for all public functions and exported classes to document purpose, parameters, return types, and usage examples
Use const by default, let when variable reassignment is needed, avoid var
Use environment variables for configuration instead of hardcoded values
Format code with Prettier and lint with ESLint according to project configuration

Files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use TypeScript interfaces for type definitions and avoid type assertions when possible

**/*.{ts,tsx}: Do not use language-specific heuristics or English-only literals and regexes for semantic decisions; use model-based decisions for language-dependent behavior (INV-54).
Keep comments absent by default; retain only durable explanations of ordering, concurrency, non-obvious constraints, or load-bearing values. Do not add speculative TODOs or change narration (INV-25, INV-36).
Limit nested ternaries to one level and colocate variant configuration while keeping shared behavior on one path (INV-29, INV-43, INV-47).

Files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Bun commands and runtime (bun <file>, bun run test, bun install, bun build), not Node or dotenv.

Files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write unit tests for all utility functions and business logic with at least 80% code coverage
Use descriptive test names that explain what is being tested and expected outcome

Files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
apps/backend/src/features/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/backend/src/features/**/*.ts: Backend feature logic belongs in colocated feature folders; keep lib/ limited to cross-cutting infrastructure and use index.ts barrels for cross-feature imports (INV-51, INV-52).
Keep AI component configuration beside its component in config.ts; evaluations must call production entry points (INV-44, INV-45).

Files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
apps/backend/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/backend/src/**/*.ts: Handlers and workers stay thin; services own orchestration, transactions, and domain logic; repositories provide data access (INV-5, INV-6, INV-34).
Validate request body, query, and params with Zod; throw HttpError classes and derive types from schemas or constants (INV-31, INV-32, INV-55).
Every workspace-scoped domain query and mutation filters by workspace_id; global infrastructure and authentication tables are exempt (INV-8).
Use prefixed ULIDs, no foreign keys, and no database enums; represent enum-like values as TEXT with code validation (INV-1, INV-2, INV-3).
Migrations are append-only; never edit an existing migration file (INV-17).
Race-safe writes must not use select-then-update without locking; prefer upserts, pin check-then-act guards to row identity or generation, and use integer versions rather than timestamp equality (INV-20, INV-66).
Use set-based or batch operations instead of per-row loops; pass pool for single queries; do not hold database connections during slow AI or network work (INV-30, INV-41, INV-56).
Real-time delivery must use the outbox pattern; write outbox events in the same transaction as domain writes, and commit event-source updates with read projections (INV-4, INV-7).
Do not use hidden singletons except the logger and web-push bootstrap; pass constructed dependencies and construct long-lived collaborators once (INV-9, INV-12, INV-13).
Use createAI for every AI call, only use current-generation models from docs/model-reference.md, and always include telemetry metadata (INV-16, INV-19, INV-28).

Files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{test,spec}.{ts,tsx}: Never ship unexecuted tests, .skip(), or .todo(); fix failing tests rather than dismissing them as pre-existing (INV-22, INV-26).
Assert specific event presence and content rather than counts; prefer one object comparison over chains of narrow assertions (INV-23, INV-24).
Do not mock shared modules with mock.module() or vi.mock(); use scoped spyOn against namespace imports. Frontend integration tests mount real components and test observable behavior (INV-39, INV-48).

Files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
apps/backend/tests/integration/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Verify SQL against a real schema by seeding rows, executing statements, and asserting returned data; do not use query-text assertions as SQL correctness tests (INV-68).

Files:

  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
🧠 Learnings (17)
📚 Learning: 2026-07-19T07:16:49.609Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1400
File: apps/backend/src/db/migrations/20260719120000_workspace_integrations_multi_install.sql:27-35
Timestamp: 2026-07-19T07:16:49.609Z
Learning: In PostgreSQL migrations, only use `CREATE INDEX CONCURRENTLY` for hot/high-volume tables where avoiding write-blocking during build is important. For small configuration tables (e.g., `workspace_integrations`), prefer non-concurrent index creation so migrations remain atomic and “fail-loud” on errors/retries. Avoid using patterns like pairing concurrent unique-index creation with `IF NOT EXISTS` in these cases, since a failed concurrent build can leave an invalid index or an existing name on retry and `IF NOT EXISTS` can silently skip enforcement, violating the fail-loud principle (INV-11). In code review, flag concurrent/`IF NOT EXISTS` usage on such tables unless there is a documented need.

Applied to files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
📚 Learning: 2026-07-21T06:43:42.801Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1466
File: apps/backend/src/db/migrations/20260720233005_add_command_dispatch_idempotency.sql:1-9
Timestamp: 2026-07-21T06:43:42.801Z
Learning: In the command dispatch idempotency flow, `command_dispatches` rows are intentionally durable to support long offline periods: pending frontend operations may replay after an arbitrarily long time. During code review, avoid suggesting TTL-based cleanup (or other retention-based eviction) for `command_dispatches` unless the system already enforces a maximum client retry horizon or otherwise retains an equivalent durable deduplication key. In other words, keep the idempotency record lifetime aligned with the command/event history; do not shorten it without the corresponding retry/compatibility constraints.

Applied to files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
📚 Learning: 2026-08-03T21:43:51.620Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1668
File: apps/backend/src/db/migrations/20260729103000_agent_outcome_indexes.sql:0-0
Timestamp: 2026-08-03T21:43:51.620Z
Learning: In threahq/threa PostgreSQL migrations, the migration runner executes each migration file through a single pool.query call, so multiple statements run in an implicit transaction. Because CREATE INDEX CONCURRENTLY cannot run inside a transaction, place exactly one CREATE INDEX CONCURRENTLY statement in each migration file when a non-blocking index build is warranted. Follow apps/backend/src/db/migrations/20260711120100_memo_scope_index.sql as the repository pattern. Do not assume check:migrations requires CONCURRENTLY; choose it based on whether the table and index build require avoiding write blocking.

Applied to files:

  • apps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql
  • apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql
📚 Learning: 2026-05-12T07:32:39.480Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/issues.ts:59-91
Timestamp: 2026-05-12T07:32:39.480Z
Learning: In the threahq/threa repository, do not recommend adding JSDoc comments for exported/public functions during code review. Treat such documentation suggestions as unnecessary unless there is a concrete, enforceable requirement—e.g., a specific lint rule or a documented convention explicitly requiring JSDoc for public/exported APIs. Only raise documentation concerns when that requirement can be verified from the repo’s tooling or docs.

Applied to files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:44.564Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/format.ts:1-31
Timestamp: 2026-05-12T07:33:44.564Z
Learning: In the threahq/threa repository, do not raise any code review comments (of any severity) about missing, insufficient, or requested JSDoc/documentation comments. This applies to all exported/public TypeScript entities (e.g., exported functions, classes, interfaces, constants) across the repo—missing documentation should never be flagged as a review issue under any circumstances.

Applied to files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:11.118Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/projects.ts:45-76
Timestamp: 2026-05-12T07:33:11.118Z
Learning: In the threahq/threa repository, do not flag missing JSDoc on exported functions during code review. The team does not require JSDoc for exported APIs and considers it review noise; only raise review issues for other concerns (e.g., correctness, types, tests, security), not for absent JSDoc comments on exports.

Applied to files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-12T14:06:33.911Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 882
File: packages/agent-runtime/src/runtime/negotiate-capabilities.ts:4-15
Timestamp: 2026-06-12T14:06:33.911Z
Learning: In this repo, code comments may include invariant/finding anchors (e.g., E2EE-11, INV-E1, E2EE-9, C-1, Phase 2.4) as stable references that map to docs/audits and docs/plans, following the documented convention in CLAUDE.md to “reference it in nearby code comments if the constraint is non-obvious.” When you see such anchor references, do not treat them as violations of INV-25 or INV-36. Also preserve “rollout-phase” notes that describe *current* tolerated behavior (e.g., “absent token tolerated today”)—these are load-bearing context for reviewers and should not be removed as if they were generic change-history narration.

Applied to files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-12T07:33:41.940Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/deps.ts:3-10
Timestamp: 2026-05-12T07:33:41.940Z
Learning: In the threahq/threa repository, do not suggest adding JSDoc comments anywhere during code review (not for interfaces, exported functions, types, or other constructs). Treat missing JSDoc as intentional and acceptable; avoid any review comments recommending JSDoc additions across the codebase.

Applied to files:

  • packages/types/src/constants.ts
  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • packages/types/src/index.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • packages/types/src/domain.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/tests/integration/bot-invocation-source-revisions-migration.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/tests/integration/setup.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/tests/integration/bot-invocation-control-protocol.test.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/tests/integration/bot-invocation-source-mutations.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-05T14:24:15.849Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 776
File: packages/types/src/prosemirror.ts:446-452
Timestamp: 2026-06-05T14:24:15.849Z
Learning: In this repo, prevent circular dependencies: files under `packages/types` must not import anything from `packages/prosemirror` (e.g., avoid imports from `packages/prosemirror` into `packages/types`). Where host-validation logic is needed (such as the `*.giphy.com`-style check used in `packages/types/src/prosemirror.ts`), it should remain intentionally duplicated inside `packages/types` as an inline helper rather than being shared with `packages/prosemirror`, because `packages/prosemirror` depends on `packages/types`. If a refactor is proposed, ensure it removes the circular dependency (e.g., via a third shared package with clear dependency direction) before changing this pattern.

Applied to files:

  • packages/types/src/constants.ts
  • packages/types/src/index.ts
  • packages/types/src/domain.ts
📚 Learning: 2026-05-12T07:31:56.525Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 500
File: apps/backend/src/features/agents/tools/linear/trace.ts:3-10
Timestamp: 2026-05-12T07:31:56.525Z
Learning: In this repo’s TypeScript code under apps/backend/src, avoid recommending or requiring JSDoc comments for exported/public functions solely for documentation purposes. The team considers such suggestions unnecessary; only ask for JSDoc if there is a concrete technical requirement (for example, an enforced documentation generation/lint rule or an existing documented convention that the code must follow).

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-19T08:40:01.120Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 572
File: apps/backend/src/features/memos/repository.ts:662-664
Timestamp: 2026-05-19T08:40:01.120Z
Learning: In the threahq/threa codebase, the PostgreSQL Full-Text Search (FTS) dictionary configuration for the memo search subsystem (e.g., `MemoRepository.hybridSearch`, `MemoRepository.fullTextSearch`, `MemoRepository.exactSearch`, and the message-search layer) is intentionally set to `'english'` as a subsystem-wide convention (INV-35/37). During code review, do not flag `'english'` usage as a language-neutrality problem and do not recommend swapping to `'simple'` (or another dictionary) for any single method/path. Only treat dictionary strategy changes as valid if they are part of a deliberate, repo-wide decision that updates the entire search subsystem consistently (with the corresponding coordinated change), rather than an isolated modification.

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-05-23T13:57:24.350Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 605
File: apps/backend/src/features/conversations/boundary-extraction-service.ts:200-203
Timestamp: 2026-05-23T13:57:24.350Z
Learning: In threahq/threa (apps/backend), treat the current absence of `workspaceId` in calls to `AttachmentRepository.findByMessageId` and `AttachmentRepository.findByMessageIdsWithExtractions` (INV-8) as a known, intentionally unaddressed gap. During code review, do not flag individual call sites as new violations for missing `workspaceId` until the planned follow-up PR lands that updates all `AttachmentRepository.findByMessage*` signatures and updates the affected ~9 call sites in one pass. After that follow-up is merged, start enforcing that `workspaceId` is provided in these calls.

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-11T16:46:01.779Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 832
File: apps/backend/src/routes.ts:514-514
Timestamp: 2026-06-11T16:46:01.779Z
Learning: In the threahq/threa repository, do not raise code review findings for missing explicit per-endpoint rate limiting on any HTTP endpoint. The project’s CodeRabbit calibration / coding guidelines treat rate limiting (and related DoS concerns) as out of scope for review flags, assuming the global/baseline rate limiting is already in place. If you identify any need for rate-limit tuning, defer it to a follow-up PR until real usage patterns are available.

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/lib/outbox/broadcast-handler.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/lib/outbox/repository.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-06-11T10:44:53.003Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 825
File: apps/backend/src/features/bot-runtimes/repository.ts:26-34
Timestamp: 2026-06-11T10:44:53.003Z
Learning: In the bot-runtimes feature (apps/backend/src/features/bot-runtimes/), treat operational tuning knobs like BOT_CLAIM_MAX_ATTEMPTS, BOT_RUNTIME_BIK_STALENESS_MS, and ENCLAVE_RUNTIME_STALENESS_MS as intentionally hardcoded, module-level constants. Do not flag them in code review with “should use env vars” guidance unless there is already a dedicated bot-runtime feature-config surface. If/when runtime tuning becomes necessary, add a single cohesive feature-config surface that covers all these knobs together (avoid speculative per-knob env/plumbing).

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-07-13T19:46:31.849Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1324
File: apps/backend/src/features/agents/persona-config-service.ts:1116-1231
Timestamp: 2026-07-13T19:46:31.849Z
Learning: When implementing attachment cleanup/deletion logic (e.g., persona-context-attachments or persona-related cleanup), call `AttachmentService.deleteIfUnbound` rather than doing a select-then-delete. `deleteIfUnbound` should enforce the unbound condition (e.g., `message_id IS NULL`) directly in the `DELETE` statement, making the operation race-safe against concurrent attachment claiming (e.g., `attachToMessage()` claiming the file between a check and a delete). If the attachment has become bound in the meantime, the DELETE should be skipped (log as appropriate) and the underlying file/extraction/S3 object should survive.

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/agents/session-repository.ts
  • apps/backend/src/features/public-api/routes.ts
  • apps/backend/src/features/public-api/runtime-write-ops.test.ts
  • apps/backend/src/features/public-api/schemas.ts
  • apps/backend/src/features/public-api/sealed-turn-context.ts
  • apps/backend/src/features/public-api/handlers.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/public-api/runtime-write-ops.ts
  • apps/backend/src/features/bot-runtimes/service.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
  • apps/backend/src/features/bot-runtimes/repository.ts
📚 Learning: 2026-07-16T19:45:30.012Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 1365
File: apps/backend/src/features/bot-runtimes/repository.test.ts:328-360
Timestamp: 2026-07-16T19:45:30.012Z
Learning: In bot-runtimes repository unit tests, mocked `Querier` instances should be used intentionally, and assertions should verify the generated SQL structure (shape) rather than relying on real DB execution. Also ensure the runtime-session archived/retired invariant is covered by a real-database integration/e2e test: once an identity/session is retired, a later unarchive must not allow reclaiming that retired identity.

Applied to files:

  • apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.ts
  • apps/backend/src/features/bot-runtimes/repository.test.ts
  • apps/backend/src/features/bot-runtimes/service.test.ts
📚 Learning: 2026-06-04T10:58:07.517Z
Learnt from: kristofferremback
Repo: threahq/threa PR: 727
File: apps/backend/src/features/agents/orphan-session-cleanup.ts:73-77
Timestamp: 2026-06-04T10:58:07.517Z
Learning: In agent session failure/completion lifecycles in apps/backend/src/features/agents/**, it may be intentional to use a two-tier emit pattern: (1) perform the durable lifecycle updates atomically inside withTransaction (e.g., status update + OutboxRepository.insert + the corresponding stream event such as agent_session:failed/agent_session:completed, meeting INV-4); and (2) emit a separate best-effort in-process notification directly to the per-session room (e.g., io.to(...agent_session:${sessionId}...).emit(...)) outside the transaction for live UI/trace dialog purposes that the outbox stream does not cover. Do not flag the direct per-session room emit as an INV-4 violation, since it is explicitly non-durable and mirrors the in-process trace.notifyFailed/notifyCompleted behavior.

Applied to files:

  • apps/backend/src/features/agents/session-repository.ts
🪛 ast-grep (0.45.0)
apps/backend/src/features/agents/session-repository.ts

[error] 556-565: Avoid SQL injection
Context: db.query(sqlUPDATE agent_sessions session SET reply_key_generation = ${params.replyKeyGeneration} FROM bot_invocations invocation WHERE session.id = ${params.invocationId} AND session.status = ${SessionStatuses.RUNNING} AND invocation.id = session.id AND invocation.workspace_id = ${params.workspaceId} AND invocation.status = 'claimed')
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)

apps/backend/tests/integration/setup.ts

[error] 120-125: Avoid SQL injection
Context: pool.query(
INSERT INTO bot_runtime_instances (id, workspace_id, bot_id, instance_id, runtime_kind, status, accepting_invocations, manifest) VALUES ($1, $2, $3, $4, 'openclaw', 'available', TRUE, NULL),
[bri_${crypto.randomUUID().replaceAll("-", "")}, workspace, bot, instanceId]
)
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)

apps/backend/tests/integration/bot-invocation-control-protocol.test.ts

[error] 652-657: Avoid SQL injection
Context: pool.query(
INSERT INTO bot_runtime_instances (id, workspace_id, bot_id, instance_id, runtime_kind, status, accepting_invocations, manifest) VALUES ($1, $2, $3, $4, 'openclaw', 'available', TRUE, NULL),
[bri_${crypto.randomUUID().replaceAll("-", "")}, workspace, replacementBot, ${instance}-replacement]
)
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)

apps/backend/tests/integration/bot-invocation-source-mutations.test.ts

[error] 576-580: Avoid SQL injection
Context: pool.query(
INSERT INTO bot_runtime_instances (id, workspace_id, bot_id, instance_id, runtime_kind, status, accepting_invocations) VALUES ($1, $2, $3, $4, 'openclaw', 'available', TRUE),
[bri_${crypto.randomUUID().replaceAll("-", "")}, workspace, otherBot, otherInstance]
)
Note: [CWE-89] Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection').

(sql-injection-typescript)

🪛 SQLFluff (4.2.2)
apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql

[error] 56-58: CREATE INDEX should use CONCURRENTLY to avoid locking the table during the build.

(PG01)

🪛 Squawk (2.61.0)
apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sql

[warning] 2-2: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.

(prefer-bigint-over-int)


[warning] 21-21: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.

(prefer-bigint-over-int)


[warning] 22-22: Using 32-bit integer fields can result in hitting the max int limit. Use 64-bit integer values instead to prevent hitting this limit.

(prefer-bigint-over-int)


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

(require-concurrent-index-creation)


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

(require-concurrent-index-creation)

Comment on lines +2 to +3
ALTER TABLE bot_invocations
ADD COLUMN IF NOT EXISTS claimed_runtime_session_id TEXT;

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 | 🟡 Minor | ⚡ Quick win

Drop IF NOT EXISTS so the migration fails loudly.

Migrations run once and the runner records each applied file. If claimed_runtime_session_id already exists, that indicates schema drift, and IF NOT EXISTS hides it. Use a plain ADD COLUMN so the drift surfaces as an error instead of a silent skip.

🛡️ Proposed fix
 ALTER TABLE bot_invocations
-ADD COLUMN IF NOT EXISTS claimed_runtime_session_id TEXT;
+ADD COLUMN claimed_runtime_session_id TEXT;

Based on learnings: "prefer non-concurrent index creation so migrations remain atomic and 'fail-loud' on errors/retries … IF NOT EXISTS can silently skip enforcement, violating the fail-loud principle (INV-11)."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ALTER TABLE bot_invocations
ADD COLUMN IF NOT EXISTS claimed_runtime_session_id TEXT;
ALTER TABLE bot_invocations
ADD COLUMN claimed_runtime_session_id TEXT;
🤖 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/20260808180131_add_bot_invocation_claimed_runtime_session.sql`
around lines 2 - 3, Remove the IF NOT EXISTS clause from the ALTER TABLE
statement adding claimed_runtime_session_id in the migration, leaving a plain
ADD COLUMN so existing-column schema drift causes the migration to fail loudly.

Source: Learnings

Comment on lines +641 to +656
private async emitCancellationHints(db: Querier, invocations: BotInvocation[]): Promise<void> {
for (const invocation of invocations) {
if (invocation.status !== "cancelled" || !invocation.cancellationReason) continue
await OutboxRepository.insert(db, "bot_invocation:cancelled", {
workspaceId: invocation.workspaceId,
botId: invocation.actorId,
invocationId: invocation.id,
sourceRevision: invocation.sourceMessageRevision,
targetInstanceId: invocation.claimedByInstanceId ?? invocation.targetInstanceId,
targetRuntimeSessionId:
invocation.claimedRuntimeSessionId ??
(invocation.claimedByInstanceId == null ? invocation.targetRuntimeSessionId : null),
reason: invocation.cancellationReason,
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Extract the duplicated control-event routing fallback.

emitCancellationHints and the input_updated emitter in insertCanonicalRoute compute the same target pair with the same three-part rule:

targetInstanceId: invocation.claimedByInstanceId ?? invocation.targetInstanceId
targetRuntimeSessionId:
  invocation.claimedRuntimeSessionId ??
  (invocation.claimedByInstanceId == null ? invocation.targetRuntimeSessionId : null)

This rule decides whether a control event reaches the claiming session or the originally targeted session. If one copy changes and the other does not, cancellations and input updates route to different runtimes for the same invocation. Extract one helper and call it from both emitters.

♻️ Proposed refactor
+  private static controlTarget(invocation: BotInvocation): {
+    targetInstanceId: string | null
+    targetRuntimeSessionId: string | null
+  } {
+    return {
+      targetInstanceId: invocation.claimedByInstanceId ?? invocation.targetInstanceId,
+      targetRuntimeSessionId:
+        invocation.claimedRuntimeSessionId ??
+        (invocation.claimedByInstanceId == null ? invocation.targetRuntimeSessionId : null),
+    }
+  }
+
   private async emitCancellationHints(db: Querier, invocations: BotInvocation[]): Promise<void> {
     for (const invocation of invocations) {
       if (invocation.status !== "cancelled" || !invocation.cancellationReason) continue
       await OutboxRepository.insert(db, "bot_invocation:cancelled", {
         workspaceId: invocation.workspaceId,
         botId: invocation.actorId,
         invocationId: invocation.id,
         sourceRevision: invocation.sourceMessageRevision,
-        targetInstanceId: invocation.claimedByInstanceId ?? invocation.targetInstanceId,
-        targetRuntimeSessionId:
-          invocation.claimedRuntimeSessionId ??
-          (invocation.claimedByInstanceId == null ? invocation.targetRuntimeSessionId : null),
+        ...BotRuntimeService.controlTarget(invocation),
         reason: invocation.cancellationReason,
       })
     }
   }

Apply the same substitution in the bot_invocation:input_updated insert.

Also applies to: 745-756

🤖 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/features/bot-runtimes/service.ts` around lines 641 - 656,
Extract the shared target routing calculation from emitCancellationHints and the
bot_invocation:input_updated emitter in insertCanonicalRoute into one helper,
then use that helper in both OutboxRepository.insert payloads. Preserve the
existing claimed-instance, claimed-session, and original-target fallback rule
exactly so both control events resolve the same target pair.

Comment on lines +639 to +646
wraps: z.array(
z.object({
keyGeneration: z.number().int().min(0),
wrapEnc: z.string(),
wrapCt: z.string(),
})
),
reply: z.object({ keyGeneration: z.number().int().min(0), senderId: z.string() }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared SSK wrap schema.

Lines 639-645 repeat the wrap object already declared inline at Lines 516-522 in sealedTurnContextSchema. Both describe the same wire shape. Extract one sealedWrapSchema and reuse it in both places, so a future field addition cannot land in only one copy.

♻️ Proposed extraction
+const sealedWrapSchema = z.object({
+  keyGeneration: z.number().int().min(0),
+  wrapEnc: z.string(),
+  wrapCt: z.string(),
+})
-    wraps: z.array(
-      z.object({
-        keyGeneration: z.number().int().min(0),
-        wrapEnc: z.string(),
-        wrapCt: z.string(),
-      })
-    ),
+    wraps: z.array(sealedWrapSchema),
🤖 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/features/public-api/routes.ts` around lines 639 - 646,
Extract the repeated wrap object schema into a shared sealedWrapSchema, then
replace both the inline wrap definition in sealedTurnContextSchema and the wraps
item schema in the shown route definition with that shared symbol, preserving
the existing validation rules and wire shape.

Comment on lines +142 to +151
cleanup: async () => {
await pool.query("DELETE FROM agent_sessions WHERE stream_id = $1", [stream])
await pool.query("DELETE FROM bot_invocations WHERE workspace_id = $1", [workspace])
await pool.query("DELETE FROM bot_runtime_instances WHERE workspace_id = $1", [workspace])
await pool.query("DELETE FROM stream_active_actors WHERE workspace_id = $1", [workspace])
await pool.query("DELETE FROM bots WHERE workspace_id = $1", [workspace])
await pool.query("DELETE FROM messages WHERE stream_id = $1", [stream])
await pool.query("DELETE FROM streams WHERE id = $1", [stream])
await isolated.cleanup()
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant DELETE statements from cleanup.

isolated.cleanup() ends the pool and runs DROP DATABASE ... WITH (FORCE). The seven preceding DELETE statements target tables inside that database, so they do no useful work.

They also add a failure mode. If any DELETE throws, cleanup rejects before isolated.cleanup() runs. The pool stays open and the isolated database is leaked for the rest of the CI run.

♻️ Proposed simplification
-    cleanup: async () => {
-      await pool.query("DELETE FROM agent_sessions WHERE stream_id = $1", [stream])
-      await pool.query("DELETE FROM bot_invocations WHERE workspace_id = $1", [workspace])
-      await pool.query("DELETE FROM bot_runtime_instances WHERE workspace_id = $1", [workspace])
-      await pool.query("DELETE FROM stream_active_actors WHERE workspace_id = $1", [workspace])
-      await pool.query("DELETE FROM bots WHERE workspace_id = $1", [workspace])
-      await pool.query("DELETE FROM messages WHERE stream_id = $1", [stream])
-      await pool.query("DELETE FROM streams WHERE id = $1", [stream])
-      await isolated.cleanup()
-    },
+    cleanup: isolated.cleanup,

Also update the doc comment on Line 94, which states that cleanup drains the fixture tables before dropping the database.

🤖 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/tests/integration/setup.ts` around lines 142 - 151, Remove all
table-specific DELETE queries from the cleanup function and leave
isolated.cleanup() as the cleanup operation so database teardown cannot be
skipped by a query failure. Update the cleanup doc comment near the fixture
setup to describe closing the pool and dropping the isolated database, rather
than draining fixture tables.

"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"sourceRevision": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 },

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 | 🟡 Minor | ⚡ Quick win

Stale-completion 409 is undocumented in both version snapshots. Both files add sourceRevision to the completion request bodies, but neither documents the 409 INVOCATION_INPUT_STALE response the new revision fencing returns. Generated clients therefore treat the fenced path as an undeclared error.

  • docs/public-api/versions/2026-07-12.json#L2710-L2710: add a 409 response to completeBotInvocationSealed and to completeBotInvocation at line 2851.
  • docs/public-api/versions/2026-07-22.json#L2710-L2710: add the same 409 response to completeBotInvocationSealed and to completeBotInvocation at line 2851.
📍 Affects 2 files
  • docs/public-api/versions/2026-07-12.json#L2710-L2710 (this comment)
  • docs/public-api/versions/2026-07-22.json#L2710-L2710
🤖 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 `@docs/public-api/versions/2026-07-12.json` at line 2710, Add the documented
409 INVOCATION_INPUT_STALE response to both completeBotInvocationSealed and
completeBotInvocation in docs/public-api/versions/2026-07-12.json (including
line 2851) and docs/public-api/versions/2026-07-22.json (including line 2851),
using the same response schema and description for each operation.

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