feat: add distributed project manifest primitives#53
Conversation
|
Looking for one thing? Review this PR in Change Stack to search files, summaries, diffs, and code without losing your place. Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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. ChangesDistributed Manifest and Metadata Serialization
Aggregate, Snapshot, and Outbox Integration
microsvc runtime and dependencies
Scaffolding and Tooling
Docs, examples, and tests
Estimated code review effort: 🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs:
✨ Finishing Touches🧪 Generate unit tests (beta)
|
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]>
There was a problem hiding this comment.
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
📒 Files selected for processing (43)
README.mdsrc/aggregate/mod.rssrc/aggregate/repository.rssrc/bus/run_options.rssrc/lib.rssrc/microsvc/dependencies.rssrc/microsvc/grpc.rssrc/microsvc/http.rssrc/microsvc/mod.rssrc/microsvc/runtime.rssrc/microsvc/service.rssrc/outbox/commit.rssrc/outbox/mod.rssrc/outbox_worker/bus_publisher.rssrc/outbox_worker/mod.rssrc/outbox_worker/outbox_source.rssrc/outbox_worker/publish_hook.rssrc/snapshot/mod.rssrc/snapshot/repository.rstests/distributed_read_model/checkout_saga_service/service.rstests/distributed_read_model/main.rstests/distributed_read_model/projection_service/service.rstests/distributed_read_model/seat_inventory_service/service.rstests/distributed_read_model_board/board_service/service.rstests/distributed_read_model_board/projections_service/mod.rstests/durable_enqueue_sqlite/main.rstests/kafka_transport/main.rstests/knative_cloudevents/main.rstests/microsvc/basic.rstests/microsvc/convention.rstests/microsvc/session.rstests/microsvc/transport_grpc.rstests/microsvc/transport_http.rstests/microsvc/transport_listen.rstests/microsvc/transport_subscribe.rstests/nats_transport/main.rstests/postgres_transport/main.rstests/rabbitmq_transport/main.rstests/sagas/microsvc_saga.rstests/snapshots/main.rstests/sourced_snapshot/main.rstests/transport_conformance/mod.rstests/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
…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]>
There was a problem hiding this comment.
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
📒 Files selected for processing (12)
Cargo.tomlREADME.mddistributed_tooling/Cargo.tomldistributed_tooling/src/generate/github.rsdistributed_tooling/src/generate/gitops.rsdistributed_tooling/src/generate/mod.rsdistributed_tooling/src/generate/names.rsdistributed_tooling/src/generate/service_crate.rsdistributed_tooling/src/lib.rssrc/lib.rssrc/outbox/commit.rssrc/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
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]>
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
.github/workflows/on-v-tag-publish.yaml
| # 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" |
There was a problem hiding this comment.
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-02Apply 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.
| # 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
…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]>
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]>
Summary
Tests
Notes
Summary by CodeRabbit
New Features
Documentation
Tests