Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e4a177f
feat: add contract catalog foundation
patrickleet Aug 3, 2026
705fa10
fix: harden contract audit boundaries
patrickleet Aug 3, 2026
7bce4ef
fix: close contract tooling boundary audit
patrickleet Aug 3, 2026
53da4c0
fix: close final contract tooling containment audit
patrickleet Aug 3, 2026
ae8b7fa
feat: generate migration inventory and protect history
patrickleet Aug 3, 2026
604e8e2
fix: harden migration inventory inputs and history checks
patrickleet Aug 3, 2026
5102c71
fix: redact migration paths and restore deterministic traversal
patrickleet Aug 3, 2026
1adcc19
feat: compile application composition contracts
patrickleet Aug 3, 2026
b3b1ff2
feat: split eligible and schema application roles on wire
patrickleet Aug 3, 2026
469450c
feat: compile DeploymentPlan with mount algebra and capabilities
patrickleet Aug 3, 2026
37aed48
feat: semantic snapshots and predecessor chain classification
patrickleet Aug 3, 2026
0344f6f
feat: ship distributed CLI with contracts check and accept
patrickleet Aug 3, 2026
9bd9741
feat: add CommandDispatcher with local and approved remote adapters
patrickleet Aug 3, 2026
ce76422
feat: add RuntimeHost bind for plan capability closure
patrickleet Aug 3, 2026
7ab0635
feat: start e2e-ui application composition root
patrickleet Aug 3, 2026
52f3052
feat: prefer GraphQL router with local CommandDispatcher
patrickleet Aug 3, 2026
cf146e6
feat: wire fail-fast contracts check into make, e2e, and PR CI
patrickleet Aug 3, 2026
4114d16
feat: client program descriptors and compatibility classification
patrickleet Aug 3, 2026
37d2602
feat: local contract-chain closeout without deploy/XR scope
patrickleet Aug 3, 2026
930548b
feat: collapse e2e-ui runner to one-screen host bootstrap
patrickleet Aug 3, 2026
4c41f42
refactor: split e2e-ui service into bounded-context modules
patrickleet Aug 3, 2026
d605395
refactor: drop e2e service.rs compatibility façade
patrickleet Aug 3, 2026
27dbec6
feat: pull e2e projection topology and workers into framework
patrickleet Aug 3, 2026
7a2db50
feat: auto-optimism and domain command transitions for e2e demos
patrickleet Aug 4, 2026
9f094c6
fix: align e2e optimism tests with thin blob move after rebase
patrickleet Aug 4, 2026
1bdb16c
fix: gate worker loops on Tokio features; use distributed CLI bin in …
patrickleet Aug 4, 2026
80f18a5
fix: restore surface_mismatch diagnostics and ungate typed dispatch test
patrickleet Aug 4, 2026
c760f98
fix: validate describe JSON as ApplicationManifest
patrickleet Aug 4, 2026
e94bf0e
fix: send eligible_roles/schema_roles on application surface wire
patrickleet Aug 4, 2026
e6cd768
fix: align CI tests with auto-optimism and thin Atomic move
patrickleet Aug 4, 2026
4cb4a24
fix: single-click complete in ordered-optimism Playwright test
patrickleet Aug 4, 2026
a855a70
fix: wait for Eventual seal on reopen in ordered-optimism test
patrickleet Aug 4, 2026
1086620
feat: pure reduce_known_record optimism for blob board moves
patrickleet Aug 4, 2026
367f191
feat: emit pureReduces + pures.ts through gen-client
patrickleet Aug 4, 2026
055a041
refactor(e2e-ui): admit user commands via session guards
patrickleet Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ existing unbounded-tech quality provider plus the integration/* jobs below —
| [`integration-js.yaml`](./integration-js.yaml) | yes | **This repo:** install, typecheck, test, build, and packed-consumer smoke test for `js/` |
| [`on-pr-quality.yaml`](./on-pr-quality.yaml) | entry | **This repo** PR gate (not the consumer quality contract) |
| [`on-push-main-version-and-tag.yaml`](./on-push-main-version-and-tag.yaml) | entry | **This repo** main → **vnext** tag |
| [`on-v-tag-publish.yaml`](./on-v-tag-publish.yaml) | entry | **This repo** crates.io + npm + `dctl` binary release |
| [`on-v-tag-publish.yaml`](./on-v-tag-publish.yaml) | entry | **This repo** crates.io + npm + `distributed` binary release |

**Version tagging (anywhere):** `unbounded-tech/workflow-vnext-tag`
**GitHub Release only (domain crates):** `unbounded-tech/workflow-simple-release`
Expand Down Expand Up @@ -136,4 +136,4 @@ quality contract.
## Out of scope (for now)

- crates.io publish reusable (framework still uses `unbounded-tech` publish helpers in `on-v-tag-publish.yaml`)
- image / GitOps promote (service scaffolds via `dctl`)
- image / GitOps promote (service scaffolds via `distributed`)
2 changes: 1 addition & 1 deletion .github/workflows/integration-distributed-cli.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: distributed_cli Integration Tests

# Reusable workflow: referenced via `uses: ./.github/workflows/integration-distributed-cli.yaml`.
# Runs the `dctl` integration tests, including the `#[ignore]`d manifest-harness
# Runs the `distributed` integration tests, including the `#[ignore]`d manifest-harness
# e2e tests (describe/schema) that compile a fixture service via nested cargo.
on:
workflow_call:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/integration-observability.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
kubeconform -v
- name: Scaffold service with metrics and tracing
run: |
cargo run -p distributed_cli --bin dctl -- scaffold observability-orders \
cargo run -p distributed_cli --bin distributed -- scaffold observability-orders \
--path target/tmp/scaffold-observability \
--store in-memory --transport http \
--metrics prometheus --tracing --gitops \
Expand Down
28 changes: 28 additions & 0 deletions .github/workflows/on-pr-quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,30 @@ concurrency:
cancel-in-progress: true

jobs:
contracts:
name: Run fail-fast contract lifecycle check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve PR merge-base
id: base
run: |
git fetch origin "${{ github.base_ref }}" --depth=1
base=$(git merge-base HEAD "origin/${{ github.base_ref }}" || true)
echo "sha=${base}" >> "$GITHUB_OUTPUT"
echo "merge-base=${base}"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run contracts check
run: |
if [ -f contracts/catalog.json ]; then
cargo run -p distributed_cli --quiet -- contracts check --root . --catalog contracts/catalog.json --output json
else
echo "No contracts/catalog.json yet; gate is present and ready for producers."
fi

# Framework monorepo keeps its existing quality provider. The reusable
# quality.yaml in this directory is the contract for *consumers* (domain
# crates / Distributed libraries), not for this workspace's entry pipeline.
Expand All @@ -34,16 +58,20 @@ jobs:
uses: ./.github/workflows/integration-kafka.yaml

distributed-cli:
needs: [contracts]
uses: ./.github/workflows/integration-distributed-cli.yaml

observability:
uses: ./.github/workflows/integration-observability.yaml

graphql:
needs: [contracts]
uses: ./.github/workflows/integration-graphql.yaml

e2e-ui:
needs: [contracts]
uses: ./.github/workflows/integration-e2e-ui.yaml

js-client:
needs: [contracts]
uses: ./.github/workflows/integration-js.yaml
6 changes: 3 additions & 3 deletions .github/workflows/on-v-tag-publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
manifest_path: distributed_macros/Cargo.toml
cargo_publish_args: "--locked"

# distributed_cli (the `dctl` binary + generation library) has no internal
# distributed_cli (the `distributed` binary + generation library) has no internal
# workspace dependencies, so it publishes independently of the macros/core crates.
publish-cli:
needs: release-preflight
Expand Down Expand Up @@ -116,6 +116,6 @@ jobs:
contents: write
uses: unbounded-tech/workflows-rust/.github/workflows/release.yaml@v2.3.0
with:
binary_name: dctl
binary_name: distributed
# Cargo.lock is intentionally untracked for this library workspace.
build_args: "--release --package distributed_cli --bin dctl"
build_args: "--release --package distributed_cli --bin distributed"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,6 @@ tests/workshop-service/ui/build/
tests/workshop-service/ui/.svelte-kit/
tests/workshop-service/**/target/
tests/workshop-service/*.db

# fixture crate build artifacts
tests/fixtures/**/target/
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ required-features = ["graphql", "sqlite"]

[features]
default = []
application-runtime = []
runtime = ["application-runtime"]
emitter = ["dep:event-emitter-rs"]
metrics = []
http = ["dep:axum", "dep:reqwest", "dep:tokio"]
Expand All @@ -48,6 +50,7 @@ graphql = ["dep:async-graphql", "dep:async-graphql-axum", "dep:hmac", "dep:jsonw

[dependencies]
async-nats = { version = "0.49", optional = true }
async-trait = "0.1"
async-graphql = { version = "7", optional = true }
async-graphql-axum = { version = "7", optional = true }
axum = { version = "0.8", optional = true }
Expand Down Expand Up @@ -77,6 +80,9 @@ tracing-opentelemetry = { version = "0.33", default-features = false, optional =
uuid = { version = "1", features = ["v7"] }

[build-dependencies]
serde = { version = "1.0.210", features = ["derive"] }
serde_json = "1.0.128"
sha2 = "0.10"
tonic-build = { version = "0.14", default-features = false, features = ["transport"] }

[dev-dependencies]
Expand Down
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ compose-up:

compose-down:
$(DOCKER_COMPOSE) down $(COMPOSE_DOWN_FLAGS)

.PHONY: contracts-check

## Read-only aggregate contract lifecycle check (never writes tracked files).
contracts-check:
$(CARGO) run -p distributed_cli --quiet -- contracts check --root . --catalog contracts/catalog.json --output human
61 changes: 32 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ stack of deliberate files. Start here:
│ │
│ @hops-ops/distributed ├── mutations → aggregates
│ causal replica + commands ├── Atomic rows (blob) with events
dctl-generated ops └── projector rows (todos, chat)
distributed-generated ops └── projector rows (todos, chat)
```

JS package deep-dive: [`js/README.md`](js/README.md).
Expand Down Expand Up @@ -93,7 +93,7 @@ e2e-ui boots **Zitadel** for the browser path; the three stacks prove the same
| GraphQL query service | Filters, order, pagination, relationships, RBAC, live subs, causal mutations |
| npm JS client | Artifacts, HTTP/WS transport, **causal replica**, diagnostics, SvelteKit/React |
| microsvc | One handler inventory on HTTP, gRPC, bus, GraphQL, or direct dispatch |
| `dctl` | Scaffold, SQL/Atlas/SDL, **client-manifest / client** codegen |
| `distributed` | Scaffold, SQL/Atlas/SDL, **client-manifest / client** codegen |

## Use as a Dependency

Expand Down Expand Up @@ -184,7 +184,7 @@ Most application crates should depend on `distributed` only. The proc macros
(`#[sourced]`, `#[digest]`, `#[derive(ReadModel)]`, `#[derive(Snapshot)]`) are
re-exported from `distributed`; do not add `distributed_macros` directly unless
you are working on the macro crate itself. The `distributed_cli` crate installs
the `dctl` tooling and is not needed as a runtime dependency unless you are
the `distributed` tooling and is not needed as a runtime dependency unless you are
embedding the CLI in another command such as `hops service`.

## Quick Start (library)
Expand Down Expand Up @@ -1515,7 +1515,7 @@ let loaded = repo
- **Internal loads:** PK-anchored includes —
`store.workspace().load(...).include(...).one()` (one-level, opt-in).
- **Schema lifecycle:** `ReadModelSchemaRegistry` + adapter for migration artifacts
and startup verification; `dctl schema` / `distributed_manifest()` for SQL.
and startup verification; `distributed schema` / `read_model_catalog()` for SQL.
- **Non-goals:** public query APIs belong on the GraphQL layer below (not the ORM
include loader); do not write projections outside the projection path.

Expand All @@ -1531,7 +1531,7 @@ This is the public query/command edge for full-stack apps. The companion
TypeScript package [`@hops-ops/distributed`](js/) (see
[`js/README.md`](js/README.md)) supplies transport, a normalized causal replica,
command runtime, diagnostics, and SvelteKit/React adapters. End-to-end template:
[`tests/e2e-ui/`](tests/e2e-ui/). Scaffold with `dctl scaffold … --query-api`.
[`tests/e2e-ui/`](tests/e2e-ui/). Scaffold with `distributed scaffold … --query-api`.
Example playground: `cargo run --example graphiql --features "graphql,sqlite"`.

### Enable
Expand All @@ -1546,7 +1546,7 @@ distributed = { version = "0.1", features = ["graphql", "postgres"] }

`graphql` implies `http` (Axum router, including `/graphql/ws`). SDL helpers under
`distributed::graphql::{naming,sdl}` compile without the feature so
`dctl schema --format graphql` works in tooling crates.
`distributed schema --format graphql` works in tooling crates.

### Scope

Expand Down Expand Up @@ -1591,7 +1591,7 @@ let service = Service::new()
// Optional: keep commands on GraphQL/bus/direct dispatch only.
.without_http_command_routes();

let engine = GraphqlEngine::from_manifest(&manifest, &repository)?
let engine = GraphqlEngine::from_schema_catalog(&manifest, &repository)?
// This exact executable inventory is the only mutation source.
.service(&service)
// Stable nonzero deployment secret shared by replicas of this endpoint.
Expand Down Expand Up @@ -1704,14 +1704,14 @@ GraphiQL is a **developer** tool. Default headers in the playground trust

```bash
# Optional human-readable GraphQL SDL artifact
dctl schema --format graphql --out schema.graphql
distributed schema --format graphql --out schema.graphql
git diff --exit-code schema.graphql # drift gate
```

The Rust `Service` inventory and GraphQL `Surface` IR are the source of truth
for schema, authorization, commands, optimistic effects, and client artifacts.
`dctl client-manifest` exports one role or named application surface, and
`dctl client` compiles that manifest with co-located `.graphql` operations into
`distributed client-manifest` exports one role or named application surface, and
`distributed client` compiles that manifest with co-located `.graphql` operations into
typed query/live/command modules. Common and elevated applications use separate
manifest entrypoints, document sets, generated directories, virtual modules,
and request-local replicas; an admin superset is never bundled into the common
Expand Down Expand Up @@ -1767,8 +1767,8 @@ make check-client # generated user/admin clients are current
Generate app clients from the Rust surface:

```bash
dctl client-manifest … # export role/app surface IR
dctl client … # compile co-located .graphql → typed modules
distributed client-manifest … # export role/app surface IR
distributed client … # compile co-located .graphql → typed modules
```

See [`js/README.md`](js/README.md) for package API and packaging.
Expand Down Expand Up @@ -1934,17 +1934,18 @@ A v1 event automatically chains through v1→v2→v3; a v2 event only goes throu
- **No stored data modified**: Upcasters are read-time transformations.
- **Zero overhead when unused**: Aggregates with no upcasters take the fast hydration path.

## Service CLI (`dctl`)
## Service CLI (`distributed`)

The [`distributed_cli`](distributed_cli/) crate ships `dctl` — tooling to scaffold
services, inspect a service's project manifest, and render schema artifacts. It is
The [`distributed_cli`](distributed_cli/) crate ships `distributed` — tooling to scaffold
services, inspect a service's logical application artifact, and render physical
read-model schema artifacts. It is
also a library, so `hops` mounts the same commands under `hops service` (anything
below as `dctl <cmd>` works as `hops service <cmd>`).
below as `distributed <cmd>` works as `hops service <cmd>`).

The CLI exists to keep the generated and handwritten parts of a back-end service
separate. A Distributed service should usually reduce to a small custom surface:
aggregate models, command/event handlers, read models, and the occasional
handwritten integration. The framework, macros, manifest, and CLI generate the
handwritten integration. The framework, macros, application artifacts, and CLI generate the
repeatable wiring around that surface.

That boundary matters for AI-assisted development. AI generation is
Expand All @@ -1957,9 +1958,9 @@ shapes. Boilerplate service setup, manifest discovery, schema output, and GitOps
artifacts stay deterministic.

```bash
cargo install distributed_cli # installs `dctl`
cargo install distributed_cli # installs `distributed`

dctl scaffold orders \
distributed scaffold orders \
--model order \
--read-models \
--command order.submit \
Expand All @@ -1972,8 +1973,8 @@ dctl scaffold orders \

cd orders
cargo test
dctl describe # print the project manifest as JSON
dctl schema --dialect postgres # render migration SQL from read models
distributed describe # print the ApplicationManifest as JSON
distributed schema --dialect postgres # render migration SQL from read models
```

Use the event-storming board as the input:
Expand Down Expand Up @@ -2004,26 +2005,28 @@ a **private** listener — unauthenticated by design.
applicable. Do **not** label metrics with `user_id`, `tenant_id`, free-form paths,
or raw command input (unknown commands bucket as `message=unknown`).

`describe`/`schema` compile your crate and call its `distributed_manifest()`
entrypoint (override with `--entrypoint`), which registers the [read
models](#read-models) and tables that define the schema:
`describe`/`schema` compile your crate and call explicit artifact entrypoints
(override with `--entrypoint`). `describe` reads the logical
`application_manifest()` owner; `schema` reads the separate
`read_model_catalog()` owner that registers the [read models](#read-models) and
tables defining physical schema:

```rust,ignore
pub fn distributed_manifest() -> distributed::DistributedProjectManifest {
distributed::DistributedProjectManifest::new("orders").read_model::<OrderView>()
pub fn read_model_catalog() -> distributed::ReadModelCatalog {
distributed::ReadModelCatalog::new("orders").read_model::<OrderView>()
}
```

### Apply schema in-cluster with Atlas

`dctl schema --format atlas` wraps the desired-state SQL into an `AtlasSchema`
`distributed schema --format atlas` wraps the desired-state SQL into an `AtlasSchema`
(`db.atlasgo.io/v1alpha1`) for the [ariga atlas-operator](https://github.com/ariga/atlas-operator),
so migrations apply declaratively in-cluster. The resource is written to
**stdout** — redirect it wherever you keep schema manifests (a file, or a separate
GitOps repo); `dctl` does not choose a location for it.
GitOps repo); `distributed` does not choose a location for it.

```bash
dctl schema --format atlas --name orders --db-secret orders-db > orders.schema.yaml
distributed schema --format atlas --name orders --db-secret orders-db > orders.schema.yaml
```

Use `--db-secret`/`--db-secret-key` for a Secret reference (GitOps-friendly) or
Expand Down
Loading
Loading