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:
Omar Sobh
2026-06-09 22:25:47 -05:00
co-authored by Claude Fable 5
commit 0afb359183
49 changed files with 7171 additions and 0 deletions
+88
View File
@@ -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");
}