#![doc = include_str!("../Documentation.md")]
#![forbid(unsafe_code)]
use kcode_k1_access_kmap::K1AccessKmap;
use kcode_k1_access_launch_nodes::{
AccessContext, AccessId, AccessPolicy, Authority, FilteredAuthorities, K1AccessLaunchNodes,
ModelId, ProfileId, TargetId, TargetName, TxId, UserId, ViewerSubject,
};
use kcode_k1_access_profiles::{K1AccessProfiles, ProfileColor, ProfileName};
use kcode_k1_bootstrap_identity::{BootstrapIdentity, prompt};
use kcode_k1_bootstrap_state::{
BeginRecord, BootstrapStatus, CompleteRecord, ImportedPackage as StatePackage, K1BootstrapState,
};
use kcode_k1_groups::{
ALL_MODELS, ALL_MODELS_MEMBER, ALL_USERS, Group, GroupId, GroupName, GroupRole, K1Groups,
};
use kcode_k1_invites::K1Invites;
use kcode_k1_launch_nodes::LaunchNodes;
use kcode_k1_rust_bootstrap_archive::{LoadedArchive, read};
use kcode_k1_rust_bootstrap_import::{PreparedPackage, import_all, prepare};
use kcode_k1_rust_projection::K1RustProjection;
use kcode_k1_users::{K1Users, NewUser, User};
use std::path::Path;
const GROUP_NAME: &str = "loom-devs";
const PROFILE_NAME: &str = "loom-devs";
const ROOT_TITLE: &str = "loom-devs Kmap Root";
const ROOT_HINT: &str = "The public starting point for the loom-devs group Kmap.";
const ROOT_NARRATIVE: &str = "This is the public root of the loom-devs group Kmap.";
const LAUNCH_TARGET: &str = "KmapLaunchNode";
pub struct BootstrapServices<'a> {
pub state: &'a K1BootstrapState,
pub invites: &'a K1Invites,
pub users: &'a K1Users,
pub groups: &'a K1Groups,
pub profiles: &'a K1AccessProfiles,
pub access_kmap: &'a K1AccessKmap,
pub access_launch_nodes: &'a K1AccessLaunchNodes,
pub launch_nodes: &'a LaunchNodes,
pub rust_projection: &'a K1RustProjection,
pub model: ModelId,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct BootstrapResult {
record: CompleteRecord,
completed_now: bool,
}
impl BootstrapResult {
pub fn record(&self) -> &CompleteRecord {
&self.record
}
pub const fn completed_now(&self) -> bool {
self.completed_now
}
}
pub fn ensure(root: &Path, services: BootstrapServices<'_>) -> Result<BootstrapResult, String> {
if let BootstrapStatus::Complete { record, .. } = services.state.status()? {
return Ok(BootstrapResult {
record,
completed_now: false,
});
}
let loaded = load_archive(root)?;
let identity = prompt(kcode_k1_terms::text().as_bytes())?;
let begin = BeginRecord::new(
loaded.sha256,
identity.username().to_owned(),
identity.full_name().to_owned(),
identity.public_key(),
);
services.state.begin(begin.clone())?;
let user = reconcile_user(services.invites, services.users, &identity)?;
let account_user_bytes = *user.user_id().as_tx_id().as_bytes();
let user_id = UserId::from_tx_id(TxId::from_bytes(account_user_bytes));
let group = reconcile_group(services.groups, user_id)?;
let group_id = group.id();
services
.groups
.set_model_membership(user_id, group_id, ALL_MODELS_MEMBER, true)?;
let policy = public_policy(group_id)?;
let profile_id = reconcile_profile(services.profiles, user_id, group_id, &policy)?;
let prepared = prepare(&loaded.archive, *group_id.txid().as_bytes())?;
let context = AccessContext::new(user_id, services.model, FilteredAuthorities::empty())?;
let root_id = reconcile_root(&services, &context, profile_id, &policy, group_id)?;
let imported = import_all(services.rust_projection, &prepared)?;
verify_inventory(&prepared, &imported)?;
let inventory = imported
.into_iter()
.map(|package| {
StatePackage::new(
package.logical_name().to_owned(),
package.version().clone(),
package.source_sha256(),
)
})
.collect::<Result<Vec<_>, _>>()?;
let record = CompleteRecord::new(
begin,
account_user_bytes,
*group_id.txid().as_bytes(),
*profile_id.txid().as_bytes(),
*root_id.txid().as_bytes(),
inventory,
)?;
services.state.complete(record.clone())?;
Ok(BootstrapResult {
record,
completed_now: true,
})
}
fn load_archive(root: &Path) -> Result<LoadedArchive, String> {
let loaded = read(&root.join("bootstrap/k1-rust-code.zip"))?;
loaded.archive.require_complete()?;
if loaded.archive.root_library != "loom" {
return Err("bootstrap archive root library is not loom".to_owned());
}
Ok(loaded)
}
fn reconcile_user(
invites: &K1Invites,
users: &K1Users,
identity: &BootstrapIdentity,
) -> Result<User, String> {
if let Some(existing) = users.find_by_username(identity.username())? {
require_matching_user(&existing, identity)?;
return Ok(existing);
}
let (_, invite) = invites.create()?;
let candidate = NewUser::new(
identity.username(),
identity.full_name(),
identity.public_key(),
kcode_k1_terms::REVISION,
kcode_k1_terms::sha256(),
identity.message_signature(),
)?;
let user = users.register(&invite, candidate)?;
require_matching_user(&user, identity)?;
Ok(user)
}
fn require_matching_user(user: &User, identity: &BootstrapIdentity) -> Result<(), String> {
let matches = user.username() == identity.username()
&& user.full_name() == identity.full_name()
&& user.public_key() == identity.public_key()
&& user.tos_revision() == kcode_k1_terms::REVISION
&& user.tos_digest() == kcode_k1_terms::sha256()
&& user.acceptance_signature() == identity.message_signature();
if matches {
Ok(())
} else {
Err("existing bootstrap Account conflicts with entered identity".to_owned())
}
}
fn reconcile_group(groups: &K1Groups, user: UserId) -> Result<Group, String> {
let mut matches = Vec::new();
for id in groups.groups_for_user(user)? {
if id.sentinel().is_none()
&& let Some(group) = groups.get(id)?
&& group.name().as_str() == GROUP_NAME
{
matches.push(group);
}
}
let group = match matches.len() {
0 => {
let revision = groups.create(user, GroupName::new(GROUP_NAME.to_owned())?)?;
groups
.get(revision.group_id())?
.ok_or_else(|| "created loom-devs group is unavailable".to_owned())?
}
1 => matches.pop().expect("one group"),
_ => {
return Err("multiple loom-devs groups are visible to the bootstrap user".to_owned());
}
};
if !group
.users()
.iter()
.any(|entry| entry.user_id() == user && entry.role() == GroupRole::Owner)
{
return Err("bootstrap user is not an Owner of loom-devs".to_owned());
}
Ok(group)
}
fn public_policy(group: GroupId) -> Result<AccessPolicy, String> {
AccessPolicy::new(
Authority::Group(group),
Vec::new(),
vec![
ViewerSubject::Group(ALL_USERS),
ViewerSubject::Group(ALL_MODELS),
],
)
}
fn reconcile_profile(
profiles: &K1AccessProfiles,
user: UserId,
group: GroupId,
policy: &AccessPolicy,
) -> Result<ProfileId, String> {
let mut exact = Vec::new();
let mut conflicting = false;
for profile in profiles.list_for_user(user)? {
if profile.name().as_str() != PROFILE_NAME {
continue;
}
let matches = profile.owner() == user
&& !profile.archived()
&& profile
.color()
.is_some_and(|color| color.as_str() == "amber")
&& profile.policy() == policy;
if matches {
exact.push(profile.profile_id());
} else if profile.policy().authority() == Authority::Group(group) {
conflicting = true;
}
}
if conflicting || exact.len() > 1 {
return Err("loom-devs Profile is ambiguous or conflicts with bootstrap policy".to_owned());
}
if let Some(profile) = exact.pop() {
return Ok(profile);
}
let revision = profiles.create(
user,
ProfileName::new(PROFILE_NAME.to_owned())?,
Some(ProfileColor::new("amber".to_owned())?),
policy.clone(),
)?;
Ok(revision.profile_id())
}
fn reconcile_root(
services: &BootstrapServices<'_>,
context: &AccessContext,
profile: ProfileId,
policy: &AccessPolicy,
group: GroupId,
) -> Result<AccessId, String> {
let target = TargetId::new(
Authority::Group(group),
TargetName::new(LAUNCH_TARGET.to_owned())?,
);
let root = if services.launch_nodes.get(&target)?.is_some() {
services.access_launch_nodes.lookup(context, &target)?
} else {
let revision = services.access_kmap.create_node(
context,
profile,
policy.clone(),
ROOT_TITLE.to_owned(),
ROOT_HINT.to_owned(),
ROOT_NARRATIVE.to_owned(),
Vec::new(),
)?;
let root = revision.access_id();
match services.access_launch_nodes.create(
context,
profile,
policy.clone(),
target.clone(),
root,
) {
Ok(()) => root,
Err(error) => {
let found = services
.access_launch_nodes
.lookup(context, &target)
.map_err(|_| error)?;
if found != root {
return Err(
"loom-devs launch-node binding conflicts with newly created root"
.to_owned(),
);
}
root
}
}
};
let node = services.access_kmap.get_node(context, root)?;
if node.title != ROOT_TITLE
|| node.navigation_hint != ROOT_HINT
|| node.narrative != ROOT_NARRATIVE
|| !node.connections.is_empty()
{
return Err("existing loom-devs Kmap root conflicts with bootstrap root".to_owned());
}
Ok(root)
}
fn verify_inventory(
prepared: &[PreparedPackage],
imported: &[kcode_k1_rust_bootstrap_import::ImportedPackage],
) -> Result<(), String> {
if prepared.len() != imported.len()
|| !prepared.iter().zip(imported).all(|(left, right)| {
left.logical_name() == right.logical_name()
&& left.version() == right.version()
&& left.source_sha256() == right.source_sha256()
})
{
return Err("imported Rust package inventory differs from prepared closure".to_owned());
}
Ok(())
}