feat: add bot invocation runtime control protocol - #1823
Conversation
📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesBot invocation control protocol
Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (32)
apps/backend/src/features/bot-runtimes/index.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/repository.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/lib/outbox/repository.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsdocs/public-api/openapi.jsondocs/public-api/versions/2026-07-12.jsondocs/public-api/versions/2026-07-22.jsondocs/public-api/versions/2026-07-24.jsonpackages/types/src/constants.tspackages/types/src/domain.tspackages/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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; keeplib/limited to cross-cutting infrastructure and useindex.tsbarrels for cross-feature imports (INV-51, INV-52).
Keep AI component configuration beside its component inconfig.ts; evaluations must call production entry points (INV-44, INV-45).
Files:
apps/backend/src/features/bot-runtimes/index.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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; throwHttpErrorclasses and derive types from schemas or constants (INV-31, INV-32, INV-55).
Every workspace-scoped domain query and mutation filters byworkspace_id; global infrastructure and authentication tables are exempt (INV-8).
Use prefixed ULIDs, no foreign keys, and no database enums; represent enum-like values asTEXTwith 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; passpoolfor 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).
UsecreateAIfor every AI call, only use current-generation models fromdocs/model-reference.md, and always include telemetry metadata (INV-16, INV-19, INV-28).
Files:
apps/backend/src/features/bot-runtimes/index.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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 scopePlan 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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsdocs/public-api/versions/2026-07-12.jsondocs/public-api/versions/2026-07-24.jsondocs/public-api/versions/2026-07-22.jsondocs/public-api/openapi.jsonapps/backend/src/features/bot-runtimes/service.tsapps/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/sdkor 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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/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 withmock.module()orvi.mock(); use scopedspyOnagainst 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.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/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.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tspackages/types/src/constants.tspackages/types/src/index.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tsapps/backend/src/features/public-api/sealed-complete.test.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/renew-heartbeat.test.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/bot-runtimes/ws-http-schema-parity.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsapps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/service.tsapps/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.tspackages/types/src/constants.tspackages/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.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/socket-handler.test.tsapps/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)
| async cancelClaimedForInputRestart( | ||
| db: Querier, | ||
| params: { | ||
| workspaceId: string | ||
| invocationId: string | ||
| sourceMessageRevision: number | ||
| reason: BotInvocationCancellationReason | ||
| instanceId?: string | ||
| claimToken?: string | ||
| } | ||
| ): Promise<BotInvocation | null> { |
There was a problem hiding this comment.
📐 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.
| 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`) |
There was a problem hiding this comment.
🚀 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.
| serverGeneratedAt: Date | ||
| available: BotInvocation[] | ||
| ownedClaims: BotInvocation[] | ||
| recentCancellations: import("./repository").BotInvocationCancellation[] |
There was a problem hiding this comment.
📐 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.
| const chosenWraps = wraps | ||
| .filter((wrap) => wrap.recipientKind === "bot" && wrap.recipientKeyId === bikKeyId) | ||
| .map((wrap) => ({ keyGeneration: wrap.keyGeneration, wrapEnc: wrap.wrapEnc, wrapCt: wrap.wrapCt })) |
There was a problem hiding this comment.
📐 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
| 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 |
There was a problem hiding this comment.
🩺 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.
| "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 | ||
| } | ||
| ] |
There was a problem hiding this comment.
🗄️ 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 packagesRepository: 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.jsonRepository: 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 --statRepository: 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.mdRepository: 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.mdRepository: 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.jsonRepository: 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 || trueRepository: 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.
Code reviewConfidence: 2/7 — Significant Concerns Found 3 issues:
📐 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 |
|
Addressed all three findings in
Post-fix affected gates: 80 unit tests and 18 real-Postgres integration tests passed; backend typecheck, changed-file lint, 261 migration checks, and |
25d8584 to
88eb613
Compare
- 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
88eb613 to
08ca561
Compare
There was a problem hiding this comment.
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
repairDeletedSourceSessionscan loop forever if a source is not cleared.The
for (;;)loop re-queriesfindDeletedSourcesWithRunningSessionsand only exits when the query returns an empty batch. Progress depends entirely oncancelInvocationsForDeletedSourceclearing the RUNNING session for every source in the batch.
terminalizeCancelledSessionsskips any invocation whosestatus !== "cancelled", andAgentSessionRepository.updateStatusis gated ononlyIfStatus: SessionStatuses.RUNNING. If a source has a RUNNING session whose invocation is neither incancelled.transitionednor incancelled.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.repairedSourcesalso 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 | 🔵 TrivialPlan the lock window for the
messagesbackfill.
ADD COLUMN ... NOT NULL DEFAULT 1is metadata-only on PostgreSQL 11 and later, so the column add is cheap. The twoUPDATE messagesstatements are not: they rewrite every row that has amessage_versionsrow and every soft-deleted row, inside the migration's implicit transaction. On a largemessagestable 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
VACUUMafterwards.🤖 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
📒 Files selected for processing (27)
apps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sqlapps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sqlapps/backend/src/features/agents/session-repository.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/repository.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/lib/outbox/repository.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/tests/integration/setup.tsdocs/public-api/openapi.jsondocs/public-api/versions/2026-07-12.jsondocs/public-api/versions/2026-07-22.jsondocs/public-api/versions/2026-07-24.jsonpackages/types/src/constants.tspackages/types/src/domain.tspackages/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.sqlapps/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 scopePlan 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.sqlpackages/types/src/constants.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sqlpackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsdocs/public-api/versions/2026-07-12.jsonapps/backend/tests/integration/bot-invocation-control-protocol.test.tsdocs/public-api/openapi.jsondocs/public-api/versions/2026-07-22.jsonapps/backend/src/features/public-api/handlers.tsdocs/public-api/versions/2026-07-24.jsonapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.sqlapps/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/sdkor 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.sqlapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/db/migrations/20260808073545_bot_invocation_source_revisions.sqlapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/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; keeplib/limited to cross-cutting infrastructure and useindex.tsbarrels for cross-feature imports (INV-51, INV-52).
Keep AI component configuration beside its component inconfig.ts; evaluations must call production entry points (INV-44, INV-45).
Files:
apps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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; throwHttpErrorclasses and derive types from schemas or constants (INV-31, INV-32, INV-55).
Every workspace-scoped domain query and mutation filters byworkspace_id; global infrastructure and authentication tables are exempt (INV-8).
Use prefixed ULIDs, no foreign keys, and no database enums; represent enum-like values asTEXTwith 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; passpoolfor 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).
UsecreateAIfor every AI call, only use current-generation models fromdocs/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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 withmock.module()orvi.mock(); use scopedspyOnagainst 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.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/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.tsapps/backend/tests/integration/setup.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/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.sqlapps/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.sqlapps/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.sqlapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/invocation-outbox-handler.test.tspackages/types/src/index.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tspackages/types/src/domain.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/tests/integration/bot-invocation-source-revisions-migration.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/tests/integration/setup.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-source-mutations.test.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tspackages/types/src/index.tspackages/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/lib/outbox/repository.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/agents/session-repository.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/runtime-write-ops.test.tsapps/backend/src/features/public-api/schemas.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/handlers.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/src/features/bot-runtimes/service.test.tsapps/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.tsapps/backend/src/features/bot-runtimes/repository.test.tsapps/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)
| ALTER TABLE bot_invocations | ||
| ADD COLUMN IF NOT EXISTS claimed_runtime_session_id TEXT; |
There was a problem hiding this comment.
🗄️ 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.
| 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
| 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, | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
📐 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.
| 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() }), |
There was a problem hiding this comment.
📐 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.
| 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() | ||
| }, |
There was a problem hiding this comment.
📐 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 }, |
There was a problem hiding this comment.
🗄️ 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 a409response tocompleteBotInvocationSealedand tocompleteBotInvocationat line 2851.docs/public-api/versions/2026-07-22.json#L2710-L2710: add the same409response tocompleteBotInvocationSealedand tocompleteBotInvocationat 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.
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:
live/restartinput modes, source revisions, typed cancellation reasons, and authoritative renewal control state.input_updatedhints for live claims; cancel and replace restart claims; keep legacy claims fenced without assuming callback support.key_grant_lostrather than downgrading.409 INVOCATION_INPUT_STALEbefore reply or trace persistence.This PR is backend protocol only. Shared-client callbacks and runtime adapter behavior remain in PRs 3–5.
Files
packages/types/src/{constants,domain,index}.tsapps/backend/src/features/bot-runtimes/{manifest-schema,index,runtime-write-ops}.tsapps/backend/src/db/migrations/20260808180131_add_bot_invocation_claimed_runtime_session.sql;apps/backend/src/features/agents/session-repository.tsapps/backend/src/features/bot-runtimes/{repository,service}.tsapps/backend/src/features/bot-runtimes/{socket-handler,ws-http-schema-parity.test}.tsapps/backend/src/features/public-api/{schemas,routes,handlers,runtime-write-ops}.tsapps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/lib/outbox/{repository,broadcast-handler}.tsdocs/public-api/{openapi.json,versions/*.json}*.test.ts;apps/backend/tests/integration/bot-invocation-{control-protocol,source-mutations}.test.tsTest plan
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
messages.revisionauthoritative.contentJson.onInputUpdated/onCancelledcallbacks.liveonly with native steering; otherwise advertisesrestart.What Was Built
Shared protocol contracts
BOT_INPUT_UPDATE_MODESdefinesliveandrestartonce; Zod and TypeScript derive from that source (INV-31/33).BotRuntimeManifest.input.updatesis optional. Absence remains legacy behavior.InvocationControlStateunion:activewith lease expiry, current revision, and optional update;cancelledwith revision and typed reason.Files:
packages/types/src/constants.tspackages/types/src/domain.tspackages/types/src/index.tsapps/backend/src/features/bot-runtimes/manifest-schema.tsManifest persistence and claim pinning
bot:hellovalidates and stores the declared manifest.Files:
apps/backend/src/features/bot-runtimes/socket-handler.tsapps/backend/src/features/bot-runtimes/repository.tsapps/backend/src/features/bot-runtimes/service.tsEdit, route, and deletion reconciliation
liveedits advance the source projection and emitbot_invocation:input_updatedwithout ending the claim.restartedits cancel the old claim/session, emit cancellation, and insert one current replacement.Files:
apps/backend/src/features/bot-runtimes/repository.tsapps/backend/src/features/bot-runtimes/service.tsapps/backend/tests/integration/bot-invocation-control-protocol.test.tsAuthoritative renewal control sync
knownSourceRevisionandrestartRequiredRevision.Files:
apps/backend/src/features/bot-runtimes/runtime-write-ops.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/routes.tsapps/backend/src/features/public-api/handlers.tsPlaintext and sealed update delivery
key_grant_lostrather than exposing plaintext.Files:
apps/backend/src/features/public-api/sealed-turn-context.tsapps/backend/src/features/public-api/runtime-write-ops.tsapps/backend/src/features/public-api/sealed-turn-context.test.tsDurable, narrowly routed hints
Files:
apps/backend/src/lib/outbox/repository.tsapps/backend/src/lib/outbox/broadcast-handler.tsapps/backend/src/lib/outbox/broadcast-handler.test.tsReconnect and bootstrap recovery
claimed_runtime_session_idrecords the actual claim winner independently of route targeting and is overwritten on reclaim.PoolClient; no concurrentpgoperations share a transaction client.Files:
apps/backend/src/features/bot-runtimes/repository.tsapps/backend/src/features/bot-runtimes/service.tsRevision-aware completion
409 INVOCATION_INPUT_STALE.Files:
apps/backend/src/features/public-api/{routes,handlers,runtime-write-ops}.tsapps/backend/src/features/bot-runtimes/{repository,service}.tsapps/backend/src/features/public-api/complete-invocation-floor.test.tsapps/backend/src/features/public-api/sealed-complete.test.tsDesign 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_modewhen 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:
liveupdates in place;restartcancels/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
Schema Changes
20260808180131_add_bot_invocation_claimed_runtime_session.sqladds nullablebot_invocations.claimed_runtime_session_id. Each claim/reclaim overwrites it with the actual requesting runtime session; bootstrap and control-event routing use it independently oftarget_runtime_session_id, which remains route intent.Explicit Exclusions
observeClaimimplementation.onInputUpdatedoronCancelledadapter callbacks.Status
🤖 PR by Codex
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.