///|
/// A point in time, in milliseconds since 1970-01-01T00:00:00Z.
struct Instant {
millis : Int64
} derive(Eq, Compare, Hash, Debug)
///|
pub fn Instant::of_millis(millis : Int64) -> Instant {
{ millis, }
}
///|
pub fn Instant::millis(self : Instant) -> Int64 {
self.millis
}
///|
/// Days since 1970-01-01 for a proleptic Gregorian date.
fn days_from_civil(year : Int32, month : Int32, day : Int32) -> Int64 {
let y = if month <= 2 { year - 1 } else { year }
let era = (if y >= 0 { y } else { y - 399 }) / 400
let yoe = y - era * 400
let mp = (month + 9) % 12
let doy = (153 * mp + 2) / 5 + day - 1
let doe = yoe * 365 + yoe / 4 - yoe / 100 + doy
(era * 146097 + doe - 719468).to_int64()
}
///|
fn digits(text : String, start : Int32, count : Int32) -> Int32? {
if start + count > text.length() {
return None
}
let mut value = 0
for i in start..<(start + count) {
let c = text[i]
if c < '0' || c > '9' {
return None
}
value = value * 10 + (c.to_int() - '0'.to_int())
}
Some(value)
}
///|
fn days_in_month(year : Int32, month : Int32) -> Int32 {
match month {
2 =>
if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 {
29
} else {
28
}
4 | 6 | 9 | 11 => 30
_ => 31
}
}
///|
/// Parses an ISO 8601 datetime with an explicit offset, such as
/// `2026-06-30T14:00:00Z` or `2026-06-30T14:00:00.250+02:00`. With
/// `date_only=true`, a `YYYY-MM-DD` date is the start of that day in UTC.
pub fn Instant::parse(text : String, date_only~ : Bool = false) -> Instant? {
let text = text.trim(chars=" \t").to_owned()
guard digits(text, 0, 4) is Some(year) &&
text.length() >= 10 &&
text[4] == '-' &&
text[7] == '-' &&
digits(text, 5, 2) is Some(month) &&
digits(text, 8, 2) is Some(day) else {
return None
}
guard month >= 1 &&
month <= 12 &&
day >= 1 &&
day <= days_in_month(year, month) else {
return None
}
let days = days_from_civil(year, month, day)
if text.length() == 10 {
return if date_only { Some({ millis: days * 86400000L, }) } else { None }
}
guard (text[10] == 'T' || text[10] == 't' || text[10] == ' ') &&
digits(text, 11, 2) is Some(hour) &&
text.length() >= 16 &&
text[13] == ':' &&
digits(text, 14, 2) is Some(minute) else {
return None
}
let mut i = 16
let mut second = 0
let mut millis = 0
if i < text.length() && text[i] == ':' {
guard digits(text, i + 1, 2) is Some(s) else { return None }
second = s
i += 3
if i < text.length() && (text[i] == '.' || text[i] == ',') {
i += 1
let mut scale = 100
while i < text.length() && text[i] >= '0' && text[i] <= '9' {
millis += (text[i].to_int() - '0'.to_int()) * scale
scale /= 10
i += 1
}
}
}
guard hour <= 23 && minute <= 59 && second <= 60 else { return None }
guard i < text.length() else { return None }
let offset_minutes = if text[i] == 'Z' || text[i] == 'z' {
guard i + 1 == text.length() else { return None }
0
} else if text[i] == '+' || text[i] == '-' {
guard digits(text, i + 1, 2) is Some(oh) else { return None }
let om = if i + 3 < text.length() && text[i + 3] == ':' {
guard digits(text, i + 4, 2) is Some(m) && i + 6 == text.length() else {
return None
}
m
} else {
guard digits(text, i + 3, 2) is Some(m) && i + 5 == text.length() else {
return None
}
m
}
let sign = if text[i] == '-' { -1 } else { 1 }
sign * (oh * 60 + om)
} else {
return None
}
let total = days * 86400000L +
(hour * 3600 + minute * 60 + second - offset_minutes * 60).to_int64() *
1000L +
millis.to_int64()
Some({ millis: total, })
}
///|
/// Parses a calendar date written exactly as `YYYY-MM-DD`, such as `2026-10-23`,
/// as the start of that day in UTC. Other forms and dates that do not exist
/// give `None`.
pub fn Instant::parse_date(text : String) -> Instant? {
if text.length() == 10 {
Instant::parse(text, date_only=true)
} else {
None
}
}
///|
/// The lifecycle status of a concept (OKF v0.2, section 5.4).
pub(all) enum Status {
Draft
Stable
Deprecated
/// Any other status word.
Unknown(UnknownStatus)
} derive(Eq, Debug)
///|
/// A status word that is not draft, stable or deprecated. Only `Status::parse`
/// makes one, so it is never blank and never a known word.
struct UnknownStatus {
text : String
} derive(Eq, Hash, Debug)
///|
pub fn UnknownStatus::to_string(self : UnknownStatus) -> String {
self.text
}
///|
pub impl Show for UnknownStatus with fn output(self, logger) {
logger.write_string(self.text)
}
///|
/// Parses a status word, ignoring letter case and surrounding white space. A
/// blank text has no status.
pub fn Status::parse(text : String) -> Status? {
let t = text.trim(chars=" \t").to_owned()
match t.to_lower() {
"" => None
"draft" => Some(Draft)
"stable" => Some(Stable)
"deprecated" => Some(Deprecated)
_ => Some(Unknown({ text: t, }))
}
}
///|
pub fn Status::to_string(self : Status) -> String {
match self {
Draft => "draft"
Stable => "stable"
Deprecated => "deprecated"
Unknown(word) => word.text
}
}
///|
/// Who produced or verified knowledge (OKF v0.2, section 7). Only
/// `Actor::parse` makes one, so no case holds an empty ID.
pub enum Actor {
/// An automated producer, `/`.
Producer(name~ : String, version~ : String)
/// A person, `human:`, with an ID that is not empty.
Human(String)
/// A process, `process:`, with an ID that is not empty.
Process(String)
/// Any other form that is not blank, kept as written.
Other(String)
} derive(Eq, Debug)
///|
/// Parses an actor. A blank text names no actor.
pub fn Actor::parse(text : String) -> Actor? {
let text = text.trim(chars=" \t").to_owned()
if text.length() == 0 {
return None
}
if text.has_prefix("human:") && text.length() > 6 {
return Some(Human(text[6:].to_owned()))
}
if text.has_prefix("process:") && text.length() > 8 {
return Some(Process(text[8:].to_owned()))
}
match text.rev_find("/") {
Some(i) if i > 0 && i < text.length() - 1 && !text.contains(":") =>
Some(Producer(name=text[:i].to_owned(), version=text[i + 1:].to_owned()))
_ => Some(Other(text))
}
}
///|
pub fn Actor::is_human(self : Actor) -> Bool {
self is Human(_)
}
///|
/// One `sources` entry (OKF v0.2, section 5.1).
pub(all) struct Source {
resource : String
id : String?
title : String?
author : String?
usage_count : Int32?
last_modified : String?
usage_window : UsageWindow?
} derive(Eq, Debug)
///|
/// A `usage_window` mapping with its raw `from` and `to` texts.
pub(all) struct UsageWindow {
from : String?
to : String?
} derive(Eq, Debug)
///|
/// A `{by, at}` record, as in `generated` and `verified`. An attribution
/// without an actor, such as a v0.1 `timestamp`, has no `by`.
pub(all) struct Attribution {
by : Actor?
at : String?
} derive(Eq, Debug)
///|
/// The derived trust tier (OKF v0.2, section 5.3).
pub(all) enum TrustTier {
Unverified
MachineConfirmed
HumanReviewed
} derive(Eq, Compare, Debug)
///|
/// One `parameters` entry of an Attested Computation.
pub(all) struct Parameter {
name : String
type_ : String?
required : Bool
} derive(Eq, Debug)
///|
/// The `executor` of an Attested Computation.
pub(all) struct Executor {
resource : String?
receipt : ReadOnlyArray[String]
} derive(Eq, Debug)
///|
fn usage_window_of(fields : Fields) -> UsageWindow {
{ from: fields.text("from"), to: fields.text("to"), }
}
///|
fn attribution_of(fields : Fields) -> Attribution? {
match fields.text("by") {
Some(by) => Some({ by: Actor::parse(by), at: fields.text("at"), })
None => None
}
}
///|
/// The OKF `type`, or `None` when it is missing or blank.
pub fn Metadata::concept_type(self : Metadata) -> ConceptType? {
match self.text("type") {
Some(t) =>
try ConceptType(t) catch {
_ => None
} noraise {
c => Some(c)
}
None => None
}
}
///|
pub fn Metadata::title(self : Metadata) -> String? {
self.text("title")
}
///|
pub fn Metadata::description(self : Metadata) -> String? {
self.text("description")
}
///|
pub fn Metadata::resource(self : Metadata) -> String? {
self.text("resource")
}
///|
pub fn Metadata::tags(self : Metadata) -> Array[String] {
self.texts("tags")
}
///|
/// The top-level `usage_window`.
pub fn Metadata::usage_window(self : Metadata) -> UsageWindow? {
match self.mappings("usage_window") {
[fields, ..] => Some(usage_window_of(fields))
[] => None
}
}
///|
/// The `sources` entries that have a `resource`. Entries without one are left out.
pub fn Metadata::sources(self : Metadata) -> Array[Source] {
self
.mappings("sources")
.iter()
.filter_map(fields => {
match fields.text("resource") {
Some(resource) =>
Some({
resource,
id: fields.text("id"),
title: fields.text("title"),
author: fields.text("author"),
usage_count: fields.int("usage_count"),
last_modified: fields.text("last_modified"),
usage_window: match fields.get("usage_window") {
Some(Mapping(window)) => Some(usage_window_of(window))
_ => None
},
})
None => None
}
})
.collect()
}
///|
/// `generated`, or with `v01_fields` a v0.1 `timestamp` as `generated.at`.
pub fn Metadata::generated(
self : Metadata,
options~ : Options = Options::default(),
) -> Attribution? {
match self.mappings("generated") {
[fields, ..] => attribution_of(fields)
[] =>
if options.v01_fields {
match self.text("timestamp") {
Some(at) => Some({ by: None, at: Some(at), })
None => None
}
} else {
None
}
}
}
///|
/// `verified` as a list. A single mapping is a list with one entry.
pub fn Metadata::verified(self : Metadata) -> Array[Attribution] {
self.mappings("verified").iter().filter_map(attribution_of).collect()
}
///|
/// The trust tier, derived from `verified` only.
pub fn Metadata::trust_tier(self : Metadata) -> TrustTier {
let verified = self.verified()
if verified.is_empty() {
Unverified
} else if verified.any(a => a.by is Some(Human(_))) {
HumanReviewed
} else {
MachineConfirmed
}
}
///|
/// The written `status`, or `None` when it is absent.
pub fn Metadata::status(self : Metadata) -> Status? {
match self.text("status") {
Some(text) => Status::parse(text)
None => None
}
}
///|
/// The status after the default of `options` applies.
pub fn Metadata::effective_status(
self : Metadata,
options~ : Options = Options::default(),
) -> Status? {
match self.status() {
Some(status) => Some(status)
None => options.status_default
}
}
///|
/// The raw `stale_after` text.
pub fn Metadata::stale_after(self : Metadata) -> String? {
self.text("stale_after")
}
///|
/// True when `now` is at or after `stale_after`. A value that does not parse
/// under `options` gives false.
pub fn Metadata::is_stale(
self : Metadata,
now : Instant,
options~ : Options = Options::default(),
) -> Bool {
match self.stale_after() {
Some(text) =>
match Instant::parse(text, date_only=options.date_only_instants) {
Some(limit) => now >= limit
None => false
}
None => false
}
}
///|
pub fn Metadata::runtime(self : Metadata) -> String? {
self.text("runtime")
}
///|
pub fn Metadata::parameters(self : Metadata) -> Array[Parameter] {
self
.mappings("parameters")
.iter()
.filter_map(fields => {
match fields.text("name") {
Some(name) =>
Some({
name,
type_: fields.text("type"),
required: fields.bool("required").unwrap_or(false),
})
None => None
}
})
.collect()
}
///|
pub fn Metadata::computation(self : Metadata) -> String? {
self.text("computation")
}
///|
pub fn Metadata::executor(self : Metadata) -> Executor? {
match self.mappings("executor") {
[fields, ..] =>
Some({
resource: fields.text("resource"),
receipt: ReadOnlyArray::from_array(fields.texts("receipt")),
})
[] => None
}
}
///|
/// The `attester.resource` of an Attested Computation.
pub fn Metadata::attester(self : Metadata) -> String? {
match self.mappings("attester") {
[fields, ..] => fields.text("resource")
[] => None
}
}
///|
/// The keys that OKF v0.2 defines for concepts.
pub let okf_concept_keys : ReadOnlyArray[String] = [
"type", "title", "description", "resource", "tags", "sources", "usage_window",
"generated", "verified", "status", "stale_after", "runtime", "parameters", "computation",
"executor", "attester",
]
///|
pub extend Instant with Eq::{not_equal, equal}
///|
pub extend Instant with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend Instant with Hash::{hash, hash_combine}
///|
pub extend Instant with @debug.Debug::{to_repr}
///|
pub extend Status with Eq::{not_equal, equal}
///|
pub extend Status with @debug.Debug::{to_repr}
///|
pub extend Actor with Eq::{not_equal, equal}
///|
pub extend Actor with @debug.Debug::{to_repr}
///|
pub extend Source with Eq::{not_equal, equal}
///|
pub extend Source with @debug.Debug::{to_repr}
///|
pub extend UsageWindow with Eq::{not_equal, equal}
///|
pub extend UsageWindow with @debug.Debug::{to_repr}
///|
pub extend Attribution with Eq::{not_equal, equal}
///|
pub extend Attribution with @debug.Debug::{to_repr}
///|
pub extend TrustTier with Eq::{not_equal, equal}
///|
pub extend TrustTier with Compare::{op_lt, op_le, op_ge, compare, op_gt}
///|
pub extend TrustTier with @debug.Debug::{to_repr}
///|
pub extend Parameter with Eq::{not_equal, equal}
///|
pub extend Parameter with @debug.Debug::{to_repr}
///|
pub extend Executor with Eq::{not_equal, equal}
///|
pub extend Executor with @debug.Debug::{to_repr}
///|
pub extend UnknownStatus with Eq::{not_equal, equal}
///|
pub extend UnknownStatus with Hash::{hash, hash_combine}
///|
pub extend UnknownStatus with @debug.Debug::{to_repr}
///|
pub extend UnknownStatus with Show::{output}