///|
priv struct ContextFile {
path : String
bytes : Bytes
digest : String
}
///|
priv struct RetainedLibrary {
manifest : Bytes
native : Bytes
native_path : String
revision : String
contexts : Array[ContextFile]
}
///|
/// A shape-checked wire response, NOT an authenticated capability. Only a host
/// that freshly completed its qualified service may install its views. All
/// original graph buffers remain retained; returned core/metadata are reparsed
/// so caller mutation cannot change this response's accepted inventory.
pub struct Snapshot {
priv libraries : Array[RetainedLibrary]
priv root : Int32
priv core : String
priv metadata : String?
}
///|
/// Raw retained native identity. A decoded snapshot alone grants no authority.
pub fn Snapshot::native_revision(self : Snapshot) -> String {
self.libraries[self.root].revision
}
///|
fn inventory_path(path : String) -> String raise {
if path == "" ||
path.has_prefix("/") ||
path.contains("\\") ||
path.contains(":") ||
path.split("/").any(p => p == "" || p == "." || p == "..") {
invalid("Noncanonical inventory path")
}
path
}
///|
fn checked_bytes(value : @ion.IonValue, digest : String) -> Bytes raise {
let bytes = blob(value)
if @identity.sha256(bytes) != digest {
invalid("Retained byte digest mismatch")
}
bytes
}
///|
/// Validate one correlated response and retain its complete graph. This checks
/// transport identities only; it does not independently verify signatures.
pub fn Snapshot::decode(bytes : Bytes, expected : Expected) -> Snapshot raise {
let values = frame(bytes, "morphir_provider_snapshot_response_v1", [
"invocation", "projectScope", "service", "profile", "profileVersion", "release",
"policySha256", "lockSha256", "codec", "core", "metadata", "libraries",
])
correlate(values, expected.invocation, expected.project_scope)
if text(required(values, "release")) != expected.release ||
raw_digest(text(required(values, "policySha256"))) != expected.policy_sha256 ||
raw_digest(text(required(values, "lockSha256"))) != expected.lock_sha256 {
invalid("Release, policy or lock identity mismatch")
}
let libraries : Array[RetainedLibrary] = []
let releases : Map[String, Bool] = Map([])
let mut root = -1
let mut total_contexts = 0
for value in list(required(values, "libraries"), 128) {
let library = fields(value, [
"release", "manifest", "nativePath", "revision", "native", "contexts",
])
let release = text(required(library, "release"))
if releases.contains(release) {
invalid("Duplicate Library release")
}
releases[release] = true
let revision = raw_digest(text(required(library, "revision")))
let native = checked_bytes(required(library, "native"), revision)
let native_path = inventory_path(text(required(library, "nativePath")))
let manifest = blob(required(library, "manifest"))
let contexts = []
let paths = Map::from_array([(native_path, true)])
for value in list(required(library, "contexts"), 256) {
let context = fields(value, ["path", "sha256", "bytes"])
let path = inventory_path(text(required(context, "path")))
if paths.contains(path) {
invalid("Duplicate inventory path")
}
paths[path] = true
let digest = raw_digest(text(required(context, "sha256")))
contexts.push({
path,
digest,
bytes: checked_bytes(required(context, "bytes"), digest),
})
total_contexts += 1
if total_contexts > 1024 {
invalid("Context inventory limit")
}
}
if release == expected.release {
root = libraries.length()
}
libraries.push({ manifest, native, native_path, revision, contexts, })
}
if root < 0 {
invalid("Missing requested root Library")
}
let native = members(
@ion_json.read_json(
@utf8.decode(libraries[root].native[:])[:],
fuel=Limited(@ion.Budget(128)),
),
)
if text(required(native, "formatVersion")) != "4.1.0" {
invalid("Wrong native root format")
}
let distribution = members(required(native, "distribution"))
let library = members(required(distribution, "Library"))
if text(required(library, "packageName")) != "morphir/naming" {
invalid("Wrong native IR package")
}
let core = @utf8.decode(blob(required(values, "core"))[:])
guard @ir_json.read(core).distribution
is @ir.Distribution::Library(name, _, _) &&
name.to_canonical() == "morphir/naming" else {
invalid("Wrong core Library identity")
}
let raw_metadata = required(values, "metadata")
let metadata = if raw_metadata.kind() is @ion.Null(@ion.Blob) &&
!raw_metadata.has_annotations() {
if native.contains("$meta") {
invalid("Missing authored metadata")
}
None
} else {
let body = @utf8.decode(blob(raw_metadata)[:])
guard native.get("$meta") is Some(authored) &&
authored.equals(
@ion_json.read_json(body[:], fuel=Limited(@ion.Budget(128))),
) else {
invalid("Native metadata view differs from original payload")
}
Some(body)
}
{ libraries, root, core, metadata, }
}
///|
/// Return a fresh semantic view after the host has granted this response.
pub fn Snapshot::restored(
self : Snapshot,
) -> @linked_metadata.RestoredLibrary raise {
let revision = self.libraries[self.root].revision
{
core: @ir_json.read(self.core),
format: "4.1.0",
revision,
document: "morphir://ir/pkg/morphir/naming?format=4.1.0&rev=sha256:" +
revision +
"#/distribution",
document_metadata: match self.metadata {
Some(body) => @ion_json.read_json(body[:], fuel=Limited(@ion.Budget(128)))
None => @ion.IonValue::from_fields([])
},
}
}
///|
/// Resolve only retained inventory bytes. A document URI is not a filesystem
/// grant. Relative imports remain within their selected Library's inventory.
pub fn Snapshot::context(
self : Snapshot,
resource : String,
base : String,
) -> @linked_metadata.ContextDocument raise {
if resource.has_prefix("morphir://context/sha256/") {
let digest = resource["morphir://context/sha256/".length():].to_owned()
let mut found : @linked_metadata.ContextDocument? = None
for library in self.libraries {
for context in library.contexts {
if context.digest == digest {
let url = "morphir-provider-context://" +
@identity.sha256(library.manifest) +
"/" +
context.path
if found is Some(previous) && previous.url != url {
invalid(
"Ambiguous context digest: use an inventory-relative reference from its admitted base",
)
}
found = Some({
url,
bytes: Bytes::from_array(context.bytes.to_array()),
sha256: context.digest,
})
}
}
}
if found is Some(document) {
return document
}
invalid("Context digest is absent from retained inventory")
}
let root = self.libraries[self.root]
let document = "morphir://ir/pkg/morphir/naming?format=4.1.0&rev=sha256:" +
root.revision +
"#/distribution"
let mut selected = self.root
let mut current = root.native_path
if base != document {
let mut found = false
for index, library in self.libraries {
let prefix = "morphir-provider-context://" +
@identity.sha256(library.manifest) +
"/"
if base.has_prefix(prefix) {
selected = index
current = base[prefix.length():].to_owned()
found = library.contexts.any(c => c.path == current)
break
}
}
if !found {
invalid("Unknown context base")
}
}
if resource.has_prefix("/") ||
resource.contains(":") ||
resource.contains("\\") {
invalid("Context reference is not a relative inventory path")
}
let parts = current.split("/").map(p => p.to_owned()).collect()
ignore(parts.pop())
for part in resource.split("/") {
if part == "." {
continue
}
if part == ".." {
if parts.pop() is None {
invalid("Context path escapes retained inventory")
}
} else if part == "" {
invalid("Empty context path component")
} else {
parts.push(part.to_owned())
}
}
let path = parts.join("/")
let library = self.libraries[selected]
guard library.contexts.iter().find_first(c => c.path == path) is Some(context) else {
invalid("Context path is absent from retained inventory")
}
{
url: "morphir-provider-context://" +
@identity.sha256(library.manifest) +
"/" +
path,
bytes: Bytes::from_array(context.bytes.to_array()),
sha256: context.digest,
}
}