///|
/// A caller-selected format. Ion is preferred; JSON is an explicit opt-out.
pub(all) enum GrantFormat {
IonText
IonBinary
Json
}
///|
priv enum Flavor {
Posix
Windows
}
///|
/// Exact, separately authorized paths. Configuration is only a selection.
pub(all) struct GrantedService {
node : String
node_sha256 : String
helper : String
helper_bundle_sha256 : String
service : String
service_sha256 : String
policy : String
lock : String
registry : String
state : String
release : String
}
///|
priv struct ProjectGrant {
project : String
service : String
service_sha256 : String
policy : String
lock : String
registry : String
state : String
}
///|
/// Opaque authority from a host-selected file, never a discovered project file.
/// Parsing and matching perform no filesystem access, probing or acquisition.
pub struct Grants {
priv flavor : Flavor
priv workspace : String
priv node : String
priv node_sha256 : String
priv helper : String
priv helper_bundle_sha256 : String
priv projects : Array[ProjectGrant]
}
///|
fn[T] invalid(code : String) -> T raise {
raise @config.Invalid("metadata.restore_" + code)
}
///|
fn fields(
value : @ion.IonValue,
names : Array[String],
) -> Map[String, @ion.IonValue] raise {
guard !value.has_annotations() && value.kind() is @ion.Struct(entries) else {
invalid("invalid_grants")
}
let result = Map([])
for field in entries {
guard field.name().text() is Some(name) &&
names.contains(name) &&
!result.contains(name) else {
invalid("invalid_grants")
}
result[name] = field.value()
}
if result.length() != names.length() {
invalid("invalid_grants")
}
result
}
///|
fn text(
fields : Map[String, @ion.IonValue],
key : String,
empty~ : Bool = false,
) -> String raise {
guard fields.get(key) is Some(value) &&
!value.has_annotations() &&
value.kind() is @ion.String(s) &&
s.length() <= 4096 &&
!s.contains("\u0000") &&
(empty || s != "") else {
invalid("invalid_grants")
}
s
}
///|
fn pin(fields : Map[String, @ion.IonValue], key : String) -> String raise {
let s = text(fields, key)
if s.length() != 71 ||
!s.has_prefix("sha256:") ||
!s[7:]
.to_owned()
.to_array()
.all(c => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
invalid("invalid_grant_pin")
}
s[7:].to_owned()
}
///|
fn absolute(path : String, flavor : Flavor) -> Bool {
match flavor {
Posix => @posix.Path(path).is_absolute()
Windows => {
let normalized = path
.split("\\")
.map(StringView::to_owned)
.collect()
.join("/")
@win32.Path(path).is_absolute() &&
(
(normalized.length() >= 3 && normalized[1:3] == ":/") ||
(
normalized.has_prefix("//") &&
normalized.split("/").filter(p => p != "").count() >= 2 &&
!normalized.has_prefix("//?/")
)
)
}
}
}
///|
fn resolve(path : String, base : String, flavor : Flavor) -> String raise {
if path == "" || path.contains("\u0000") || !absolute(base, flavor) {
invalid("path_origin_unknown")
}
match flavor {
Posix => {
let p = @posix.Path(path)
(if p.is_absolute() { p } else { @posix.Path(base).join(p) }).normalize()
|> Show::to_string
}
Windows => {
let p = @win32.Path(path)
// Drive-relative and root-relative paths depend on ambient drive state.
if !absolute(path, flavor) &&
(path.contains(":") || path.has_prefix("/") || path.has_prefix("\\")) {
invalid("invalid_path")
}
let result = (if p.is_absolute() { p } else { @win32.Path(base).join(p) }).normalize()
|> Show::to_string
result.split("\\").map(StringView::to_owned).collect().join("/")
}
}
}
///|
fn parent(path : String, flavor : Flavor) -> String {
match flavor {
Posix => Show::to_string(@posix.Path(path).dirname())
Windows => Show::to_string(@win32.Path(path).dirname())
}
}
///|
/// Decode a bounded, closed host grant document. Relative paths resolve from
/// this explicitly selected file's absolute origin, not a project selection.
pub fn Grants::decode(
bytes : Bytes,
format : GrantFormat,
origin : String,
system : String,
) -> Grants raise {
if bytes.length() > 1048576 {
invalid("grants_limit")
}
let flavor = match system {
"windows" => Windows
"linux" | "macos" => Posix
_ => invalid("runtime_unavailable")
}
if !absolute(origin, flavor) {
invalid("path_origin_unknown")
}
let base = parent(origin, flavor)
let value = match format {
IonBinary =>
@binary.read_binary_value(bytes[:], fuel=Limited(@ion.Budget(128)))
IonText =>
@ion_text.read_ion(
@utf8.decode(bytes[:])[:],
fuel=Limited(@ion.Budget(128)),
)
Json =>
@ion_json.read_json(
@utf8.decode(bytes[:])[:],
fuel=Limited(@ion.Budget(128)),
)
}
let root = fields(value, ["profile", "workspace", "runtime", "projects"])
if text(root, "profile") != "morphir-metadata-host-grants-v1" {
invalid("invalid_grants")
}
let workspace = resolve(text(root, "workspace"), base, flavor)
let runtime = fields(root["runtime"], [
"node", "nodeSha256", "helper", "helperBundleSha256",
])
let node = resolve(text(runtime, "node"), base, flavor)
let helper = resolve(text(runtime, "helper"), base, flavor)
if !helper.has_suffix("/metadata.mjs") {
invalid("unqualified_helper")
}
guard root["projects"].kind() is @ion.List(items) &&
!root["projects"].has_annotations() &&
items.length() <= 128 else {
invalid("invalid_grants")
}
let projects = []
let seen = Map([])
for item in items {
let fields = fields(item, [
"project", "service", "serviceSha256", "policy", "lock", "registry", "state",
])
let project = text(fields, "project", empty=true)
if project.has_prefix("/") ||
project.contains("\\") ||
project.contains(":") ||
project
.split("/")
.any(p => p == "." || p == ".." || (p == "" && project != "")) ||
seen.contains(project) {
invalid("invalid_project_grant")
}
seen[project] = true
projects.push({
project,
service: resolve(text(fields, "service"), base, flavor),
service_sha256: pin(fields, "serviceSha256"),
policy: resolve(text(fields, "policy"), base, flavor),
lock: resolve(text(fields, "lock"), base, flavor),
registry: resolve(text(fields, "registry"), base, flavor),
state: resolve(text(fields, "state"), base, flavor),
})
}
{
flavor,
workspace,
node,
node_sha256: pin(runtime, "nodeSha256"),
helper,
helper_bundle_sha256: pin(runtime, "helperBundleSha256"),
projects,
}
}
///|
fn selection(
path : @config.MetadataPath,
workspace : String,
invocation : String,
flavor : Flavor,
) -> String raise {
if absolute(path.path, flavor) {
return resolve(path.path, workspace, flavor)
}
let base = if path.origin == "CLI" {
invocation
} else if absolute(path.origin, flavor) ||
[".toml", ".json", ".yaml", ".yml", ".scm", ".ion", ".ionb"].any(suffix => {
path.origin.has_suffix(suffix)
}) {
parent(resolve(path.origin, workspace, flavor), flavor)
} else {
invalid("path_origin_unknown: " + path.origin)
}
resolve(path.path, base, flavor)
}
///|
/// Match this frozen project selection against independent exact grants. The
/// workspace/invocation roots come from the owning host, not provider metadata.
pub fn Grants::select(
self : Grants,
scope : @engine.ProjectMetadataScope,
workspace : String,
invocation : String,
) -> GrantedService raise {
let root = resolve(
if scope.workspace_root == "" {
"."
} else {
scope.workspace_root
},
workspace,
self.flavor,
)
if root != self.workspace {
invalid("workspace_not_granted")
}
guard scope.request
is @config.MetadataProviderRequest::SharedLocalRestore(request) else {
invalid("provider_disabled")
}
guard self.projects.iter().find_first(p => p.project == scope.project_root)
is Some(grant) else {
invalid("project_not_granted")
}
if request.assurance != "portable" {
invalid("unsupported_assurance")
}
for
(key, path, allowed) in [
("service", request.service, grant.service),
("policy", request.policy, grant.policy),
("lock", request.lock, grant.lock),
("registry", request.registry, grant.registry),
("state", request.state, grant.state),
] {
if selection(path, workspace, invocation, self.flavor) != allowed {
invalid("path_not_granted: metadata." + key + " set in " + path.origin)
}
}
{
node: self.node,
node_sha256: self.node_sha256,
helper: self.helper,
helper_bundle_sha256: self.helper_bundle_sha256,
service: grant.service,
service_sha256: grant.service_sha256,
policy: grant.policy,
lock: grant.lock,
registry: grant.registry,
state: grant.state,
release: request.release,
}
}
///|
/// Resolve an explicit CLI host path from its invocation directory. This does
/// not consult filesystem state, environment configuration or project metadata.
pub fn host_path(path : String, base : String, system : String) -> String raise {
let flavor = match system {
"windows" => Windows
"linux" | "macos" => Posix
_ => invalid("runtime_unavailable")
}
resolve(path, base, flavor)
}