v3.4.1 de8f68e

guard.go RawHistoryFolder

1// Package pruneguard holds the ownership-aware prune policy: pure
2// planning from complete inputs to per-candidate verdicts. Every
3// exported function here is pure — inputs in, verdicts out, no store
4// reads, no runtime calls, no deletion.
5//
6// The policy is fail-closed per candidate, never per operation:
7//
8//   - A resource is eligible only when every fact needed to prove it
9//     safe was read completely and no durable root claims it.
10//   - A durable root (desired/active/recovery/apply/operation/ownership
11//     /container use/pending upload/shared closure/pin) makes the
12//     candidate protected.
13//   - Anything else — unknown identity, unknown provenance, incomplete
14//     inventory, unreadable manifest — makes the candidate unknown.
15//     Unknown candidates are never deleted; they do not disable the
16//     whole operation.
17//
18// Ownership is never inferred from names, and labels never override a
19// contradictory or missing durable ownership record.
20package pruneguard
21
22import (
23	"github.com/bnema/gordon/internal/domain"
24)
25
26// Provenance classifies a runtime resource by the labels it carries.
27type Provenance int
28
29const (
30	// ProvenanceUnmanaged carries no Gordon labels: never adopted,
31	// never deleted.
32	ProvenanceUnmanaged Provenance = iota
33	// ProvenanceLegacyManaged carries the managed label but no app
34	// ownership label: the legacy/unknown class.
35	ProvenanceLegacyManaged
36	// ProvenanceServiceManaged carries standalone service labels.
37	ProvenanceServiceManaged
38	// ProvenanceAppOwned carries app ownership labels.
39	ProvenanceAppOwned
40)
41
42// ClassifyResource maps a label set to its provenance. App ownership
43// wins over service labels, and the managed marker alone is never
44// treated as app ownership.
45func ClassifyResource(labels map[string]string) Provenance {
46	if len(labels) == 0 {
47		return ProvenanceUnmanaged
48	}
49	if labels[domain.LabelApp] != "" {
50		return ProvenanceAppOwned
51	}
52	if labels[domain.LabelService] != "" || labels[domain.LabelServiceName] != "" {
53		return ProvenanceServiceManaged
54	}
55	if labels[domain.LabelManaged] == "true" {
56		return ProvenanceLegacyManaged
57	}
58	return ProvenanceUnmanaged
59}
60
61// protected builds a protected verdict with canonical reasons, falling
62// back to a reason that always fits when no root kind mapped.
63func protected(reasons []domain.PruneReason, fallback domain.PruneReason) (domain.PruneVerdict, []domain.PruneReason) {
64	canonical := domain.CanonicalReasons(reasons...)
65	if len(canonical) == 0 {
66		canonical = domain.CanonicalReasons(fallback)
67	}
68	return domain.PruneVerdictProtected, canonical
69}
70
71// eligibleResult builds an eligible verdict with its single reason.
72func eligibleResult(reason domain.PruneReason) (domain.PruneVerdict, []domain.PruneReason) {
73	return domain.PruneVerdictEligible, domain.CanonicalReasons(reason)
74}
75
76// unknownResult builds an unknown verdict with its single reason.
77func unknownResult(reason domain.PruneReason) (domain.PruneVerdict, []domain.PruneReason) {
78	return domain.PruneVerdictUnknown, domain.CanonicalReasons(reason)
79}
80
81// rootReasonsOrFallback collects the mapped reasons of every root
82// claiming refs, falling back to a shared-content reason when a root
83// matched but its kind cannot justify a verdict reason.
84func rootReasonsOrFallback(snapshot *domain.PruneProtectionSnapshot, refs ...string) (bool, []domain.PruneReason) {
85	if snapshot == nil {
86		return false, nil
87	}
88	matched := false
89	for _, ref := range refs {
90		if ref != "" && snapshot.ProtectsAnyRef(ref) {
91			matched = true
92			break
93		}
94	}
95	if !matched {
96		return false, nil
97	}
98	reasons := snapshot.RootReasons(refs...)
99	if len(reasons) == 0 {
100		reasons = domain.CanonicalReasons(domain.PruneReasonProtectedSharedContent)
101	}
102	return true, reasons
103}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close