Repository navigation
docs(security): number inbox search invariant 83, not a second 82 - #252
Merged
Merged
Conversation
Ahmustufa
added a commit
that referenced
this pull request
Sep 23, 2026
main now has the audit log at 82 and inbox search at 83 (#252). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ahmustufa
added a commit
that referenced
this pull request
Sep 24, 2026
main now has the audit log at 82 and inbox search at 83 (#252). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ahmustufa
added a commit
that referenced
this pull request
Sep 24, 2026
* feat(sequences): conditional branching for campaign steps
A step can now carry one branch that routes each enrollment after the step
is sent: always / opened / clicked / replied / not_opened / not_replied,
within N days (1-90), optionally narrowed to a reply label, with a yes exit
and a no exit (a null exit ends the path). Steps without a branch keep the
original linear code path unchanged, and a test pins that.
- New table sequence_step_branches, one row per source step. Composite FKs
keep both exits in the same campaign and tenant. Deleting a target step
nulls only that exit.
- The route is recomputed from events inside a fixed window measured from
the step's own send, so a decided answer can't flip. Opens and clicks
count human events only. Replies look at inbox_messages (the other leg)
and exclude auto-replies unless the branch names that label.
- Cycles are refused at save time under a per-campaign lock, including for
step delete and reorder. A runtime backstop ends the path if a loop ever
gets in.
- Mid-flight edits use the graph as it is when the decision is made. The
rule is documented in branchroute.go.
- GET /campaigns/{id}/graph, PUT/DELETE /campaigns/{id}/steps/{stepId}/branch.
- Threading replies to the most recently sent email, not the
highest-numbered step, since a path can go 1 -> 3 -> 2.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(api): quote the yes_step_id description so the contract lints
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(sequences): branching review and security fixes
Reply branches (product decision: stop-on-reply stays, so labels win):
- A reply branch on a label that stops the sequence is refused at save
(400 reply_label_stops_sequence). The API says every default human label
stops, so a default-label "replied" branch never fires. The replacement
tests go through the real poll -> classify -> dispatch path.
- A reply nudge only pulls forward a due time that a condition wait set,
never an out-of-office deferral.
Routing:
- The loop backstop compares against the current step's own send row, not
enrollment.last_sent_at, which a recover-forward re-stamps.
- Paused and done campaigns neither finish nor park enrollments; the
status gate now runs before routing.
- opened/clicked/not_opened are refused without tracking or an HTML body
(400 tracking_required).
- Condition waits no longer count as "deferred" sends.
- Only a real miss is a 404.
- LatestSentForContact is workspace-pinned.
- New test: a routed send still honours suppression.
Migration: the inbox_threads index is its own single-statement
CONCURRENTLY migration (asserted indisvalid on a scratch DB), so the
branching migration no longer blocks the send path.
Docs: Postgres 15+ minimum; mid-flight edit rules corrected; invariant 82
notes that reply evidence is thread-scoped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(sequences): cite the branching invariant as 84 after the rebase
main now has the audit log at 82 and inbox search at 83 (#252).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fix(db): serialize migrations with a polling lock so CONCURRENTLY builds can't deadlock
golang-migrate's pgx5 driver waits for its advisory lock inside a
running statement, which holds a snapshot. CREATE INDEX CONCURRENTLY
waits for every snapshot, so a second migrator waiting on the lock
deadlocks the build, and the migration is left dirty. This reproduced 3
of 3 times with 6 concurrent migrators on a fresh DB, and it hits both
CI (-p 4) and multi-replica deploys.
Migrate, MigrateDown, MigrateTo and Version now take a separate
session-level lock by polling pg_try_advisory_lock on a dedicated
connection. A waiter is idle between tries and holds no snapshot. The
wait is bounded (15 min, ErrMigrationLockTimeout), and unlock/close run
on a detached context.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* docs(sequences): renumber the branching invariant to 86 after the rebase
#253 landed invariants 83-85 (the mail transport seam, AUTH negotiation, EHLO
validation) while this branch was open, so its own 84 collided. Renumbered to
86 and re-pointed the five code comments that cited it.
That last part is the half a textual merge does not catch: main's 84 is now
"IMAP and SMTP authentication negotiate a mechanism", so every comment here
still saying "invariant 84" would have pointed a reader at an unrelated
invariant while reading as correct. This repo has already lost a Critical to a
comment whose attribution outlived its truth.
stepbranch.sql.go is regenerated from its .sql source rather than hand-edited.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VECELVAKe8Xcp7GH9wGR5t
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#239 (audit log) and #240 (inbox search) both added a `security.md` invariant 82. The audit log keeps 82 because code comments cite it, inbox search becomes 83, and #242 (branching) takes 84.
Docs-only change. No code references the search invariant by number.
🤖 Generated with Claude Code