SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
c01fd1c verified
|
Raw History Blame Contribute Delete
36.4 kB
# cloud-forge
![tests](https://img.shields.io/badge/tests-362%20passing-brightgreen)
![license](https://img.shields.io/badge/license-AGPLv3%20%2F%20Commercial-blue)
![unsafe](https://img.shields.io/badge/unsafe-forbidden%20in%2039%2F39%20crates-brightgreen)
![rust](https://img.shields.io/badge/rust-2021%20edition-orange)
![status](https://img.shields.io/badge/status-phase%2016%20of%2046-yellow)
A from-first-principles cloud-resource substrate: the primitives every
AWS-shaped service (compute, storage, database, messaging, …) would
compose from, built for real before any service-shaped crate existed β€”
and, as of Phase 11, all four (`cloud-compute`, Phase 8; `cloud-storage`,
Phase 9; `cloud-database`, Phase 10; `cloud-messaging`, Phase 11)
actually composed from them, with Phase 12 (`cloud-orchestration`)
composing two of those services *together* for the first time, Phase 13
building out the IAM surface (`cloud-credentials`, `cloud-session`,
`cloud-policy-document`) `cloud-identity` deferred all the way back in
Phase 1, Phase 14 (`cloud-iam`) composing those three IAM primitives
into a fifth real service, and now Phase 15 giving `cloud-orchestration`
a second cross-service composition β€” `cloud-compute` + `cloud-messaging`
β€” with this workspace's first rollback spanning two independent
services. This is a **separate Cargo workspace** from the rest of this
repository's 100-crate `jxcl`/`pq-*` stack and from `verification-forge`
β€” nothing here depends on either, and neither
depends on this.
> **The non-negotiable rule:** do not create one crate per AWS service.
> Build the primitives once, then compose services from those
> primitives. See [`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md)
> for the full reasoning, the layering, and what's deliberately not
> built yet.
## What's here: Phase 1, the primitive kernel
This workspace currently implements **Phase 1** of a much larger
roadmap (a control plane, a service layer, and eventual AWS-shaped
services like compute/storage/database come later, in that order β€” see
the architecture doc's phase table). Phase 1 is the resource model
every later phase composes from:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-errors`](./crates/cloud-errors) | `CloudError`, the structured error every crate returns | 5 |
| [`cloud-types`](./crates/cloud-types) | `ResourceId`, `ResourceType`, `AccountId`, `RegionId`, `AzId`, `Arn`, `Timestamp` | 27 |
| [`cloud-tags`](./crates/cloud-tags) | A validated tag map: length limits, a reserved key prefix, a max tag count | 9 |
| [`cloud-lifecycle`](./crates/cloud-lifecycle) | The `Creating→Active→Updating→Deleting→Deleted`/`Failed` state machine | 8 |
| [`cloud-resource`](./crates/cloud-resource) | `Resource<T>`: id/type/region/owner/lifecycle/version/tags/timestamps + payload `T` | 7 |
| [`cloud-region`](./crates/cloud-region) | A region β†’ availability-zone registry | 5 |
| [`cloud-account`](./crates/cloud-account) | `Account` + a registry rejecting duplicate account ids | 5 |
| [`cloud-identity`](./crates/cloud-identity) | A minimal `Principal` (`User`/`Role`/`Service`) | 5 |
| [`cloud-policy`](./crates/cloud-policy) | Allow/Deny evaluation; **explicit deny always dominates** | 9 |
| [`cloud-events`](./crates/cloud-events) | An append-only, strictly-ordered event log | 6 |
| [`cloud-quota`](./crates/cloud-quota) | Limit/usage tracking; a rejected reservation commits nothing | 7 |
| [`cloud-core`](./crates/cloud-core) | A facade re-exporting all of the above, plus an integration test | 1 |
## What's here: Phase 2, the control plane
Phase 2 adds the pipeline that turns Phase 1's independent data types
into an actual "create a resource" operation β€” authorized, quota
checked, placed, constructed, and recorded, atomically:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-scheduler`](./crates/cloud-scheduler) | Least-loaded-AZ placement over a region's registered AZs (Phase 4 adds a capacity-aware variant) | 11 |
| [`cloud-service-registry`](./crates/cloud-service-registry) | Named control-plane services and their health (`Healthy`/`Unhealthy`/`Unknown`) | 7 |
| [`cloud-reconciler`](./crates/cloud-reconciler) | BFS over `cloud-lifecycle`'s transition graph: shortest path from a current to a desired state | 7 |
| [`cloud-provisioner`](./crates/cloud-provisioner) | The `AUTHORIZE→VALIDATE→PLAN→APPLY→VERIFY→AUDIT` pipeline; **rolls back every earlier stage's reservation if a later stage fails** | 6 |
| [`cloud-control-plane`](./crates/cloud-control-plane) | `ControlPlane<T>`: registries + policy + quota + events + a resource store, with real `create`/`get`/`list`/`delete` | 8 |
## What's here: Phase 3, AWS-like resource identification
Phase 3 gives every provisioned resource a canonical, globally
resolvable name β€” an `Arn` β€” and a registry that resolves it back to
the resource it names:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-resource-registry`](./crates/cloud-resource-registry) | An `Arn` ↔ `ResourceId` registry: rejects a second registration of an already-used ARN, and a resource claiming a second ARN | 7 |
The roadmap names four crates for this phase
(`cloud-arn`, `cloud-resource-id`, `cloud-resource-parser`,
`cloud-resource-registry`); three of the four already existed since
Phase 1 as `cloud_types::Arn`, `cloud_types::ResourceId`, and `Arn`'s
own `FromStr` impl β€” see
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for why.
`cloud-control-plane::create()` now computes and registers each new
resource's `Arn` right after provisioning succeeds, with the same
rollback discipline as every other stage: a failed ARN registration
releases the quota and placement reservations `cloud-provisioner`
already made. `resolve_arn()`/`arn_of()` expose the mapping in both
directions, and a resource's ARN stays resolvable even after it's
deleted (consistent with the CLOUD-I003 no-id-reuse invariant).
## What's here: Phase 4, compute primitives
Phase 4 builds the three primitives a compute-shaped resource needs
that nothing before it required: something to track whether it's
actually running, a pool of capacity to run it on, and something to
boot it from.
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-runtime`](./crates/cloud-runtime) | `RuntimeState`: the execution-state machine (`Pending`/`Running`/`Stopping`/`Stopped`/`Terminating`/`Terminated`) β€” deliberately separate from `cloud-lifecycle::Lifecycle` | 9 |
| [`cloud-capacity`](./crates/cloud-capacity) | Per-AZ vCPU/memory-MiB capacity; reservations fail closed against an unregistered AZ, unlike `cloud-quota`'s default-unlimited caps | 12 |
| [`cloud-image`](./crates/cloud-image) | A registry of machine images to launch from: validated id/name/size/architecture, immutable once registered | 7 |
`cloud-scheduler` also gains `place_least_loaded_with_capacity`,
composing `cloud-capacity` into placement so a resource is never
assigned to an AZ without room for it β€” the one real composition this
phase adds, kept generic (any resource type can ask for
capacity-aware placement) rather than folded into a compute-specific
crate that doesn't exist yet. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for why
`cloud-runtime` and `cloud-lifecycle` are two state machines rather
than one, and why `cloud-provisioner`/`cloud-control-plane` are
untouched by this phase.
## What's here: Phase 5, storage primitives
Compute (Phase 4) needed something to execute, a pool to run it in,
and something to boot it from; storage needs an entirely different
set of concepts, built with the same discipline:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-checksum`](./crates/cloud-checksum) | `Checksum`: a from-scratch CRC-32/ISO-HDLC content-integrity checksum, streaming or one-shot β€” anchored to the algorithm's own published check value, not just internal self-consistency | 8 |
| [`cloud-redundancy`](./crates/cloud-redundancy) | `RedundancyScheme`: replication or erasure coding, shard counts, reconstruction threshold, max tolerable loss | 9 |
| [`cloud-attachment`](./crates/cloud-attachment) | `AttachmentState`: the attach/detach state machine for a volume-like resource β€” a third state machine alongside `Lifecycle` and `RuntimeState`, with **no terminal state** (every state has a path back to `Detached`) | 9 |
`cloud-checksum` has zero dependencies, like every crate in this
workspace β€” CRC-32/ISO-HDLC is implemented directly rather than
pulled in from crates.io. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for why
that matters here specifically, why `AttachmentState` is a genuinely
different state machine from `Lifecycle`/`RuntimeState` rather than a
rename, and why no storage-capacity or storage-service crate exists
yet.
## What's here: Phase 6, database primitives
Compute needed execution state, capacity, and images (Phase 4);
storage needed integrity, durability, and attachment state (Phase 5);
database-shaped resources need a third, disjoint set of concepts:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-consistency`](./crates/cloud-consistency) | `ConsistencyLevel` (`Eventual`/`BoundedStaleness`/`Strong`): a total order (via `#[derive(Ord)]`, not a hand-rolled comparison) plus `satisfies()` against a caller's minimum requirement | 7 |
| [`cloud-migration`](./crates/cloud-migration) | `MigrationLedger`: enforces that schema migrations apply strictly sequentially and without gaps, starting at version 1 | 7 |
| [`cloud-retention`](./crates/cloud-retention) | `RetentionPolicy`: which backups/snapshots are eligible for deletion by age, with a floor that protects the most recent N regardless of age | 8 |
`cloud-migration` and `cloud-retention` deliberately don't depend on
each other β€” schema shape over time and data lifetime are unrelated
questions. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for why,
and for why no `cloud-database`, query engine, or storage engine
exists yet.
## What's here: Phase 7, messaging primitives
Compute, storage, and database (Phases 4-6) each needed their own
disjoint set of concepts; messaging-shaped resources need a fourth β€”
the last of the four service categories this README names before
AWS-shaped service composition begins:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-delivery`](./crates/cloud-delivery) | `DeliverySemantics` (`AtMostOnce`/`AtLeastOnce`/`ExactlyOnce`): a genuine **partial order** (two independent axes β€” loss, duplication), not a total one like `cloud-consistency` | 6 |
| [`cloud-visibility`](./crates/cloud-visibility) | `MessageLease`: the visibility-timeout mechanism that actually implements at-least-once delivery, plus a dead-letter threshold on receive count | 8 |
| [`cloud-fanout`](./crates/cloud-fanout) | `FanoutRegistry`: the topic-to-subscriber pub/sub topology | 8 |
`AtMostOnce` and `AtLeastOnce` are **incomparable** β€” neither
satisfies the other, since each permits something the other forbids β€”
which is why `cloud-delivery` checks both axes explicitly instead of
deriving `Ord` the way `cloud-consistency` (Phase 6) correctly does
for its own, genuinely total, order. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning, and for why no `cloud-queue`/`cloud-topic` or FIFO
ordering primitive exists yet.
## What's here: Phase 8, `cloud-compute` β€” the first real composed service
Phases 4-7 built sixteen primitive crates and, at every single one,
deferred composing them into anything. Phase 8 ends that deferral for
compute, the category with the most complete primitive set:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-compute`](./crates/cloud-compute) | `ComputeService`: `launch`/`transition_runtime`/`terminate`, composing eleven existing crates β€” no new primitive | 13 |
`ComputeService` deliberately does **not** reuse
`cloud-provisioner`/`cloud-control-plane` (Phase 2): those pipelines
place with `cloud_scheduler::place_least_loaded`, which knows nothing
about vCPU/memory, and teaching a generic pipeline about compute
capacity would be exactly the one-off special-casing this workspace's
rule exists to prevent. Instead `cloud-compute` runs its own
`AUTHORIZE -> VALIDATE -> PLAN -> APPLY`-shaped pipeline directly
against `cloud-capacity`/`cloud-image`/`cloud-runtime`, with the same
rollback discipline: a capacity-exhaustion failure at `PLAN` rolls back
the `VALIDATE`-stage quota reservation, tested directly. `RuntimeState`
lives in its own store, separate from `Resource<InstanceSpec>` β€” never
re-coupling what `cloud-runtime` (Phase 4) deliberately kept
independent. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning and for why storage/database/messaging remain
primitives without a composed service for now.
## What's here: Phase 9, `cloud-storage` β€” the second composed service
Phase 8's closing note named exactly this: the other three primitive
categories built in Phases 5-7 remain uncomposed. Phase 9 composes the
second β€” storage β€” following `cloud-compute`'s own shape:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-storage`](./crates/cloud-storage) | `StorageService`: `create_volume`/`transition_attachment`/`delete_volume`, composing nine existing crates β€” no new primitive | 13 |
Unlike `cloud-compute`, `StorageService` places nothing onto a
specific AZ β€” Phase 5 never built a `cloud-storage-capacity`
equivalent to `cloud-capacity`, so a volume's size is tracked as an
account-level `cloud-quota` reservation instead, the same pattern
`cloud-compute` uses for instance count. `delete_volume` adds this
workspace's first cross-primitive gate at the service level: it
refuses unless the volume's `cloud-attachment::AttachmentState` is
`Detached`, checked before any lifecycle reconciliation or quota
release β€” a real business rule (an in-use volume can't be deleted),
not a restatement of something a single primitive already enforces on
its own. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning, including why attachment targets aren't validated
against `cloud-compute`.
## What's here: Phase 10, `cloud-database` β€” the third composed service
The third of the four service categories gets composed, following
exactly the shape `cloud-compute` and `cloud-storage` established:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-database`](./crates/cloud-database) | `DatabaseService`: `create_database`/`apply_migration`/`create_snapshot`/`expire_snapshots`/`delete_database`, composing ten existing crates β€” no new primitive | 16 |
`cloud-retention` (Phase 6) finally gets a stateful caller: Phase 6
only computed which snapshots a policy would allow deleting, given a
list; `create_snapshot`/`expire_snapshots` are the first things in
this workspace that actually keep such a list and act on the answer β€”
resolving `cloud-storage`'s own Phase 9 deferral note. `delete_database`
refuses while any snapshot is still recorded, a cross-cutting gate like
`cloud-storage::delete_volume`'s, but reading this service's *own*
recorded state rather than a different primitive's state machine.
`apply_migration` is a thin pass-through to `MigrationLedger::apply`,
the same pattern `transition_runtime`/`transition_attachment` already
established. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning.
## What's here: Phase 11, `cloud-messaging` β€” the fourth and last composed service
The fourth and final service category gets composed, completing the
set `cloud-compute`, `cloud-storage`, and `cloud-database` began:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-messaging`](./crates/cloud-messaging) | `MessagingService`: `create_queue`/`create_topic`/`subscribe`/`publish`/`enqueue`/`receive`/`delete_message`/`delete_queue`/`delete_topic`, composing eleven existing crates β€” no new primitive | 24 |
Unlike the other three, `MessagingService` has two top-level resource
kinds instead of one: a queue, which holds messages directly as
`cloud-visibility::MessageLease`s, and a topic, which holds none of its
own β€” only a fan-out list of subscriber queues via
`cloud-fanout::FanoutRegistry`. `publish` is exactly the composition
`cloud-fanout`'s own Phase 7 doc comment named as deferred: fanning a
message out to every subscriber and giving each one a real
`MessageLease`, so `receive` gets at-least-once redelivery and
dead-lettering for free regardless of whether a message arrived by
direct `enqueue` or by fan-out. `subscribe` refuses to link a queue to
a topic whose required `cloud-delivery::DeliverySemantics` the queue's
own semantics don't satisfy β€” the same cross-primitive gate shape
`cloud-storage::delete_volume` (Phase 9) used, applied here to a new
operation. `delete_queue`/`delete_topic` each refuse based on their own
recorded state (a non-empty inbox; a remaining subscriber) β€” the same
own-state gate shape `cloud-database::delete_database` (Phase 10) used,
applied here twice. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning, including how `publish` avoids partial fan-out on a
message-id collision.
## What's here: Phase 12, `cloud-orchestration` β€” the first cross-service composition
Phases 8-11 each composed primitives *within* one service category.
Phase 12 composes two already-real **services** together for the first
time, resolving a deferral `cloud-storage` (Phase 9) stated in its own
words:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-orchestration`](./crates/cloud-orchestration) | `attach_volume`/`detach_volume`: free functions coordinating `ComputeService` and `StorageService` β€” no new state machine, no new primitive | 8 |
`attach_volume` takes a `&ComputeService` and a `&mut StorageService`
together, checks the target instance exists and isn't `Terminating`/
`Terminated`, and only then drives the volume's attachment transitions
β€” exactly the "orchestration concern for whatever future layer calls
both services" Phase 9's own doc comment named as deferred. An unknown
or terminated instance leaves the volume's `AttachmentState` completely
untouched. `cloud-orchestration` owns no resource store, quota, policy,
or event log of its own β€” it borrows both services for the duration of
one call and returns, the same shape `cloud-provisioner` (Phase 2)
already established for a pipeline with nothing to hold between calls.
`detach_volume` deliberately does not consult `compute` at all: force-detaching
a volume must stay possible even from an instance that's since been
terminated. See [`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md)
for the full reasoning, including why this phase, unlike every earlier
pipeline in this workspace, has no rollback path to write.
## What's here: Phase 13, the IAM surface
`cloud-identity`'s own Phase 1 doc comment named this explicitly:
"credentials, sessions, federation, policy documents ... is Phase 13
in the roadmap." Phase 13 builds that surface as three new primitive
crates β€” the same shape Phases 4-7 each used, primitives without a
service yet:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-credentials`](./crates/cloud-credentials) | `CredentialStore`: at most two `Active` credentials per principal, the real IAM rule AWS itself enforces | 9 |
| [`cloud-session`](./crates/cloud-session) | `SessionStore`: assumed-role and federated sessions, valid only while un-revoked and unexpired | 9 |
| [`cloud-policy-document`](./crates/cloud-policy-document) | `to_json`/`from_json`: a `cloud-policy::Policy` serialized to and parsed from JSON via a hand-rolled, schema-restricted parser | 11 |
`cloud-credentials` models a credential's lifecycle β€” id, principal,
`Active`/`Inactive` β€” with no real secret material at all, since secure
secret generation needs randomness this zero-dependency workspace
doesn't pull in; its one real business rule is AWS's own: a principal
may hold at most two `Active` credentials at once, and reactivating a
credential re-checks that same cap. `cloud-session`'s `Session::is_valid`
checks two independent invalidity paths β€” expiry and explicit revocation
β€” the same two-axis shape `cloud-visibility::MessageLease` (Phase 7)
already established; `SessionSource` covers both "sessions" and
"federation" from `cloud-identity`'s deferral in one type, since a
federated identity's session is still just a session with a different
origin. `cloud-policy-document` hand-rolls a JSON parser restricted to
exactly its fixed schema (strings, arrays, objects β€” no numbers,
booleans, or `null`), the same zero-dependency posture `cloud-checksum`
(Phase 5) upheld with hand-rolled CRC-32; `cloud-policy` itself gained
one small `statements()` accessor to make this possible, the same
precedent `cloud-scheduler` set gaining `place_least_loaded_with_capacity`
in Phase 4. See [`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md)
for the full reasoning, including why there's no `cloud-federation`
crate and no IAM service yet.
## What's here: Phase 14, `cloud-iam` β€” the fifth composed service
Phase 13 built the IAM surface as three disjoint primitives without a
service, the same shape Phases 4-7 used before their own service
phases arrived later. Phase 14 is that service:
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-iam`](./crates/cloud-iam) | `IamService`: `create_credential`/`assume_role`/`federate`/`revoke_session`/`authorize`/`load_policy_document`/`export_policy_document`, composing five existing crates β€” no new primitive | 12 |
Every composed service so far has called `Policy::authorize` before
mutating, but always to gate *another* service's resource operation.
`assume_role`/`federate` are the first operations anywhere in this
workspace where `cloud-policy` (Phase 1) governs something within the
IAM surface itself: a principal now needs permission to assume a role
or federate in at all, checked before any session is created, leaving
`cloud-session`'s store untouched on denial. `load_policy_document`
replaces the active policy immediately β€” every subsequent authorization
decision uses it β€” and a malformed document is a pure no-op, since the
assignment only happens after `cloud-policy-document::from_json`
already succeeded. Unlike every prior composed service, `IamService`
validates no account or region and reserves no quota, and registers no
`Arn`: real IAM is inherently global, and neither a credential nor a
session is region-scoped infrastructure with a canonical external name
β€” mirroring real IAM's own inconsistent resource model (a role has an
ARN; an access key does not), not a gap left to fill later. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning.
## What's here: Phase 15, `cloud-orchestration`'s second composition
Phase 12 gave `cloud-orchestration` its first cross-service
composition, resolving `cloud-storage`'s own Phase 9 deferral. Phase
15 gives it a second, in the same crate, resolving a deferral
`cloud-messaging` itself named in its own Phase 11 closing note: "what
remains deferred is composing these services *together* (e.g. a
compute instance's logs delivered through a queue)."
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-orchestration`](./crates/cloud-orchestration) | Adds `transition_runtime_and_notify`/`terminate_and_notify` (`cloud-compute` + `cloud-messaging`) alongside Phase 12's `attach_volume`/`detach_volume` | 13 |
Both new functions enqueue a notification message into `cloud-messaging`
*before* attempting the compute mutation, then delete that message if
the mutation fails β€” unlike Phase 12's `attach_volume`, which needed no
rollback at all (its second step was unconditionally valid once the
first succeeded), a `RuntimeState` transition genuinely can fail, and
`RuntimeState` transitions aren't generally reversible the way
`cloud-attachment`'s are. This is the first rollback anywhere in this
workspace that spans two independent services rather than undoing
steps within one. See
[`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for the
full reasoning, including why this composition still has no message
body, no notification on other operations, and no fan-out.
## What's here: Phase 16, `cloud-orchestration`'s third composition
Phase 15 composed `cloud-compute` with `cloud-messaging`. Phase 16
adds a third composition in the same crate: `cloud-database` +
`cloud-messaging`. [`apply_migration_and_notify`] and
[`delete_database_and_notify`] follow the same enqueue-first,
rollback-on-failure discipline as Phase 15, since database operations
like schema migrations can fail (an invalid version jump) and are
typically non-reversible (schema changes and deletions are one-way).
| Crate | Owns | Tests |
|---|---|---:|
| [`cloud-orchestration`](./crates/cloud-orchestration) | Adds `apply_migration_and_notify`/`delete_database_and_notify` alongside Phase 12's attach/detach and Phase 15's compute+messaging | 23 |
**367 tests pass** (`cargo test --workspace --release` from this
directory), all `cargo clippy --workspace --all-targets -- -D
warnings` clean, all `cargo fmt --all -- --check` clean.
## Deliberately skipped or deferred
- **`cloud-metadata`** isn't a separate crate β€” its proposed fields are
exactly `cloud-resource`'s `created_at`/`updated_at`/`version` and
`cloud-tags`'s `Tags`.
- **`resource-manager`, `lifecycle-manager`, `quota-manager`,
`policy-engine`, `account-manager`, `region-manager` are not separate
crates** β€” each would be a thin wrapper forwarding to a Phase 1
primitive, exactly the one-crate-per-name anti-pattern the
non-negotiable rule exists to prevent. `cloud-control-plane` holds
those primitives directly.
- **`control-api`** (a REST/gRPC layer) is deferred β€” the roadmap's own
execution order puts the API layer well after the control plane it
would expose.
- **Image deregistration is not implemented** β€” whether an in-use
image can be safely removed depends on resource-to-image references
`cloud-image` alone cannot see.
- **No `cloud-storage-capacity` crate exists yet** β€” no per-AZ
byte-capacity primitive to feed it; `cloud-storage` (Phase 9) tracks
volume size as an account-level `cloud-quota` reservation instead,
same as `cloud-compute` does for instance count.
- **`cloud-orchestration` only covers attach/detach, not database or
messaging cross-service composition** β€” this phase resolves exactly
the one deferral `cloud-storage` stated; wiring a database or a queue
to a compute instance is a different cross-service question with no
equivalent stated deferral yet.
- **`cloud-storage` still has no snapshots or `cloud-checksum`/
`cloud-retention` integration** β€” a volume has no content-integrity
check or backup lifecycle; `cloud-database` (Phase 10) is where a
snapshot concept first exists, since a database's point-in-time
backups made more sense there than on a raw block volume.
- **No erasure-coding encoder/decoder** β€” `cloud-redundancy` models
the arithmetic a real erasure code guarantees, not the encoding
itself.
- **No `cloud-table`/`cloud-index`/`cloud-query` crates, no query
language, and no execution/storage engine in `cloud-database`** β€”
`apply_migration` records that a version was applied without any
idea what SQL or transformation it represents, exactly as
`cloud-migration` itself (Phase 6) does not run migrations.
- **`create_snapshot` does not use `cloud-checksum`** β€” a snapshot is
just an id and a creation time; content-integrity checking would
need the snapshot to hold real bytes, which this phase's model
doesn't have.
- **`cloud-messaging` has no message body, and no per-message quota** β€”
a message is just a `ResourceId`, exactly as a `cloud-database`
snapshot (Phase 10) was just an id and a creation time; `"queues"`
and `"topics"` are quota'd at creation, but messages flowing through
them are not, the same pattern Phase 10 used for snapshots.
- **No message ordering (FIFO) guarantee** β€” `cloud-messaging` uses
`BTreeMap`'s key order for deterministic tests and listings, not as a
delivery-order promise.
- **`cloud-compute` has no instance resize, no attached volumes, and
no HTTP/gRPC API surface** β€” `terminate` releases exactly what
`launch` reserved; nothing about an instance's lifecycle beyond
launch/run/stop/terminate is modeled yet, and `control-api` remains
deferred from Phase 2's own reasoning.
- **All four originally-planned service categories are now composed,
one cross-service composition (`cloud-compute` + `cloud-messaging`)
was real as of Phase 15, and a second
(`cloud-database` + `cloud-messaging`) is real as of Phase 16.**
`cloud-database`/`cloud-compute` cross-service composition remains
an open question with no equivalent stated deferral yet.
- **`cloud-orchestration`'s notifications carry no message body, fire
on no operation besides a runtime transition or termination, and
fan out to exactly one queue** β€” extending any of these needs a
concrete motivating case, not speculation ahead of one.
- **`cloud-iam` links no credential to any session.** A long-term
credential and a short-term session are tracked independently;
exchanging one for the other the way real STS `GetSessionToken` does
needs secret material `cloud-credentials` doesn't have (see below).
- **No policy versioning or multiple named policies in `cloud-iam`.**
`load_policy_document` replaces the single active policy outright β€”
no history, no policy identifiers, no per-principal policy
attachment.
- **No real cryptographic secret material in `cloud-credentials`** β€” no
secret bytes, signing, or verification; secure secret generation
needs randomness this zero-dependency workspace doesn't pull in.
- **No `cloud-federation` crate** β€” federation is one `SessionSource`
variant in `cloud-session`, not a fourth crate, since a federated
identity's session is still just a session with a different origin.
- **`cloud-policy-document` doesn't support AWS's bare-string-or-array
polymorphism** for `Principal`/`Action`/`Resource` β€” one shape per
field (always an array, or the literal `"*"` for `Principal`) is all
this phase needs.
- **No resource-based policies** β€” a policy document attached to a
specific resource rather than a principal remains the Phase 13 (IAM)
concept `cloud-resource`'s own Phase 1 doc comment named as not
pulled forward into `Resource<T>`.
- **No AWS-named crate exists anywhere in this workspace.** `services/`
cannot start honestly until the primitives it would compose (compute,
storage, network β€” later phases) are themselves real.
## See it compose
Two tests are the best reads for how these primitives fit together:
- `cloud-core`'s
[`provisioning_a_resource_composes_every_phase_1_primitive`](./crates/cloud-core/src/lib.rs)
β€” registers a region and an account, authors a policy (and proves an
explicit deny beats a broader allow), creates a tagged resource and
walks it through its lifecycle while checking the version invariant
at every step, formats its internal ARN, records a creation event,
and hits a quota limit on a second reservation, all using only Phase
1 primitives directly.
- `cloud-provisioner`'s
[`apply_failure_rolls_back_both_quota_and_placement`](./crates/cloud-provisioner/src/lib.rs)
β€” the sharpest single test of Phase 2's actual claim: a pipeline
built from atomic parts is not itself atomic unless someone wires
the rollback, and this proves it's wired, not just asserted.
- `cloud-compute`'s
[`exhausting_capacity_fails_the_launch_and_reserves_no_quota`](./crates/cloud-compute/src/lib.rs)
β€” the same rollback claim one layer up, now across eleven composed
crates instead of `cloud-provisioner`'s five: a capacity-exhaustion
failure at `PLAN` still rolls back the `VALIDATE`-stage quota
reservation, even though neither of those stages is `cloud-provisioner`'s
own code anymore.
- `cloud-storage`'s
[`delete_volume_refuses_an_attached_volume`](./crates/cloud-storage/src/lib.rs)
β€” a different kind of composition test: not rollback, but a service
reading one primitive's current state (`cloud-attachment`) to gate
an operation on a different resource store entirely, and proving the
refusal leaves the earlier quota reservation untouched.
- `cloud-database`'s
[`expire_snapshots_removes_only_expired_ones_beyond_the_floor`](./crates/cloud-database/src/lib.rs)
β€” proves `cloud-retention`'s pure `eligible_for_deletion` computation
and a service's real, mutated snapshot list agree: the floor
protects the newest snapshot even though the older one is well past
its age window, and only the actually-expired one is removed from
the store.
- `cloud-messaging`'s
[`publish_with_a_colliding_message_id_leaves_no_partial_fanout`](./crates/cloud-messaging/src/lib.rs)
β€” proves the validate-before-apply discipline holds across a fan-out
to multiple subscribers, not just a single resource's creation: a
message id already present in one subscriber's inbox rejects the
whole `publish` call, and the *other* subscriber, which had room,
still never receives it.
- `cloud-orchestration`'s
[`attach_volume_rejects_a_terminated_instance`](./crates/cloud-orchestration/src/lib.rs)
β€” the first test in this workspace of a composition spanning two
independent services: it terminates a real `ComputeService` instance,
then proves `attach_volume` refuses to touch a `StorageService`
volume's `AttachmentState` at all on account of the *other* service's
state, leaving it exactly `Detached`.
- `cloud-policy-document`'s
[`multiple_statements_with_a_one_of_principal_round_trip`](./crates/cloud-policy-document/src/lib.rs)
β€” builds a real `cloud-policy::Policy` with an `Allow`-all statement
and a `Deny` scoped to specific principals, serializes it through the
hand-rolled `to_json`, parses it back with `from_json`, and asserts
full equality with the original β€” proving the parser and serializer
agree on every field this schema has, not just the fields easiest to
test.
- `cloud-iam`'s
[`exported_policy_document_round_trips_through_load`](./crates/cloud-iam/src/lib.rs)
β€” exports one service's active policy to JSON, loads it into a
*second*, independently-created `IamService` that started out
denying everything, and proves `assume_role` now succeeds there too β€”
showing `cloud-policy-document`'s round trip and `cloud-iam`'s
policy-gated session creation compose correctly together, not just
each in isolation.
- `cloud-orchestration`'s
[`transition_runtime_and_notify_rolls_back_the_message_on_an_invalid_transition`](./crates/cloud-orchestration/src/lib.rs)
β€” attempts an invalid `RuntimeState` jump through the composed
function, then proves the notification message it enqueued into a
real `MessagingService` queue moments earlier is gone: `receive`
on that exact message id comes back `NotFound`, showing the
rollback actually reached across into the other service's own
store rather than just returning an error.
## Building and testing
```
cd cloud-forge
cargo build --workspace
cargo test --workspace --release
cargo clippy --workspace --all-targets -- -D warnings
cargo fmt --all -- --check
```
Per-crate, e.g.:
```
cargo test -p cloud-policy --release
```
This workspace is independent of the root `Cargo.toml` and of
`verification-forge/` β€” building or testing either of those does not
touch `cloud-forge`, and vice versa. See the [repository root
README](../README.md) for how this fits into the rest of the project,
and [`docs/CLOUD_ARCHITECTURE.md`](./docs/CLOUD_ARCHITECTURE.md) for
the full roadmap beyond Phase 1.
## License
This workspace is dual-licensed, identically to the repository root:
the GNU Affero General Public License v3.0
([`LICENSE-AGPL`](./LICENSE-AGPL)), or a separately negotiated
commercial license ([`LICENSE-COMMERCIAL`](./LICENSE-COMMERCIAL), a
non-binding draft template). See [`LICENSE-NOTICE`](./LICENSE-NOTICE),
[`COPYRIGHT.md`](./COPYRIGHT.md), and [`TRADEMARKS.md`](./TRADEMARKS.md)
for the copyright holder, licensing contacts, and trademark terms. This
workspace carries its own copy of the license set so it can be
distributed independently of the root repository.