Skip to content

fix: reject automation trigger events no producer emits - #358

Merged
pedronauck merged 7 commits into
compozy:mainfrom
franciscpd:fix/trigger-event-name-validation
Aug 12, 2026
Merged

pedronauck merged 7 commits into
compozy:mainfrom
franciscpd:fix/trigger-event-name-validation

Conversation

@franciscpd

@franciscpd franciscpd commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Closes #357.

Problem

Trigger creation accepted any non-empty event name, but the runtime only emits activations for session.created, session.stopped, memory.consolidated, hook.<hook_name>.completed, webhook, and ext.*. Any other name — a typo or an unsupported family such as loop.terminal — produced an enabled trigger that could never fire, silently, on every surface. Repro in #357.

Change

  • ValidateTriggerEvent in internal/automation/model: closed builtin set, hook.<name>.completed requires a hook name, ext.* stays free-form past the prefix (per the extension ingress contract). Called from Trigger.Validate, so CLI, HTTP, UDS, Web, and native-tool creation all fail fast with the supported families spelled out.
  • Builtin event names become exported model constants; the envelope producers in internal/automation now reference them instead of duplicating literals.
  • CLI --event help enumerates the families at the point of use.
  • docs/automation/triggers.mdx updated: the "does not restrict" paragraph now documents the closed families and the rejection.

Deliberately not included (discussed in #357, maintainer's call): a loop.terminal producer — may or may not be product intent, and watch-events covers loop-reactive wakes after #356; and an event discovery endpoint for a Web picker, since hook.*/ext.* are open grammars.

Coverage

Five new TestTriggerValidate cases: named hook completion and ext. name accepted; producer-less name, hook completion without a hook name, and bare ext. prefix rejected. Full internal/automation, internal/api, internal/daemon, and internal/store suites pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added strict validation for standard, hook, webhook, and namespaced extension trigger events.
    • Added automation notifications after successful memory consolidation.
    • Added cleanup of unsupported trigger definitions during database upgrades.
  • Bug Fixes
    • Rejected malformed, whitespace-padded, unnamed, and unsupported events.
    • Prevented invalid triggers from being created or updated.
    • Improved event handling in automation forms and previews.
  • Documentation
    • Updated trigger command help with supported event formats.

Trigger creation accepted any non-empty event name, but the runtime only
emits activations for session.created, session.stopped,
memory.consolidated, hook.<hook_name>.completed, webhook, and ext.*. A
trigger created for any other name — a typo or an unsupported family such
as loop.terminal — sat enabled and permanently silent, with no error on
any surface and no way to notice short of waiting forever.

Validate the event name in Trigger.Validate so every creation surface
(CLI, HTTP, UDS, Web, native tools) fails fast with the supported
families spelled out. Hook completions require a hook name and extension
events require a suffix after the ext. prefix; the open ext.* namespace
stays free-form past that. The builtin names move to exported model
constants and the envelope producers now reference them, and the CLI
--event help enumerates the families at the point of use.

Co-Authored-By: Claude Fable 5 <[email protected]>
@franciscpd
franciscpd requested a review from pedronauck as a code owner August 12, 2026 13:12
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@franciscpd is attempting to deploy a commit to the Compozy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a0222982-fdec-4f0f-a980-eb94632f0b18

📥 Commits

Reviewing files that changed from the base of the PR and between 26ed605 and cea5219.

📒 Files selected for processing (6)
  • internal/automation/manager_test.go
  • internal/automation/resource_test.go
  • internal/automation/trigger_test.go
  • internal/daemon/native_automation_tools_test.go
  • internal/memory/dream_test.go
  • internal/store/globaldb/global_db_automation_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
  • internal/daemon/native_automation_tools_test.go
  • internal/automation/resource_test.go
  • internal/automation/trigger_test.go
  • internal/store/globaldb/global_db_automation_test.go
  • internal/automation/manager_test.go
  • internal/memory/dream_test.go

Walkthrough

Trigger event validation is centralized for built-in, hook-completion, and extension formats. Creation and update paths preserve raw event values and reject invalid names. Memory consolidation now emits completion events, and migration removes invalid stored triggers.

Changes

Automation trigger event hardening

Layer / File(s) Summary
Event validation contract
internal/automation/model/..., internal/automation/extension.go, internal/automation/validate.go, internal/config/automation.go, internal/automation/*_test.go
Shared validation accepts supported event formats and rejects unsupported names, malformed hooks, bare extensions, and surrounding whitespace.
Validation entrypoint propagation
internal/api/core/..., internal/automation/manager_*.go, internal/cli/..., internal/extension/..., internal/daemon/native_automation_trigger_tools.go, internal/store/globaldb/global_db_automation_normalize.go
Creation, update, resource, API, CLI, extension, and native-tool paths preserve event input and validate it before persistence or dispatch.
Memory consolidation completion events
internal/memory/..., internal/daemon/boot_automation.go, internal/daemon/daemon_test.go
Consolidation returns a ConsolidationResult. Successful runs notify a registered observer after completion. The daemon forwards completion data to automation.
Stored data and web editor contracts
internal/store/globaldb/schema/migrations/..., internal/store/globaldb/*migration_test.go, web/src/systems/automation/...
The migration removes invalid trigger definitions and dependent records. The web editor rejects malformed and unknown events and uses namespaced extension fixtures.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • compozy/compozy#51: Adds signal-based workflow trigger and event handling that overlaps with trigger-event validation.
  • compozy/compozy#80: Introduces extension event infrastructure related to the ext.* trigger namespace.

Suggested reviewers: pedronauck

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the main change: rejecting automation trigger events that no runtime producer emits.
Linked Issues check ✅ Passed The PR satisfies issue #357 by centralizing event validation and enforcing supported built-in, hook, webhook, and extension event forms.
Out of Scope Changes check ✅ Passed The changes support issue #357 through validation, producer updates, invalid-trigger cleanup, client updates, and related tests.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

Greptile Summary

The PR closes trigger-event validation to producer-backed built-ins and valid hook or extension event families, while aligning event producers, creation surfaces, stored definitions, migration cleanup, documentation, and Web previews.

  • Rejects unsupported, padded, and incomplete trigger event names before persistence or dispatch.
  • Aligns extension ingress with trigger creation by rejecting the bare ext. prefix.
  • Adds the memory.consolidated activation path and replaces duplicated producer literals with shared constants.
  • Removes persisted unsupported triggers during the schema upgrade and updates public guidance and focused coverage.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported extension grammar mismatch is fixed because both extension ingress and trigger creation now reject a bare ext. event.

Important Files Changed

Filename Overview
internal/automation/model/trigger_event_validate.go Defines the shared closed event grammar and rejects unsupported, padded, or incomplete event names.
internal/automation/extension.go Completes the prior fix by rejecting a bare extension prefix at ingress, matching trigger-definition validation.
internal/automation/manager_triggers.go Enforces canonical event validation at dynamic trigger creation and update boundaries.
internal/store/globaldb/schema/migrations/00060_trigger_event_hard_cut.sql Applies the hard cut to persisted unsupported trigger definitions and related records.
internal/memory/consolidation/runtime_trigger.go Connects successful memory consolidation to the newly supported automation event.
web/src/systems/automation/lib/trigger-event-id.ts Aligns Web-side event identifiers with the backend-supported trigger families.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Producer["Built-in, hook, webhook, or extension producer"] --> Validation["Canonical event validation"]
  Definition["Trigger creation or update"] --> Validation
  Validation -->|valid matching grammar| Runtime["Automation trigger engine"]
  Validation -->|unsupported or incomplete| Reject["Reject before persistence or dispatch"]
  Runtime --> Run["Matching automation run"]
Loading

Reviews (6): Last reviewed commit: "test: wrap migration run assertion" | Re-trigger Greptile

Comment thread internal/automation/model/validate.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@internal/automation/model/target_validate.go`:
- Around line 106-108: Update the validation call in the trigger validation flow
to pass the raw t.Event value, then modify ValidateTriggerEvent to reject values
with leading or trailing whitespace while preserving existing event validation.
Add test cases covering both leading and trailing whitespace in Trigger.Event.

In `@internal/automation/model/validate.go`:
- Around line 409-468: Move the trigger event constants and ValidateTriggerEvent
implementation out of validate.go into a focused trigger-event validation file,
such as trigger_event_validate.go. Preserve the existing exported and unexported
symbol names, validation behavior, and required imports while keeping
validate.go within the 500-line production-source limit.
🪄 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: CHILL

Plan: Pro Plus

Run ID: a0042c5b-5022-41b9-aa2b-0ad3587190a5

📥 Commits

Reviewing files that changed from the base of the PR and between 594d9fd and 92b7b32.

⛔ Files ignored due to path filters (1)
  • packages/site/content/docs/automation/triggers.mdx is excluded by !**/*.mdx
📒 Files selected for processing (6)
  • internal/automation/model/target_validate.go
  • internal/automation/model/validate.go
  • internal/automation/trigger.go
  • internal/automation/trigger_envelope.go
  • internal/automation/validate_test.go
  • internal/cli/automation_trigger_commands.go

Comment thread internal/automation/model/target_validate.go Outdated
Comment thread internal/automation/model/validate.go Outdated
Address the PR compozy#358 review round:

- Move the trigger event constants and ValidateTriggerEvent into
  model/trigger_event_validate.go so validate.go stays under the 500-line
  file cap.
- Reject surrounding whitespace in ValidateTriggerEvent, passing the raw
  event through from Trigger.Validate. Runtime matching trims both sides,
  so a padded name still matched — the reject keeps stored triggers
  canonical and mirrors the extension ingress contract.
- Close the ext-namespace divergence Greptile found: the extension
  ingress now rejects a bare "ext." event, matching the trigger
  validator, so an accepted activation always has a creatable consumer.
- Regenerate the CLI reference for the --event help text (codegen-check
  CI failure).

Co-Authored-By: Claude Fable 5 <[email protected]>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
internal/memory/dream_test.go (1)

277-298: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Wrap each edited top-level test in a named subtest.

  • internal/memory/dream_test.go#L277-L298: wrap the successful spawner contract case in t.Run("Should ...").
  • internal/memory/dream_test.go#L794-L798: wrap the missing workspace resolver case in t.Run("Should ...").
  • internal/memory/dream_test.go#L832-L835: wrap the workspace resolution case in t.Run("Should ...").
  • internal/memory/dream_test.go#L864-L868: wrap the workspace resolution error case in t.Run("Should ...").
  • internal/memory/dream_test.go#L902-L906: wrap the workspace directory error case in t.Run("Should ...").
  • internal/memory/dream_test.go#L929-L931: wrap the spawner failure case in t.Run("Should ...").
  • internal/memory/dream_test.go#L960-L962: wrap the joined failure case in t.Run("Should ...").
  • internal/memory/dream_test.go#L984-L988: wrap the lock-unavailable case in t.Run("Should ...").
  • internal/memory/dream_test.go#L1005-L1012: wrap the input-validation cases in t.Run("Should ...").
  • internal/memory/dream_test.go#L1242-L1249: wrap the concurrent-run case in t.Run("Should ...").
  • internal/daemon/daemon_test.go#L2492-L2510: wrap the boot automation integration case in t.Run("Should ...").

As per path instructions, **/*_test.go requires t.Run("Should...") for all test cases.

🤖 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 `@internal/memory/dream_test.go` around lines 277 - 298, Wrap each specified
top-level test case in a named t.Run subtest using a descriptive “Should ...”
name, preserving the existing assertions and test logic. Apply this to
internal/memory/dream_test.go ranges 277-298, 794-798, 832-835, 864-868,
902-906, 929-931, 960-962, 984-988, 1005-1012, and 1242-1249, plus
internal/daemon/daemon_test.go range 2492-2510; ensure every *_test.go case
follows the required t.Run convention.

Source: Path instructions

🤖 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 `@internal/automation/manager_test.go`:
- Around line 1422-1437: Add named t.Run subtests using the exact “Should...”
names around each padded-event rejection case:
internal/automation/manager_test.go lines 1422-1437 (“Should reject a padded
event on trigger creation”), internal/automation/manager_test.go lines 1554-1560
(“Should reject a padded event on trigger update”), and
internal/daemon/native_automation_tools_test.go lines 362-375 (“Should reject a
padded event on trigger creation”); keep each existing test body and assertions
inside its corresponding subtest.

In `@internal/automation/trigger_test.go`:
- Around line 749-753: Wrap each padded-event validation scenario in a named
t.Run("Should...") subtest: update internal/automation/trigger_test.go lines
749-753, internal/automation/resource_test.go lines 83-93, and
internal/store/globaldb/global_db_automation_test.go lines 2222-2230. Keep each
scenario’s existing assertions inside its subtest and use a descriptive “Should
reject...” name.

In `@internal/memory/consolidation/runtime_completion.go`:
- Around line 46-50: Update the observer delivery flow around notifyCtx and
observer to restore a bounded deadline after context.WithoutCancel(ctx), using
the established completion-delivery timeout if available. Ensure the observer
receives that deadline-bound context, and treat deadline expiry as a delivery
failure by logging the timeout through the existing failure path.

In `@internal/memory/dream_test.go`:
- Around line 1005-1012: Strengthen the two `service.Run` validation tests by
asserting the specific expected error type or message for the nil context and
nil spawner cases, rather than only checking that an error is returned. Keep the
existing test setup and ensure each assertion verifies the corresponding
validation failure.

---

Outside diff comments:
In `@internal/memory/dream_test.go`:
- Around line 277-298: Wrap each specified top-level test case in a named t.Run
subtest using a descriptive “Should ...” name, preserving the existing
assertions and test logic. Apply this to internal/memory/dream_test.go ranges
277-298, 794-798, 832-835, 864-868, 902-906, 929-931, 960-962, 984-988,
1005-1012, and 1242-1249, plus internal/daemon/daemon_test.go range 2492-2510;
ensure every *_test.go case follows the required t.Run convention.
🪄 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: CHILL

Plan: Pro Plus

Run ID: c37090df-6c69-489b-92d9-0fffc1f8d386

📥 Commits

Reviewing files that changed from the base of the PR and between 6e9b9c6 and 9cd0c73.

⛔ Files ignored due to path filters (10)
  • docs/qa/charters/CH-producer-backed-trigger-events.md is excluded by !**/*.md
  • docs/qa/journeys/J-create-and-activate-trigger.md is excluded by !**/*.md
  • docs/qa/reports/2026-08-12-pr-358-trigger-events.md is excluded by !**/*.md
  • docs/qa/scenarios/TA-057.md is excluded by !**/*.md
  • internal/store/globaldb/schema/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
  • packages/site/content/docs/automation/triggers.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/cli/automation/triggers/create.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/cli/automation/triggers/update.mdx is excluded by !**/*.mdx
  • packages/site/content/docs/configuration/config-toml.mdx is excluded by !**/*.mdx
  • skills/compozy/references/configuration.md is excluded by !**/*.md
📒 Files selected for processing (44)
  • internal/api/core/automation_test.go
  • internal/api/core/automation_trigger_mapping.go
  • internal/automation/manager_definition_sync.go
  • internal/automation/manager_test.go
  • internal/automation/manager_triggers.go
  • internal/automation/model/trigger_event_validate.go
  • internal/automation/resource.go
  • internal/automation/resource_test.go
  • internal/automation/trigger_registration.go
  • internal/automation/trigger_test.go
  • internal/automation/validate.go
  • internal/automation/validate_test.go
  • internal/cli/automation_request.go
  • internal/cli/automation_trigger_commands.go
  • internal/config/automation.go
  • internal/config/automation_test.go
  • internal/daemon/boot_automation.go
  • internal/daemon/daemon_extension_kit_e2e_helpers_test.go
  • internal/daemon/daemon_test.go
  • internal/daemon/native_automation_tools_test.go
  • internal/daemon/native_automation_trigger_tools.go
  • internal/extension/automation_resources.go
  • internal/extension/automation_resources_test.go
  • internal/extension/host_api_automation.go
  • internal/extension/host_api_test.go
  • internal/extension/host_api_workspace_automation_mapping.go
  • internal/memory/consolidation/runtime.go
  • internal/memory/consolidation/runtime_completion.go
  • internal/memory/consolidation/runtime_spawner_test.go
  • internal/memory/consolidation/runtime_test.go
  • internal/memory/consolidation/runtime_trigger.go
  • internal/memory/dream_result.go
  • internal/memory/dream_run.go
  • internal/memory/dream_test.go
  • internal/store/globaldb/global_db_automation_normalize.go
  • internal/store/globaldb/global_db_automation_test.go
  • internal/store/globaldb/global_db_trigger_event_migration_test.go
  • internal/store/globaldb/schema/migrations/00060_trigger_event_hard_cut.sql
  • web/src/systems/automation/components/__tests__/automation-trigger-form.test.tsx
  • web/src/systems/automation/hooks/use-automation-trigger-form.ts
  • web/src/systems/automation/lib/__tests__/trigger-preview.test.ts
  • web/src/systems/automation/lib/trigger-event-id.ts
  • web/src/systems/automation/lib/trigger-preview.ts
  • web/src/systems/automation/mocks/fixtures.ts
💤 Files with no reviewable changes (4)
  • internal/automation/resource.go
  • internal/store/globaldb/global_db_automation_normalize.go
  • internal/automation/trigger_registration.go
  • internal/extension/automation_resources.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/cli/automation_trigger_commands.go
  • internal/automation/validate_test.go
  • internal/automation/model/trigger_event_validate.go

Comment thread internal/automation/manager_test.go Outdated
Comment thread internal/automation/trigger_test.go Outdated
Comment on lines +46 to +50
notifyCtx := context.Background()
if ctx != nil {
notifyCtx = context.WithoutCancel(ctx)
}
if err := observer(notifyCtx, result); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Set a deadline for completion delivery.

context.WithoutCancel removes the request deadline. The synchronous observer call can then wait forever if automation dispatch blocks. Use a bounded context for observer delivery and log a timeout as a delivery failure.

As per coding guidelines, work that outlives a request must restore a deadline when needed.

🤖 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 `@internal/memory/consolidation/runtime_completion.go` around lines 46 - 50,
Update the observer delivery flow around notifyCtx and observer to restore a
bounded deadline after context.WithoutCancel(ctx), using the established
completion-delivery timeout if available. Ensure the observer receives that
deadline-bound context, and treat deadline expiry as a delivery failure by
logging the timeout through the existing failure path.

Source: Coding guidelines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Verified but deferring to the maintainer on this one: context.WithoutCancel here is a deliberate detach so completion delivery survives the request that produced it, and the observer chain ends in automation dispatch, which has its own persistence-side effects — bounding it changes delivery semantics (a timeout would drop the trigger activation rather than the log line). If a deadline is wanted, it belongs in the observer wiring where the delivery guarantee is decided, not in this publisher. Leaving the design call to @pedronauck since this path is part of the producer-backed rework.

Comment thread internal/memory/dream_test.go Outdated
pedronauck and others added 4 commits August 12, 2026 14:05
CodeRabbit round on 9cd0c73: wrap each new padded-event validation case
in a t.Run("Should ...") subtest per the repo test convention, and assert
the specific guard errors on the dream service nil-context and
nil-spawner paths.

Co-Authored-By: Claude Fable 5 <[email protected]>
@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@pedronauck
pedronauck merged commit fee93b7 into compozy:main Aug 12, 2026
33 of 36 checks passed
@franciscpd
franciscpd deleted the fix/trigger-event-name-validation branch August 12, 2026 20:17
This was referenced Aug 18, 2026
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.

Automation triggers accept event names no producer emits, creating permanently silent triggers

2 participants