Skip to content

Primitive event bus, local PostOffice delivery, and the engines' actuator endpoints - #17

Merged
acn-ericlaw merged 8 commits into
mainfrom
feature/primitive-event-bus
Aug 23, 2026
Merged

Primitive event bus, local PostOffice delivery, and the engines' actuator endpoints#17
acn-ericlaw merged 8 commits into
mainfrom
feature/primitive-event-bus

Conversation

@acn-ericlaw

@acn-ericlaw acn-ericlaw commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

What

Three features that complete the wrapper as a deployable polyglot function host:

  1. Primitive in-process event bus (bus.py) — the single dispatch pipeline. Every
    invocation reaches a function the same way: a per-route FIFO mailbox (asyncio.Queue)
    consumed by instances worker tasks, making the @preload parameter faithful. Two
    operations only: deliver (RPC, ttl-bounded → the standard 408 envelope, with a
    dead-work skip for queued calls whose caller already timed out) and publish
    (drop-n-forget, returning the 202-shape ack). The HTTP host and the local side of
    PostOffice are thin ingress adapters over it; neither has its own invocation path.
  2. Local PostOffice deliveryPostOffice without an endpoint delivers through the
    application's own bus (engine semantics): private=True now means exactly what it
    means in the engines — callable in-app while the wire keeps its 403; headers pass
    verbatim; trace context chains through local calls; the reply envelope shape is
    identical to the remote path.
  3. Actuator endpoints (actuator.py) — GET /info, /info/routes, /env,
    /health, /livenessprobe on the Event API port, mirroring the Java
    ActuatorServices shapes as ported to Rust. Health check functions are normal
    registered functions speaking the engines' type=info / type=health interface
    contract, listed in mandatory.health.dependencies / optional.health.dependencies
    and called through the event bus; /health answers UP (200) / DOWN (400) and feeds
    /livenessprobe.

Supporting: log.format now carries the engines' three presentations — text (default),
json (pretty-printed) and compact (single-line JSONL) — verified against the engine's
JsonAppender/CompactAppender pair; a sample examples/resources/application.yml
demonstrates the resources convention and the well-known keys (the demo now runs with
plain mercury-serve examples/demo_app.py); version single-sourced in version.py.

Why

  • The instances and private parameters must be faithful, not decorative. Before
    the bus, instances was a semaphore and private only a wire-side rejection — a
    developer reading engine documentation would form expectations the wrapper didn't meet.
    With the bus, worker assignment is real and private functions are reachable in-app
    through PostOffice, exactly like an engine po call.
  • Deliberately minimalist (ratified design): no spill tier and no queue cap —
    back-pressure belongs to the tier that owns recovery, the engines' flows and graphs; a
    leaf host fails fast by deadline instead of hoarding work. Local eventing is for simple
    leaf-side composition; workflow processing stays in Event Script / Knowledge Graph.
  • Wrapper apps deploy as Kubernetes PODs, so they need the engines' operational
    surface — one probe wiring and one dashboard shape across Java, Rust, Python and
    Node.js apps (livenessProbe/livenessprobe, readinessProbe/health), with
    the same health-function contract developers already know from the engines.

Verification

  • 50/50 pytest (8 bus pins: FIFO order, instances=2 concurrency, local 408 + dead-work
    skip, private reachability, trace chain; 10 actuator pins: all five shapes,
    liveness-follows-health round trip, optional-failure-never-downs, engine
    elapsed-time/origin format vectors; JSON pretty-vs-compact pin); ruff clean;
    basedpyright 0 errors.
  • Live wire proof: hosted hello.chain calls the private demo.suffix.helper through
    the bus ({'text': 'polyglot!', ...}); direct wire call to the private route → 403.
  • Live actuator drive via the sample yml: all five endpoints engine-shaped; /health UP
    with the full demo.health dependency entry; -Dlog.format=compact emits JSONL.

Twin PR on mercury-nodejs implements the identical surface in lock-step.

Co-Authored-By: Claude Code noreply@anthropic.com

🤖 Generated with Claude Code

acn-ericlaw and others added 8 commits August 22, 2026 19:42
Ratified design (Eric, 2026-08-23): every invocation reaches a function the
same way - a per-route FIFO mailbox consumed by 'instances' worker tasks
(the parameter becomes faithful; the semaphore is gone). The HTTP host and
the local side of PostOffice are thin ingress adapters over the bus.

- bus.py (internal): deliver (RPC, ttl-bounded -> 408; dead-work skip for
  queued calls whose caller already timed out) and publish (drop-n-forget,
  202-shape ack). Worker owns the invocation pipeline: per-delivery trace
  context, AppException -> portable error envelope, exec_time, sync handlers
  via the executor, error logging for un-replied publishes.
- PostOffice without an endpoint = local ingress with the engines' semantics:
  private routes callable in-app (the wire keeps its 403), headers verbatim,
  reply envelope identical to the remote path; unregistered route -> 404
  envelope. With an endpoint: unchanged wire client.
- No spill tier, no queue cap (ratified): back-pressure belongs to the tier
  that owns recovery - the engines' flows and graphs; a leaf host fails fast
  by deadline. In-memory only; send() has no ttl valve (engine parity).
- Demo gains the local-composition pair (public hello.chain -> private
  demo.suffix.helper); README documents local calls, faithful private and
  the workflow boundary; scope statement amended.

Pins: tests/test_bus.py - local RPC public+private, unregistered 404, FIFO
order, instances=2 concurrency peak, local 408 + dead-work skip proven,
trace chained caller -> entry -> private helper, local send ack. The whole
existing host suite now exercises the bus path implicitly.

Gates: ruff clean, basedpyright 0 errors, tests 40/40.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…c execute

Eric's Sonar scan of the new code: the trailing 'None = drop-n-forget'
comment parsed as commented-out code (the S125 wording lesson); close() now
uses gather(return_exceptions=True) - the workers' own CancelledError
outcomes are collected while a cancellation of close() itself still
propagates; _execute is a staticmethod (uses nothing from self).

Gates: ruff clean, basedpyright 0 errors, tests 40/40.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Eric's IDE review: the entry handler's local 'po' shadowed the test's outer
'po' - renamed inner_po; node twin aligned in its own commit.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…netes

GET /info, /info/routes, /env, /health, /livenessprobe on the Event API
host, mirroring the Java ActuatorServices shapes as ported to Rust.
Health-check functions are normal registered functions speaking the
engines' type=info / type=health contract, listed in
mandatory/optional.health.dependencies and called through the event bus;
/livenessprobe follows the most recent /health outcome (400 when down,
engine parity). Version moved to version.py (single Python-side source).

Co-Authored-By: Claude Code <noreply@anthropic.com>
…rowing, spelling

One async handle() dispatcher (awaits the /health branch; the constant
endpoints render synchronously) replaces four async-without-await
handlers - and now mirrors the node twin's dispatcher shape. app_origin
uses the local-narrowing pattern (checkers do not narrow module
globals). 'health check' spelling; origin test no longer compares an
expression to itself.

Co-Authored-By: Claude Code <noreply@anthropic.com>
…or the demo

log.format now carries the engines' three presentations: text (default),
json (pretty-printed, matching the engine's pretty serializer) and
compact (single-line JSONL for log aggregators). The demo app gains
examples/resources/application.yml - the engines' resources convention,
auto-loaded next to the app file - wiring the demo.health dependency and
demonstrating the well-known keys. README quick start teaches the -D
override syntax instead of --port.

Co-Authored-By: Claude Code <noreply@anthropic.com>
@acn-ericlaw
acn-ericlaw merged commit f38ac17 into main Aug 23, 2026
2 checks passed
@acn-ericlaw
acn-ericlaw deleted the feature/primitive-event-bus branch August 23, 2026 04:17
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