Skip to content

docs: propose callback ingress and maintenance ADRs#277

Merged
hardbyte merged 2 commits into
mainfrom
brian/callback-ingress-design
May 31, 2026
Merged

docs: propose callback ingress and maintenance ADRs#277
hardbyte merged 2 commits into
mainfrom
brian/callback-ingress-design

Conversation

@hardbyte
Copy link
Copy Markdown
Owner

@hardbyte hardbyte commented May 31, 2026

Summary

  • add ADR-027 proposing callback ingress as a deployable surface separate from awa serve / admin UI
  • add ADR-028 proposing a maintenance-only runtime role that runs promotion, rescue, pruning, and metadata maintenance without claiming jobs
  • update the ADR index

Both ADRs depended on a story for "how do durable lifecycle effects flow when the resolving / rescuing process has no handler registry?" That story is now ADR-029, proposed separately in #284, and the latest revision of this PR wires ADR-027 and ADR-028 to reference it explicitly (callback ingress and maintenance rescue both emit follow-up jobs in the same transaction as their state commit).

Related design

Follow-up issues

Validation

  • `git diff --check`

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds two architectural decision records (ADRs) that define how callback ingress and maintenance-only runtime roles should operate as separate deployable surfaces within the awa system. ADR-027 specifies callback ingress extraction with signature verification and a dedicated CLI command. ADR-028 defines a maintenance-only runtime that executes internal loops independently. Both are indexed in the ADR README.

Changes

Architectural Decisions

Layer / File(s) Summary
ADR-027: Callback ingress as a deployable surface
docs/adr/027-callback-ingress-surface.md
Callback ingress becomes a first-class, separately deployable surface. The design splits admin UI/API from a callback receiver, extracts a shared callback contract with BLAKE3-based signature verification, defines a callback-only axum router and service layer, adds awa callbacks serve CLI support, and requires durable side effects rather than process-local lifecycle hooks.
ADR-028: Maintenance-only runtime role
docs/adr/028-maintenance-only-runtime-role.md
A maintenance-only runtime executes internal maintenance loops (promotion, heartbeat rescue, deadline rescue, queue storage maintenance) without claiming jobs or invoking handlers. Public library and CLI entry points are defined, with boundaries clarified against callback ingress and future tick API.
ADR index update
docs/adr/README.md
The ADR index is updated to list the two new proposed architectural decisions with their titles and relationship notes.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes


A queue dreams of callbacks, clean and signed,
While maintenance keeps the heartbeat in time,
Two surfaces, now free—no more entwined,
Each role distinct, each purpose sublime.
Our architecture grows, feature by feature designed. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately summarizes the main change: adding two new Architecture Decision Records (ADRs 027 and 028) about callback ingress and maintenance runtime roles.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch brian/callback-ingress-design

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 and usage tips.

ADR-029 (#284) provides the durable lifecycle-effect mechanism ADR-027
explicitly punted on and ADR-028 implicitly relied on. Update both ADRs and
the index to reference it:

- ADR-027: replace the "needs a future durable mechanism" passage with a
  reference to ADR-029; observation hooks stay in-process, durable side
  effects flow through transactional follow-up jobs from the callback
  receiver's resolution transaction.
- ADR-028: note that rescues close the rescue/timeout event gap by emitting
  follow-up Awa jobs in the rescue transaction, no handler registry required
  in the maintenance process.
- ADR index: cross-reference ADR-029 from the 027/028 rows.
hardbyte added a commit that referenced this pull request May 31, 2026
- Fix wrong claim that cancellation produces no hooks today: `Cancelled` is a
  hook event in ADR-015; the ADR now states cancellation has both a hook and
  an enqueue counterpart.
- Use the `on_*_enqueue` family naming consistently in the boundary section
  (was mixing a stray generic `on_event_enqueue`).
- Acknowledge that `WaitingForCallback` is a new event introduced by the
  ADR-015 amendment in PR #276; on `main` prior to that PR neither the hook
  variant nor the `on_waiting_for_callback_enqueue` counterpart exists.
- Point the index row at the PR that proposes ADR-027/028 (#277) rather than
  the ADR file paths, which only exist on that branch until it lands.
@hardbyte hardbyte merged commit 595580f into main May 31, 2026
13 checks passed
hardbyte added a commit that referenced this pull request May 31, 2026
- Fix wrong claim that cancellation produces no hooks today: `Cancelled` is a
  hook event in ADR-015; the ADR now states cancellation has both a hook and
  an enqueue counterpart.
- Use the `on_*_enqueue` family naming consistently in the boundary section
  (was mixing a stray generic `on_event_enqueue`).
- Acknowledge that `WaitingForCallback` is a new event introduced by the
  ADR-015 amendment in PR #276; on `main` prior to that PR neither the hook
  variant nor the `on_waiting_for_callback_enqueue` counterpart exists.
- Point the index row at the PR that proposes ADR-027/028 (#277) rather than
  the ADR file paths, which only exist on that branch until it lands.
hardbyte added a commit that referenced this pull request May 31, 2026
…cycle effects (#284)

* docs: propose ADR-029 — transactional follow-up jobs for durable lifecycle effects

Codify ADR-015's prose advice ("enqueue another job") as a first-class API.
In-process hooks (ADR-015) remain for observation; durable side effects are
delivered by enqueuing a follow-up Awa job in the same transaction as the
triggering state commit, inheriting Awa's at-least-once delivery, retries,
DLQ, and admin visibility.

Resolves the durable-callback-notification punt in ADR-027 (callback ingress
enqueues follow-ups in the resolving transaction) and closes the
rescue/timeout event gap implied by ADR-028 (maintenance can enqueue
follow-ups in the rescue transaction). Composes with ADR-013's guarded
finalization: rolled-back outcomes do not emit follow-ups.

Considers and rejects: NOTIFY as the substrate (at-most-once, drops to
disconnected listeners), a dedicated awa.events outbox (duplicates Awa's own
durable queue engine), and WAL CDC (heavy infra, contrary to ADR-001).

* docs(adr-029): address review feedback

- Fix wrong claim that cancellation produces no hooks today: `Cancelled` is a
  hook event in ADR-015; the ADR now states cancellation has both a hook and
  an enqueue counterpart.
- Use the `on_*_enqueue` family naming consistently in the boundary section
  (was mixing a stray generic `on_event_enqueue`).
- Acknowledge that `WaitingForCallback` is a new event introduced by the
  ADR-015 amendment in PR #276; on `main` prior to that PR neither the hook
  variant nor the `on_waiting_for_callback_enqueue` counterpart exists.
- Point the index row at the PR that proposes ADR-027/028 (#277) rather than
  the ADR file paths, which only exist on that branch until it lands.
hardbyte added a commit that referenced this pull request Jun 2, 2026
… (#293)

* feat(callbacks): callback-only receiver router and CLI subcommand

Adds `awa_ui::callback_router(pool, CallbackReceiverConfig)` — an axum
router that mounts only the three callback ingress routes
(`complete`, `fail`, `heartbeat`). It deliberately omits everything in
`awa serve` that doesn't belong on a publicly-reachable surface:

- no static UI assets
- no admin REST routes (jobs, queues, DLQ, runtime, stats)
- no permissive CORS
- writable DB required at build time (read-only pools fail fast instead
  of surfacing as runtime 503s on every request)

`CallbackAuth` is an explicit enum (`Signed([u8; 32])` vs `Unsigned`) so
the security choice cannot be made by forgetting a setter. `Unsigned`
is intentionally awkward to type — choose it only when the receiver
lives on a trusted network (mTLS at the load balancer, IP allow-list,
private VPC).

CLI surface: `awa callbacks serve` with its own tuned pool, the same
host/port/pool flags as `awa serve`, plus `--callback-hmac-secret`,
`--path-prefix`, and `--allow-unsigned`. Mutually-exclusive validation
fails fast with a useful message.

Tests cover:
- valid + invalid signature on every route
- admin and static paths returning 404 on the receiver
- `--allow-unsigned` accepting unauthenticated requests
- custom `path_prefix` routing (and default 404ing under that config)

docs/http-callbacks.md gains a "Deploying callback ingress separately"
section explaining when to use the receiver vs `awa serve` and showing
the CLI invocation.

Closes #279. Refs #277 (design), ADR-027.

* feat(callbacks): user-owned callback receiver helpers and docs

Adds the minimum surface needed for hosting Awa's callback ingress
routes inside a user-owned API layer (FastAPI, axum, Flask, etc.)
without copying the signing algorithm.

Rust side: the `awa::callback_contract` module already exposes
`sign`, `verify`, `callback_url`, and the protocol constants. The new
`docs/callback-receivers.md` shows the canonical custom-axum pattern
that composes those helpers with `awa::admin::*_external`.

Python side:
- New PyO3 wrappers in `awa-python/src/callback_contract.rs` export
  the same three Rust functions plus the constants. They take and
  return the same types so signatures produced from Python are
  bit-for-bit identical to what the worker emits.
- New `awa.callback_contract` Python module re-exports them with
  ergonomic names (`sign`, `verify`, `build_callback_url`,
  `SIGNATURE_HEADER`, `DEFAULT_HEARTBEAT_TIMEOUT_SECS`,
  `DEFAULT_PATH_PREFIX`).
- `awa.__init__` exposes it as `awa.callback_contract`.
- `awa/_awa.pyi` typestubs describe the new surface.
- New `tests/test_callback_contract.py` (11 tests) pins the contract:
  roundtrip, wrong-secret/id/hex rejection, secret-length validation,
  URL normalization, constant parity, AND a known BLAKE3 test vector
  asserted from both Python and Rust so the bindings cannot drift.

Docs:
- `docs/callback-receivers.md` is the new home for the user-owned API
  pattern, covering custom axum and FastAPI receivers end-to-end and
  pointing back at the `callback_path_prefix` worker knob from #280.
- `docs/http-callbacks.md` links to it for the third deployable
  option (alongside `awa serve` and `awa callbacks serve`).

Closes #281. Refs #277 (design draft), ADR-027.
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