Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 42 additions & 11 deletions dag/gunbc/spark/pair_serving_d0.dag
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import std.content_hash { ContentHash, content_hash_of_value, serialize_content_
import std.temporal_effect { HeldLease, LeaseEpoch, held_lease, LeaseRunningExpected }
import std.scoped_authorization {
OperatorGrant, ScopedAuthorization, AuthorizationGranted, AuthorizationRequest, AuthorizationScope, AttemptIdentity, AuthorizedAction,
AuthorizationPermitted, AuthorizationRefused, AuthorizationRefusalCause, AuthorizationEscalationMismatch, authorize, authorization_scope_render, authorization_refusal_reason,
AuthorizationPermitted, AuthorizationRefused, AuthorizationRefusalCause, AuthorizationEscalationMismatch, authorize, authorization_scopes_render, authorization_refusal_reason,
intent_frame_head, parse_intent_scopes, authorization_scope_eq,
AuthorizationClaimState, Unclaimed, ClaimedBy, CompletedBy, AbortedBy, claim_authorization, claim_transition_claimed, claim_transition_completed, claim_transition_aborted,
AuthorizationClaimOutcome, ClaimHeld, ClaimLost, ClaimUndecided, authorization_claim_outcome, authorization_claim_key, parse_authorization_claim_state,
}
Expand Down Expand Up @@ -228,12 +229,24 @@ fn d0_subject_wire(subject: D0Subject) -> String {
// collision class there is the same defect the pending-state binding was moved off it for.
// The digest is taken through the sha256sum realization, so it is an effect and can be
// unavailable -- which is a refusal, never a substituted structural hash.
data d0_intent_schema: String = "pair-serving-d0-intent/v1"
data d0_intent_schema: String = "pair-serving-d0-intent/v2"

// THE HEAD IS THE std.scoped_authorization INTENT FRAME: the schema line and the scope written by
// the one scope codec, so a verifier holding this text and the approved revision reads the approved
// scope with parse_intent_scopes and no knowledge of D0 (the fabric store door does exactly that).
// A scope the codec cannot write has no frame, and then there is NO intent: the refusal is here, at
// the filer, before anything is hashed or filed -- never an empty head standing in for one (review
// 72009). d0_required_scope has a wire, so the refusing arm is unreachable today; it stays typed.
fn d0_intent_text(subject: D0Subject, group: FabricGroup, transaction: NonEmptyStr) -> String? {
match intent_frame_head(schema: d0_intent_schema as NonEmptyStr, scopes: [d0_required_scope()]) {
Absent => none
Present { value: head } => Present { value: d0_intent_body(head: head, subject: subject, group: group, transaction: transaction) }
}
}

fn d0_intent_text(subject: D0Subject, group: FabricGroup, transaction: NonEmptyStr) -> String {
fn d0_intent_body(head: String, subject: D0Subject, group: FabricGroup, transaction: NonEmptyStr) -> String {
join([
d0_intent_schema,
"\nscope=", authorization_scope_render(scope: d0_required_scope()),
head,
"\ntarget=", fabric_group_wire(g: group) as String,
"\nattempt=", transaction as String,
"\n", d0_subject_wire(subject: subject),
Expand All @@ -242,7 +255,10 @@ fn d0_intent_text(subject: D0Subject, group: FabricGroup, transaction: NonEmptyS
}

fn d0_intent_hash(subject: D0Subject, group: FabricGroup, transaction: NonEmptyStr) -> ContentHash? {
sha256_of_text(text: d0_intent_text(subject: subject, group: group, transaction: transaction))
match d0_intent_text(subject: subject, group: group, transaction: transaction) {
Absent => none
Present { value: text } => sha256_of_text(text: text)
}
}

fn sha256_of_text(text: String) -> ContentHash? {
Expand All @@ -252,6 +268,18 @@ fn sha256_of_text(text: String) -> ContentHash? {
}
}

// WHAT THE OPERATOR READS IS RENDERED FROM WHAT THE STORE CHECKS. The approval notification shows
// the filed purpose, not the intent text, and a constant sentence here let the operator approve an
// operation whose scope and subject they never saw. The purpose is therefore rendered from the same
// typed scope the intent frame carries and the same subject wire the intent text carries.
fn d0_purpose(subject: D0Subject, transaction: NonEmptyStr) -> NonEmptyStr {
join([
"Cut D D0 (transaction ", transaction as String, "): suspend the pair-serving authority for a bounded successor. Scope: ",
authorization_scopes_render(scopes: [d0_required_scope()]),
". Over: ", d0_subject_wire(subject: subject), ".",
], "") as NonEmptyStr
}

// The request D0 makes of a grant: the escalation the caller names, the required scope, the
// grant's own subject (what is being checked is that the grant agrees with itself and with this
// run), the transaction as the attempt, and the intent over the exact operation. Destructive.
Expand All @@ -260,7 +288,7 @@ fn d0_request(escalation_id: NonEmptyStr, subject: D0Subject, transaction: NonEm
escalation_id: escalation_id,
scopes: [d0_required_scope()],
subject: subject,
purpose: "suspend the pair-serving authority for a bounded successor transaction (Cut D D0)" as NonEmptyStr,
purpose: d0_purpose(subject: subject, transaction: transaction),
attempt: (transaction as String) as AttemptIdentity,
intent_hash: intent,
destructive: true,
Expand Down Expand Up @@ -334,8 +362,11 @@ fn line_at(lines: List<String>, i: Int) -> String {

fn parse_d0_intent_text(text: String) -> D0IntentParsed? {
let lines = intent_lines(text: text)
if line_at(lines: lines, i: 0) != d0_intent_schema { none }
else if line_at(lines: lines, i: 1) != join(["scope=", authorization_scope_render(scope: d0_required_scope())], "") { none }
let framed = match parse_intent_scopes(text: text) {
Present { value: f } => (f.schema as String) == d0_intent_schema && count(f.scopes) == 1 && all(f.scopes, sc => authorization_scope_eq(a: sc, b: d0_required_scope()))
Absent => false
}
if !framed { none }
else {
match intent_field(line: line_at(lines: lines, i: 2), key: "target") {
Absent => none
Expand Down Expand Up @@ -376,7 +407,7 @@ fn parse_d0_intent_text(text: String) -> D0IntentParsed? {
Absent => none
Present { value: hw } => {
let subject = D0Subject { group: subject_group, hosts: parse_d0_hosts(wire: hw), successor: successor, cleanup: cleanup, term: term }
if d0_intent_text(subject: subject, group: group, transaction: attempt as NonEmptyStr) == text {
if (match d0_intent_text(subject: subject, group: group, transaction: attempt as NonEmptyStr) { Present { value: t } => t == text Absent => false }) {
Present { value: D0IntentParsed { group: group, transaction: attempt as NonEmptyStr, subject: subject } }
} else { none }
}
Expand Down Expand Up @@ -1448,7 +1479,7 @@ fn d0_admission_text(a: D0GrantAdmission, group: String) -> String {
D0GrantAdmitted { admitted: _ } => "admitted"
D0GrantForAnotherGroup { granted: og } => join(["the grant authorizes ", fabric_group_wire(g: og) as String, ", not ", group], "")
D0GrantForAnotherPopulation { granted: gr, current: cu } => join(["the grant authorizes hosts [", hosts_wire(hs: gr), "] but ", group, " is currently [", hosts_wire(hs: cu), "]; the operator did not see this population"], "")
D0GrantIntentUnavailable => "the intent over the exact operation could not be digested (sha256sum unavailable), so the grant cannot be compared to it"
D0GrantIntentUnavailable => "the intent over the exact operation could not be stated and digested (sha256sum unavailable, or the scope has no wire), so the grant cannot be compared to it"
D0GrantRefused { cause: c } => join(["the authorization does not permit this operation: ", authorization_refusal_reason(cause: c)], "")
D0GrantClaimLost { holder: h } => join(["the grant is held by another attempt: ", h as String], "")
D0GrantClaimUndecided { reason: r } => join(["the grant's claim could not be decided: ", r as String], "")
Expand Down
6 changes: 3 additions & 3 deletions dag/gunbc/spark/pair_serving_d0_authorization.dag
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fn d0_authorization_request(group: FabricGroup, escalation_id: NonEmptyStr, tran
D0SubjectRefused { defects: d } => D0RequestRefused { defects: d }
D0SubjectFor { subject: s } =>
match d0_intent_hash(subject: s, group: group, transaction: transaction) {
Absent => D0RequestUnestablished { obligations: ["the intent over the exact operation could not be digested (sha256sum unavailable)" as NonEmptyStr] }
Absent => D0RequestUnestablished { obligations: ["the intent over the exact operation could not be stated and digested (sha256sum unavailable, or the scope has no wire)" as NonEmptyStr] }
Present { value: i } => D0RequestFor { request: d0_request(escalation_id: escalation_id, subject: s, transaction: transaction, intent: i) }
}
}
Expand Down Expand Up @@ -101,7 +101,7 @@ fn d0_filed_request(text: String, group: FabricGroup, escalation_id: NonEmptyStr
D0FiledRequestRefused { reason: join(["the frozen filing under this escalation is transaction ", parsed.transaction as String, ", not ", transaction as String], "") as NonEmptyStr }
} else {
match d0_intent_hash(subject: parsed.subject, group: group, transaction: transaction) {
Absent => D0FiledRequestRefused { reason: "the frozen intent could not be digested (sha256sum unavailable)" as NonEmptyStr }
Absent => D0FiledRequestRefused { reason: "the frozen intent could not be stated and digested (sha256sum unavailable, or the scope has no wire)" as NonEmptyStr }
Present { value: i } => D0FiledRequestFor { request: d0_request(escalation_id: escalation_id, subject: parsed.subject, transaction: transaction, intent: i), intent_text: frozen.intent_text }
}
}
Expand All @@ -111,7 +111,7 @@ fn d0_filed_request(text: String, group: FabricGroup, escalation_id: NonEmptyStr
}

// The intent text a live request is frozen as: the same bytes its intent hash was taken over.
fn d0_request_intent_text(request: AuthorizationRequest<D0Subject>, group: FabricGroup, transaction: NonEmptyStr) -> String {
fn d0_request_intent_text(request: AuthorizationRequest<D0Subject>, group: FabricGroup, transaction: NonEmptyStr) -> String? {
d0_intent_text(subject: request.subject, group: group, transaction: transaction)
}

Expand Down
6 changes: 5 additions & 1 deletion dag/gunbc/spark/pair_serving_d0_door.dag
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,10 @@ fn d0_run_kind(store: FabricStorageBinding, g: FabricGroup, escalation_id: NonEm
D0RequestUnestablished { obligations: o } => D0RunRefused { reason: join(["the consent has no subject to be asked over yet; still to be established: ", join(map(o, x => x as String), "; ")], "") }
D0RequestRefused { defects: d } => D0RunRefused { reason: join(["the consent has no subject to be asked over: the candidate's source is refused and must be corrected, not read further: ", join(map(d, x => x as String), "; ")], "") }
D0RequestFor { request: request } =>
match d0_freeze_filing(store: store, escalation_id: escalation_id, intent_text: d0_request_intent_text(request: request, group: g, transaction: transaction), execution_revision: execution_revision) {
match d0_request_intent_text(request: request, group: g, transaction: transaction) {
Absent => D0RunRefused { reason: "the consent's scope has no wire, so no intent can be stated over it (std.scoped_authorization intent_frame_head refused)" }
Present { value: intent_text } =>
match d0_freeze_filing(store: store, escalation_id: escalation_id, intent_text: intent_text, execution_revision: execution_revision) {
D0FilingFreezeRefused { reason: r } => D0RunRefused { reason: r as String }
D0FilingFrozen => D0FirstRun { request: request }
D0FilingAlreadyHeld { text: t } =>
Expand All @@ -129,6 +132,7 @@ fn d0_run_kind(store: FabricStorageBinding, g: FabricGroup, escalation_id: NonEm
D0FiledRequestFor { request: filed, intent_text: _ } => D0Rerun { request: filed }
}
}
}
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion dag/std/effect_grant.dag
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module std.effect_grant

import std.types { Bool, String, List }
import v2.std.algebra { length, filter }
import std.effects { EffectShape, ReadEffect, UpsertEffect, DeleteEffect, CreateEffect, AppendEffect, ExecuteEffect }
import std.materialization_ladder { Frame }
import std.access {
Expand Down Expand Up @@ -239,11 +240,35 @@ fn namespace_tree_label(tree: NamespaceTree) -> String {
FilesystemPathTree => "filesystem"
UriTree => "uri"
ProcTree => "proc"
ServiceOpTree { service: svc } => concat("service:", svc)
ServiceOpTree { service: svc } => concat(service_tree_label_prefix, svc)
CodeNameTree => "code-name"
}
}

// THE LABELS' INVERSES, DERIVED FROM THE LABELS (review 72135): each inverse searches the closed
// value set through the forward label function, so a label is spelled once, where it is written, and
// a reader cannot drift from the writer. A service tree naming no service is refused rather than read
// as some default service; its prefix is the one the writer uses.
data effect_verb_values: List<Verb> = [Read, Write, Execute]

fn verb_of_label(label: String) -> Verb? {
(filter(effect_verb_values, v => effect_verb_label(verb: v) == label)).first()
}

data service_tree_label_prefix: String = "service:"

data namespace_trees_without_parameters: List<NamespaceTree> = [FilesystemPathTree, UriTree, ProcTree, CodeNameTree]

fn namespace_tree_of_label(label: String) -> NamespaceTree? {
match (filter(namespace_trees_without_parameters, t => namespace_tree_label(tree: t) == label)).first() {
Present { value: t } => Present { value: t }
Absent =>
if starts_with(s: label, prefix: service_tree_label_prefix) && length(label) > length(service_tree_label_prefix) {
Present { value: ServiceOpTree { service: substring(s: label, start: length(service_tree_label_prefix), end: length(label)) } }
} else { none }
}
}

fn namespace_position_label(position: NamespacePosition) -> String {
concat(
namespace_tree_label(tree: position.tree),
Expand Down
Loading