Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2d0a479
:memo: docs(spec): open the dead-exported-surface change (kollect-xco…
konih Oct 6, 2026
9b48bed
:memo: docs(spec): fix spec-set review findings on dead-exported-surf…
konih Oct 6, 2026
4a184d0
:memo: docs(spec): apply round-2 spec-set review findings (coverage a…
konih Oct 6, 2026
232cc45
:recycle: refactor(sink): delete dead items-level export runner
konih Oct 6, 2026
711f761
:memo: docs(spec): record T1 closure, prepare T2 task prompt
konih Oct 6, 2026
9ff0d5b
:recycle: refactor(api)!: delete unused exported symbols (MergeReques…
konih Oct 6, 2026
0a5cf12
:memo: docs(spec): record T2 closure, prepare T3 task prompt
konih Oct 6, 2026
ecd47a6
:recycle: refactor(collect): delete superseded store methods (Store.R…
konih Oct 6, 2026
c06b874
:memo: docs(spec): record T3 closure, prepare T4 task prompt
konih Oct 6, 2026
ea0dc3a
:recycle: refactor(git): delete superseded git entry points (git.Expo…
konih Oct 6, 2026
4e5ffe8
:memo: docs(spec): record T4 closure, prepare T5 task prompt
konih Oct 7, 2026
5eddbb0
:recycle: refactor(sink): delete test-only capability aliases
konih Oct 7, 2026
35d5801
:memo: docs(spec): record T5 closure, prepare T6 task prompt
konih Oct 7, 2026
75d405e
:recycle: refactor(collect): delete superseded engine binding
konih Oct 7, 2026
2c415d1
:memo: docs(spec): record T6 closure, prepare T7 task prompt
konih Oct 7, 2026
028cfe5
:recycle: refactor(inventory): drop dead user field from auth cache
konih Oct 7, 2026
21af06e
:memo: docs(spec): record T7 closure, prepare T8 final-gate task prompt
konih Oct 7, 2026
f224fcf
:white_check_mark: test: record final gates for dead-exported-surface
konih Oct 7, 2026
40ccf00
:memo: docs(spec): record T8 final-gate evidence, open T9 lint-debt fix
konih Oct 7, 2026
4ecb725
:white_check_mark: test: close govet shadow debt from the deletion sweep
konih Oct 7, 2026
310638e
:memo: docs(spec): record T9 closure and stage-B fitness baseline
konih Oct 7, 2026
f327e74
:memo: docs(spec): record stage-B review (CLEAN 9/9), open T10 hygien…
konih Oct 7, 2026
a837617
:recycle: test(git): drop the deleted identifier from test names
konih Oct 7, 2026
7ad4302
:memo: docs(spec): record T10 closure and stage-B hand-off
konih Oct 7, 2026
3a58fc4
:rotating_light: fix(docs): clear markdown lint in the dead-exported-…
konih Oct 8, 2026
b2d07aa
:recycle: refactor(controller): migrate delete-watch spy to cap.Snaps…
konih Oct 10, 2026
16aba48
:white_check_mark: test(collect): pin sorted NamespacesForClusterTarg…
konih Oct 10, 2026
fdb9de5
:white_check_mark: test(sink): cover empty snapshot skip for stream-e…
konih Oct 10, 2026
a9a3f57
:white_check_mark: test(cap): assert RelationalStore capabilities
konih Oct 10, 2026
0292641
:white_check_mark: test(git): drop circular in-memory commit helper t…
konih Oct 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions api/v1alpha1/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ package v1alpha1

// Condition and annotation keys used across reconcilers and sinks.
const (
ConditionConnectionVerified = "ConnectionVerified"
ConditionTLSInsecure = "TLSInsecure"
ConditionConnected = "Connected"
ConditionCredentialsVerified = "CredentialsVerified"
ConditionSinkReachable = "SinkReachable"
ConditionSynced = "Synced"
ConditionExportSucceeded = "ExportSucceeded"
ConditionReady = "Ready"
ConditionDegraded = "Degraded"
ConditionConnectionVerified = "ConnectionVerified"
ConditionTLSInsecure = "TLSInsecure"
ConditionSinkReachable = "SinkReachable"
ConditionSynced = "Synced"
ConditionExportSucceeded = "ExportSucceeded"
ConditionReady = "Ready"
ConditionDegraded = "Degraded"

ReasonExportTerminal = "ExportTerminal"

Expand Down
20 changes: 0 additions & 20 deletions internal/collect/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,26 +397,6 @@ func (e *Engine) ItemCount(namespace, name string) int {
return e.store.CountForTarget(namespace, name)
}

// BindClusterTargetNamespaces records workload namespaces for a cluster-scoped target name.
//
// It writes a bare targetState, so any collection-state fingerprint for that key is
// cleared and the next RegisterTarget backfills the store once. That is the safe
// direction (re-dispatch rather than silent under-collection), but it means this must
// not be called per-reconcile alongside RegisterTarget.
func (e *Engine) BindClusterTargetNamespaces(targetName string, namespaces []string) {
e.mu.Lock()
defer e.mu.Unlock()

for _, ns := range namespaces {
key := targetKey(ns, targetName)
e.targets[key] = targetState{
target: kollectdevv1alpha1.KollectTarget{
ObjectMeta: metav1.ObjectMeta{Name: targetName, Namespace: ns},
},
}
}
}

// NamespacesForClusterTarget returns workload namespaces where a cluster target name is registered.
func (e *Engine) NamespacesForClusterTarget(targetName string) []string {
e.mu.RLock()
Expand Down
8 changes: 4 additions & 4 deletions internal/collect/engine_extract_failure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
// TestEngineDispatchExtractionFailureInvalidatesPriorRow is the REL-01 red proof:
// success → later required-extraction failure must remove the last-good row so
// export/fingerprint no longer present the stale value as current.
func TestEngineDispatchExtractionFailureInvalidatesPriorRow(t *testing.T) {

Check failure on line 182 in internal/collect/engine_extract_failure_test.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this method to reduce its Cognitive Complexity from 20 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=PlatformRelay_Kollect&issues=AaEUg2NoiTu2bu-nwubq&open=AaEUg2NoiTu2bu-nwubq&pullRequest=454
t.Parallel()

store := NewStore()
Expand Down Expand Up @@ -254,9 +254,9 @@
if fpAfterSuccess == "" {
t.Fatal("expected non-empty fingerprint after successful store")
}
envelopeAfterSuccess, err := store.MarshalTargetJSON("team-a", "deploys")
envelopeAfterSuccess, err := store.MarshalTargetExport("team-a", "deploys", ExportMetadata{})
if err != nil {
t.Fatalf("MarshalTargetJSON after success: %v", err)
t.Fatalf("MarshalTargetExport after success: %v", err)
}
if len(envelopeAfterSuccess) == 0 {
t.Fatal("expected non-empty export envelope after success")
Expand Down Expand Up @@ -285,9 +285,9 @@
if fpAfterFailure == fpAfterSuccess {
t.Fatalf("fingerprint unchanged after failure removal: %q", fpAfterFailure)
}
envelopeAfterFailure, err := store.MarshalTargetJSON("team-a", "deploys")
envelopeAfterFailure, err := store.MarshalTargetExport("team-a", "deploys", ExportMetadata{})
if err != nil {
t.Fatalf("MarshalTargetJSON after failure: %v", err)
t.Fatalf("MarshalTargetExport after failure: %v", err)
}
items, err := ItemsFromExportPayload(envelopeAfterFailure)
if err != nil {
Expand Down
79 changes: 79 additions & 0 deletions internal/collect/engine_namespaces_sorted_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2026 Konrad Heimel

package collect

import (
"context"
"slices"
"testing"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
dynamicfake "k8s.io/client-go/dynamic/fake"

kollectdevv1alpha1 "github.com/platformrelay/kollect/api/v1alpha1"
)

// TestEngineNamespacesForClusterTarget_sorted pins that the namespaces bound
// to a cluster target come back sorted regardless of registration order, and
// that a differently named target is not matched.
func TestEngineNamespacesForClusterTarget_sorted(t *testing.T) {
t.Parallel()

gvr := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}
dyn := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(
runtime.NewScheme(),
map[schema.GroupVersionResource]string{gvr: "DeploymentList"},
)

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

engine, err := NewEngine(dyn, nil, NewStore(), EngineConfig{})
if err != nil {
t.Fatalf("NewEngine: %v", err)
}
if err := engine.Start(ctx); err != nil {
t.Fatalf("Start: %v", err)
}

profile := &kollectdevv1alpha1.KollectProfile{
ObjectMeta: metav1.ObjectMeta{Name: "cluster-binding-profile", Namespace: "kollect-system"},
Spec: kollectdevv1alpha1.KollectProfileSpec{
TargetGVK: kollectdevv1alpha1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
},
}

register := func(name, ns string) {
t.Helper()

target := &kollectdevv1alpha1.KollectTarget{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
Spec: kollectdevv1alpha1.KollectTargetSpec{
ProfileRef: "cluster-binding-profile",
NamespaceSelector: &metav1.LabelSelector{
MatchLabels: map[string]string{corev1.LabelMetadataName: ns},
},
},
}
if err := engine.RegisterTarget(ctx, target, profile, RegisterTargetOptions{
EffectiveNamespaces: []string{ns},
}); err != nil {
t.Fatalf("register %s/%s: %v", ns, name, err)
}
}

for _, ns := range []string{"team-c", "team-a", "team-b"} {
register("shared-target", ns)
}
register("other-target", "team-z")

got := engine.NamespacesForClusterTarget("shared-target")
want := []string{"team-a", "team-b", "team-c"}
if !slices.Equal(got, want) {
t.Fatalf("NamespacesForClusterTarget() = %v, want %v", got, want)
}
}
8 changes: 1 addition & 7 deletions internal/collect/engine_setters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestEngineNamespaceDefaultsAndSnapshot(t *testing.T) {
}
}

func TestEngineSetScrubKeysAndBindClusterTargetNamespaces(t *testing.T) {
func TestEngineSetScrubKeys(t *testing.T) {
t.Parallel()

e, err := NewEngine(nil, nil, NewStore(), EngineConfig{})
Expand All @@ -47,12 +47,6 @@ func TestEngineSetScrubKeysAndBindClusterTargetNamespaces(t *testing.T) {
t.Fatalf("scrubKeys = %#v", e.scrubKeys)
}
e.mu.RUnlock()

e.BindClusterTargetNamespaces("shared-target", []string{"team-b", "team-a"})
got := e.NamespacesForClusterTarget("shared-target")
if len(got) != 2 || got[0] != "team-a" || got[1] != "team-b" {
t.Fatalf("NamespacesForClusterTarget = %#v, want sorted team-a/team-b", got)
}
}

func TestEngineNamespaceMetaSnapshot(t *testing.T) {
Expand Down
40 changes: 11 additions & 29 deletions internal/collect/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ type Store struct {
nsWatchers map[chan string]struct{}

// versionMu/versions track a per-namespace mutation counter (AR-10),
// deliberately kept separate from shards: RemoveCluster deletes the
// shard entirely, and a counter living on the shard would reset to 0
// and could re-issue a value a stale cache entry already holds. Keeping
// it in its own map (never deleted, only incremented) makes it survive
// shard deletion/recreation and stay strictly monotonic per namespace.
// deliberately kept separate from shards: a counter living on the
// shard would reset to 0 if that shard were ever deleted and
// recreated (no current mutation path deletes one), re-issuing a
// value a stale cache entry already holds. Keeping it in its own
// map (never deleted, only incremented) makes it survive shard
// deletion/recreation and stay strictly monotonic per namespace.
versionMu sync.Mutex
versions map[string]uint64
}
Expand Down Expand Up @@ -171,15 +172,15 @@ func (s *Store) bumpNamespaceVersion(namespace string) {
}

// NamespaceVersion returns a counter bumped on every mutation (Upsert/Remove/
// RemoveTarget/RemoveCluster) scoped to this namespace (AR-10). Two reads
// RemoveTarget) scoped to this namespace (AR-10). Two reads
// returning the same value guarantee the namespace's item content has not
// changed in between, so callers can skip a full SnapshotNamespace + content
// fingerprint recompute when the version is unchanged since the last one
// they computed. The counter is strictly monotonic per namespace for the
// lifetime of the Store — including across RemoveCluster, which deletes the
// shard but never the version entry, so a value is never re-issued. Every
// bump happens while the caller still holds the content lock (shard mu or
// shardsMu) that made the corresponding change visible, so "this version was
// lifetime of the Store — the version map is never pruned, not even by a
// hypothetical future shard deletion, so a value is never re-issued. Every
// bump happens while the caller still holds the content lock (shard mu)
// that made the corresponding change visible, so "this version was
// observed" always implies "this content (or later) is visible" — never the
// reverse, which would let a cache entry serve a fingerprint for content
// that has already moved on.
Expand All @@ -202,20 +203,6 @@ func (s *Store) RemoveTarget(targetNamespace, targetName string) {
s.notifyWatchers(targetNamespace)
}

// RemoveCluster drops all targets for one cluster target namespace.
func (s *Store) RemoveCluster(cluster string) {
if cluster == "" {
return
}

s.shardsMu.Lock()
delete(s.shards, cluster)
s.bumpNamespaceVersion(cluster) // atomic with the content change; see Upsert
s.shardsMu.Unlock()

s.notifyWatchers(cluster)
}

// CountForTarget returns items collected for one target.
func (s *Store) CountForTarget(targetNamespace, targetName string) int {
sh := s.shardFor(targetNamespace)
Expand Down Expand Up @@ -246,11 +233,6 @@ func (s *Store) SnapshotTarget(targetNamespace, targetName string) []Item {
return out
}

// MarshalTargetJSON returns a versioned export envelope for one target (ADR-0405).
func (s *Store) MarshalTargetJSON(targetNamespace, targetName string) ([]byte, error) {
return s.MarshalTargetExport(targetNamespace, targetName, ExportMetadata{})
}

// MarshalTargetExport returns a versioned export envelope for one target.
func (s *Store) MarshalTargetExport(
targetNamespace, targetName string,
Expand Down
40 changes: 0 additions & 40 deletions internal/collect/store_cluster_test.go

This file was deleted.

63 changes: 2 additions & 61 deletions internal/collect/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,65 +100,6 @@ func TestStoreNamespaceVersion_BumpsOnMutationAndIsolatesNamespaces(t *testing.T
}
}

// TestStoreNamespaceVersion_MonotonicAcrossRemoveClusterAndShardRecreation
// guards a specific trap: RemoveCluster deletes the whole storeShard for a
// namespace, so if the version counter lived on the shard itself, the next
// Upsert into that namespace would recreate the shard at version 0 and could
// climb back to a version number a stale cache entry was holding — a true
// (version, content) collision, not just a missed cache hit. The version
// must survive shard deletion and never repeat a previously-issued value for
// the same namespace.
func TestStoreNamespaceVersion_MonotonicAcrossRemoveClusterAndShardRecreation(t *testing.T) {
t.Parallel()

s := NewStore()
s.Upsert(Item{
TargetNamespace: "ns-a",
TargetName: "deploys",
UID: "1",
Namespace: "ns-a",
Name: "app",
Version: "v1",
Kind: "Deployment",
})
vBefore := s.NamespaceVersion("ns-a")

s.RemoveCluster("ns-a")
vAfterRemove := s.NamespaceVersion("ns-a")
if vAfterRemove <= vBefore {
t.Fatalf("version must strictly increase after RemoveCluster, not reset: before=%d after=%d", vBefore, vAfterRemove)
}

// Repopulate the namespace (shard recreated from scratch) enough times
// that a naive shard-local counter reset to 0 would climb back through
// vBefore and vAfterRemove (both are small single-digit values here).
var vAfterRepopulate uint64
for i := range 5 {
s.Upsert(Item{
TargetNamespace: "ns-a",
TargetName: "deploys",
UID: "1",
Namespace: "ns-a",
Name: "app",
Version: "v2",
Kind: "Deployment",
Attributes: map[string]any{"i": i},
})
v := s.NamespaceVersion("ns-a")
if v == vBefore {
t.Fatalf("version repeated a previously-issued value (vBefore=%d) after RemoveCluster + repopulate — a cache entry recorded at vBefore would now incorrectly hit", vBefore)
}
if v == vAfterRemove {
t.Fatalf("version repeated a previously-issued value (vAfterRemove=%d) after repopulate — a cache entry recorded at vAfterRemove would now incorrectly hit", vAfterRemove)
}
vAfterRepopulate = v
}

if vAfterRepopulate <= vAfterRemove {
t.Fatalf("version must keep increasing across repopulation: vAfterRemove=%d vAfterRepopulate=%d", vAfterRemove, vAfterRepopulate)
}
}

func TestStoreNamespaceIsolation(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -249,9 +190,9 @@ func TestStoreSubscribeAndMarshal(t *testing.T) {
t.Fatalf("CountForTarget = %d", got)
}

payload, err := s.MarshalTargetJSON("team-a", "deploys")
payload, err := s.MarshalTargetExport("team-a", "deploys", ExportMetadata{})
if err != nil || len(payload) == 0 {
t.Fatalf("MarshalTargetJSON: %v (%q)", err, payload)
t.Fatalf("MarshalTargetExport: %v (%q)", err, payload)
}

nsPayload, err := s.MarshalNamespaceJSON("team-a")
Expand Down
9 changes: 2 additions & 7 deletions internal/controller/cluster_target_finalizer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,20 +52,15 @@ func TestKollectClusterTargetReconciler_deleteUnregistersAndRemovesFinalizer(t *
WithStatusSubresource(ct).
Build()

engine, err := collect.NewEngine(nil, nil, store, collect.EngineConfig{})
if err != nil {
t.Fatalf("NewEngine: %v", err)
}

engine.BindClusterTargetNamespaces("platform", []string{"team-a"})
engine := newEngineWithBoundClusterTargets(t, store, map[string][]string{"platform": {"team-a"}})

rec := &KollectClusterTargetReconciler{
Client: cl,
Scheme: scheme,
Engine: engine,
}

_, err = rec.Reconcile(context.Background(), reconcile.Request{
_, err := rec.Reconcile(context.Background(), reconcile.Request{
NamespacedName: types.NamespacedName{Name: "platform"},
})
if err != nil {
Expand Down
Loading
Loading