Repository navigation
Expand file tree
/
Copy pathmodel.go
More file actions
258 lines (227 loc) · 10.2 KB
/
Copy pathmodel.go
File metadata and controls
258 lines (227 loc) · 10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// SPDX-License-Identifier: Apache-2.0
// Package typeset is GitOps Reverser's single decision surface for "is this
// resource type followable, and if not, what is the one reason it is not?".
//
// It is the greenfield model from docs/spec/type-followability.md.
// Every served type carries one TypeRecord with one Followability: a verdict, a
// one-line summary, and the full funnel-ordered list of requirement checks. That
// single value replaces the old split between a "requirements" table and a "health
// conditions" table — the failing check is the explanation.
//
// The package is deliberately a leaf: it depends only on apimachinery schema, never
// on a Kubernetes client, controller runtime, or the watch manager. That lets both
// the live cluster path (internal/watch) and the no-cluster manifest analyzer share
// one decision surface and one reason-code vocabulary.
package typeset
import "k8s.io/apimachinery/pkg/runtime/schema"
// Scope is whether a type is namespaced, cluster-scoped, or not yet known. Unknown
// feeds the scope requirement's scope-unknown reason; discovery normally resolves
// it, so Unknown is reserved for synthetic or un-enriched observations.
type Scope string
const (
// ScopeNamespaced is a namespaced resource (lives inside a namespace).
ScopeNamespaced Scope = "Namespaced"
// ScopeCluster is a cluster-scoped resource.
ScopeCluster Scope = "ClusterScoped"
// ScopeUnknown is a type whose scope discovery has not established.
ScopeUnknown Scope = "Unknown"
)
// Identity is the one true name of a type. For a followable type the GVK <-> GVR
// bijection is closed, so GVK and GVR always round-trip.
type Identity struct {
GVK schema.GroupVersionKind
GVR schema.GroupVersionResource
Scope Scope
}
// OriginKind classifies where a served type comes from.
type OriginKind string
const (
// OriginBuiltin is a core or built-in Kubernetes API group/version.
OriginBuiltin OriginKind = "builtin"
// OriginCRD is a type backed by a CustomResourceDefinition.
OriginCRD OriginKind = "crd"
// OriginAggregated is a type served by an aggregated API server (APIService).
OriginAggregated OriginKind = "aggregated"
// OriginUnknown is a served type the scan could not classify; it fails the
// origin requirement.
OriginUnknown OriginKind = "unknown"
)
// Confidence records how strongly the origin classification is held: observed from
// direct CRD/APIService evidence, inferred from the group/version alone, or unknown.
type Confidence string
const (
// ConfidenceObserved is backed by direct evidence (a CRD or APIService object).
ConfidenceObserved Confidence = "observed"
// ConfidenceInferred is derived from the group/version shape, without an object.
ConfidenceInferred Confidence = "inferred"
// ConfidenceUnknown is no basis for classification.
ConfidenceUnknown Confidence = "unknown"
)
// Origin is the provenance of a served type plus how strongly it is held.
type Origin struct {
Kind OriginKind
Confidence Confidence
// Evidence is bounded human detail, e.g. crontabs.stable.example.com.
Evidence string
}
// StatusFact records whether a type exposes a /status subresource. It is reporting
// only — GitOps Reverser never writes /status — so it carries no write path.
type StatusFact struct {
Enabled bool
}
// ScaleBinding is the only subresource fact the writer needs: where a /scale
// mutation lands on the parent's desired state. SpecReplicasPath drives the scale
// write path; the selector facts are for reporting. See
// docs/spec/type-followability.md.
type ScaleBinding struct {
Enabled bool
Source string // discovery | crd | builtin-registry | aggregated | unknown
ResponseGVK schema.GroupVersionKind
SpecReplicasPath string
StatusReplicasPath string
SelectorPath string
SelectorKind string // serialized-string | label-selector | unknown
// Usable is true only when a /scale audit event can be mapped back to a durable
// parent field. False feeds the scale requirement's scale-path-unresolved reason.
Usable bool
}
// Subresources are folded into the parent record, never followed as their own
// types.
type Subresources struct {
Status StatusFact
Scale ScaleBinding
}
// Verdict is the top-level answer for one type. Every other surface (health level,
// status condition, "why ignored?" diagnostic) is a rendering of this.
type Verdict string
const (
// VerdictFollowable means every required check passed; the type is in the live set.
VerdictFollowable Verdict = "followable"
// VerdictRetained means a transient check (served/trusted) is failing now but the
// removal grace has not elapsed; the type is still treated as live.
VerdictRetained Verdict = "retained"
// VerdictRefused means a permanent check failed; the type will not be followed.
VerdictRefused Verdict = "refused"
// VerdictUnknown means the registry could not assess the type (catalog unavailable).
VerdictUnknown Verdict = "unknown"
)
// Requirement is one named check in the followability funnel.
type Requirement string
const (
// RequirementServed — discovery serves this as a top-level resource.
RequirementServed Requirement = "served"
// RequirementTrusted — the backing group/version came from trusted discovery.
RequirementTrusted Requirement = "trusted"
// RequirementStable — the type is not mid-disappearance, or is inside the grace.
RequirementStable Requirement = "stable"
// RequirementIdentity — GVK <-> GVR is 1:1 in both directions.
RequirementIdentity Requirement = "identity"
// RequirementScope — the type is known namespaced or cluster-scoped.
RequirementScope Requirement = "scope"
// RequirementVerbs — discovery advertises get, list, watch, patch.
RequirementVerbs Requirement = "verbs"
// RequirementOrigin — classified builtin, crd, or aggregated with evidence.
RequirementOrigin Requirement = "origin"
// RequirementPolicy — product policy permits mirroring this type.
RequirementPolicy Requirement = "policy"
// RequirementSensitivity — not sensitive, or sensitivity is supported.
RequirementSensitivity Requirement = "sensitivity"
// RequirementScale — scale is unused, or its parent replica path is known.
RequirementScale Requirement = "scale"
)
// Result is one requirement's outcome.
type Result string
const (
// ResultPass — the requirement is satisfied.
ResultPass Result = "pass"
// ResultFail — the requirement is not satisfied; Reason names the single cause.
ResultFail Result = "fail"
// ResultSkip — the requirement does not apply (e.g. scale when scale is unused).
ResultSkip Result = "skip"
// ResultUnknown — the requirement could not be assessed.
ResultUnknown Result = "unknown"
)
// Reason is the single machine-readable cause of a failed check. It is the one
// vocabulary used everywhere a type is turned away — lookups, the live-set report,
// and operator status — so "why isn't this picked up?" always has the same answer.
type Reason string
const (
// ReasonNotServed — trusted discovery has no top-level resource for this kind.
ReasonNotServed Reason = "not-served"
// ReasonSubresourceOnly — the kind is served only as a subresource.
ReasonSubresourceOnly Reason = "subresource-only"
// ReasonDiscoveryDegraded — discovery currently fails for the backing group/version.
ReasonDiscoveryDegraded Reason = "discovery-degraded"
// ReasonCatalogUnavailable — no trusted catalog data exists yet.
ReasonCatalogUnavailable Reason = "catalog-unavailable"
// ReasonAbsenceExpired — the type disappeared and the removal grace has elapsed.
ReasonAbsenceExpired Reason = "absence-expired"
// ReasonGVKNotUnique — the GVK is served by more than one GVR.
ReasonGVKNotUnique Reason = "gvk-not-unique"
// ReasonGVRNotUnique — the GVR resolves to more than one Kind.
ReasonGVRNotUnique Reason = "gvr-not-unique"
// ReasonScopeUnknown — discovery did not establish a namespaced/cluster scope.
ReasonScopeUnknown Reason = "scope-unknown"
// ReasonMissingVerb — discovery does not advertise a required verb (Detail names it).
ReasonMissingVerb Reason = "missing-verb"
// ReasonOriginUnknown — the served type could not be classified.
ReasonOriginUnknown Reason = "origin-unknown"
// ReasonDeniedByPolicy — product policy refuses to mirror this type.
ReasonDeniedByPolicy Reason = "denied-by-policy"
// ReasonSensitiveUnsupported — sensitive type without supported write handling.
ReasonSensitiveUnsupported Reason = "sensitive-unsupported"
// ReasonScalePathUnresolved — scale is used but the parent replica path is unknown.
ReasonScalePathUnresolved Reason = "scale-path-unresolved"
)
// Check is one requirement's evaluated result in the funnel.
type Check struct {
Requirement Requirement
Result Result
Reason Reason // empty on pass/skip; otherwise the single reason code
Detail string // bounded human detail, e.g. "patch"
}
// Failed reports whether the check is a hard fail.
func (c Check) Failed() bool { return c.Result == ResultFail }
// Followability answers "can I act on this, and why not?" in one value.
type Followability struct {
Verdict Verdict
Summary string // one line, e.g. "not followable — missing required verb: patch"
Checks []Check
}
// Check returns the evaluated check for a requirement, if present.
func (f Followability) Check(req Requirement) (Check, bool) {
for _, c := range f.Checks {
if c.Requirement == req {
return c, true
}
}
return Check{}, false
}
// FirstFailure returns the first failed check in funnel order, if any.
func (f Followability) FirstFailure() (Check, bool) {
for _, c := range f.Checks {
if c.Failed() {
return c, true
}
}
return Check{}, false
}
// TypeRecord is the unit everything passes around. It answers "can I act on this?"
// and "why not?" in one object, so the safe path and the diagnostic path are the
// same call.
type TypeRecord struct {
Identity Identity
Origin Origin
Preferred bool
Verbs []string
Subresources Subresources
Sensitive bool
Followability Followability
Generation uint64
}
// Followable is the safe-path helper. Most callers never inspect Verdict directly:
// a followable or retained type is live, everything else is not.
func (r TypeRecord) Followable() bool {
return r.Followability.Verdict == VerdictFollowable ||
r.Followability.Verdict == VerdictRetained
}