johnandersen777.bsky.social/atlibs-socialweb-computer-requester
socialweb.computer market requester and policy modules for atlibs, with its web pages and lexicons
0
0
// socialweb.computer market policies as step protocol modules.
// One contract for every policy: entry `evaluate`, input = the kcp policy action's inputs
// { args, perspective, mode: "scope"|"full", selfDid, counterpartyDid, subjectDid, rootRequesterDid,
// rfp?, bid?, accept? } plus an optional host-seeded `snapshot` { operatorOf, vouchedBy }; result =
// kcp's Verdict { allow, violations: [{ msg, policyId }] }.
//
// Decisions follow socialweb-computer-kcp (atlibs-bidder-fixes-rc159): the trust snapshot is
// trust.BuildSnapshotWith (internal/policyengine/trust/trust.go:316) with the default association
// kinds (claim + association, trust/association.go:23), the decisions are deno-typescript-shared
// trust.ts (onlyMeAllows, tangledVouchAllows, mutualsAllows), scope.ts scopeDecide and the work
// policies bid-payload.ts / under-4-cpus.ts. Without a `snapshot` the module gathers it itself with
// public com.atproto.repo.getRecord reads: every handler call re-runs the decision over the reads
// it has, asks for the ones it is missing, and answers as soon as nothing is missing.
use abi::{Call, Event, Handle, Run};
use serde_json::{Map, Value, json};
use sha2::{Digest, Sha256};
pub const GET_RECORD: &str = "com.atproto.repo.getRecord";
const VOUCH: &str = "sh.tangled.graph.vouch";
const BADGE: &str = "com.publicdomainrelay.temp.badgeBlueKeys";
const ASSOCIATION: &str = "com.publicdomainrelay.temp.market.association";
const ACCEPT: &str = "com.publicdomainrelay.temp.market.accept";
const RECEIPT: &str = "com.publicdomainrelay.temp.market.receipt";
const VM_DELETE: &str = "com.publicdomainrelay.temp.compute.events.vm.delete";
const VM_TERMINATED: &str = "com.publicdomainrelay.temp.compute.events.vm.terminated";
const BIDS_FREE: &str = "com.publicdomainrelay.temp.market.bids.free";
const BIDDER_ASSOCIATE: &str = "bidder_associate";
const REQUESTER_ASSOCIATE: &str = "requester_associate";
#[derive(Clone, Copy, PartialEq)]
pub enum Kind {
OnlyMe,
TangledVouch,
Mutuals,
Open,
BidPayload,
UnderCpus,
}
pub struct Policy {
pub name: &'static str,
pub kind: Kind,
}
impl Policy {
// action-common.ts policyPerspective: a work policy's first perspective, else the name's side.
fn perspective(&self) -> &'static str {
match self.kind {
Kind::BidPayload => "requester",
Kind::UnderCpus => "bidder",
_ if self.name.starts_with("bidder-") => "bidder",
_ => "requester",
}
}
fn trust(&self) -> bool {
matches!(self.kind, Kind::OnlyMe | Kind::TangledVouch | Kind::Mutuals)
}
}
/// The handler every policy module exports as `evaluate`.
pub fn evaluate(policy: &Policy, run: Run<Value>) -> Result<Handle, String> {
// got: at-uri -> getRecord output (null: no such record, {$error}: the read failed);
// issued: call id -> at-uri (call ids are short).
let mut got = run.state.get("got").and_then(Value::as_object).cloned().unwrap_or_default();
let mut issued = run.state.get("issued").and_then(Value::as_object).cloned().unwrap_or_default();
match run.event {
Event::Start => {}
Event::Result { id, result, .. } => {
let Some(uri) = issued.get(&id).and_then(Value::as_str) else { return Ok(Handle::new()) };
let value = match result.output() {
Ok(output) => output.clone(),
Err(error) if error.contains("RecordNotFound") => Value::Null,
Err(error) => json!({ "$error": error }),
};
got.insert(uri.to_string(), value);
}
_ => return Ok(Handle::new()),
}
let mut reads = Reads { got: &got, need: Vec::new() };
if let Some(verdict) = decide(policy, &run.args, &mut reads)? {
return Ok(Handle::new().result(verdict).done());
}
let need: Vec<String> = reads.need.into_iter().filter(|uri| !issued.values().any(|u| u == uri)).collect();
if need.is_empty() && issued.values().all(|uri| uri.as_str().is_some_and(|u| got.contains_key(u))) {
return Err("policy stalled: no reads pending and no verdict".into());
}
let mut out = Handle::new();
for uri in need {
let id = format!("r{}", issued.len());
let (repo, collection, rkey) = split(&uri);
out = out.call(Call::new(&id, GET_RECORD).params(json!({ "repo": repo, "collection": collection, "rkey": rkey })).service(repo));
issued.insert(id, json!(uri));
}
Ok(out.state(json!({ "got": got, "issued": issued })))
}
fn verdict(policy: &Policy, deny: Option<String>) -> Value {
match deny {
None => json!({ "allow": true, "violations": [] }),
Some(msg) => json!({ "allow": false, "violations": [{ "msg": msg, "policyId": policy.name }] }),
}
}
fn text<'a>(input: &'a Value, key: &str) -> &'a str {
input[key].as_str().map(str::trim).unwrap_or("")
}
// Ok(None): a read is missing (asked for in reads.need).
fn decide(policy: &Policy, input: &Value, reads: &mut Reads) -> Result<Option<Value>, String> {
let self_did = text(input, "selfDid");
if self_did.is_empty() {
return Err("selfDid is required (the evaluator's own DID)".into());
}
let perspective = match text(input, "perspective") {
"" => policy.perspective(),
p => p,
};
let args = &input["args"];
if input["mode"] == "scope" {
// action-common.ts evaluateScope: the counterparty is the subject; work policies abstain in
// scopeDecide and their full evaluation then sees no records, so they allow.
let cp = text(input, "counterpartyDid");
if cp.is_empty() {
return Err("scope mode requires counterpartyDid".into());
}
if !policy.trust() {
return Ok(Some(verdict(policy, None)));
}
let Some(snapshot) = snapshot_for(input, reads, perspective, self_did, cp) else { return Ok(None) };
let deny = (!allows(policy.kind, &snapshot, self_did, cp)).then(|| format!("scope denied by {}", policy.name));
return Ok(Some(verdict(policy, deny)));
}
// ctx.ts createPolicyCtx: subject from the records when not given, counterparty defaults to it.
let subject = match text(input, "subjectDid") {
"" if perspective == "requester" => author(&input["bid"]),
"" if input["rfp"].is_object() => author(&input["rfp"]),
"" => author(&input["accept"]),
s => s,
};
let cp = match text(input, "counterpartyDid") {
"" => subject,
c => c,
};
Ok(match policy.kind {
Kind::Open => Some(verdict(policy, None)),
Kind::OnlyMe | Kind::TangledVouch | Kind::Mutuals => {
// trust.ts evaluateTrust
if cp.is_empty() {
return Ok(Some(verdict(policy, Some("no counterparty to evaluate".into()))));
}
if !subject.is_empty() && subject != cp {
return Ok(Some(verdict(policy, Some("the subject and the counterparty differ".into()))));
}
let Some(snapshot) = snapshot_for(input, reads, perspective, self_did, cp) else { return Ok(None) };
let msg = match policy.kind {
Kind::OnlyMe => "operator mismatch",
Kind::TangledVouch => "not vouched",
_ => "not mutual follows",
};
Some(verdict(policy, (!allows(policy.kind, &snapshot, self_did, cp)).then(|| msg.into())))
}
Kind::BidPayload => {
// bid-payload.ts: the bid's payload collection must be allowed (free bids by default).
let Some(bid) = hydrate(reads, &input["bid"]) else { return Ok(None) };
let Some(nsid) = payload_ref(&bid).map(|uri| split(uri).1.to_string()) else { return Ok(Some(verdict(policy, None))) };
let free = [json!(BIDS_FREE)];
let allowed = args["allowedPayloadNsids"].as_array().map(Vec::as_slice).unwrap_or(&free);
let deny = (!allowed.contains(&json!(nsid))).then(|| format!("bid payload type {nsid} not allowed"));
Some(verdict(policy, deny))
}
Kind::UnderCpus => {
// under-4-cpus.ts: the RFP's payload cpus must be at most args.maxCpus (a number, else 4).
let max = args["maxCpus"].as_f64().unwrap_or(4.0);
let Some(rfp) = hydrate(reads, &input["rfp"]) else { return Ok(None) };
let Some(uri) = payload_ref(&rfp) else { return Ok(Some(verdict(policy, None))) };
let payload = match &input["rfp"]["payload"] {
p if truthy(p) => p.clone(),
_ => match reads.get(uri) {
None => return Ok(None),
Some(Rec::Found(output)) => output.get("value").cloned().unwrap_or_else(|| json!({})),
Some(Rec::Absent) => return Ok(Some(verdict(policy, Some(format!("failed to resolve demand payload: Error: getRecord {uri} failed: RecordNotFound"))))),
Some(Rec::Failed(e)) => return Ok(Some(verdict(policy, Some(format!("failed to resolve demand payload: Error: getRecord {uri} failed: {e}"))))),
},
};
let deny = payload["cpus"].as_f64().filter(|cpus| *cpus > max).map(|cpus| format!("VM needs {} cpus; this bidder's cap is {}", js_number(cpus), js_number(max)));
Some(verdict(policy, deny))
}
})
}
fn truthy(value: &Value) -> bool {
match value {
Value::Null => false,
Value::Bool(b) => *b,
Value::Number(n) => n.as_f64().is_some_and(|n| n != 0.0),
Value::String(s) => !s.is_empty(),
_ => true,
}
}
fn js_number(n: f64) -> String {
if n.fract() == 0.0 && n.abs() < 1e21 { format!("{}", n as i64) } else { format!("{n}") }
}
fn author(record: &Value) -> &str {
record["uri"].as_str().map(|uri| split(uri).0).unwrap_or("")
}
// ctx.ts hydrate: a record given as a ref is read; a failed read keeps the ref without a value.
fn hydrate(reads: &mut Reads, record: &Value) -> Option<Value> {
if !record.is_object() || truthy(&record["value"]) {
return Some(record["value"].clone());
}
let Some(uri) = record["uri"].as_str() else { return Some(Value::Null) };
Some(match reads.get(uri)? {
Rec::Found(output) => output.get("value").cloned().unwrap_or_else(|| json!({})),
_ => Value::Null,
})
}
// ctx.ts deriveOffer / deriveDemand: the record's payload strongRef uri, when it has uri and cid.
fn payload_ref(value: &Value) -> Option<&str> {
let payload = &value["payload"];
(truthy(&payload["uri"]) && truthy(&payload["cid"])).then(|| payload["uri"].as_str()).flatten()
}
// ---- trust decisions ---------------------------------------------------------------------------
pub struct Snapshot {
operator_of: Map<String, Value>,
vouched_by: Map<String, Value>,
}
impl Snapshot {
fn operator(&self, did: &str) -> Option<&str> {
self.operator_of.get(did).and_then(Value::as_str).filter(|op| !op.is_empty())
}
// A self-vouch never counts (BuildSnapshotWith skips subject == voucher, trust.go:391).
fn vouched(&self, voucher: &str, subject: &str) -> bool {
voucher != subject && self.vouched_by.get(voucher).and_then(Value::as_array).is_some_and(|l| l.iter().any(|d| d == subject))
}
}
// trust.ts onlyMeAllows / tangledVouchAllows / mutualsAllows over scope.ts buildTrustSnapshot.
fn allows(kind: Kind, s: &Snapshot, self_did: &str, did: &str) -> bool {
let trusted = [self_did, s.operator(self_did).unwrap_or(self_did)];
let op = s.operator(did);
let only_me = did == self_did || op.is_some_and(|op| trusted.contains(&op));
let trust_vouched = || trusted.iter().any(|t| s.vouched(t, did) || op.is_some_and(|op| s.vouched(t, op)));
let vouches_trusted = || {
let vouchers: &[&str] = match op {
Some(op) if op != did => &[did, op],
_ => &[did],
};
vouchers.iter().any(|v| trusted.iter().any(|m| s.vouched(v, m)))
};
match kind {
Kind::OnlyMe => only_me,
Kind::TangledVouch => only_me || trust_vouched(),
Kind::Mutuals => only_me || (trust_vouched() && vouches_trusted()),
_ => true,
}
}
fn snapshot_for(input: &Value, reads: &mut Reads, perspective: &str, self_did: &str, cp: &str) -> Option<Snapshot> {
if let Some(seeded) = input["snapshot"].as_object() {
let map = |key: &str| seeded.get(key).and_then(Value::as_object).cloned().unwrap_or_default();
return Some(Snapshot { operator_of: map("operatorOf"), vouched_by: map("vouchedBy") });
}
// executor/ghalite.go snapshotServices: each side's operator is resolved under its own role.
let (self_svc, cp_svc) = if perspective == "requester" { (REQUESTER_ASSOCIATE, BIDDER_ASSOCIATE) } else { (BIDDER_ASSOCIATE, REQUESTER_ASSOCIATE) };
let now = instant(text(input, "now"));
build_snapshot(reads, self_did, cp, self_svc, cp_svc, now)
}
// ---- trust.BuildSnapshotWith over getRecord reads ----------------------------------------------
pub enum Rec<'a> {
Absent,
Failed(&'a str),
Found(&'a Value),
}
pub struct Reads<'a> {
got: &'a Map<String, Value>,
need: Vec<String>,
}
impl<'a> Reads<'a> {
// None: not read yet (now asked for).
fn get(&mut self, uri: &str) -> Option<Rec<'a>> {
match self.got.get(uri) {
None => {
if !self.need.iter().any(|u| u == uri) {
self.need.push(uri.to_string());
}
None
}
Some(Value::Null) => Some(Rec::Absent),
Some(v) if v.get("$error").is_some() => Some(Rec::Failed(v["$error"].as_str().unwrap_or("call failed"))),
Some(v) => Some(Rec::Found(v)),
}
}
// atproto.Client.GetRecordAt + cid check (trust/association.go pinnedRecord): the value, or None.
fn pinned(&mut self, uri: &str, cid: &str) -> Option<Option<&'a Value>> {
Some(match self.get(uri)? {
Rec::Found(output) if output["cid"] == cid => Some(&output["value"]),
_ => None,
})
}
}
fn uri(repo: &str, collection: &str, rkey: &str) -> String {
format!("at://{repo}/{collection}/{rkey}")
}
fn split(uri: &str) -> (&str, &str, &str) {
let mut parts = uri.strip_prefix("at://").unwrap_or(uri).splitn(3, '/');
(parts.next().unwrap_or(""), parts.next().unwrap_or(""), parts.next().unwrap_or(""))
}
fn strong_ref(value: &Value) -> Option<(&str, &str)> {
let uri = value["uri"].as_str().filter(|u| u.starts_with("at://"))?;
let cid = value["cid"].as_str().filter(|c| !c.is_empty())?;
Some((uri, cid))
}
#[derive(PartialEq)]
struct Claim<'a> {
operator: &'a str,
subject: &'a str,
service: &'a str,
}
// trust.go claimOf
fn claim_of(value: &Value) -> Option<Claim<'_>> {
let field = |k: &str| value[k].as_str().map(str::trim).unwrap_or("");
let claim = Claim { operator: field("challenge"), subject: field("keyId"), service: field("service") };
(claim.operator.starts_with("did:") && claim.subject.starts_with("did:")).then_some(claim)
}
fn claim_rkey(service: &str, subject: &str) -> String {
format!("{service}-{}", subject.replace(':', "-"))
}
fn sha(parts: &[&[u8]]) -> [u8; 32] {
let mut hasher = Sha256::new();
for part in parts {
hasher.update(part);
}
hasher.finalize().into()
}
fn hex16(sum: &[u8; 32]) -> String {
sum[..16].iter().map(|b| format!("{b:02x}")).collect()
}
// market.ReceiptRKey, SessionRKey("delete", receipt) (ReleasePayloadRKey), EventRKeys(TerminationKey) payload rkey.
fn receipt_rkey(accept_uri: &str, accept_cid: &str) -> String {
format!("receipt-{}", hex16(&sha(&[accept_uri.trim().as_bytes(), b"\n", accept_cid.trim().as_bytes()])))
}
fn release_rkey(receipt_uri: &str) -> String {
const ALPHABET: &[u8] = b"234567abcdefghijklmnopqrstuvwxyz";
let sum = sha(&[b"delete", &[0], receipt_uri.as_bytes()]);
sum[..13].iter().map(|b| ALPHABET[*b as usize % ALPHABET.len()] as char).collect()
}
fn termination_rkey(receipt_uri: &str) -> String {
format!("termination-payload-{}", hex16(&sha(&[b"terminated:", receipt_uri.as_bytes()])))
}
// trust.go ResolveOperatorDidWith / operatorOf: Some(Some(op)) a verified operator, Some(None) none.
fn operator(r: &mut Reads, did: &str, service: &str, now: Option<(i64, u32)>) -> Option<Option<String>> {
let Rec::Found(mirror) = r.get(&uri(did, BADGE, "association"))? else { return Some(None) };
let value = &mirror["value"];
let Some(want) = claim_of(value).filter(|c| c.subject == did && c.operator != did && c.service == service) else { return Some(None) };
let held = if let Some(proof) = value.as_object().and_then(|o| o.get("proof")) {
proof_held(r, &want, proof, now)?
} else {
let association = association_held(r, &want, now);
let claim = claim_held(r, &want);
match (association?, claim?) {
(Err(()), _) => false,
(Ok(held), claim) => held || claim,
}
};
Some(held.then(|| want.operator.to_string()))
}
// trust/association.go Matches for the default kinds.
fn matches(collection: &str, value: &Value, want: &Claim) -> bool {
if !value.is_object() || want.operator.is_empty() || want.subject.is_empty() || want.operator == want.subject || value["$type"] != collection {
return false;
}
match collection {
BADGE => claim_of(value).is_some_and(|c| c == *want),
ASSOCIATION => value["subject"] == want.subject && value["service"] == want.service,
_ => false,
}
}
fn proof_held(r: &mut Reads, want: &Claim, proof: &Value, now: Option<(i64, u32)>) -> Option<bool> {
let Some((proof_uri, cid)) = strong_ref(proof) else { return Some(false) };
let (repo, collection, _) = split(proof_uri);
// Posts (bskyPost, delvePost) are association kinds kcp accepts only when configured; the
// default Association{} (what executor/ghalite.go uses) accepts claim and association.
if repo != want.operator || (collection != BADGE && collection != ASSOCIATION) {
return Some(false);
}
let Some(value) = r.pinned(proof_uri, cid)? else { return Some(false) };
if !matches(collection, value, want) {
return Some(false);
}
scope_held(r, value, want, now)
}
// Err(()): the read failed, which kcp treats as no operator at all (no fallback to the claim).
fn association_held(r: &mut Reads, want: &Claim, now: Option<(i64, u32)>) -> Option<Result<bool, ()>> {
let value = match r.get(&uri(want.operator, ASSOCIATION, &claim_rkey(want.service, want.subject)))? {
Rec::Found(output) => &output["value"],
Rec::Absent => return Some(Ok(false)),
Rec::Failed(_) => return Some(Err(())),
};
if !matches(ASSOCIATION, value, want) {
return Some(Ok(false));
}
scope_held(r, value, want, now).map(Ok)
}
fn claim_held(r: &mut Reads, want: &Claim) -> Option<bool> {
Some(match r.get(&uri(want.operator, BADGE, &claim_rkey(want.service, want.subject)))? {
Rec::Found(output) => claim_of(&output["value"]).is_some_and(|c| c == *want),
_ => false,
})
}
fn scope_held(r: &mut Reads, value: &Value, want: &Claim, now: Option<(i64, u32)>) -> Option<bool> {
let fields = value.as_object();
let scoped = fields.is_some_and(|f| f.contains_key("contract"));
let expiring = fields.is_some_and(|f| f.contains_key("expiresAt"));
let expires = value["expiresAt"].as_str().unwrap_or("");
if scoped && expires.is_empty() {
return Some(false);
}
if expiring && !matches!((now, instant(expires)), (Some(now), Some(deadline)) if now < deadline) {
return Some(false);
}
if !scoped {
return Some(true);
}
let Some((contract, cid)) = strong_ref(&value["contract"]) else { return Some(false) };
contract_held(r, contract, cid, want.operator)
}
// trust/association.go contractHeld: a held, unreleased, unterminated market.accept of the operator.
fn contract_held(r: &mut Reads, contract: &str, cid: &str, operator: &str) -> Option<bool> {
let (repo, collection, _) = split(contract);
if repo != operator || collection != ACCEPT {
return Some(false);
}
let Some(accept) = r.pinned(contract, cid)? else { return Some(false) };
let Some((bid, _)) = strong_ref(&accept["bid"]) else { return Some(false) };
let bidder = split(bid).0;
if !bidder.starts_with("did:") {
return Some(false);
}
let receipt_uri = uri(bidder, RECEIPT, &receipt_rkey(contract, cid));
let Rec::Found(receipt) = r.get(&receipt_uri)? else { return Some(false) };
if receipt["uri"] != receipt_uri.as_str() || strong_ref(&receipt["value"]["accept"]) != Some((contract, cid)) {
return Some(false);
}
let released = r.get(&uri(operator, VM_DELETE, &release_rkey(&receipt_uri)));
let terminated = r.get(&uri(bidder, VM_TERMINATED, &termination_rkey(&receipt_uri)));
Some(matches!((released?, terminated?), (Rec::Absent, Rec::Absent)))
}
// trust.go Vouches: a vouch record (rkey = subject) that is not a denouncement.
fn vouches(r: &mut Reads, voucher: &str, subject: &str) -> Option<bool> {
if voucher.is_empty() || !subject.starts_with("did:") {
return Some(false);
}
Some(match r.get(&uri(voucher, VOUCH, subject))? {
Rec::Found(output) => output["value"]["kind"] != "denounce",
_ => false,
})
}
fn distinct<'a>(values: &[&'a str]) -> Vec<&'a str> {
let mut out: Vec<&str> = Vec::new();
for v in values {
if !v.is_empty() && !out.contains(v) {
out.push(v);
}
}
out
}
fn build_snapshot(r: &mut Reads, self_did: &str, cp: &str, self_svc: &str, cp_svc: &str, now: Option<(i64, u32)>) -> Option<Snapshot> {
let self_op = operator(r, self_did, self_svc, now);
let cp_op = if cp.is_empty() || cp == self_did { Some(None) } else { operator(r, cp, cp_svc, now) };
let self_op = self_op?.unwrap_or_else(|| self_did.to_string());
let cp_op = cp_op?;
let co = cp_op.as_deref().unwrap_or("");
let mut operator_of = Map::new();
operator_of.insert(self_did.into(), json!(self_op));
if !co.is_empty() {
operator_of.insert(cp.into(), json!(co));
}
let trusted = distinct(&[self_did, &self_op]);
let vouchers = distinct(&[&trusted[..], &[cp, co]].concat());
let subjects = distinct(&[self_did, &self_op, cp, co]);
let mut pending = false;
let mut vouched_by = Map::new();
for voucher in &vouchers {
let mut list = Vec::new();
for subject in subjects.iter().filter(|s| *s != voucher) {
match vouches(r, voucher, subject) {
None => pending = true,
Some(true) => list.push(json!(subject)),
Some(false) => {}
}
}
vouched_by.insert(voucher.to_string(), Value::Array(list));
}
(!pending).then_some(Snapshot { operator_of, vouched_by })
}
// RFC 3339 (time.RFC3339Nano) to (unix seconds, nanoseconds).
fn instant(s: &str) -> Option<(i64, u32)> {
let b = s.as_bytes();
let num = |from: usize, to: usize| -> Option<i64> { s.get(from..to)?.parse().ok().filter(|_| b[from..to].iter().all(u8::is_ascii_digit)) };
if b.len() < 20 || b[4] != b'-' || b[7] != b'-' || b[10] != b'T' || b[13] != b':' || b[16] != b':' {
return None;
}
let (y, mo, d, h, mi, sec) = (num(0, 4)?, num(5, 7)?, num(8, 10)?, num(11, 13)?, num(14, 16)?, num(17, 19)?);
if !(1..=12).contains(&mo) || !(1..=31).contains(&d) || h > 23 || mi > 59 || sec > 59 {
return None;
}
let mut i = 19;
let mut nanos = 0u32;
if b[i] == b'.' {
let start = i + 1;
i = start;
while i < b.len() && b[i].is_ascii_digit() {
i += 1;
}
if i == start || i - start > 9 {
return None;
}
nanos = format!("{:0<9}", &s[start..i]).parse().ok()?;
}
let offset = match b.get(i)? {
b'Z' if i + 1 == b.len() => 0,
sign @ (b'+' | b'-') if i + 6 == b.len() && b[i + 3] == b':' => {
let minutes = num(i + 1, i + 3)? * 60 + num(i + 4, i + 6)?;
if *sign == b'+' { minutes * 60 } else { -minutes * 60 }
}
_ => return None,
};
let yy = if mo <= 2 { y - 1 } else { y };
let era = yy.div_euclid(400);
let yoe = yy - era * 400;
let doy = (153 * ((mo + 9) % 12) + 2) / 5 + d - 1;
let days = era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy - 719468;
Some((days * 86400 + h * 3600 + mi * 60 + sec - offset, nanos))
}