P0: workspace scaffold, CI gates, tc-domain, tc-config, tc-db vs real Postgres
- Cargo workspace with 1250-line and no-placeholder CI gates wired first - tc-domain: id newtypes, SessionKey codec (proptest round-trip), Role, GatedCategory (spec §15), AccessPolicy, core entities - tc-config: figment TOML+env config, DeployTarget/provider/auth selection with semantic validation - migrations/0001: full spec §14 schema incl. DB-enforced append-only audit_log - tc-db: compile-time-checked sqlx repos (workspaces, users, agents+policies, credits, audit) with committed .sqlx offline metadata - tc-testkit: per-test real-Postgres databases (testcontainers or TC_TEST_DATABASE_URL), embedded migrations Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use tc_domain::{AccessPolicy, AgentId, AgentScope, GatedCategory, HumanScope, Role, UserId};
|
||||
|
||||
#[test]
|
||||
fn ids_display_as_uuid_and_parse_back() {
|
||||
let id = AgentId::new();
|
||||
let parsed = AgentId::from_str(&id.to_string()).unwrap();
|
||||
assert_eq!(parsed, id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_ids_are_unique_and_v7() {
|
||||
let a = UserId::new();
|
||||
let b = UserId::new();
|
||||
assert_ne!(a, b);
|
||||
assert_eq!(a.as_uuid().get_version_num(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distinct_id_types_serialize_as_plain_uuid_strings() {
|
||||
let id = AgentId::new();
|
||||
let json = serde_json::to_string(&id).unwrap();
|
||||
assert_eq!(json, format!("\"{id}\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn role_serde_uses_lowercase() {
|
||||
assert_eq!(serde_json::to_string(&Role::Owner).unwrap(), "\"owner\"");
|
||||
assert_eq!(serde_json::to_string(&Role::Member).unwrap(), "\"member\"");
|
||||
let r: Role = serde_json::from_str("\"owner\"").unwrap();
|
||||
assert_eq!(r, Role::Owner);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gated_categories_cover_spec_section_15() {
|
||||
// The six gated categories are fixed by spec §15; serde names are the
|
||||
// wire contract with the frontend approval queue.
|
||||
let all = GatedCategory::ALL;
|
||||
assert_eq!(all.len(), 6);
|
||||
let names: Vec<String> = all
|
||||
.iter()
|
||||
.map(|c| serde_json::to_string(c).unwrap())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
names,
|
||||
vec![
|
||||
"\"outbound_message\"",
|
||||
"\"secret_sharing\"",
|
||||
"\"access_change\"",
|
||||
"\"financial_transaction\"",
|
||||
"\"file_deletion\"",
|
||||
"\"infra_access_grant\"",
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_access_policy_is_entire_team_and_any_claw() {
|
||||
let policy = AccessPolicy::default();
|
||||
assert_eq!(policy.humans, HumanScope::EntireTeam);
|
||||
assert_eq!(policy.agents, AgentScope::Any);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn specific_scopes_carry_member_lists() {
|
||||
let user = UserId::new();
|
||||
let agent = AgentId::new();
|
||||
let policy = AccessPolicy {
|
||||
humans: HumanScope::Specific(vec![user]),
|
||||
agents: AgentScope::Specific(vec![agent]),
|
||||
};
|
||||
let json = serde_json::to_value(&policy).unwrap();
|
||||
assert_eq!(json["humans"]["mode"], "specific");
|
||||
assert_eq!(json["humans"]["ids"][0], user.to_string());
|
||||
assert_eq!(json["agents"]["mode"], "specific");
|
||||
assert_eq!(json["agents"]["ids"][0], agent.to_string());
|
||||
let back: AccessPolicy = serde_json::from_value(json).unwrap();
|
||||
assert_eq!(back, policy);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entire_team_scope_serializes_with_mode_tag() {
|
||||
let policy = AccessPolicy::default();
|
||||
let json = serde_json::to_value(&policy).unwrap();
|
||||
assert_eq!(json["humans"]["mode"], "entire_team");
|
||||
assert_eq!(json["agents"]["mode"], "any");
|
||||
}
|
||||
Reference in New Issue
Block a user