Skip to content

feat(hooks): C4 — internal/hooks thin runner + hook_run ledger CRUD (spec 11) - #5

Merged
gustavobertoi merged 1 commit into
mainfrom
nightly/C4
Jun 29, 2026
Merged

feat(hooks): C4 — internal/hooks thin runner + hook_run ledger CRUD (spec 11)#5
gustavobertoi merged 1 commit into
mainfrom
nightly/C4

Conversation

@gustavobertoi

Copy link
Copy Markdown
Contributor

Chunk C4 (M2-remainder)

Runs the declarative lifecycle hooks at saga phase boundaries, with the firstRun idempotency that replaces Postgres initdb.d. Per spec 11.

What

  • internal/state hook_run CRUD: HookSatisfied (lock-free read), RecordHookRun (idempotent; success-only, under the flock), DeleteHookRuns (--force-hooks re-arm). A row reflects only success → hooks are resumable for free.
  • internal/hooks:
    • Execerhost via os/exec from the documented base dir; exec via docker compose exec -T -w -e NAME=VALUE (the only transport that takes per-run env values, DECISIONS D10). OSExecer is the production impl.
    • Runner — per-hook timeout, retries (fixed backoff), onFailure (abort/warn/continue).
    • RunPhase — orders a phase's hooks, skips ledger-satisfied idempotent/once hooks, records success inside the flock while the hook body runs outside it (spec 08 feat(state): C1 — saga_phase v2 migration + resumability CRUD #1 rule: a long npm install must not serialize other invocations). run:exec without a service is rejected.

Layering

Thin runner — ${ref}/secret:// interpolation happens upstream in the saga and arrives via PhaseOpts.ExtraEnv (secrets last). The package never imports internal/state (uses Ledger/Locker interfaces) or resolves secrets. Full workspace-scope ordering is X3.

Tests

  • Unit (fake Execer + fake Ledger): host/exec routing, retries→success, retries exhausted, timeout, abort/warn/continue, idempotent skip, record-on-success-not-failure under the lock, env ordering.
  • Hermetic OSExecer host test (real os/exec) + a //go:build integration test exec'ing into a real busybox compose stack with inline -e env injection. Verified locally against Engine 29.5.3.

Gate

make ci green; go test -tags=integration ./internal/hooks green locally.

🤖 Generated with Claude Code

…spec 11)

Run declarative lifecycle hooks at saga phase boundaries:

- internal/state hook_run CRUD: HookSatisfied (lock-free read), RecordHookRun
  (idempotent; success-only, under the flock), DeleteHookRuns (--force-hooks
  re-arm). The cardinal rule — a row reflects only success — makes hooks
  resumable for free.
- internal/hooks: Execer (host via os/exec from the documented base dir; exec
  via `docker compose exec -T -w -e NAME=VALUE`, the only transport that takes
  per-run env values, DECISIONS D10) + OSExecer production impl. Runner applies
  per-hook timeout, retries (fixed backoff), and onFailure (abort/warn/continue).
  RunPhase orders a phase's hooks, skips ledger-satisfied idempotent/once hooks,
  and records success INSIDE the flock — the hook BODY runs OUTSIDE it so a long
  hook never serializes other invocations (spec 08 #1 rule). run:exec without a
  service is rejected (the transport rule config can't express).

Thin runner: ${ref}/${env}/${self}/secret:// interpolation happens upstream in
the saga and arrives via PhaseOpts.ExtraEnv (secrets last); the package never
imports internal/state (Ledger/Locker interfaces) or resolves secrets. Full
workspace-scope ordering is X3.

Unit tests (fake Execer + fake Ledger): host/exec routing, retries→success,
retries exhausted, timeout, abort/warn/continue, idempotent skip, record-on-
success-not-failure under the lock, env ordering. A hermetic OSExecer host test
(real os/exec) + a `//go:build integration` test exec'ing into a real busybox
compose stack with inline -e env injection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@gustavobertoi
gustavobertoi merged commit de08588 into main Jun 29, 2026
9 checks passed
@gustavobertoi
gustavobertoi deleted the nightly/C4 branch June 29, 2026 12:53
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