Documentation
¶
Overview ¶
Package why answers "why did this happen": it loads one receipt out of the log by run and step, parses the delegation chain embedded in it, and renders the authority tree with its three verification states — verified, asserted, broken (Q12, D5) — plus any scope excess computed at read time from the raw per-hop grants (Q11, Q13).
Two disciplines govern this package:
- Nothing is written back. The scope excess, the attenuation classification and the verification rollups rendered here are computed from the stored bytes on every read, stamped with ComparatorVersion. Raw inputs are hashed evidence; computed values live on the read path, so a comparison bug can never freeze into evidence (Q11, schema §1).
- Nothing is invented. Receipts are pseudonymous — key thumbprints for actors, issuer plus sub-digest for the human principal (Q16, Q40) — so every human-readable name in the output comes from the local alias map (alias.go) and is an asserted label, not a cryptographic claim.
Index ¶
- Constants
- func ColorFor(w io.Writer) bool
- func CompareGrantsDetail(parent, child []Grant) (Attenuation, string, *I4Violation)
- func Render(w io.Writer, res *Result, opt Options) error
- func RenderRuns(w io.Writer, rows []RunRow, opt Options) error
- type Address
- type Aliases
- type Attenuation
- type Credential
- type Grant
- type Hop
- type I4Violation
- type Limit
- type Options
- type Privilege
- type Result
- type RootBinding
- type RunRow
- type ScopeExcess
- type Verification
Constants ¶
const AATGrantType = "attenuating_agent_token"
AATGrantType is the draft's RFC 9396 `authorization_details` type (§3.3, registered in §10.2).
const AliasFileName = "aliases.json"
AliasFileName is the local alias map inside the log dir: a JSON object of key thumbprint -> display label.
const ComparatorVersion = "behalf.sh/attenuation/v1"
ComparatorVersion stamps every attenuation comparison and scope check this package computes. Computed values are recomputable and travel stamped with the comparator that produced them, so a comparison bug never freezes into evidence (Q11, Q13, schema §1).
const MaxConstraintDepth = 32
MaxConstraintDepth bounds how deep a nested `all`/`any` tree this comparison will walk. The draft requires a finite ceiling and RECOMMENDS 32 (§3.4); a tree past it is not rejected as a token here — behalf's resource limits are a separate, tracked question (profile §9.4) — it is simply not something this comparison can prove to be a narrowing, so it fails closed like any other unprovable subsumption.
Variables ¶
This section is empty.
Functions ¶
func ColorFor ¶
ColorFor reports whether w should get ANSI colour: a terminal, and NO_COLOR unset (no-color.org).
func CompareGrantsDetail ¶
func CompareGrantsDetail(parent, child []Grant) (Attenuation, string, *I4Violation)
CompareGrantsDetail is CompareGrants plus the structured finding behind a `broadened` verdict, when the verdict came from the AAT draft's capability monotonicity rules (I4) and therefore names a tool and a constraint. Mint uses it to refuse with a typed error rather than a sentence; everything that only needs the classification calls CompareGrants.
Which comparison runs ¶
Two grant vocabularies live here and the routing between them is by the RFC 9396 `type` member alone:
- An entry of type `attenuating_agent_token` on EITHER side selects the vendored draft's own comparison — §4.5's subsumption matrix over the `tools` map, as §7 steps 4n–4p apply it (aat_i4.go). The draft defines this shape; behalf implements the draft for it.
- Everything else keeps compareV1: behalf's own `type` + `actions` + `privileges[].limit` rules, unchanged and still stamped ComparatorVersion.
Both comparisons always run and the stricter answer wins — the draft's rules never replace behalf's for a shape the draft does not define, and a side that carries none of the other's vocabulary is a known-empty side rather than an uncomparable one.
Types ¶
type Address ¶
Address is a receipt's product-level address: a run id and a step.
The step is the run-relative ordinal — the receipt's zero-based position in the run view, which is log-index order filtered to the run, the authoritative order for reconstruction (Q58, Q82). It is a read-path coordinate, not a stored field: the log index is global and shifts with interleaved runs, so `run_c71e:31` names the 32nd receipt of run_c71e however the log interleaved it. The fixture runs encode the same ordinal at capture in emitter.counter (which the index projects), so the demo's step 31 is the refund.issue in both runs.
func ParseAddress ¶
ParseAddress parses "<run>:<step>".
type Aliases ¶
Aliases is the local, versioned alias map that turns key thumbprints into names on screen (Q16). It exists because the canonical actor identity is the hop's key thumbprint — keys are what the cryptography proves — while names are self-reported. Receipts therefore carry no human-readable identity at all (Q40): the human principal is issuer plus sub-digest, and the display name lives here, locally, under the operator's control. Every label this map produces is an asserted label, never evidence.
func LoadAliases ¶
LoadAliases reads logDir/aliases.json over the built-in demo names. A missing file is not an error: the map is a display convenience, and an unnamed key renders as its own thumbprint.
type Attenuation ¶
type Attenuation string
Attenuation is the read-time comparison of one hop's grants against its parent's (Q13). `unknown` is a first-class outcome: vocabularies the AAT invariants cannot compare are recorded and flagged, never swallowed — and since D8.7 they hold the hop at `asserted`, because an invariant that could not be checked is not one that held. The comparator itself is unchanged by that decision; it answers, and internal/aat decides what the answer is worth.
const ( AttenuationUnchanged Attenuation = "unchanged" AttenuationAttenuated Attenuation = "attenuated" AttenuationBroadened Attenuation = "broadened" AttenuationUnknown Attenuation = "unknown" )
func CompareGrants ¶
func CompareGrants(parent, child []Grant) (Attenuation, string)
CompareGrants compares a child hop's grants against its parent's and returns the classification plus a human-readable reason for the non-obvious ones.
This is a pure read-time computation over the raw stored grants. Nothing it produces is written back to the record (Q11).
type Credential ¶
type Credential struct {
Issuer string `json:"issuer"`
Kind string `json:"kind"`
ID string `json:"id"`
Exp int64 `json:"exp"`
JKT string `json:"jkt"`
AuthTime int64 `json:"auth_time"`
AMR []string `json:"amr"`
}
Credential is the canonical per-hop credential reference — never the token itself (Q23).
type Grant ¶
type Grant struct {
Type string `json:"type"`
Actions []string `json:"actions"`
Locations []string `json:"locations"`
Datatypes []string `json:"datatypes"`
Identifier string `json:"identifier"`
Privileges []Privilege `json:"privileges"`
// Intent is a named behalf extension on the RFC 9396 object: the
// human's words for what was delegated ("resolve ticket 4417").
Intent string `json:"intent"`
// Tools is the AAT draft's own profile of RFC 9396 (§3.3): a map of tool
// name to argument constraint set, carried on an entry whose Type is
// AATGrantType. It is held as raw bytes rather than a decoded map so that
// a malformed `tools` member cannot abort the projection of the fields
// beside it — the draft's shape is read, and validated, in aat_i4.go.
Tools json.RawMessage `json:"tools"`
Raw json.RawMessage `json:"-"`
}
Grant is one RFC 9396 authorization_details object, read out of a hop verbatim. Raw keeps the exact stored bytes so nothing is normalized away: the fields below are a read-time projection for comparison, never a rewrite of the record (Q11).
type Hop ¶
type Hop struct {
Depth int
MaxDepth int
ParHash string
JKT string // RFC 7638 thumbprint of the hop's cnf.jwk (Q16)
JTI string
Exp int64
Grants []Grant
Credential Credential
RootBinding *RootBinding
Verification Verification
Carriage string
// StoredFlag is the attenuation_flag as captured (schema §7); Computed
// and ComputedReason are this read's comparison against the parent hop
// and are never written back (Q11).
StoredFlag string
Computed Attenuation
ComputedReason string
}
Hop is one delegation hop as rendered: the stored per-hop fields plus the read-time comparison against its parent.
type I4Violation ¶
type I4Violation struct {
Tool string
Argument string
ParentType string
DerivedType string
Detail string
}
I4Violation names the specific §4.5 rule a hop broke, so a caller — Mint above all — can refuse with something more precise than a sentence. Tool is always set; Argument is empty when the finding is tool-level (a tool the parent never granted, a key set that changed shape); ParentType and DerivedType are the two `constraint_type` values when both are known.
func (*I4Violation) String ¶
func (v *I4Violation) String() string
String renders the violation the way a refusal should read.
type Limit ¶
Limit is a decimal ceiling on an operation. Amount is kept as the verbatim decimal string — it is rendered exactly as captured and compared as an exact rational, never through a float.
type Options ¶
Options controls rendering. Colour is opt-in and off by default so piped and captured output is plain text.
type Result ¶
type Result struct {
Address Address
LogIndex uint64
LeafHash string
// Payload is the exact stored payload span — the signed bytes,
// untouched (the span rule).
Payload []byte
ReceiptID string
Kind string
CapturedAt string
Operation string
Target string
Outcome string
// Amount is the operation's amount as captured when the surface reported
// a decimal, and the read-time rendering of `amount_cents` when it
// reported minor units (see amountOf). It is what the scope check
// compares.
Amount string
Currency string
ActorJKT string
// StoredAttribution is the receipt-level rollup as captured (§8).
StoredAttribution string
AttributionClass string
Chain []Hop
Excess *ScopeExcess
// VerifiedHops of TotalHops is the "chain intact for N of M hops" line.
VerifiedHops int
TotalHops int
}
Result is everything one `behalf why` render needs.
func FromPayload ¶
FromPayload builds the same model Load builds, from a receipt payload the caller has already read out of the log.
It exists for readers that walk a whole run rather than one address — the HTML export — and would otherwise re-open the index and re-parse the checkpoint once per step. Everything Load computes is computed here too: the attenuation deltas and the scope check are read-time comparisons over the stored grants and are recomputed on every read, never cached and never written back (Q11).
The payload must already have been checked against its indexed leaf hash — tlog.BundleReader.Payload does that, and Load reaches it that way. A caller that skips the check renders bytes the index does not vouch for.
type RootBinding ¶
type RootBinding struct {
Nonce string `json:"nonce"`
DeviceJKT string `json:"device_jkt"`
IDTokenRef string `json:"id_token_ref"`
}
RootBinding is the depth-0 OIDC nonce-thumbprint binding (D5).
type RunRow ¶
type RunRow struct {
RunID string
Started string // the first captured_at in the run, verbatim
// Status is the run's outcome: `ok` unless some receipt records a
// failed operation. Two successful runs both reading `ok` is the
// point of the listing, not a gap in it — an error tracker shows
// nothing here, and the divergence that matters is only visible to
// `behalf diff`.
//
// Run *completeness* (Q82) is deliberately not this column: it is
// marked by a session-end receipt, the frozen kind enum has no such
// kind yet, so on v1 data every run would read `open` forever. That
// belongs in the listing once the kind exists, not before.
Status string
// Actions counts the action-family receipts — the denominator of the
// attribution metric (Q86).
Actions int64
// Actor names the human at the root of the delegation chain — who the
// run was carried out on behalf of, which is the question the product
// exists to answer. It is a display label off the local alias map
// (Q16): asserted, never evidence.
Actor string
// Attribution is the run's weakest attribution, rendered from the
// stored per-hop verification states (Q12, Q86).
Attribution string
}
RunRow is one line of `behalf runs`: a run, and how much of it is attributable.
type ScopeExcess ¶
type ScopeExcess struct {
Operation string
Limit string // the tightest delegated ceiling, verbatim
Currency string
Amount string // what the operation actually did, verbatim
ComparatorVersion string
}
ScopeExcess is a read-time finding: the operation exceeded the ceiling the chain delegated. It is computed from the raw per-hop authorization_details against the operation every time it is rendered, stamped with the comparator version, and never stored — a computed delta that froze into evidence would be a computation bug preserved forever (Q11, Q13).
func CheckScope ¶
func CheckScope(hops []Hop, operation, amount string) *ScopeExcess
CheckScope compares operation/amount against the tightest ceiling any hop of the chain placed on that operation. It returns nil when the operation is inside the delegated ceiling, when no hop constrained it, or when the values are not comparable — behalf records, it does not enforce.
type Verification ¶
type Verification struct {
Status string `json:"status"`
Method string `json:"method"`
EvidenceRef string `json:"evidence_ref"`
}
Verification is the stored per-hop three-state (Q12).