Skip to content

feat: add distributed project manifest primitives#53

Merged
patrickleet merged 24 commits into
mainfrom
codex/hops-service-create-microsvc-scaffold
Jun 6, 2026
Merged

feat: add distributed project manifest primitives#53
patrickleet merged 24 commits into
mainfrom
codex/hops-service-create-microsvc-scaffold

Conversation

@patrickleet
Copy link
Copy Markdown
Collaborator

@patrickleet patrickleet commented Jun 2, 2026

Summary

  • add a versioned Distributed project manifest envelope with project, service, endpoint, and transport metadata
  • make read-model/table schema metadata serializable for compiled manifest JSON
  • expose manifest SQL rendering through the existing deterministic table schema renderer

Tests

  • cargo check
  • cargo test manifest --lib
  • cargo test --lib
  • cargo fmt --check

Notes

Summary by CodeRabbit

  • New Features

    • Distributed manifests: create/export project manifests and generate JSON + SQL migration artifacts.
    • Immediate outbox publishing: commits return receipts and support best‑effort publish hooks; bus-backed publishers for commands/events.
    • Bus-backed runtime: attach a bus and run consumers to drain and publish outbox entries.
    • Integrated snapshot support: optional snapshotting for faster repository hydration.
    • Project scaffolding tool: generates service crates, GitOps charts and GitHub workflows.
    • Read-model metadata now serializes via JSON.
  • Documentation

    • Examples use the new Service::new().with_* builder style; expanded outbox and service-bus docs.
  • Tests

    • New/updated tests covering manifests, outbox, runtime, snapshots and scaffolding.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place.

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a serializable distributed manifest API; enables Serde for read-model metadata; makes AggregateRepository snapshot-aware and supports immediate outbox publish; adds outbox publish hooks and bus adapters; introduces a bus-backed microsvc runtime and builder-style Service API; adds a service scaffolding crate and updates docs/tests.

Changes

Distributed Manifest and Metadata Serialization

Layer / File(s) Summary
Manifest types and crate wiring
src/manifest.rs, src/lib.rs
Adds DistributedManifestEnvelope, DistributedProjectManifest, Service/Endpoint/Transport manifests, schema version constant, unit test, crate-root manifest module and re-exports.
Read-model metadata Serde derives
src/read_model/metadata.rs
Adds Serialize/Deserialize derives and Serde imports to read-model metadata types enabling schema (de)serialization.

Aggregate, Snapshot, and Outbox Integration

Layer / File(s) Summary
AggregateRepository: snapshot + outbox storage
src/aggregate/repository.rs, src/aggregate/mod.rs
Adds SnapshotPolicy and OutboxPublisherConfig fields, snapshot-aware hydration helpers, and stages snapshot writes inside commit batches.
Snapshot repository refactor
src/snapshot/repository.rs, src/snapshot/mod.rs
Removes SnapshotAggregateRepository wrapper; AggregateRepository::with_snapshots configures snapshot policy and adds hydrate/record helpers; tests updated.
Outbox commit immediate-publish API
src/outbox/commit.rs, src/outbox/mod.rs
Replaces OutboxCommit with AggregateCommit returning CommitReceipt; adds OutboxPublishHook and OutboxPublisherConfig to claim+publish committed rows (best-effort).
Outbox worker adapters
src/outbox_worker/*
Adds BusPublisher adapter and BusOutboxPublishHook to publish claimed rows and settle claims; exposes via outbox_worker re-exports.

microsvc runtime and dependencies

Layer / File(s) Summary
microsvc runtime and Service builder/runner
src/microsvc/runtime.rs, src/microsvc/service.rs
Adds Service::with_bus, Service::run, run_consumers, ServiceRunner type alias, and builder-style Service::new()/with_repo/with_read_model_store; tests for immediate publish and consumer-run draining.
microsvc dependencies: outbox integration
src/microsvc/dependencies.rs, src/microsvc/mod.rs
Adds ConfigurableOutboxPublisher and HasOutboxStore traits; resolves outbox stores through repository wrappers and expands public re-exports.
RunOptions Clone derives
src/bus/run_options.rs
Adds Clone derives to ConsumerDeliveryMode and RunOptions.

Scaffolding and Tooling

Layer / File(s) Summary
distributed_tooling scaffold generator
distributed_tooling/src/generate/*, distributed_tooling/src/lib.rs, distributed_tooling/Cargo.toml
New crate providing generate_service_scaffold, name normalization, service crate templates, GitOps and GitHub workflow/chart generators, and tests for emitted files and post-create actions.

Docs, examples, and tests

Layer / File(s) Summary
README and module docs
README.md, src/microsvc/*.rs
Examples and docs updated to use Service::new().with_repo(...), describe with_bus(...).run(opts) convenience and outbox publishing behavior.
Tests: Service::new() builder migration & snapshot tests
tests/**
Many tests updated to construct services via Service::new()/builder chaining; snapshot tests adjusted to new access paths; adds durable SQLite end-to-end test and updates transport tests.
Cargo workspace
Cargo.toml
Workspace members list extended to include distributed_tooling.

Estimated code review effort: 🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

"🐰 I hop through code with glee,
New manifests stitched carefully,
Snapshots nest and outboxes sing,
Runners run — the bus takes wing,
Tests and docs all dance with me."

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/hops-service-create-microsvc-scaffold

patrickleet and others added 13 commits June 4, 2026 21:36
OutboxCommit::commit now returns a CommitReceipt carrying the inserted
outbox message id(s) instead of (), so an after-commit dispatcher can
publish exactly the rows the transaction wrote. Source-compatible:
?-statement callers discard the receipt.

Step 1 of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Routes outbox-derived messages by MessageKind: commands to send_message
(point-to-point), events to publish_message (fan-out). This is the missing
adapter that lets the outbox dispatcher publish through any *Bus uniformly.

Step 2 of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
New trait abstracting 'produce a durable outbox store', resolving through the
AggregateRepository -> QueuedRepository -> leaf repo wrapper chain. Lets the
runtime build an OutboxDispatcher without naming the concrete repository type.
Impls for HashMap (and feature-gated Sqlite/Postgres) leaves + the wrappers.

Step 3 (store access) of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Service::with_bus(bus) wraps the consumer Service into a Microservice carrying
the transport config. Microservice::dispatcher() assembles an OutboxDispatcher
over the service's own outbox store + a BusPublisher, so committed outbox rows
drain to the bus routed by kind. Test proves commit -> dispatch -> published
end to end over InMemoryBus.

Consume side (run() auto listen/subscribe) and the in-transaction commit_outbox
land next.

Step 6 (runtime, produce side) of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claims the outbox row for publication in the same transaction that commits the
aggregate: the row inserts already InFlight under the worker's lease
(attempts = 1), so the after-commit publish needs no separate claim and cannot
race the poller. Returns the claimed message clone so the caller can build the
transport message and settle the claim. Test proves the row is in-flight,
leased, and not poller-claimable.

Step 4 (claim-in-transaction) of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Wires the durable-enqueue command path end to end:
- DynPublisher: object-safe (boxed-future) form of AsyncMessagePublisher, so a
  publisher can sit behind Arc<dyn> without making Service generic over it.
- Service carries an optional ImmediatePublish (publisher + worker id + lease +
  attempts), set by with_bus; Context receives it.
- Context::commit_outbox: with a bus attached, claims the outbox row in the
  commit transaction then publishes immediately through the bus, completing or
  releasing the claim; with no bus, commits pending for the poller. Best-effort
  publish never rolls back the committed aggregate.

Test: dispatch -> commit_outbox -> row published immediately, none left pending.

Steps 3+5 (DynPublisher + commit_outbox) of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
run() reads the service's subscription_plan and drives the consumers
concurrently on the caller's runtime: command handlers via competing listen,
event handlers via fan-out subscribe. Uses an executor-agnostic poll-join (no
spawn, no timer) so it works in core without pulling tokio. Returns on first
error or when the consumers stop. Derive Clone for RunOptions/ConsumerDeliveryMode
so one options value drives both consumers. Test: run() consumes a queued
command and the handler's commit_outbox publishes immediately.

Producing happy-path is commit_outbox (immediate); the backstop poll loop (needs
a timer) is driven from dispatcher() by a runtime that provides one.

Step 6 (runtime, consume side) of [[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Exercises commit_outbox (claim-in-transaction + immediate publish) and run()
against a real SQL backend (in-memory SQLite), not just HashMapRepository.
Proves the HasOutboxStore impls and the SQL commit path persist the in-flight
claim and complete it. Also fixes a must_use warning on the finished-consumer
future in run().

[[tasks/durable-enqueue-outbox-dispatch-impl]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Generalize the durable-enqueue command path with an OutboxCommitting<A> trait
that commits an aggregate + outbox row in one transaction, staging whatever the
repo needs. Implemented for AggregateRepository (delegates to the existing
OutboxCommit) and SnapshotAggregateRepository (stages the snapshot + outbox row
together via CommitBatch — previously these could not compose). Context::commit_outbox
now binds D::Repo: OutboxCommitting<A> + HasOutboxStore instead of the concrete
AggregateRepository, so snapshot-backed services get claim-in-transaction +
immediate publish too. Test: snapshot-backed commit_outbox publishes immediately.

[[tasks/durable-enqueue-outbox-dispatch-impl]]
Builds on [[specs/transactional-commit-boundary]]
Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Fold SnapshotAggregateRepository into AggregateRepository via an optional
SnapshotPolicy whose Snapshottable/SnapshotStore requirements are captured as
monomorphized fn-pointers at with_snapshots() time, keeping the generic
get/commit methods unbounded. Now:

- .with_snapshots(n) returns AggregateRepository<R,A> (same type), so handler
  dependency types are identical with/without snapshots.
- every method works either way; commit stages a snapshot (when due) in the same
  CommitBatch, get hydrates from a snapshot when present. The full repo surface
  (peek/abort/get_with/outbox/...) is available with snapshots on — previously
  the wrapper dropped most of it.
- exactly ONE OutboxCommitting impl (on AggregateRepository); the snapshot-
  specific impl and the whole SnapshotAggregateRepository type are removed.

with_snapshots now requires R: SnapshotStore (you can't cache snapshots in a
store that can't hold them) — stricter and more correct than the old wrapper.
Tests migrated to the unified type; assertions unchanged. Full suite + sqlite green.

Implements [[specs/snapshots-as-transparent-optimization]] [[tasks/snapshots-transparent-optimization]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Make the existing API do the new functionality instead of adding a method.
Attaching a bus (Service::with_bus) installs an outbox publisher on the
repository; OutboxCommit::commit then claims the row in the commit transaction
and publishes it immediately via that bus, settling the claim (complete, or
release for the worker on failure). No bus configured -> commit stays pending
for the worker, exactly as before.

Removed: ctx.commit_outbox, the OutboxCommitting trait, OutboxCommit::commit_claimed,
Service's ImmediatePublish + the Context publisher plumbing, and the now-unused
DynPublisher (the snapshot unification already collapsed the two repo types, so
the polymorphism trait was dead weight).

Added: OutboxPublishHook (object-safe) + OutboxPublisherConfig on the repo,
BusOutboxPublishHook (store + BusPublisher), ConfigurableOutboxPublisher. Tests
migrated to repo.outbox(msg).commit(agg); full default + sqlite suites green.

Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Fold Microservice back into Service. Attaching a bus no longer changes the
type: with_bus(bus) returns the same Service<D> and run() is a method on it, so
the whole thing reads as one fluent builder —
  Service::with_repo(r).command(..).handle(..).with_bus(bus).run(opts)

The bus's consume behavior is type-erased into a single closure field on the
service (ServiceRunner), so Service stays single-param — message_router, the
register_handlers! macro, and every existing Service<D> call site are untouched.
Removes the Microservice type and the speculative dispatcher() accessor (the
backstop poll loop is a later, runtime-gated addition).

Net simpler: one type, one builder, less code.

Implements [[specs/durable-enqueue-outbox-dispatch]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Replace the with_repo / with_read_model_store / with_repo_and_read_model_store
constructors with one fluent builder: every service starts at Service::new() and
chains dependency + bus steps —
  Service::new().with_repo(r).with_read_model_store(s).with_bus(bus)

with_repo/with_read_model_store are type-state transitions that produce exactly
the same D as before (Service<R>, or RepoReadModelDependencies<R,S> for both), so
handler signatures are unchanged — only construction call sites move. Combined
deps now delegate HasOutboxStore + ConfigurableOutboxPublisher to the repo so a
repo+read-model service can also with_bus. Migrated all call sites + README.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/microsvc/runtime.rs`:
- Around line 82-86: The public Service::run currently panics via expect when
take_runner() is None; change this to return a recoverable error instead: detect
if self.take_runner() returns None and return an appropriate TransportError
(e.g., add or use a Configuration/MissingBus variant or convert into an existing
error kind) with a clear message indicating that with_bus() was not called;
update any signatures/From impls as needed so runner(Arc::new(self),
options).await is only invoked on Ok(runner) and callers receive a typed error
rather than a process abort.

In `@src/snapshot/repository.rs`:
- Around line 184-190: The with_snapshots method currently accepts frequency ==
0 which (given snapshot_due()) makes every commit eligible; add a guard at the
start of with_snapshots to reject zero by validating frequency > 0 and failing
fast (e.g. panic! or return an Err) with a clear message like "snapshot
frequency must be > 0"; place this check in the with_snapshots function before
calling SnapshotPolicy::new (referencing with_snapshots and SnapshotPolicy::new)
so misconfiguration is caught at construction time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32f6ad27-525c-4352-babe-a5106bc16802

📥 Commits

Reviewing files that changed from the base of the PR and between 4d28854 and 25d5895.

📒 Files selected for processing (43)
  • README.md
  • src/aggregate/mod.rs
  • src/aggregate/repository.rs
  • src/bus/run_options.rs
  • src/lib.rs
  • src/microsvc/dependencies.rs
  • src/microsvc/grpc.rs
  • src/microsvc/http.rs
  • src/microsvc/mod.rs
  • src/microsvc/runtime.rs
  • src/microsvc/service.rs
  • src/outbox/commit.rs
  • src/outbox/mod.rs
  • src/outbox_worker/bus_publisher.rs
  • src/outbox_worker/mod.rs
  • src/outbox_worker/outbox_source.rs
  • src/outbox_worker/publish_hook.rs
  • src/snapshot/mod.rs
  • src/snapshot/repository.rs
  • tests/distributed_read_model/checkout_saga_service/service.rs
  • tests/distributed_read_model/main.rs
  • tests/distributed_read_model/projection_service/service.rs
  • tests/distributed_read_model/seat_inventory_service/service.rs
  • tests/distributed_read_model_board/board_service/service.rs
  • tests/distributed_read_model_board/projections_service/mod.rs
  • tests/durable_enqueue_sqlite/main.rs
  • tests/kafka_transport/main.rs
  • tests/knative_cloudevents/main.rs
  • tests/microsvc/basic.rs
  • tests/microsvc/convention.rs
  • tests/microsvc/session.rs
  • tests/microsvc/transport_grpc.rs
  • tests/microsvc/transport_http.rs
  • tests/microsvc/transport_listen.rs
  • tests/microsvc/transport_subscribe.rs
  • tests/nats_transport/main.rs
  • tests/postgres_transport/main.rs
  • tests/rabbitmq_transport/main.rs
  • tests/sagas/microsvc_saga.rs
  • tests/snapshots/main.rs
  • tests/sourced_snapshot/main.rs
  • tests/transport_conformance/mod.rs
  • tests/upcasting/main.rs
💤 Files with no reviewable changes (1)
  • tests/upcasting/main.rs
✅ Files skipped from review due to trivial changes (8)
  • src/snapshot/mod.rs
  • src/microsvc/grpc.rs
  • tests/microsvc/session.rs
  • src/microsvc/http.rs
  • tests/postgres_transport/main.rs
  • tests/kafka_transport/main.rs
  • tests/rabbitmq_transport/main.rs
  • README.md

Comment thread src/microsvc/runtime.rs
Comment thread src/microsvc/service.rs
Comment thread src/snapshot/repository.rs
patrickleet and others added 6 commits June 4, 2026 21:47
…loop

Update the Quick Start to the Service::new().with_repo(..).with_bus(bus).run()
builder and make the produce loop explicit: step 2 commits an outbox row, step 3
attaches a bus so that commit publishes on commit. Rewrite Draining the Outbox
as Publishing the Outbox (immediate-on-commit vs pending+worker), and document
the backstop poll loop as the composable OutboxDispatcher + your timer. Note the
with_bus().run() convenience alongside the lower-level listen/subscribe facade.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Close the last 'all features compose' gap. The AggregateRepository-level commit
(formerly OutboxCommit, now AggregateCommit) carries outbox rows AND read-model
write plans, and stages a snapshot from the repo's policy — all in one
CommitBatch. New entry repo.read_models(plan) mirrors repo.outbox(msg); both
chain (.outbox(..).read_models(..)) and end in .commit(agg), which also publishes
the outbox rows on commit when a bus is attached.

Previously read-model commits ran at the raw-repo level (CommitBuilder, no
snapshot policy) so snapshots and read-models could not compose. Now a
snapshot-backed repo commits streams + outbox + read-models + snapshot
atomically. Test proves read-model row + snapshot land in one transaction.

The raw-repo CommitBuilder (repo: &R) is unchanged for non-aggregate-repo use.

Implements [[specs/durable-enqueue-outbox-dispatch]] [[tasks/snapshot-readmodel-commit-compose]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Single test exercising all four staged in one commit
(repo.outbox(msg).read_models(plan).commit(agg) on a with_snapshots(1) repo):
asserts the aggregate stream, outbox row, read-model row, and snapshot all
land together.

[[tasks/snapshot-readmodel-commit-compose]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
New workspace crate implementing specs/distributed-service-scaffold-tooling: a
pure ServiceScaffoldSpec -> GeneratedProject API (no fs, network, or CLI). Owns
the deterministic generation rules — name/message normalization + validation,
GitHub repo parsing, and the core service-crate templates (Cargo.toml, lib/main/
manifest/service/models/handlers/read_models). Returns GeneratedFile list +
warnings + PostCreateAction (EnsureGithubRepository) for the caller to act on.

Generated service.rs uses the new Service::new().with_repo(repo) builder. The
public API includes the gitops/github spec fields; those artifact templates +
the hops-cli rewire are the next slices.

7 tests green; workspace builds; clippy clean.

Implements [[specs/distributed-service-scaffold-tooling]] [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
generate/ becomes a module: mod.rs (Scaffold + orchestration + entry + tests),
names.rs (name/message normalization + validation), service_crate.rs (the Rust
templates as impl Scaffold), github.rs (repo parsing). Sets up gitops.rs and the
GitHub workflow templates as their own files for the next slice. No behavior
change; 7 tests green.

Implements [[specs/distributed-service-scaffold-tooling]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
generate/gitops.rs: .gitops/deploy Helm chart (HTTP Deployment+Service or
Knative Service+Brokers+Triggers) + optional .gitops/promote Argo/Flux chart,
with Knative broker/trigger inference and image-repo selection. generate/github.rs
gains the release/preview/promote workflow templates + the Argo CD promotion
chart. generate() now emits these (deploy chart whenever any gitops/github option
is set, matching the original); the placeholder warning is gone.

10 crate tests (added GitOps HTTP, Knative brokers/triggers, Flux promote, and
full GitHub workflow coverage). Next: the hops-cli rewire.

Implements [[specs/distributed-service-scaffold-tooling]] [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@distributed_tooling/src/generate/gitops.rs`:
- Around line 123-137: knative_triggers currently builds Triggers from commands
and events and can create duplicate metadata.name values after normalization;
update knative_triggers (and the similar logic in the 270-299 block) to
guarantee unique Trigger names by deriving the name from a collision-resistant
token (e.g., include the full message_name with a type prefix or append a short
hash of message_name + type) before calling KnativeTrigger::new; use the same
de-duplication strategy for both commands.iter().map(...) and
events.iter().map(...) branches and ensure
command_model/command_broker_for_message/event_broker_for_message logic remains
unchanged while only changing the computed name fed to KnativeTrigger::new so
normalized names cannot collide.

In `@distributed_tooling/src/generate/names.rs`:
- Around line 23-24: ScaffoldNames::new currently builds crate_ident from
package_name.replace('-', "_") which can produce invalid Rust identifiers
(leading digits, reserved keywords, or other non-alphanumerics); update
ScaffoldNames::new to sanitize package_name by: 1) replacing any
non-[A-Za-z0-9_] chars with '_' (not just '-'), 2) if the first char is a digit,
prefix the identifier with '_' to ensure it starts with a letter/underscore, 3)
if the result equals a Rust reserved keyword (e.g., "crate", "self", "super",
etc.), append or prepend an underscore (e.g., "crate_") to avoid keyword
collisions, and 4) ensure command_name still uses the sanitized crate_ident
(format!("{crate_ident}.create")). Apply these changes where crate_ident and
command_name are computed so generated templates (e.g., main.rs using
{crate_ident}::service::in_memory()) produce valid Rust paths.

In `@README.md`:
- Around line 741-744: Update the README wording so it does not say the outbox
row is claimed "in the commit transaction"; instead state that when using
service.with_bus(bus) / repo.outbox(msg).commit(agg) the row is scheduled for
and then claimed by the immediate post-commit dispatch pass (the outbox_dispatch
immediate dispatch), so claiming happens after the DB commit and therefore does
not provide stronger transactional guarantees — a crash or publish failure
leaves the row claimed under a short lease and the polling worker will take it
when the lease expires.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5c0ebd5-ef23-45ea-9f18-d3bd02119289

📥 Commits

Reviewing files that changed from the base of the PR and between 25d5895 and 8ff658f.

📒 Files selected for processing (12)
  • Cargo.toml
  • README.md
  • distributed_tooling/Cargo.toml
  • distributed_tooling/src/generate/github.rs
  • distributed_tooling/src/generate/gitops.rs
  • distributed_tooling/src/generate/mod.rs
  • distributed_tooling/src/generate/names.rs
  • distributed_tooling/src/generate/service_crate.rs
  • distributed_tooling/src/lib.rs
  • src/lib.rs
  • src/outbox/commit.rs
  • src/outbox/mod.rs
✅ Files skipped from review due to trivial changes (3)
  • Cargo.toml
  • distributed_tooling/Cargo.toml
  • distributed_tooling/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/outbox/mod.rs

Comment thread distributed_tooling/src/generate/gitops.rs
Comment thread distributed_tooling/src/generate/gitops.rs
Comment thread distributed_tooling/src/generate/names.rs
Comment thread README.md Outdated
patrickleet and others added 4 commits June 4, 2026 23:09
The original hops-cli scaffold exposes --github, --github-preview, and
--github-promote as independent flags: --github emits the version/release
workflows and the repo-create action; --github-preview emits the preview
workflow + .gitops/preview/helm chart; --github-promote emits the promote
workflow + .gitops/promote/helm chart. Each can be set without the others
(e.g. preview-only), and only --github triggers repo creation.

The crate previously nested preview/promote under a required GithubScaffoldSpec
repository, which could not represent preview-only and tied the workflows to a
service repo. Replace it with three flat Option<GithubRepo> fields on
ServiceScaffoldSpec (github / github_preview / github_promote), mirroring the
flags 1:1 and dropping the GithubScaffoldSpec wrapper. The deploy chart is now
emitted when any of the five gitops/github signals is set. Adds a regression
test for the preview-only path.

Prepares the faithful hops-cli rewire onto this crate.

Implements [[specs/distributed-service-scaffold-tooling]] [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The CLI adapter needs the normalized kebab package name to compute the default
output directory (./<name>) before generating. Expose the existing
ScaffoldNames normalization as a public helper instead of duplicating the
casing rule in the CLI.

Implements [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…uards

- reject service/model names that yield invalid Rust identifiers, instead of
  emitting a crate that won't compile
- deploy Helm templates honor image.repository/tag rather than hardcoding
  :latest, so values.yaml/release automation actually drives the image
- dedupe Knative trigger names that normalize to the same metadata.name, which
  otherwise breaks `kubectl apply`
- fail fast when dependency builders (with_repo/with_read_model_store) run after
  handler/bus setup, which silently dropped registrations
- reject snapshot frequency 0 (would snapshot on every commit)
- correct outbox claim-timing wording: the row is claimed post-commit under a
  short lease, not within the commit transaction

Implements [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 <[email protected]>
distributed_tooling was a workspace member but absent from the release
pipeline, so it could never become a crates.io dependency. Add a
publish-tooling job alongside publish-macros (the crate only depends on
serde_json, so it has no internal publish ordering) and gate the release on it.

Implements [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/on-v-tag-publish.yaml:
- Around line 20-28: The workflow currently references third-party workflow
branches (e.g., the publish step under the publish-tooling job using "uses:
unbounded-tech/workflows-rust/.github/workflows/publish.yaml@feat/cargo-publish"),
which must be pinned to commit SHAs; update each "uses:" reference in this file
(including the other occurrences of the same external workflow) to replace
branch refs like "`@feat/cargo-publish`" with the corresponding full commit hash
(e.g., "@<commit-sha>") for the referenced repository so the publish-tooling job
and the other uses entries point to immutable commits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 09022e85-1e96-4cf6-9516-9fb4e94ac1d7

📥 Commits

Reviewing files that changed from the base of the PR and between 7547033 and d931a6e.

📒 Files selected for processing (1)
  • .github/workflows/on-v-tag-publish.yaml

Comment on lines +20 to +28
# distributed_tooling is a standalone crate (only depends on serde_json), so it
# publishes independently of the macros/core crates.
publish-tooling:
uses: unbounded-tech/workflows-rust/.github/workflows/publish.yaml@feat/cargo-publish
secrets:
crates_io_token: ${{ secrets.CRATES_IO_TOKEN }}
with:
manifest_path: distributed_tooling/Cargo.toml
cargo_publish_args: "--locked"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin workflow references to commit hashes per security policy.

The workflow reference on line 23 uses a branch reference (@feat/cargo-publish) instead of a pinned commit hash. This violates the blanket policy flagged by static analysis and creates a supply-chain security risk—if the referenced branch is compromised, secrets could be exfiltrated or malicious code executed.

Note that this issue also affects lines 13, 32, and 43 in this file. Consider pinning all workflow references to specific commit hashes.

🔒 Example fix for pinning workflow references
-    uses: unbounded-tech/workflows-rust/.github/workflows/publish.yaml@feat/cargo-publish
+    uses: unbounded-tech/workflows-rust/.github/workflows/publish.yaml@a1b2c3d4e5f6  # feat/cargo-publish as of 2026-06-02

Apply the same pattern to lines 13, 32, and 43.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# distributed_tooling is a standalone crate (only depends on serde_json), so it
# publishes independently of the macros/core crates.
publish-tooling:
uses: unbounded-tech/workflows-rust/.github/workflows/publish.yaml@feat/cargo-publish
secrets:
crates_io_token: ${{ secrets.CRATES_IO_TOKEN }}
with:
manifest_path: distributed_tooling/Cargo.toml
cargo_publish_args: "--locked"
# distributed_tooling is a standalone crate (only depends on serde_json), so it
# publishes independently of the macros/core crates.
publish-tooling:
uses: unbounded-tech/workflows-rust/.github/workflows/publish.yaml@a1b2c3d4e5f6 # feat/cargo-publish as of 2026-06-02
secrets:
crates_io_token: ${{ secrets.CRATES_IO_TOKEN }}
with:
manifest_path: distributed_tooling/Cargo.toml
cargo_publish_args: "--locked"
🧰 Tools
🪛 zizmor (1.25.2)

[error] 23-23: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/on-v-tag-publish.yaml around lines 20 - 28, The workflow
currently references third-party workflow branches (e.g., the publish step under
the publish-tooling job using "uses:
unbounded-tech/workflows-rust/.github/workflows/publish.yaml@feat/cargo-publish"),
which must be pinned to commit SHAs; update each "uses:" reference in this file
(including the other occurrences of the same external workflow) to replace
branch refs like "`@feat/cargo-publish`" with the corresponding full commit hash
(e.g., "@<commit-sha>") for the referenced repository so the publish-tooling job
and the other uses entries point to immutable commits.

Source: Linters/SAST tools

@patrickleet patrickleet merged commit 59a73e4 into main Jun 6, 2026
7 checks passed
patrickleet added a commit that referenced this pull request Jun 6, 2026
…uards

- reject service/model names that yield invalid Rust identifiers, instead of
  emitting a crate that won't compile
- deploy Helm templates honor image.repository/tag rather than hardcoding
  :latest, so values.yaml/release automation actually drives the image
- dedupe Knative trigger names that normalize to the same metadata.name, which
  otherwise breaks `kubectl apply`
- fail fast when dependency builders (with_repo/with_read_model_store) run after
  handler/bus setup, which silently dropped registrations
- reject snapshot frequency 0 (would snapshot on every commit)
- correct outbox claim-timing wording: the row is claimed post-commit under a
  short lease, not within the commit transaction

Implements [[tasks/distributed-tooling-crate-extraction]]

Co-Authored-By: Claude Opus 4.8 <[email protected]>
patrickleet added a commit that referenced this pull request Jun 6, 2026
The PR #53 review (6885393) reworded the with_bus outbox path to say the row is
claimed *post-commit*. That described OutboxDispatcher::dispatch_ids — a separate
primitive used only by transport conformance tests. The actual with_bus path
(AggregateCommit::commit) claims each row in the commit transaction via claim_at
before commit_batch writes it (born InFlight under a short lease), then publishes
via publish_claimed after commit. Restore the accurate in-transaction wording in
the README and the with_bus doc comment.

Implements [[tasks/distributed-tooling-crate-extraction]]

Co-authored-by: Claude Opus 4.8 <[email protected]>
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