feat(services): export runtime variables from streaming targets - #77
Open
ark-archastro wants to merge 6 commits into
Open
ark-archastro wants to merge 6 commits into
ark-archastro wants to merge 6 commits into
Conversation
Streaming service targets can set exports_vars to publish JSON snapshots over a private FIFO. Direct dependents consume allowlisted names through inherit_env, and exporter targets are restricted to services up.
- Exporter start failure after ready aborted the supervisor — hold consumers instead, matching process-exit after the first snapshot. - 20s first-snapshot timer included prerequisites and later generations — start the clock when the process is running and abort only before ready. - Colliding export keys unwound the event loop — treat collisions as a hold for the consumer, not a supervisor fatal error. - aster run (and watch/executor spawn) could still execute exporters — reject at the CLI closure and at the spawn seams. - Daemon ready deadline was a single 25s window for serial exporters — refresh it as the supervisor starts each generation. - No tests for the services-up-only gate — added CLI and spawn coverage. Reviewers: grok-native, review-principles
- Ready-deadline refresh used protocol 4 records an old daemon treats as ready — bump to protocol 5 so mixed binaries replace the daemon. - Invalid snapshot reasons forwarded serde text that can include payload values — keep parser errors generic so secrets stay out of logs. - Invalid data after a first snapshot left ready hung with no 20s abort — re-arm the wait and time out on unhealthy, not only missing values. - Daemon deadline was not refreshed while a start generation ran — keep extending it while a start is in flight. - Non-Unix rejected any workspace exporter, not just selected services — gate Unix support on the selected plan. - --no-deps still walked exporter dependencies — skip that closure when the exporter would not run. Reviewers: grok-native, codex-cli, review-principles
- Homogeneous --no-deps still collected same-project exporter deps and failed the run — skip dependency expansion in execute_internal too. - Pre-ready abort still said "initial snapshot" after invalid-after- snapshot — report that the exporter did not become healthy. Reviewers: grok-native, review-principles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this enables for Aster clients
This PR lets an Aster-managed service discover configuration at runtime and publish it to selected dependent services. Clients no longer need an external launcher to start a producer, scrape a value, export it into the shell, and then hand the remaining lifecycle back to Aster.
A representative use case is a credentials helper whose token is not known until the helper is running:
This supports runtime-discovered credentials, dynamically allocated endpoints, local emulators, tunnels, and similar long-running tools while keeping startup ordering, readiness, restarts, logs, and teardown under one Aster supervisor.
Client configuration
The following is one end-to-end example spread across three
aster.tomlfiles.1. Producer target —
services/credentials/aster.toml2. Consumer target —
services/api/aster.toml3. Service wiring — workspace-root
aster.tomlThe resulting data flow is:
The producer writes newline-delimited JSON objects containing complete string-to-string snapshots. The capability is available through
aster services up; exporter targets are rejected byaster run,aster watch, and direct target execution because those paths do not provide the service-supervisor lifecycle contract.Client-visible behavior
inherit_env.env, resolved ports, Aster internals, and leading command assignments remain final.The result is a least-privilege, fail-closed mechanism for feeding runtime-discovered values into startup-only process environments without splitting ownership across Aster and a wrapper process.
Implementation notes
Streaming service targets can set
exports_vars = true; aliases preserve that capability during target resolution. The service supervisor tracks producer health and desired/applied snapshots, fences events by producer and consumer generation, coalesces rapid changes, and restarts only consumers whose resolved environment changes.Nested exporters are a stricter lifecycle boundary than ordinary consumers. If an upstream input changes, Aster immediately holds the nested exporter and its downstream tree. A publication is accepted only when it comes from a running generation whose applied values, sources, and upstream epochs are still current. This prevents output produced from stale inputs from reaching downstream services while a restart is queued.
Before the supervisor first becomes ready, every exporter must remain alive and healthy. An exporter that publishes once and then exits while another exporter is still starting now fails startup instead of leaving the supervisor permanently pending.
The daemon protocol is 5 so ready-deadline refresh records are not treated as ready by a v4 daemon.
Scope and limitations
Verification
cargo fmt --all -- --checkcargo clippy --locked --all-targets --all-features -- -D warningscargo test --locked --all-features --lib(512 unit tests)cargo test --locked --all-features --test dev_services export(four exporter lifecycle and CLI integration tests)cargo test --locked --all-features --test integration --test watch_tests --test config_bug_bashRUSTSEC-2026-0285, newly reported againstrustls 0.23.43. The lockfile now uses patchedrustls 0.23.45andrustls-webpki 0.103.15; the audit rerun passes.The regression coverage includes initial-snapshot gating, effective-value restarts, direct-dependency scoping, nested-exporter epoch fencing, failure when an exporter exits before global readiness, and rejection from execution modes outside
aster services up.Follow-up
Extract the exporter lifecycle decisions from
run_devinto a pure supervisor state machine. The state model should own service and producer generations, desired-versus-applied snapshots, readiness deadlines, holds, and restart queues, then emitStart,Stop,Hold, andPublishReadyactions for the existing runner to execute. This is intentionally left as a behavior-preserving follow-up so it does not add broad refactoring risk to this feature PR.Checklist