Commit c609237
v0.4.0 — The Safety Net Update (#77)
* feat(config): add version field with validation (#52)
Add a Version field to the Config struct, validated after Viper unmarshal:
- Missing or 0 defaults to version 1 (backward compat)
- Version 1 is accepted
- Version 2+ returns a hard error directing users to upgrade mantle
No env var binding — version is config-file-only.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(config): rename tmp section to storage with deprecation fallback (#75)
The `tmp` config section and `TmpStorage` interface were misleadingly named --
they store execution artifacts, not temporary files. Rename to `storage`/
`StorageConfig`/`Storage` throughout the codebase. A deprecation fallback
reads the old `tmp` YAML section and logs a warning, so existing configs
continue to work during migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(secrets): global key rotation with caller-managed transaction (#51)
Replace team-scoped ReEncryptAll with a package-level RotateAll function
that operates globally across all teams and accepts a caller-managed
transaction. Update the CLI rotate-key command to manage its own tx
lifecycle (begin/commit/rollback). Add ActionSecretKeyRotated audit action.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(db): add migration 016 for v0.4.0 safety net features
* feat(workflow): add concurrency fields, hooks schema, and validation (#49, #30)
Add max_parallel_executions, on_limit, and hooks to Workflow struct;
add max_parallel to Step struct; add HooksConfig type with on_success,
on_failure, on_finish blocks. Validate all new fields including hook
step uniqueness, depends_on rejection, and duration parsing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(engine): concurrency controls with advisory locks and queue promotion (#49)
Add per-workflow and per-team concurrency limits using Postgres advisory
locks. Executions that exceed limits are either queued or rejected based
on the on_limit policy. Queued executions are promoted FIFO when a slot
opens.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cel): add hooks and execution variables to CEL environment (#30)
Register `hooks` and `execution` as top-level CEL variables so hook
steps can reference hook outputs (hooks.<name>.output) and execution
metadata (execution.status, execution.failed_step, etc.).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(engine): lifecycle hooks — on_success, on_failure, on_finish (#30)
Implement lifecycle hooks that run after main workflow steps complete.
Hook failures are best-effort and never alter workflow execution status.
- Add hook audit actions (hook.step.started/completed/failed)
- Add hook Prometheus metrics (hook_steps_total, hook_steps_failed_total)
- Exclude hook steps from main DAG query (hook_block IS NULL filter)
- Create hooks.go with executeHooks/executeHookBlock/recordHookStep
- Wire hooks into resumeExecution with timeout/cancellation handling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(engine): promote queued executions after hooks complete (#49, #30)
* feat(cli): mantle retry — resume from failed step (#48)
Add `mantle retry <execution-id>` command that creates a new execution
resuming from the failure point of a previous run. Completed upstream
steps are copied from the original; the failed step and downstream
re-execute with fresh hooks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): mantle rollback — revert workflow to previous version (#50)
Add `mantle rollback <workflow> [--to-version N]` command that creates a
new version with the content of a previous version. Preserves full version
history via rollback_of column. Default target is the second most recent
version. Emits workflow.rolled_back audit event.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs: document v0.4.0 features — hooks, concurrency, retry, rollback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address code review findings — transactions, validation, indexes
- Wrap rollback read+insert in a transaction to prevent race conditions
- Add name format, timeout, and retry validation to hook steps
- Log audit emit errors instead of silently discarding them
- Add partial indexes for retry lookups and hook step filtering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use fresh context for queue promotion after timeout (security review)
* fix: wire concurrency limits into execution path (architecture review)
* fix: store unprefixed hook step names with proper unique constraint
Replace the monolithic UNIQUE(execution_id, step_name, attempt) constraint
with two partial indexes — one for main steps (WHERE hook_block IS NULL) and
one for hook steps (WHERE hook_block IS NOT NULL). This lets hook steps use
their natural names instead of "on_failure:cleanup" prefixes, with the
hook_block column providing disambiguation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add unit tests for concurrency, hooks, and retry engine logic
Also fixes the execution status check constraint to include 'queued'
and 'timed_out' statuses needed by the concurrency control feature.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add stderr warning before printing sensitive key material
* fix: secure key rotation — no argv exposure, tx-backed audit, advisory lock (PR review)
- Replace --new-key flag with --new-key-file to avoid exposing secrets in
process arguments; add --output-key for secure file-based key output
- Never echo back user-provided keys; only print auto-generated keys
- Move audit emission inside the transaction via new audit.EmitTx() so the
audit record commits atomically with the rotation
- Use context.Background() for audit emit to prevent cancellation loss
- Add pg_advisory_xact_lock in RotateAll to serialize with credential writers
- Add defer tx.Rollback() in tests; upgrade count assertions to t.Fatalf
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: atomic concurrency check+insert, audit on promotion (PR review)
Close TOCTOU race where advisory lock was released between concurrency
check and execution insert by performing both in the same transaction.
Add createExecutionTx for transactional inserts. Update PromoteQueued
and PromoteQueuedByTeam to emit audit events and return promoted IDs.
Add PromotionFailuresTotal metric and ActionExecutionPromoted constant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hooks hardening, migration fixes, validation, security improvements (PR review)
- Default 5m timeout on hook parse failure instead of proceeding without one
- Atomic variables in hooks_test.go; fix vacuous on_success assertion with request counter
- Remove redundant HookStepsFailedTotal metric (use HookStepsTotal status=failed)
- Down migration: partial unique index, data migration for queued/timed_out statuses
- Persist timed_out as terminal status in updateExecutionStatus
- Deterministic failed-step lookup via orderedSteps; DISTINCT ON in retry step copy
- Nil check on v.Sub("tmp") in config fallback
- CEL expression validation for hook step params and if conditions
- Add hook_block IS NULL to toolsteps fallback SELECT
- Improved --force flag help text
- Code block language specifiers in workflow-commands.md
- Add version: 1 to Production and Server Mode YAML examples
- Symlink-aware path validation in FilesystemStorage (EvalSymlinks)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: PR review round 2 — config semantics, exit codes, audit, security, validation
- Config: distinguish missing vs explicit version 0 via v.IsSet;
field-by-field tmp->storage merge to preserve env/flag overrides
- CLI: run/retry exit non-zero on failed/timed_out/cancelled; JSON
output no longer masks failure exit codes
- Retry: set MaxConcurrentExecutionsPerTeam; audit emitted inside
transaction; link+copy+audit in single tx
- Hooks: failed_in defaults to "" (empty), set to "steps" only when
main steps fail; hook failures update to block name as before
- Audit: EmitTx accepts optional PostgresEmitter for context enrichment
- Storage: reject absolute/.. keys before MkdirAll; fix symlink
fallback in Delete/DeleteByPrefix to use resolvedBase
- Validation: cross-block hook step name uniqueness (shared CEL ns)
- Secret store: Create acquires same advisory lock as RotateAll
- Engine: step-level timeout detection via errors.Is(DeadlineExceeded)
returns timed_out status; migration adds timed_out to step status
- Migration down: delete hook rows before dropping column, restore
proper unique constraint instead of partial index
- Tests: team-level concurrency, updated timeout/validation expectations
- Docs: --force description corrected for retry command
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: JSON encode errors, encrypt under lock, docs accuracy (PR review round 3)
- Handle JSON encoding errors in retry.go and run.go instead of discarding
- Move credential encryption inside advisory lock in store.Create to prevent
race with concurrent RotateAll
- Fix misleading --force error example in CLI docs
Not applied: --output flag registration (already exists as persistent flag on
root command), max_concurrent_executions_per_team default of 10 (spec says
0 = unlimited, Go zero value is correct)
* fix: match rollback error example to actual code output
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 81daf35 commit c609237
40 files changed
Lines changed: 3327 additions & 178 deletions
File tree
- packages
- engine
- internal
- artifact
- audit
- cel
- cli
- config
- db/migrations
- engine
- metrics
- secret
- server
- workflow
- site/src/content/docs
- cli-reference
- workflow-reference
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
72 | 74 | | |
73 | 75 | | |
74 | 76 | | |
| 77 | + | |
75 | 78 | | |
76 | 79 | | |
77 | 80 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
57 | 57 | | |
58 | 58 | | |
59 | 59 | | |
60 | | - | |
61 | | - | |
62 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
44 | | - | |
| 43 | + | |
| 44 | + | |
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
| 75 | + | |
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
79 | | - | |
| 79 | + | |
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| |||
Lines changed: 49 additions & 23 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
13 | | - | |
14 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
| 25 | + | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
32 | 37 | | |
33 | 38 | | |
34 | | - | |
| 39 | + | |
| 40 | + | |
35 | 41 | | |
36 | 42 | | |
37 | 43 | | |
38 | | - | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
39 | 49 | | |
40 | 50 | | |
41 | 51 | | |
42 | | - | |
| 52 | + | |
| 53 | + | |
43 | 54 | | |
44 | 55 | | |
45 | 56 | | |
46 | 57 | | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | 58 | | |
52 | 59 | | |
53 | 60 | | |
| |||
79 | 86 | | |
80 | 87 | | |
81 | 88 | | |
82 | | - | |
| 89 | + | |
83 | 90 | | |
84 | | - | |
| 91 | + | |
85 | 92 | | |
86 | 93 | | |
87 | 94 | | |
88 | | - | |
| 95 | + | |
89 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
90 | 107 | | |
91 | 108 | | |
92 | | - | |
| 109 | + | |
93 | 110 | | |
94 | 111 | | |
95 | 112 | | |
96 | | - | |
| 113 | + | |
97 | 114 | | |
98 | 115 | | |
99 | 116 | | |
100 | | - | |
101 | | - | |
| 117 | + | |
| 118 | + | |
102 | 119 | | |
103 | 120 | | |
104 | 121 | | |
105 | | - | |
| 122 | + | |
106 | 123 | | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
107 | 133 | | |
108 | 134 | | |
109 | | - | |
| 135 | + | |
110 | 136 | | |
111 | 137 | | |
112 | 138 | | |
113 | | - | |
| 139 | + | |
114 | 140 | | |
115 | 141 | | |
116 | 142 | | |
| |||
Lines changed: 6 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| |||
40 | 40 | | |
41 | 41 | | |
42 | 42 | | |
43 | | - | |
| 43 | + | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | | - | |
| 33 | + | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
36 | 38 | | |
37 | 39 | | |
38 | 40 | | |
39 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
40 | 47 | | |
41 | 48 | | |
42 | 49 | | |
43 | 50 | | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
44 | 57 | | |
45 | 58 | | |
46 | 59 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
36 | 55 | | |
37 | 56 | | |
38 | 57 | | |
| |||
61 | 80 | | |
62 | 81 | | |
63 | 82 | | |
64 | | - | |
| 83 | + | |
65 | 84 | | |
66 | 85 | | |
67 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
| 22 | + | |
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
| |||
38 | 40 | | |
39 | 41 | | |
40 | 42 | | |
| 43 | + | |
| 44 | + | |
41 | 45 | | |
42 | 46 | | |
43 | 47 | | |
| |||
66 | 70 | | |
67 | 71 | | |
68 | 72 | | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
69 | 83 | | |
70 | 84 | | |
71 | 85 | | |
72 | 86 | | |
73 | 87 | | |
74 | 88 | | |
| 89 | + | |
| 90 | + | |
75 | 91 | | |
76 | 92 | | |
77 | 93 | | |
| |||
0 commit comments