Skip to content

feat+docs: public ticket system plan + dead UI cleanup#31

Open
mpge wants to merge 39 commits intomainfrom
feat/public-ticket-system
Open

feat+docs: public ticket system plan + dead UI cleanup#31
mpge wants to merge 39 commits intomainfrom
feat/public-ticket-system

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

Summary

Implementation plan for a public ticketing system (public form + inbound email + admin routing rules + outbound email threading + configurable guest identity policy).

File: `docs/superpowers/plans/2026-04-23-public-ticket-system.md` (~1600 lines, 9 phases)

The companion code PR lives at escalated-dev/escalated-nestjs#17.

What the plan covers

  1. Workflows vs Automations resolution — Workflows stay as the admin automation engine; dead Automations UI becomes a frontend for the existing Macro system.
  2. Contact entity for first-class guest identity with email dedupe.
  3. Guest identity policy (admin-configurable): unassigned / guest_user / prompt_signup.
  4. Workflow executor wiring — makes the existing WorkflowEngineService actually run on ticket.created and kin.
  5. Outbound + inbound email with Message-ID threading and signed Reply-To addresses.

Test plan

  • Phase 0 complete (this PR) — plan document + audit correction on Macros
  • Phase 1 — Contact entity + service (next PR)
  • Phase 2 — public submission accepts email/name
  • Phases 3-9 — routing engine, email, settings UI, macros cleanup, frontend wiring

Being executed iteratively via Ralph Loop.

mpge added 8 commits April 24, 2026 00:35
9-phase TDD plan covering:
- Contact entity + email-based guest dedupe
- Public widget submission with email/name
- WorkflowEngine executor wiring (routing goes live)
- Outbound email (Message-ID threading)
- Inbound email webhook (Postmark first)
- Guest identity policy (unassigned / guest_user / prompt_signup)
- Macros (repurposes dead Automations UI)

Plan file: docs/superpowers/plans/2026-04-23-public-ticket-system.md
Phase 0 tasks 0.1-0.3 done on escalated-nestjs feat/public-ticket-system:
- d401a83: install mail deps
- 4ff1310: type mail/inbound/guestPolicy options
- (latest): test factories

Audit correction: Macro entity + service + controllers already exist
in escalated-nestjs. Phase 7 scope reduced to insert_canned_reply +
frontend-only work.
Phase 1 tasks 1.1-1.5 done on escalated-nestjs feat/public-ticket-system:
- eab03f2: Contact entity with unique email index
- 7258712: register Contact with TypeORM
- 56e75fe: add nullable contactId to Ticket
- 3574928: ContactService with dedupe + promoteToUser

138 tests passing (+11 new).
Public widget endpoint now works end-to-end for anonymous submitters:
- Accepts email+name (or legacy requesterId)
- Resolves/creates Contact record, dedupe by email
- Applies guest policy (unassigned/guest_user/prompt_signup)
- Rate-limited at 10 submissions/hour/email
- Emits SIGNUP_INVITE event under prompt_signup (listener in Phase 4)

Tests: 152 passing (+23 in Phase 2, +34 in the series).
Routing rules now fire on ticket.created, ticket.updated,
ticket.assigned, ticket.status_changed, reply.created. Workflows
evaluate conditions, dispatch actions, and log each evaluation.

Actions shipped: change_priority, add_tag, remove_tag, change_status,
set_department, assign_agent, add_note.

Actions deferred (Phase 3b, to be scheduled): send_webhook,
add_follower, delay, assign_round_robin, send_notification, set_type.

Tests: 180 passing (+28 in Phase 3).
Outbound email infrastructure shipped:
- message-id helpers for RFC 5322 threading
- EmailService with 3 transactional templates (ticket_created, reply_posted, signup_invite)
- EmailListener wired to TICKET_CREATED / TICKET_REPLY_CREATED / SIGNUP_INVITE
- Conditional MailerModule registration (no-op without options.mail)
- Errors caught per-event (never block ticket creation)

Tests: 205 passing (+25 in Phase 4).
Inbound webhook endpoint accepts Postmark payloads, parses them,
routes via priority order (In-Reply-To → signed Reply-To → subject ref
→ new ticket), and persists an audit row.

Tests: 228 passing (+23 in Phase 5 — entity 3, parser 5, router 8,
controller 3, guard 4).
Admin/Automations/ pages had no backend counterpart (audit finding in
Phase 0). Workflows is the canonical admin automation system; Macros
handles agent canned-action bundles. Removing the dead UI prevents
404 from the sidebar link.

Files removed:
  - src/pages/Admin/Automations/Form.vue
  - src/pages/Admin/Automations/Index.vue
Nav entries removed:
  - EscalatedLayout.vue sidebar item
  - AdminDashboard.stories.js item

Phase 9 cleanup of the public ticket system plan.
@mpge mpge changed the title docs: public ticket system implementation plan + Phase 0 tracking docs+cleanup: public ticket system — plan + dead Automations removal Apr 24, 2026
Public ticket system plan is now complete end-to-end:
  Phase 0 — foundation (deps, config, factories)
  Phase 1 — Contact entity + service
  Phase 2 — public widget submission
  Phase 3 — Workflow executor + listener (routing is live)
  Phase 4 — outbound email with threading
  Phase 5 — inbound email webhook
  Phase 6 — runtime guest policy override via SettingsService
  Phase 7 — insert_canned_reply action + dead Automations UI removal
  Phase 8 — frontend already aligned (widget, Guest/Create, Macros UI)
  Phase 9 — docs (CHANGELOG, README, migration notes)

Total: 232 tests passing (up from 100). 0 lint errors.
@mpge mpge marked this pull request as ready for review April 24, 2026 05:18
@mpge mpge changed the title docs+cleanup: public ticket system — plan + dead Automations removal feat+docs: public ticket system plan + dead UI cleanup Apr 24, 2026
mpge added 17 commits April 24, 2026 01:26
Surveys the 11 host-framework implementations to document which
have guest/inbound support today, which design pattern they use
(inline fields vs Contact entity), and a convergence path.

Summary: 6 frameworks have Pattern A (inline guest fields on
Ticket), NestJS now has Pattern B (separate Contact entity + FK),
5 frameworks have no public ticketing at all.

Recommendation: ship NestJS PR as-is; defer convergence of the
inline-field frameworks; backlog the 5 not-yet-implemented.

Addresses the 'work on all frameworks' scope question from the
Ralph loop — the honest answer was a status + recommendations doc
since 11 parallel reimplementations aren't tractable in this PR.
All 6 Pattern A → B convergence PRs now open:
- escalated-nestjs#17 (reference)
- escalated-laravel#67
- escalated-rails#41
- escalated-django#38
- escalated-adonis#47
- escalated-dotnet#17
- escalated-wordpress#27

Remaining: Symfony / Filament / Phoenix / Go / Spring (greenfield).
Every framework in the ecosystem now has a PR for Pattern B:
  NestJS #17 (reference), Laravel #67, Rails #41, Django #38,
  Adonis #47, .NET #17, WordPress #27, Symfony #26, Go #26,
  Phoenix #29, Spring #20 (greenfield).
  Filament inherits via the Laravel package.

Tracks follow-up work per framework (guest controller migration,
outbound threading, workflow executor wiring, inline column
deprecation) as backlog — reference impl is NestJS.
Every framework in the Escalated ecosystem now has Pattern B wired:
Contact entity + FK + guest submission paths writing via
findOrCreateByEmail. Repeat guest submissions dedupe across all
frameworks.

One caveat tracked in follow-up backlog: escalated-go ships Contact
dedupe but doesn't set ticket.contact_id yet (SELECT scan projection
is a SQL-churn follow-up).

Follow-up work (per framework, not blocking this rollout):
- go: Ticket contact_id projection through SELECT scans
- all except NestJS: Message-ID threading, Workflow executor wiring
- all: drop inline guest_* columns after dual-read cycle
- spring: inbound email webhook (greenfield — no prior guest support)
- .NET: string constants (DecideAction doesn't return an enum)
- Spring: TicketPriority import path + ContactRepository ctor arg + Mockito
- Django: ruff format
- Symfony: dedicated ticket.priority_changed dispatch (matches NestJS event model)
Each framework (.NET, WordPress, Phoenix, Spring) now has a 3-PR stack:
executor → runner → listener. 12 new PRs total. The chain is
functionally complete end-to-end (event → listener → runner →
engine+executor → WorkflowLog). Three of the four executor PRs have
CI green; runners/listeners are stacked and won't run CI until their
base merges.
Each framework now has a pure-function MessageIdUtil (or language-
appropriate equivalent) with identical 4-method API covering RFC 5322
Message-ID generation/parsing + signed Reply-To (HMAC-SHA256/8 prefix)
for inbound ticket-identity routing. ~150 tests total across the
ecosystem.

All CI green except Phoenix (no repo CI configured).
…er 56-63)

Every outbound ticket notification now emits canonical RFC 5322
Message-IDs + signed Reply-To in every framework, stacked on the
MessageIdUtil util PRs. 20 email-related PRs total (10 util + 10
wire-up).
… frameworks (iter 64-67)

Laravel #70, Rails #45, Django #42, Adonis #50, WordPress #33.
Each has a 5-priority resolution chain (In-Reply-To → References →
signed Reply-To → subject → legacy). Forged signatures rejected
with timing-safe comparison.
Documents the outbound Message-ID format changes per-framework so
host maintainers know what to expect when upgrading. The legacy
InboundEmail.message_id lookup (strategy #5 in every inbound verify
PR) covers the gap for replies threaded off pre-migration emails.
… frameworks (iter 71-75)

.NET #23, Spring #26, Go #29, Phoenix #35, Symfony #30. Ticket-identity
routing is now complete across all 10 host frameworks: every outbound
carries canonical Message-IDs + signed Reply-To, and every framework
has the resolution chain to route inbound mail back to the right ticket.

Follow-ups per greenfield framework: provider parsers + webhook
controllers + full orchestration service.
… stacks (iter 76-80)

Every framework in the ecosystem can now receive inbound webhook
mail and route it to the right ticket. Postmark is the reference
parser; additional providers register via the framework-native
discovery pattern (DI / @component / @TaggedIterator / etc.).
…1-83)

Host maintainers can now point either Postmark or Mailgun at
/escalated/webhook/email/inbound without writing any custom adapter
code. 10 Mailgun PRs total (5 frameworks × 2 PRs — parser + test).
…iter 84-88)

Records PR links for the InboundEmailService orchestration layer in
.NET (#26), Spring (#29), Go (#32), Phoenix (#38), and Symfony (#33).
Every greenfield framework now has parser → router → reply-or-create
wired up end-to-end with test coverage, matching the NestJS reference.
mpge added 12 commits April 24, 2026 06:32
The rollout-status doc records WHAT shipped; this companion doc
records the ORDER PRs should merge in so stacked chains don't
conflict. Covers:

  - Per-greenfield-framework chain tables (5 frameworks × ~12 PRs)
    with merge-top-to-bottom rows keyed by layer.
  - Recommended 5-step sequence per framework (Contact first,
    then Workflow triplet, then MessageIdUtil chain, then
    inbound chain, then observability).
  - Legacy host-app PRs (Laravel/Rails/Django/Adonis/WordPress/
    Symfony) with Contact / Workflow / MessageIdUtil / email
    wire-up / inbound-verify links.
  - Guest-policy admin UI per-framework PRs.
  - Shared frontend, docs, and NestJS reference PRs.
  - "What to do when you pick this up" — a 4-step procedural
    guide for whoever drives the merge train.

As of iter 116: 110 open PRs across 14 repos, all feature work
complete, awaiting review. This guide exists so the merge sequence
is in one place for future human or agent to work from.
Adds the iter 118-121 NestJS reference catch-up to the rollout-status
doc and extends the merge-order guide with the new 6-PR NestJS
chain (Mailgun + SES + AttachmentDownloader + parser-equivalence).

The NestJS reference is no longer behind the greenfield ports on
inbound features — all 14 framework implementations (reference + 5
greenfield + 6 legacy host-app + 2 frontend/docs) now share the
same inbound pipeline architecture.
Automations is NOT dead — it's a time-based admin rules engine that
exists in the backend for 7/11 host plugins (Laravel, Rails, Django,
Adonis, WordPress, .NET, Spring). The prior "no backend" audit was
only accurate for the NestJS reference, which hasn't ported
AutomationRunner yet.

Correct taxonomy:
  Admin tools (both rules engines)
    - Workflows  = event-driven (fire on ticket.created, reply.created, …)
    - Automations = time-based (cron scans open tickets matching
                    hours_since_created / hours_since_updated / etc.
                    conditions; executes actions on matches)

  Agent tools
    - Macros = manual, one-click action bundles applied to a specific
               ticket by a human

The two admin engines answer different questions and neither subsumes
the other (silence doesn't emit events, so Workflows can't auto-close
stale tickets). Restoring the Automations UI + sidebar link so hosts
that have the backend keep working. Follow-ups port the backend to
NestJS / Symfony / Phoenix / Go.

This reverts commit 0b3cc25.
mpge added a commit to escalated-dev/escalated-developer-context that referenced this pull request Apr 25, 2026
Adds three new top-level surfaces:

- glossary.md — one-liners for Escalated-specific terms, with the most
  attention paid to ambiguous ones (Workflow / Automation / Macro,
  Requester / Contact / guest_*, Message-ID / signed Reply-To).
- domain-model/ — canonical explainers for core concepts:
    workflows-automations-macros.md  (the three-surface taxonomy)
    ticketing-model.md               (Ticket / Reply / Contact / SLA)
    guest-policy.md                  (the 3 modes)
    email-threading.md               (outbound headers + inbound 5-priority chain)
- decisions/ — ADR-style records, dated, append-only:
    2026-04-23-public-ticket-system.md
    2026-04-24-admin-agent-tool-split.md

The decision-driving event was the Workflows/Automations/Macros confusion
in the public-ticket-system rollout (escalated-dev/escalated#31), where
an audit incorrectly framed Automations as 'dead UI' because the NestJS
reference doesn't have an AutomationRunner — but 7 of 11 host plugins
ship a working Automation backend. The 2026-04-24 ADR locks the correct
taxonomy.

Updates README.md to surface the new sections and adds a 'when to read
which' decision tree.
The original plan framed Phase 7 as 'repurpose dead Admin/Automations
into Macros.' That framing was wrong — Automations is a separate admin
tool (time-based, cron-driven) with a working backend in 7 of 11 host
plugins. The audit that called the UI dead only observed the NestJS
reference, which doesn't have an AutomationRunner yet.

The corrected three-surface taxonomy:
  - Workflows  = admin, event-driven  (existing)
  - Automations = admin, time-based   (cron; backend in 7/11 hosts)
  - Macros     = agent, manual click  (Phase 7 ships its admin UI)

All three co-exist permanently. None is being removed or renamed.

Locked in ADR escalated-developer-context/decisions/2026-04-24-admin-agent-tool-split.md.
Implementation revert: d5fdb58 (this branch).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant