chore: initial scaffold — workspace, docs, blueprint
Sets up the PiCloud monorepo as a Cargo workspace organised around the
three-service architecture (manager / orchestrator / executor), each
backed by a *-core library crate so the same logic powers both the MVP
all-in-one `picloud` binary and the future split-process cluster mode.
* crates/shared, executor-core, orchestrator-core, manager-core
define the library surface and trait seams between the three
services (`ExecutorClient`, `ScriptResolver`, `ScriptRepository`).
* crates/picloud is the MVP entrypoint; serves /healthz on 8080
(override via PICLOUD_BIND).
* crates/picloud-{manager,orchestrator,executor} are skeleton
binaries that keep the crate boundaries honest until cluster
mode is built out in v1.3+.
* docs/git-workflow.md defines the trunk-based workflow:
short-lived branches, Conventional Commits, separate hotfix
flow with mandatory reproduction tests.
* CLAUDE.md captures the working rules for future Claude sessions.
Workspace passes `cargo fmt`, `cargo clippy -D warnings` (with
pedantic enabled), and `cargo test --workspace`. The all-in-one
binary responds on `/healthz` and `/`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
16
crates/shared/Cargo.toml
Normal file
16
crates/shared/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "picloud-shared"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
14
crates/shared/src/error.rs
Normal file
14
crates/shared/src/error.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
use thiserror::Error;
|
||||
|
||||
/// Root error type for things that genuinely cross crate boundaries.
|
||||
///
|
||||
/// Crate-specific errors (e.g. `executor_core::ExecError`) stay local. Only
|
||||
/// promote a variant to `Error` when more than one crate needs to match on it.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("script not found: {0}")]
|
||||
ScriptNotFound(crate::ScriptId),
|
||||
|
||||
#[error("invalid script source: {0}")]
|
||||
InvalidScript(String),
|
||||
}
|
||||
52
crates/shared/src/ids.rs
Normal file
52
crates/shared/src/ids.rs
Normal file
@@ -0,0 +1,52 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
macro_rules! id_type {
|
||||
($name:ident) => {
|
||||
#[derive(
|
||||
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
|
||||
)]
|
||||
#[serde(transparent)]
|
||||
pub struct $name(pub Uuid);
|
||||
|
||||
impl $name {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn into_inner(self) -> Uuid {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for $name {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Uuid> for $name {
|
||||
fn from(u: Uuid) -> Self {
|
||||
Self(u)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$name> for Uuid {
|
||||
fn from(id: $name) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
id_type!(ScriptId);
|
||||
id_type!(ExecutionId);
|
||||
id_type!(RequestId);
|
||||
13
crates/shared/src/lib.rs
Normal file
13
crates/shared/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Cross-cutting types used by every PiCloud crate.
|
||||
//!
|
||||
//! Keep this crate small. If something only one core needs, it belongs in
|
||||
//! that core's crate. Things here must be genuinely shared (IDs, the Script
|
||||
//! entity, error roots, transport DTOs).
|
||||
|
||||
pub mod error;
|
||||
pub mod ids;
|
||||
pub mod script;
|
||||
|
||||
pub use error::Error;
|
||||
pub use ids::{ExecutionId, RequestId, ScriptId};
|
||||
pub use script::Script;
|
||||
24
crates/shared/src/script.rs
Normal file
24
crates/shared/src/script.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::ScriptId;
|
||||
|
||||
/// A user-uploaded Rhai script and its execution configuration.
|
||||
///
|
||||
/// This is the canonical representation that flows between manager (storage),
|
||||
/// orchestrator (dispatch), and executor (run). It must stay serializable
|
||||
/// because in cluster mode it crosses process boundaries on every invocation.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Script {
|
||||
pub id: ScriptId,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub version: i32,
|
||||
pub source: String,
|
||||
|
||||
pub timeout_seconds: u32,
|
||||
pub memory_limit_mb: u32,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
Reference in New Issue
Block a user