app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
master
Drew Owens
Share frozen native Rust mechanical development checkpoint and qualification scope
b2d7cdb
27d ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
//! Exact-object document lifecycle. Journal permission is supplied separately by
//! the coordinator; these owner-thread methods never access storage or retry SDK effects.
use super::*;
use crate::mechanics::Inspection;
use fusion_mechanical_core::PreparedRequest;
use serde::Serialize;
use serde_json::{json, Value};
pub const MAX_OWNED_SCRATCH: usize = 8;
pub const CONTRACT: &str = "native-lifecycle-v1";
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ScratchProfile {
pub parametric: u32,
pub hybrid: u32,
pub components: u64,
pub bodies: u64,
pub sketches: u64,
pub occurrences: u64,
pub timeline: u64,
pub graphics: u64,
pub mesh_bodies: u64,
pub features: u64,
pub planes: u64,
pub axes: u64,
pub points: u64,
pub parameters: u64,
pub user_parameters: u64,
}
impl ScratchProfile {
fn allowed(&self, blank: bool) -> bool {
self.parametric == 1
&& self.hybrid == 1
&& self.components == 1
&& [
self.bodies,
self.sketches,
self.occurrences,
self.timeline,
self.graphics,
self.mesh_bodies,
self.features,
self.planes,
self.axes,
self.points,
]
.iter()
.all(|n| *n == 0)
&& self.parameters == self.user_parameters
&& self.parameters <= 512
&& (!blank || self.parameters == 0)
}
}
/// A backend publishes an available exact created lease even when acknowledgement
/// or later SDK work failed. It must not unwind after acquisition before returning
/// the lease; handle destructors never perform SDK work.
pub struct CreationAttempt<H> {
pub handle: Option<H>,
pub invoked: bool,
pub acknowledgement: Result<(), String>,
}
pub struct EffectAttempt {
pub invoked: bool,
pub acknowledgement: Result<bool, String>,
}
pub trait LifecycleSdk: DocumentSdk {
fn active_command(&self) -> Result<String, String>;
fn create_blank(&self) -> CreationAttempt<Self::Handle>;
fn make_parametric(&self, document: &Self::Handle) -> EffectAttempt;
fn scratch_profile(&self, document: &Self::Handle) -> Result<ScratchProfile, String>;
fn has_data_file(&self, document: &Self::Handle) -> Result<bool, String>;
fn inspect_mechanical(&self, document: &Self::Handle, id: &str) -> Result<Inspection, String>;
fn discard_document(&self, document: &Self::Handle) -> EffectAttempt;
fn activate_document(&self, document: &Self::Handle) -> EffectAttempt;
}
#[derive(Debug, Clone)]
pub struct CreationProof {
pub session: String,
pub operation_id: String,
pub request_digest: String,
pub document_id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum OwnedState {
Ready,
Closing,
Closed,
}
#[derive(Debug)]
pub(super) struct OwnedScratch {
serial: u64,
creation_operation: String,
digest: String,
original: Document,
state: OwnedState,
}
#[derive(Debug)]
enum Kind {
Create {
requested_name: String,
},
Discard {
document_id: String,
revision: String,
proof: CreationProof,
},
Activate {
document_id: String,
},
}
#[derive(Debug)]
pub struct Plan {
kind: Kind,
baseline: Vec<Document>,
session: String,
operation_id: String,
digest: String,
}
impl Plan {
pub fn observed_document(&self) -> Value {
match &self.kind {
Kind::Create { .. } => {
json!({"operationKind":"document_create","targetBefore":null,"baselineDocuments":self.baseline,"scope":CONTRACT})
}
Kind::Discard {
document_id,
revision,
..
} => {
json!({"documentId":document_id,"revision":revision,"scope":"owned_parameter_only_scratch_discard"})
}
Kind::Activate { document_id } => {
json!({"documentId":document_id,"scope":"exact_document_activation","expectedActiveDocumentId":self.baseline.iter().find(|d|d.active).and_then(|d|d.document_id.as_ref())})
}
}
}
}
pub fn is_lifecycle(operation: &str) -> bool {
matches!(
operation,
"new_design" | "native_scratch_discard" | "native_document_activate"
)
}
/// This runs before durable admission and SDK dispatch.
pub fn supported_arguments(request: &PreparedRequest) -> Result<(), String> {
let a = request.arguments();
if request.operation() == "new_design" {
if a.get("box").is_some() || a.get("target").is_some() {
return Err("native_blank_design_only".into());
}
let name = a
.get("name")
.and_then(Value::as_str)
.unwrap_or("Adom mechanical");
if name.is_empty() || name.chars().count() > 120 || name.contains('\0') {
return Err("invalid_requested_name".into());
}
}
Ok(())
}
fn refused(code: &str) -> Value {
json!({"success":false,"errorCode":code,"nativeExecutionStarted":false})
}
fn unknown(code: &str, id: Option<&str>) -> Value {
json!({"success":false,"errorCode":code,"outcome":"unknown","reconciliationRequired":true,"nativeExecutionStarted":true,"observedDocumentId":id})
}
fn same_metadata(a: &Document, b: &Document) -> bool {
a.document_id == b.document_id
&& a.name == b.name
&& a.creation_id == b.creation_id
&& a.saved == b.saved
&& a.modified == b.modified
}
fn stable_before(before: &[Document], after: &[Document]) -> bool {
before.len() == after.len()
&& before.iter().all(|a| {
after
.iter()
.any(|b| same_metadata(a, b) && a.active == b.active)
})
}
fn originals_preserved(
before: &[Document],
after: &[Document],
omit: Option<&str>,
extra: usize,
) -> bool {
let expected = before
.iter()
.filter(|d| d.document_id.as_deref() != omit)
.collect::<Vec<_>>();
after.len() == expected.len() + extra
&& expected
.iter()
.all(|a| after.iter().any(|b| same_metadata(a, b)))
}
impl<S: LifecycleSdk> Registry<S> {
/// Process-local ownership evidence only. No SDK calls, no document close,
/// and no authority to reconstruct these capabilities after restart.
pub fn owned_scratch_status(&self) -> Result<Value, String> {
let state = self.borrow()?;
Ok(
json!({"scope":"current_runtime_live_ownership_table","registryFaulted":self.faulted.get(),
"complete":!self.faulted.get(),"maxLifetimeEntries":MAX_OWNED_SCRATCH,
"entries":state.owned.iter().map(|entry|json!({"documentId":format!("d:{}:{:016x}",self.epoch,entry.serial),
"creationOperationId":entry.creation_operation,"state":match entry.state{OwnedState::Ready=>"owned",OwnedState::Closing=>"close_unconfirmed",OwnedState::Closed=>"closed"}})).collect::<Vec<_>>()}),
)
}
fn neutral(&self) -> Result<(), String> {
self.owner()?;
if self.sdk.active_command()? != "SelectCommand" {
return Err("native_lifecycle_command_not_neutral".into());
}
if self.faulted.get() {
return Err("document registry faulted".into());
}
Ok(())
}
fn lifecycle_entry<'a>(
&self,
state: &'a State<S::Handle>,
id: &str,
) -> Result<&'a Entry<S::Handle>, String> {
state
.entries
.iter()
.find(|e| format!("d:{}:{:016x}", self.epoch, e.serial) == id)
.ok_or("document identity is not current".into())
}
fn discard_guard(
&self,
state: &State<S::Handle>,
id: &str,
revision: &str,
proof: &CreationProof,
) -> Result<(), String> {
let entry = self.lifecycle_entry(state, id)?;
let owned = state
.owned
.iter()
.find(|o| o.serial == entry.serial)
.ok_or("no live owned scratch capability")?;
if owned.state != OwnedState::Ready
|| proof.session != self.epoch
|| proof.document_id != id
|| proof.operation_id != owned.creation_operation
|| proof.request_digest != owned.digest
{
return Err("owned scratch creation proof mismatch or capability spent".into());
}
let current = self.sdk.read(&entry.handle)?;
if !self.sdk.is_valid(&entry.handle)?
|| !current.active
|| current.saved
|| self.sdk.has_data_file(&entry.handle)?
|| current.name != owned.original.name
|| current.creation_id != owned.original.creation_id
|| !current.name.starts_with("Untitled")
{
return Err("owned scratch document state refused".into());
}
if !self.sdk.scratch_profile(&entry.handle)?.allowed(false) {
return Err("owned scratch model profile refused".into());
}
if self
.sdk
.inspect_mechanical(&entry.handle, id)?
.target
.revision
!= revision
{
return Err("target_revision_mismatch".into());
}
Ok(())
}
pub fn lifecycle_preflight(
&self,
request: &PreparedRequest,
proof: Option<CreationProof>,
) -> Result<Plan, String> {
supported_arguments(request)?;
let snapshot = self.refresh()?;
let state = self.borrow()?;
self.neutral()?;
if snapshot.documents.iter().filter(|d| d.active).count() != 1 {
return Err("unique_active_document_required".into());
}
let a = request.arguments();
let session = a["sessionId"]
.as_str()
.ok_or("session required")?
.to_owned();
if session != self.epoch {
return Err("session_mismatch".into());
}
let operation_id = a["operationId"]
.as_str()
.ok_or("operation required")?
.to_owned();
let kind = match request.operation() {
"new_design" => {
if state.entries.len() >= MAX_DOCUMENTS
|| state.owned.len() >= MAX_OWNED_SCRATCH
|| state.next == u64::MAX
{
return Err("native_lifecycle_capacity".into());
}
Kind::Create {
requested_name: a["name"].as_str().unwrap_or("Adom mechanical").to_owned(),
}
}
"native_scratch_discard" => {
let id = a["target"]["documentId"]
.as_str()
.ok_or("exact document target required")?
.to_owned();
let revision = a["target"]["revision"]
.as_str()
.ok_or("scoped revision required")?
.to_owned();
let proof = proof.ok_or("durable creation receipt required")?;
self.discard_guard(&state, &id, &revision, &proof)?;
Kind::Discard {
document_id: id,
revision,
proof,
}
}
"native_document_activate" => {
let id = a["target"]["documentId"]
.as_str()
.ok_or("exact document target required")?
.to_owned();
let expected = a["expectedActiveDocumentId"]
.as_str()
.ok_or("expected active identity required")?;
if snapshot
.documents
.iter()
.find(|d| d.active)
.and_then(|d| d.document_id.as_deref())
!= Some(expected)
{
return Err("active_document_mismatch".into());
}
let entry = self.lifecycle_entry(&state, &id)?;
if !self.sdk.is_valid(&entry.handle)? {
return Err("activation target invalid".into());
}
Kind::Activate { document_id: id }
}
_ => return Err("unsupported native lifecycle operation".into()),
};
self.neutral()?;
Ok(Plan {
kind,
baseline: snapshot.documents,
session,
operation_id,
digest: fusion_mechanical_journal::request_digest(request.canonical_bytes()),
})
}
/// Requires one durable execution permit from the coordinator. No plan clone,
/// no retry, no filesystem work, and no SDK lease leaves this registry.
pub fn lifecycle_execute(&self, plan: Plan) -> Value {
let fresh = match self.refresh() {
Ok(s) => s,
Err(_) => return refused("lifecycle_identity_unavailable"),
};
if plan.session != self.epoch || !stable_before(&plan.baseline, &fresh.documents) {
return refused("lifecycle_baseline_changed");
}
let mut state = match self.borrow() {
Ok(s) => s,
Err(_) => return refused("lifecycle_registry_unavailable"),
};
if self.neutral().is_err() {
return refused("native_lifecycle_command_not_neutral");
}
match plan.kind {
Kind::Create { requested_name } => self.create_owned(
&mut state,
&plan.baseline,
&plan.operation_id,
&plan.digest,
&requested_name,
),
Kind::Discard {
document_id,
revision,
proof,
} => self.discard_owned(&mut state, &plan.baseline, &document_id, &revision, &proof),
Kind::Activate { document_id } => {
self.activate_exact(&mut state, &plan.baseline, &document_id)
}
}
}
fn create_owned(
&self,
state: &mut State<S::Handle>,
before: &[Document],
operation: &str,
digest: &str,
name: &str,
) -> Value {
if state.entries.len() >= MAX_DOCUMENTS
|| state.owned.len() >= MAX_OWNED_SCRATCH
|| state.next == u64::MAX
{
return refused("native_lifecycle_capacity");
}
// Reserve storage before any SDK call can acquire or create an object.
state.entries.reserve(1);
state.owned.reserve(1);
let serial = state.next;
state.next += 1;
let id = format!("d:{}:{serial:016x}", self.epoch);
let mut guard = FaultGuard {
faulted: &self.faulted,
armed: true,
};
let attempt = self.sdk.create_blank();
let has_handle = attempt.handle.is_some();
if let Some(handle) = attempt.handle {
state.entries.push(Entry { handle, serial });
}
if !attempt.invoked {
if has_handle {
return unknown("create_lease_without_invocation", Some(&id));
}
guard.armed = false;
return refused("document_create_not_invoked");
}
if attempt.acknowledgement.is_err() || !has_handle {
return unknown(
"document_create_unconfirmed",
has_handle.then_some(id.as_str()),
);
}
let observed = (|| -> Result<Value, String> {
let entry = state.entries.last().unwrap();
for existing in &state.entries[..state.entries.len() - 1] {
if self.sdk.same(&existing.handle, &entry.handle)? {
return Err("creation returned an existing document".into());
}
}
if !self.sdk.is_valid(&entry.handle)? {
return Err("created document invalid".into());
}
let mut document = self.sdk.read(&entry.handle)?;
document.document_id = Some(id.clone());
if !document.active
|| document.saved
|| self.sdk.has_data_file(&entry.handle)?
|| !document.name.starts_with("Untitled")
{
return Err("created document state refused".into());
}
self.neutral()?;
let changed = self.sdk.make_parametric(&entry.handle);
if !changed.invoked || changed.acknowledgement != Ok(true) {
return Err("parametric setup unconfirmed".into());
}
let profile = self.sdk.scratch_profile(&entry.handle)?;
if !profile.allowed(true) {
return Err("created design is not a blank parametric hybrid".into());
}
let inspection = self.sdk.inspect_mechanical(&entry.handle, &id)?;
let after = self.refresh_inner(state)?;
if !originals_preserved(before, &after.documents, None, 1) {
return Err("original document metadata or membership changed".into());
}
let document = after
.documents
.into_iter()
.find(|d| d.document_id.as_deref() == Some(&id))
.ok_or("created exact reference not adopted")?;
if !document.active || document.saved {
return Err("created document state changed".into());
}
self.neutral()?;
state.owned.push(OwnedScratch {
serial,
creation_operation: operation.into(),
digest: digest.into(),
original: document.clone(),
state: OwnedState::Ready,
});
Ok(
json!({"success":true,"nativeExecutionStarted":true,"data":{"target":inspection.target,"document":document,"inspection":inspection,
"requestedName":name,"requestedNameScope":"session_and_operation_receipt_metadata","creationOperationId":operation,
"ownedScratch":true,"cleanupProfile":"parameter_only_current_session","legacyParity":false}}),
)
})();
match observed {
Ok(value) => {
guard.armed = false;
value
}
Err(_) => unknown("created_document_readback_unconfirmed", Some(&id)),
}
}
fn discard_owned(
&self,
state: &mut State<S::Handle>,
before: &[Document],
id: &str,
revision: &str,
proof: &CreationProof,
) -> Value {
if self.discard_guard(state, id, revision, proof).is_err() {
return refused("owned_scratch_guard_refused");
}
if self.neutral().is_err() {
return refused("native_lifecycle_command_not_neutral");
}
let serial = match self.lifecycle_entry(state, id) {
Ok(e) => e.serial,
Err(_) => return refused("owned_scratch_not_current"),
};
state
.owned
.iter_mut()
.find(|o| o.serial == serial)
.unwrap()
.state = OwnedState::Closing;
let mut guard = FaultGuard {
faulted: &self.faulted,
armed: true,
};
let observed = (|| -> Result<Value, String> {
let entry = self.lifecycle_entry(state, id)?;
let attempt = self.sdk.discard_document(&entry.handle);
if !attempt.invoked || attempt.acknowledgement != Ok(true) {
return Err("close unconfirmed".into());
}
if self.sdk.is_valid(&entry.handle)? {
return Err("closed document remains valid".into());
}
let after = self.refresh_inner(state)?;
if after
.documents
.iter()
.any(|d| d.document_id.as_deref() == Some(id))
|| !originals_preserved(before, &after.documents, Some(id), 0)
{
return Err("closed roster is not exact".into());
}
state
.owned
.iter_mut()
.find(|o| o.serial == serial)
.unwrap()
.state = OwnedState::Closed;
Ok(
json!({"success":true,"nativeExecutionStarted":true,"data":{"closedDocumentId":id,"closeReturned":true,"createdObjectValid":false,
"originalDocumentMetadataUnchanged":true,"activeDocumentId":after.documents.iter().find(|d|d.active).and_then(|d|d.document_id.as_ref()),"automaticActivationPerformed":false}}),
)
})();
match observed {
Ok(value) => {
guard.armed = false;
value
}
Err(_) => unknown("owned_scratch_close_unconfirmed", Some(id)),
}
}
fn activate_exact(&self, state: &mut State<S::Handle>, before: &[Document], id: &str) -> Value {
let entry = match self.lifecycle_entry(state, id) {
Ok(e) => e,
Err(_) => return refused("activation_target_not_current"),
};
let mut guard = FaultGuard {
faulted: &self.faulted,
armed: true,
};
let attempt = self.sdk.activate_document(&entry.handle);
if !attempt.invoked || attempt.acknowledgement != Ok(true) {
return unknown("document_activation_unconfirmed", Some(id));
}
match self.refresh_inner(state) {
Ok(after)
if originals_preserved(before, &after.documents, None, 0)
&& after
.documents
.iter()
.find(|d| d.active)
.and_then(|d| d.document_id.as_deref())
== Some(id)
&& !self.faulted.get() =>
{
guard.armed = false;
json!({"success":true,"nativeExecutionStarted":true,"data":{"activatedDocumentId":id,"activeIdentityVerified":true}})
}
_ => unknown("document_activation_readback_unconfirmed", Some(id)),
}
}
}
#[cfg(test)]
pub(crate) mod tests;