1package enrich23import "fmt"45// How an enriched build wins the registry fold.6//7// The registry serves the newest build of each volume, and its ordering is8// creation time, then policy revision, then stamp, then locator. Creation time9// is the newest capture the build was made from and never the build clock --10// that is a format invariant and this lane does not touch it. An enrichment of11// the same captures therefore ties on creation time with the plain build beside12// it, and the revision is what has to decide.13//14// # The mechanism15//16// The revision is one integer carrying two policy numbers: the enrich lane's in17// the high field, the generate lane's in the low one.18//19// revision = enrichPolicy*RevisionSpan + generatePolicy20//21// A plain single-source build writes its generate policy revision alone, which22// is the same number with an enrich policy of zero. So an enriched build of one23// capture always outranks the plain build of that capture, deterministically,24// with no scan of the library and no clock: the number is a pure function of25// the two lanes' compiled-in policy revisions, which is what keeps the26// determinism invariant -- same archive in, same stamp, same file name -- true27// of enriched builds as well.28//29// # Which axis dominates, and why30//31// Packing two axes into one integer makes one of them dominant, and this one32// picks the enrich axis on purpose: within one set of captures an enriched33// build is a superset of the plain build -- it holds everything the plain build34// holds plus what was contributed -- so serving the plain build over it would35// lose data the library already has. A generate policy change therefore takes36// effect for an enriched volume when the pipeline is re-run, which is how a37// policy change reaches a merged volume anyway: enrichment is a pipeline stage,38// not a separate ritual.39//40// RevisionSpan is the room the low field has. It is generous rather than tight41// because the cost of a wrong guess is asymmetric: too much room wastes42// nothing, and too little would silently reorder builds.43const (44 // PolicyRevision is the enrich lane's own policy revision. It moves when a45 // change here should supersede the enriched builds already in every46 // library.47 //48 // 1 the first enriched builds: merge, national, stdicons, lenses49 PolicyRevision = 15051 // RevisionSpan is how many generate policy revisions fit under one enrich52 // policy revision.53 RevisionSpan = 10054)5556// BuildRevision is the revision an enriched build carries, given the revision57// the generate lane would have written on its own.58//59// It refuses a generate revision that does not fit the span rather than60// wrapping into the next enrich band, because a silently reordered library is61// exactly the failure this mechanism exists to prevent. A generate lane that62// reaches a hundred policy revisions gets a wider span and one restamp, which63// is a decision somebody makes on purpose.64func BuildRevision(generatePolicy int) (int, error) {65 if generatePolicy < 0 || generatePolicy >= RevisionSpan {66 return 0, fmt.Errorf(67 "generate policy revision %d does not fit the enrich revision span of %d; "+68 "widening the span is a deliberate restamp of every enriched build (see docs/enrich.md)",69 generatePolicy, RevisionSpan)70 }71 return PolicyRevision*RevisionSpan + generatePolicy, nil72}7374// Enriched reports whether a build's revision was written by this lane, and75// which enrich policy wrote it. It is what a report uses to say "this build was76// enriched" without opening a payload.77func Enriched(revision int) (policy int, enriched bool) {78 if revision < RevisionSpan {79 return 0, false80 }81 return revision / RevisionSpan, true82}