Artifact a35b50e56f5de62f6dc83fb195aa9d7c09927153c99c7d3a95025aa56107100a:
- File
internal/ingest/derive.go
— part of check-in
[a1b816cd96]
at
2026-08-24 16:32:42
on branch trunk
— Raise the derived events from the fold
checkin.created, branch.created and tag.* come off the artifact; branch.moved comes from the computed branch after RecomputeBranches, because only a branch's first check-in carries a sym- tag.
Raising is not idempotent while folding is, so the fold asks the index which hashes are new before writing them. A rebuild raises nothing. (user: dmestas, size: 4077) [annotate] [blame] [check-ins using]
package ingest import ( "strings" "craftdesign.group/quarry/internal/artifact" "craftdesign.group/quarry/internal/evt" ) // raised is one vocabulary event a folded artifact produced. type raised struct { Kind evt.Kind Payload map[string]any } // symPrefix marks a branch name among the tags. Fossil keeps the T // card's operator on the name — `*` propagates, `+` applies to one // artifact, `-` cancels — so the operator is read off the front rather // than tracked separately. const symPrefix = "sym-" // derive names the vocabulary events one folded artifact produces // (RFC 0005 §2.1: these are the DERIVED events, the ones zit's DAG // creates rather than a forge op). It reads only the artifact, so it // cannot lie about state it did not look at — anything needing index // state is deliberately absent, see branchKind below. func derive(hash string, a *artifact.Artifact) []raised { if a == nil { return nil } base := func() map[string]any { return map[string]any{"hash": hash, "actor": a.User, "date": a.Date} } var out []raised switch a.Type { case artifact.Manifest: p := base() p["summary"] = firstLine(a.Comment) if b := branches(a.Tags); len(b) > 0 { p["branch"] = b[0] } out = append(out, raised{evt.CheckinCreated, p}) case artifact.Control: // A control artifact carries nothing but tags; the check-in it // targets is the subject of every event it produces. default: // Every other type is already raised at the op that made it. // Raising here too would double every wiki edit and forum post // that went through the forge, and the op knows more than the // artifact does (it knows whether a ticket CLOSED, which needs // the previous status). See the note in fold(). return nil } for _, t := range a.Tags { if len(t.Name) < 2 { continue // no operator, no name: nothing to say about it } op, name := t.Name[:1], t.Name[1:] if !strings.HasPrefix(name, symPrefix) { // A plain tag. `-` cancels one, anything else applies it. k := evt.TagCreated if op == "-" { k = evt.TagRemoved } p := base() p["tag"] = name p["target"] = target(t, hash) out = append(out, raised{k, p}) continue } if op == "-" { // Cancelling a branch tag is not closing the branch — the // `closed` tag does that, and it is a plain tag handled // above. Nothing in the vocabulary names this, so nothing // is raised rather than something approximate. continue } // A `sym-` tag on a check-in is that branch coming into // existence. branch.moved for the SAME check-in comes from the // pipeline once branches are recomputed (see branchKind), so // raising it here as well would double it. if a.Type == artifact.Control { p := base() p["branch"] = strings.TrimPrefix(name, symPrefix) p["target"] = target(t, hash) out = append(out, raised{evt.BranchCreated, p}) } } return out } // branchKind is deliberately not a function here, because a check-in's // branch is not in the check-in. Only a branch's FIRST check-in wears a // `sym-` tag; the hundredth commit to trunk says nothing about trunk, so // deriving branch.moved from tags alone would fire it on branch creation // and never again — the opposite of what the name promises. The index // computes the branch during the fold, and the pipeline raises // branch.moved from that answer once it exists. // target is the artifact a tag applies to: a control artifact names it, // a manifest tags itself. func target(t artifact.Tag, self string) string { if t.Target != "" && t.Target != "*" { return t.Target } return self } // branches returns the propagating branch names among a check-in's tags. func branches(tags []artifact.Tag) []string { var out []string for _, t := range tags { if len(t.Name) > 1 && strings.HasPrefix(t.Name[1:], symPrefix) && t.Name[:1] != "-" { out = append(out, strings.TrimPrefix(t.Name[1:], symPrefix)) } } return out } func firstLine(s string) string { if i := strings.IndexByte(s, '\n'); i >= 0 { return s[:i] } return s }