Checkpoint source 7 workflows and in-progress diagram authoring redesign
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
//! Real HTTP checks for caller-instance identity and operation allocation identity.
|
||||
use super::*;
|
||||
use oa_core::{
|
||||
ContextualLinkPurpose, Element, OccurrenceBody, OccurrenceEndpoint, OperationUse, Relation,
|
||||
RelationBody, SharedBody, language, persistence,
|
||||
source_project::{SourceBundle, compile_bundle},
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn operation_id(n: u128) -> Id {
|
||||
Id::from_u128(0x52060906000040008000000000000000 + n)
|
||||
}
|
||||
|
||||
fn occurrence(model: &mut Project, owner: Id, name: &str, body: OccurrenceBody) -> Id {
|
||||
let mut element = model
|
||||
.make_element(Kind::Occurrence, Layer::Oa, name, Some(owner))
|
||||
.unwrap();
|
||||
*element.shared_mut().unwrap() = SharedBody::Occurrence {
|
||||
body,
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
};
|
||||
let id = element.id;
|
||||
model.elements.insert(id, element);
|
||||
model
|
||||
.elements
|
||||
.get_mut(&owner)
|
||||
.unwrap()
|
||||
.steps_mut()
|
||||
.unwrap()
|
||||
.push(id);
|
||||
id
|
||||
}
|
||||
|
||||
fn select_operation(model: &Project, allocation: Id) -> Element {
|
||||
let mut element = model.elements[&operation_id(43)].clone();
|
||||
let Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { operation, .. },
|
||||
..
|
||||
}) = element.shared_mut()
|
||||
else {
|
||||
panic!("fixture Call");
|
||||
};
|
||||
*operation = Some(OperationUse {
|
||||
allocation: Some(allocation),
|
||||
});
|
||||
element
|
||||
}
|
||||
|
||||
struct Fixture {
|
||||
model: Project,
|
||||
bundle: SourceBundle,
|
||||
definition: Id,
|
||||
r1: Id,
|
||||
r2: Id,
|
||||
link: Id,
|
||||
}
|
||||
|
||||
fn fixture() -> Fixture {
|
||||
// Frozen source6 absence probe contains two parameterless operations on one
|
||||
// concrete boundary. Its old bytes remain compatibility inputs.
|
||||
let mut model = persistence::decode(include_bytes!(
|
||||
"../../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
))
|
||||
.unwrap();
|
||||
let selected = select_operation(&model, operation_id(30));
|
||||
model.elements.insert(selected.id, selected);
|
||||
let caller = model
|
||||
.add_element(Kind::Process, Layer::Oa, "Caller", None)
|
||||
.unwrap();
|
||||
let definition = model
|
||||
.add_element(Kind::Process, Layer::Oa, "Reusable assessment", None)
|
||||
.unwrap();
|
||||
let work = model
|
||||
.add_element(Kind::Activity, Layer::Oa, "Assess", None)
|
||||
.unwrap();
|
||||
let leaf = occurrence(
|
||||
&mut model,
|
||||
definition,
|
||||
"Assessment contribution",
|
||||
OccurrenceBody::Action {
|
||||
action: Some(work),
|
||||
lifeline: None,
|
||||
},
|
||||
);
|
||||
let x = occurrence(
|
||||
&mut model,
|
||||
caller,
|
||||
"Caller check",
|
||||
OccurrenceBody::Action {
|
||||
action: Some(work),
|
||||
lifeline: None,
|
||||
},
|
||||
);
|
||||
let r1 = occurrence(
|
||||
&mut model,
|
||||
caller,
|
||||
"First assessment",
|
||||
OccurrenceBody::SequenceReference {
|
||||
sequence: Some(definition),
|
||||
},
|
||||
);
|
||||
let r2 = occurrence(
|
||||
&mut model,
|
||||
caller,
|
||||
"Second assessment",
|
||||
OccurrenceBody::SequenceReference {
|
||||
sequence: Some(definition),
|
||||
},
|
||||
);
|
||||
let link = Id::new_v4();
|
||||
model.relations.insert(
|
||||
link,
|
||||
Relation {
|
||||
id: link,
|
||||
alias: "qualified_assessment".into(),
|
||||
name: "Check the first assessment".into(),
|
||||
description: "Caller intent survives source and REST editing.".into(),
|
||||
body: RelationBody::ContextualLink {
|
||||
owner: caller,
|
||||
source: OccurrenceEndpoint {
|
||||
references: vec![],
|
||||
occurrence: x,
|
||||
},
|
||||
target: OccurrenceEndpoint {
|
||||
references: vec![r1],
|
||||
occurrence: leaf,
|
||||
},
|
||||
purpose: ContextualLinkPurpose::Precedence {
|
||||
guard: "check accepted".into(),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
let source =
|
||||
language::format(&model).replacen("{\n", "{\n include \"rationale.arcadia\";\n", 1);
|
||||
let bundle = SourceBundle {
|
||||
entry: "assembly.arcadia".into(),
|
||||
files: BTreeMap::from([
|
||||
("assembly.arcadia".into(), source),
|
||||
(
|
||||
"rationale.arcadia".into(),
|
||||
"// Authored rationale: retain 雨 and CRLF.\r\n".into(),
|
||||
),
|
||||
]),
|
||||
};
|
||||
assert_eq!(
|
||||
compile_bundle(&bundle, Default::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project,
|
||||
model
|
||||
);
|
||||
Fixture {
|
||||
model,
|
||||
bundle,
|
||||
definition,
|
||||
r1,
|
||||
r2,
|
||||
link,
|
||||
}
|
||||
}
|
||||
|
||||
async fn transact(
|
||||
server: &TestServer,
|
||||
stored: &StoredProject,
|
||||
commands: Vec<Command>,
|
||||
) -> reqwest::Response {
|
||||
server
|
||||
.client
|
||||
.post(server.url(&format!("/projects/{}/transactions", stored.resource_id)))
|
||||
.header("if-match", stored.etag())
|
||||
.json(&json!({"commands": commands}))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn unchanged(server: &TestServer, before: &StoredProject, disk: &Path, bytes: &[u8]) {
|
||||
let after = server.get(before.resource_id).await;
|
||||
assert_eq!(after.model, before.model);
|
||||
assert_eq!(after.source_project, before.source_project);
|
||||
assert_eq!(after.revision, before.revision);
|
||||
assert_eq!(after.etag(), before.etag());
|
||||
assert_eq!(std::fs::read(disk).unwrap(), bytes);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn source7_http_instance_and_operation_edits_preserve_identity_source_and_restart() {
|
||||
let f = fixture();
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let server = TestServer::start(directory.path(), None).await;
|
||||
let response = server
|
||||
.client
|
||||
.post(server.url("/projects/import-source-project"))
|
||||
.json(&f.bundle)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
let status = response.status();
|
||||
let body: Value = response.json().await.unwrap();
|
||||
assert_eq!(status, StatusCode::CREATED, "{body}");
|
||||
let id: Id = body["resource_id"].as_str().unwrap().parse().unwrap();
|
||||
let before = server.get(id).await;
|
||||
assert_eq!(before.model, f.model);
|
||||
assert_eq!(before.source_project, f.bundle);
|
||||
let queried: Value = server
|
||||
.client
|
||||
.get(server.url(&format!("/projects/{id}/relations?kind=ContextualLink")))
|
||||
.send()
|
||||
.await
|
||||
.unwrap()
|
||||
.error_for_status()
|
||||
.unwrap()
|
||||
.json()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(queried["items"].as_array().unwrap().len(), 1);
|
||||
assert_eq!(queried["items"][0]["id"], f.link.to_string());
|
||||
|
||||
// Only two explicit records change: caller-instance address and allocation.
|
||||
// The shared procedure, both reference instances and operation items persist.
|
||||
let mut relation = before.model.relations[&f.link].clone();
|
||||
if let RelationBody::ContextualLink { target, .. } = &mut relation.body {
|
||||
target.references = vec![f.r2];
|
||||
}
|
||||
let selected = select_operation(&before.model, operation_id(31));
|
||||
let response = transact(
|
||||
&server,
|
||||
&before,
|
||||
vec![
|
||||
Command::EditRelation(f.link, relation.clone()),
|
||||
Command::EditElement(selected.id, selected.clone()),
|
||||
],
|
||||
)
|
||||
.await;
|
||||
let status = response.status();
|
||||
let body: Value = response.json().await.unwrap();
|
||||
assert_eq!(status, StatusCode::OK, "{body}");
|
||||
let after = server.get(id).await;
|
||||
let mut expected = before.model.clone();
|
||||
expected.relations.insert(f.link, relation);
|
||||
expected.elements.insert(selected.id, selected);
|
||||
assert_eq!(after.model, expected);
|
||||
assert_eq!(after.revision, before.revision + 1);
|
||||
assert_eq!(
|
||||
after.source_project.files["rationale.arcadia"],
|
||||
f.bundle.files["rationale.arcadia"]
|
||||
);
|
||||
assert_eq!(
|
||||
compile_bundle(&after.source_project, Default::default())
|
||||
.unwrap()
|
||||
.decoded
|
||||
.project,
|
||||
expected
|
||||
);
|
||||
assert_eq!(
|
||||
after.model.elements[&f.definition],
|
||||
before.model.elements[&f.definition]
|
||||
);
|
||||
assert_eq!(after.model.elements[&f.r1], before.model.elements[&f.r1]);
|
||||
|
||||
let disk = directory.path().join(format!("{id}.json"));
|
||||
let bytes = std::fs::read(&disk).unwrap();
|
||||
let mut bad_link = after.model.relations[&f.link].clone();
|
||||
if let RelationBody::ContextualLink { target, .. } = &mut bad_link.body {
|
||||
target.references = vec![f.definition]; // Definition is not an instance.
|
||||
}
|
||||
let wrong_operation = select_operation(&after.model, operation_id(20)); // Item is not an allocation.
|
||||
for commands in [
|
||||
vec![Command::EditRelation(f.link, bad_link)],
|
||||
vec![Command::EditElement(wrong_operation.id, wrong_operation)],
|
||||
] {
|
||||
let response = transact(&server, &after, commands).await;
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
unchanged(&server, &after, &disk, &bytes).await;
|
||||
}
|
||||
let mut old_json = serde_json::to_value(&after.model).unwrap();
|
||||
old_json["schema_version"] = json!(6);
|
||||
// Isolate admission gates: one new feature must not mask another.
|
||||
let mut context_only = old_json.clone();
|
||||
for element in context_only["elements"]
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.values_mut()
|
||||
{
|
||||
if let Some(body) = element.pointer_mut("/body/Sa/Shared/Occurrence/body/Exchange") {
|
||||
body.as_object_mut().unwrap().remove("operation");
|
||||
}
|
||||
}
|
||||
let mut operation_null_only: Value = serde_json::from_slice(include_bytes!(
|
||||
"../../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
|
||||
))
|
||||
.unwrap();
|
||||
operation_null_only["elements"][operation_id(43).to_string()]["body"]["Sa"]["Shared"]["Occurrence"]
|
||||
["body"]["Exchange"]["operation"] = Value::Null;
|
||||
for candidate in [old_json, context_only, operation_null_only] {
|
||||
let response = server
|
||||
.client
|
||||
.put(server.url(&format!("/projects/{id}/model")))
|
||||
.header("if-match", after.etag())
|
||||
.json(&candidate)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
unchanged(&server, &after, &disk, &bytes).await;
|
||||
}
|
||||
let mut old_source = after.source_project.clone();
|
||||
let entry = old_source.files.get_mut(&old_source.entry).unwrap();
|
||||
*entry = entry.replacen("arcadia 7;", "arcadia 6;", 1);
|
||||
let response = server
|
||||
.client
|
||||
.put(server.url(&format!("/projects/{id}/source-project")))
|
||||
.header("if-match", after.etag())
|
||||
.json(&old_source)
|
||||
.send()
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
unchanged(&server, &after, &disk, &bytes).await;
|
||||
server.stop().await;
|
||||
let server = TestServer::start(directory.path(), None).await;
|
||||
unchanged(&server, &after, &disk, &bytes).await;
|
||||
server.stop().await;
|
||||
}
|
||||
Reference in New Issue
Block a user