///|
/// The amount of operational detail requested by a Moon Suite surface.
///
/// Disclosure changes presentation only. It never changes authority, evidence,
/// or the canonical state of a run.
pub(all) enum DisclosureLevel {
User
Operator
Developer
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn DisclosureLevel::id(self : DisclosureLevel) -> String {
match self {
User => "user"
Operator => "operator"
Developer => "developer"
}
}
///|
pub fn disclosure_level_from_id(id : String) -> DisclosureLevel raise {
match id {
"user" => User
"operator" => Operator
"developer" => Developer
_ => fail("unsupported disclosure level: \{id}")
}
}
///|
/// A browser operation is typed before MoonGate evaluates its authority.
pub(all) enum BrowserOperation {
RenderLocal
InspectPage
InteractLocal
SaveWorkspace
NavigateExternal
UploadExternal
DownloadExternal
SubmitExternal
ControlPhysicalDevice
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn BrowserOperation::id(self : BrowserOperation) -> String {
match self {
RenderLocal => "render-local"
InspectPage => "inspect-page"
InteractLocal => "interact-local"
SaveWorkspace => "save-workspace"
NavigateExternal => "navigate-external"
UploadExternal => "upload-external"
DownloadExternal => "download-external"
SubmitExternal => "submit-external"
ControlPhysicalDevice => "control-physical-device"
}
}
///|
pub fn BrowserOperation::required_authority(
self : BrowserOperation,
) -> AuthorityClass {
match self {
RenderLocal | InspectPage => Observe
InteractLocal => SandboxExecution
SaveWorkspace => WorkspaceMutation
NavigateExternal | UploadExternal | DownloadExternal | SubmitExternal =>
ExternalEffect
ControlPhysicalDevice => PhysicalEffect
}
}
///|
pub fn browser_operation_from_id(id : String) -> BrowserOperation raise {
match id {
"render-local" => RenderLocal
"inspect-page" => InspectPage
"interact-local" => InteractLocal
"save-workspace" => SaveWorkspace
"navigate-external" => NavigateExternal
"upload-external" => UploadExternal
"download-external" => DownloadExternal
"submit-external" => SubmitExternal
"control-physical-device" => ControlPhysicalDevice
_ => fail("unsupported browser operation: \{id}")
}
}
///|
pub(all) enum BrowserActor {
HumanUser
Agent
Replay
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn BrowserActor::id(self : BrowserActor) -> String {
match self {
HumanUser => "user"
Agent => "agent"
Replay => "replay"
}
}
///|
pub(all) struct BrowserSessionScope {
contract_id : String
session_id : String
book_id : String
book_revision : String
code_revision : String
run_id : String
artifact_id : String
authority_envelope_id : String
origin_class : String
network_policy : String
allowed_origins : Array[String]
allowed_operations : Array[BrowserOperation]
upload_artifact_refs : Array[String]
download_root : String
storage_lifetime : String
capability_digest : String
expires_at : String
} derive(Debug, Eq, ToJson)
///|
fn embodied_relative_path_is_safe(path : String) -> Bool {
let normalized = path.trim().replace(old="\\", new="/")
!normalized.is_empty() &&
!normalized.has_prefix("/") &&
!normalized.split("/").any(segment => segment == "..")
}
///|
fn embodied_origin_is_loopback(origin : String) -> Bool {
origin.has_prefix("http://127.0.0.1:") ||
origin.has_prefix("http://[::1]:") ||
origin.has_prefix("http://localhost:")
}
///|
pub fn BrowserSessionScope::quality_issues(
self : BrowserSessionScope,
) -> Array[String] {
let issues : Array[String] = []
if self.contract_id != "moonsuite.browser-session.v1" {
issues.push("contract_id must be moonsuite.browser-session.v1")
}
if self.session_id.trim().is_empty() {
issues.push("session_id is required")
}
if self.book_id.trim().is_empty() {
issues.push("book_id is required")
}
if self.book_revision.trim().is_empty() ||
self.code_revision.trim().is_empty() {
issues.push("book_revision and code_revision are required")
}
if self.artifact_id.trim().is_empty() {
issues.push("artifact_id is required")
}
if self.authority_envelope_id.trim().is_empty() {
issues.push("authority_envelope_id is required")
}
if self.origin_class != "opaque-local" &&
self.origin_class != "isolated-loopback" &&
self.origin_class != "external" {
issues.push(
"origin_class must be opaque-local, isolated-loopback, or external",
)
}
if self.network_policy != "offline" && self.network_policy != "allowlisted" {
issues.push("network_policy must be offline or allowlisted")
}
if self.storage_lifetime != "ephemeral" &&
self.storage_lifetime != "run" &&
self.storage_lifetime != "book" {
issues.push("storage_lifetime must be ephemeral, run, or book")
}
if self.allowed_operations.is_empty() {
issues.push("at least one browser operation is required")
}
if !embodied_relative_path_is_safe(self.download_root) {
issues.push("download_root must be workspace-relative")
}
for artifact_ref in self.upload_artifact_refs {
if !embodied_relative_path_is_safe(artifact_ref) {
issues.push("upload artifact refs must be workspace-relative")
}
}
if !autonomy_digest_is_valid(self.capability_digest) {
issues.push("capability_digest must be a sha256 digest")
}
if !autonomy_timestamp_is_canonical(self.expires_at) {
issues.push("expires_at must be a canonical UTC timestamp")
}
if self.origin_class == "opaque-local" && !self.allowed_origins.is_empty() {
issues.push("opaque-local sessions cannot allow network origins")
}
if self.origin_class == "isolated-loopback" &&
self.allowed_origins.any(origin => !embodied_origin_is_loopback(origin)) {
issues.push(
"isolated-loopback sessions allow only explicit loopback origins",
)
}
if self.origin_class != "external" && self.network_policy != "offline" {
issues.push("local sessions must use the offline network policy")
}
issues
}
///|
pub(all) struct BrowserActionReceipt {
contract_id : String
receipt_id : String
session_id : String
action_id : String
actor_kind : BrowserActor
actor_id : String
operation : BrowserOperation
semantic_target : String
redacted_arguments : Array[String]
expected_observation : String
authority_decision_ref : String
before_digest : String
after_digest : String
outcome : String
observations : Array[String]
evidence_refs : Array[String]
completed_at : String
} derive(Debug, Eq, ToJson)
///|
pub fn BrowserActionReceipt::quality_issues(
self : BrowserActionReceipt,
) -> Array[String] {
let issues : Array[String] = []
if self.contract_id != "moonsuite.browser-action-receipt.v1" {
issues.push("contract_id must be moonsuite.browser-action-receipt.v1")
}
if self.receipt_id.trim().is_empty() || self.action_id.trim().is_empty() {
issues.push("receipt_id and action_id are required")
}
if self.session_id.trim().is_empty() || self.actor_id.trim().is_empty() {
issues.push("session_id and actor_id are required")
}
if self.semantic_target.trim().is_empty() {
issues.push("semantic_target is required")
}
if self.expected_observation.trim().is_empty() {
issues.push("expected_observation is required")
}
if !autonomy_digest_is_valid(self.before_digest) ||
!autonomy_digest_is_valid(self.after_digest) {
issues.push("before_digest and after_digest must be sha256 digests")
}
if self.outcome != "accepted" &&
self.outcome != "rejected" &&
self.outcome != "failed" &&
self.outcome != "no-op" {
issues.push("outcome must be accepted, rejected, failed, or no-op")
}
if self.operation.required_authority().requires_explicit_human() &&
self.authority_decision_ref.trim().is_empty() {
issues.push(
"consequential browser operations require an authority decision",
)
}
if !autonomy_timestamp_is_canonical(self.completed_at) {
issues.push("completed_at must be a canonical UTC timestamp")
}
for evidence_ref in self.evidence_refs {
if !embodied_relative_path_is_safe(evidence_ref) {
issues.push("browser evidence refs must be workspace-relative")
}
}
issues
}
///|
/// The declared purpose of one spatial artifact representation.
pub(all) enum RepresentationClass {
ReferenceBundle
SpatialIntent
EditableAuthoringModel
VisualStyledModel
EngineeringModel
SimulationModel
ManufacturingCandidate
FabricationJob
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn RepresentationClass::id(self : RepresentationClass) -> String {
match self {
ReferenceBundle => "reference-bundle"
SpatialIntent => "spatial-intent"
EditableAuthoringModel => "editable-authoring-model"
VisualStyledModel => "visual-styled-model"
EngineeringModel => "engineering-model"
SimulationModel => "simulation-model"
ManufacturingCandidate => "manufacturing-candidate"
FabricationJob => "fabrication-job"
}
}
///|
pub fn representation_class_from_id(id : String) -> RepresentationClass raise {
match id {
"reference-bundle" => ReferenceBundle
"spatial-intent" => SpatialIntent
"editable-authoring-model" => EditableAuthoringModel
"visual-styled-model" => VisualStyledModel
"engineering-model" => EngineeringModel
"simulation-model" => SimulationModel
"manufacturing-candidate" => ManufacturingCandidate
"fabrication-job" => FabricationJob
_ => fail("unsupported representation class: \{id}")
}
}
///|
pub fn RepresentationClass::is_engineering_input(
self : RepresentationClass,
) -> Bool {
match self {
EngineeringModel | SimulationModel | ManufacturingCandidate => true
_ => false
}
}
///|
pub(all) enum SpatialLineageRelation {
InterpretedFrom
ModeledFrom
DimensionedBy
StyledFrom
OptimizedFrom
CollisionDerivedFrom
PhysicsDerivedFrom
ManufacturingDerivedFrom
ValidatedBy
RejectedBy
SupersededBy
PlacedAs
SimulatedAs
} derive(Debug, Eq, Compare, Hash, ToJson)
///|
pub fn SpatialLineageRelation::id(self : SpatialLineageRelation) -> String {
match self {
InterpretedFrom => "interpreted-from"
ModeledFrom => "modeled-from"
DimensionedBy => "dimensioned-by"
StyledFrom => "styled-from"
OptimizedFrom => "optimized-from"
CollisionDerivedFrom => "collision-derived-from"
PhysicsDerivedFrom => "physics-derived-from"
ManufacturingDerivedFrom => "manufacturing-derived-from"
ValidatedBy => "validated-by"
RejectedBy => "rejected-by"
SupersededBy => "superseded-by"
PlacedAs => "placed-as"
SimulatedAs => "simulated-as"
}
}
///|
pub fn spatial_lineage_relation_from_id(
id : String,
) -> SpatialLineageRelation raise {
match id {
"interpreted-from" => InterpretedFrom
"modeled-from" => ModeledFrom
"dimensioned-by" => DimensionedBy
"styled-from" => StyledFrom
"optimized-from" => OptimizedFrom
"collision-derived-from" => CollisionDerivedFrom
"physics-derived-from" => PhysicsDerivedFrom
"manufacturing-derived-from" => ManufacturingDerivedFrom
"validated-by" => ValidatedBy
"rejected-by" => RejectedBy
"superseded-by" => SupersededBy
"placed-as" => PlacedAs
"simulated-as" => SimulatedAs
_ => fail("unsupported spatial lineage relation: \{id}")
}
}
///|
pub fn representation_transition_issues(
parent : RepresentationClass,
child : RepresentationClass,
) -> Array[String] {
let issues : Array[String] = []
if parent == VisualStyledModel && child.is_engineering_input() {
issues.push("styled geometry cannot become an engineering input")
}
if child == ManufacturingCandidate && parent != EngineeringModel {
issues.push("manufacturing candidates must descend from engineering models")
}
if child == FabricationJob && parent != ManufacturingCandidate {
issues.push("fabrication jobs must descend from manufacturing candidates")
}
issues
}
///|
pub(all) struct SpatialArtifactManifest {
contract_id : String
artifact_id : String
project_id : String
representation : RepresentationClass
parent_artifact_ids : Array[String]
digest : String
payload_ref : String
units : String
coordinate_system : String
up_axis : String
handedness : String
source_refs : Array[String]
backend_id : String
backend_version : String
procedure_ref : String
authority_envelope_id : String
assumptions : Array[String]
unresolved_gaps : Array[String]
validation_refs : Array[String]
intended_consumers : Array[String]
forbidden_consumers : Array[String]
claim_ceiling : ClaimClass
recorded_at : String
} derive(Debug, Eq, ToJson)
///|
pub fn SpatialArtifactManifest::quality_issues(
self : SpatialArtifactManifest,
) -> Array[String] {
let issues : Array[String] = []
if self.contract_id != "moonmold.spatial-artifact.v1" {
issues.push("contract_id must be moonmold.spatial-artifact.v1")
}
if self.artifact_id.trim().is_empty() || self.project_id.trim().is_empty() {
issues.push("artifact_id and project_id are required")
}
if !autonomy_digest_is_valid(self.digest) {
issues.push("digest must be a sha256 digest")
}
if !embodied_relative_path_is_safe(self.payload_ref) {
issues.push("payload_ref must be workspace-relative")
}
if self.units.trim().is_empty() ||
self.coordinate_system.trim().is_empty() ||
self.up_axis.trim().is_empty() ||
self.handedness.trim().is_empty() {
issues.push("units and coordinate conventions are required")
}
if self.backend_id.trim().is_empty() || self.backend_version.trim().is_empty() {
issues.push("backend identity and version are required")
}
if self.procedure_ref.trim().is_empty() {
issues.push("procedure_ref is required")
}
if self.authority_envelope_id.trim().is_empty() {
issues.push("authority_envelope_id is required")
}
if self.intended_consumers.is_empty() {
issues.push("at least one intended consumer is required")
}
if self.representation == VisualStyledModel &&
(
self.intended_consumers.contains("moonrobo") ||
self.intended_consumers.contains("moonmoon")
) {
issues.push("styled geometry cannot be an engineering or simulation input")
}
if self.representation == FabricationJob &&
!self.claim_ceiling.allows(PhysicalEffectClaim) {
issues.push("fabrication jobs require a physical-effect claim ceiling")
}
if self.representation != FabricationJob &&
self.claim_ceiling.allows(SimulationEvidence) {
issues.push(
"digital spatial artifacts cannot claim simulation outcomes or physical effects",
)
}
if !autonomy_timestamp_is_canonical(self.recorded_at) {
issues.push("recorded_at must be a canonical UTC timestamp")
}
issues
}
///|
pub(all) struct RepresentationTransform {
contract_id : String
transform_id : String
parent_artifact_id : String
parent_digest : String
parent_representation : RepresentationClass
child_artifact_id : String
child_digest : String
child_representation : RepresentationClass
lineage_relation : SpatialLineageRelation
operation : String
parameters_digest : String
tool_id : String
tool_version : String
authority_ref : String
declared_losses : Array[String]
validation_refs : Array[String]
recorded_at : String
} derive(Debug, Eq, ToJson)
///|
pub fn RepresentationTransform::quality_issues(
self : RepresentationTransform,
) -> Array[String] {
let issues = representation_transition_issues(
self.parent_representation,
self.child_representation,
)
match self.lineage_relation {
StyledFrom | OptimizedFrom | PlacedAs =>
if self.child_representation != VisualStyledModel {
issues.push("visual lineage relations require a styled child")
}
CollisionDerivedFrom | PhysicsDerivedFrom | SimulatedAs =>
if self.parent_representation != EngineeringModel ||
self.child_representation != SimulationModel {
issues.push(
"simulation lineage must derive a simulation child from engineering geometry",
)
}
ManufacturingDerivedFrom =>
if self.parent_representation != EngineeringModel ||
self.child_representation != ManufacturingCandidate {
issues.push(
"manufacturing lineage must derive a candidate from engineering geometry",
)
}
_ => ()
}
if self.contract_id != "moonmold.representation-transform.v1" {
issues.push("contract_id must be moonmold.representation-transform.v1")
}
if self.transform_id.trim().is_empty() ||
self.parent_artifact_id.trim().is_empty() ||
self.child_artifact_id.trim().is_empty() {
issues.push("transform and artifact identities are required")
}
if !autonomy_digest_is_valid(self.parent_digest) ||
!autonomy_digest_is_valid(self.child_digest) ||
!autonomy_digest_is_valid(self.parameters_digest) {
issues.push("parent, child, and parameter digests must be sha256 digests")
}
if self.operation.trim().is_empty() ||
self.tool_id.trim().is_empty() ||
self.tool_version.trim().is_empty() {
issues.push("operation, tool identity, and tool version are required")
}
if self.authority_ref.trim().is_empty() {
issues.push("authority_ref is required")
}
if !autonomy_timestamp_is_canonical(self.recorded_at) {
issues.push("recorded_at must be a canonical UTC timestamp")
}
issues
}