Add indexed secret dependency closure
This commit is contained in:
@@ -269,7 +269,7 @@ unchanged until Stage 11 so collection is independently reviewable.
|
||||
Validate: `go test ./internal/engine -run 'Secret|Sensitivity|Inherited|Effective|Explain'`,
|
||||
engine race tests, and the everyday check. No new predicate order/count guarantee.
|
||||
|
||||
## Stage 10: Build and test indexed sensitivity triggers
|
||||
## Stage 10: Build and test indexed sensitivity triggers — Complete
|
||||
|
||||
Roadmap candidate 6, isolated algorithm. Add private
|
||||
`internal/engine/secret_propagation.go` and focused tests.
|
||||
|
||||
69
internal/engine/secret_propagation.go
Normal file
69
internal/engine/secret_propagation.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package engine
|
||||
|
||||
type secretTrigger struct {
|
||||
target string
|
||||
ordinary bool
|
||||
}
|
||||
|
||||
func closeSecretDependencies(paths map[string]Path, sensitive map[string]bool, dependencies, inherited []secretDependency) map[string]bool {
|
||||
result := make(map[string]bool, len(sensitive))
|
||||
queue := make([]string, 0, len(sensitive))
|
||||
for pointer, classified := range sensitive {
|
||||
if classified {
|
||||
result[pointer] = true
|
||||
queue = append(queue, pointer)
|
||||
}
|
||||
}
|
||||
|
||||
var triggers pathPrefixIndex
|
||||
triggerValues := make([]secretTrigger, 0, len(dependencies)+2*len(inherited))
|
||||
insert := func(dependency Path, target string, ordinary bool) {
|
||||
if ordinary && !dependency.valid() {
|
||||
return
|
||||
}
|
||||
id := len(triggerValues)
|
||||
triggerValues = append(triggerValues, secretTrigger{target: target, ordinary: ordinary})
|
||||
triggers.insert(dependency.String(), id)
|
||||
}
|
||||
for _, dependency := range dependencies {
|
||||
insert(dependency.dependency, dependency.target.String(), true)
|
||||
}
|
||||
for _, link := range inherited {
|
||||
insert(link.dependency, link.target.String(), false)
|
||||
insert(link.target, link.dependency.String(), false)
|
||||
}
|
||||
|
||||
fired := make([]bool, len(triggerValues))
|
||||
activate := func(id int) {
|
||||
if fired[id] {
|
||||
return
|
||||
}
|
||||
fired[id] = true
|
||||
target := triggerValues[id].target
|
||||
if !result[target] {
|
||||
result[target] = true
|
||||
queue = append(queue, target)
|
||||
}
|
||||
}
|
||||
for head := 0; head < len(queue); head++ {
|
||||
pointer := queue[head]
|
||||
if classified, ok := paths[pointer]; ok && classified.valid() {
|
||||
triggers.visitAncestors(classified.String(), func(id int) {
|
||||
if triggerValues[id].ordinary {
|
||||
activate(id)
|
||||
}
|
||||
})
|
||||
triggers.visitDescendants(classified.String(), func(id int) {
|
||||
if triggerValues[id].ordinary {
|
||||
activate(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
triggers.visitDescendants(pointer, func(id int) {
|
||||
if !triggerValues[id].ordinary {
|
||||
activate(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
127
internal/engine/secret_propagation_test.go
Normal file
127
internal/engine/secret_propagation_test.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSecretDependencyClosureMatchesFixedPointScan(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
paths map[string]Path
|
||||
seed map[string]bool
|
||||
dependencies []secretDependency
|
||||
inherited []secretDependency
|
||||
}{
|
||||
{
|
||||
name: "forward reverse chains and cycles",
|
||||
paths: secretPaths(Keys("input"), Keys("derived"), Keys("output"), Keys("cycle")),
|
||||
seed: secretSet(Keys("input")),
|
||||
dependencies: []secretDependency{
|
||||
{dependency: Keys("input"), target: Keys("derived")},
|
||||
{dependency: Keys("derived"), target: Keys("output")},
|
||||
{dependency: Keys("output"), target: Keys("cycle")},
|
||||
{dependency: Keys("cycle"), target: Keys("derived")},
|
||||
{dependency: Keys("input"), target: Keys("derived")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ordinary ancestor descendant and root relations",
|
||||
paths: secretPaths(Path{}, Keys("branch", "leaf"), Keys("parent", "child")),
|
||||
seed: secretSet(Keys("branch", "leaf"), Keys("parent")),
|
||||
dependencies: []secretDependency{
|
||||
{dependency: Keys("branch"), target: Keys("from-descendant")},
|
||||
{dependency: Keys("parent", "child"), target: Keys("from-ancestor")},
|
||||
{dependency: Path{}, target: Keys("from-root")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unindexed seed only activates inherited relations",
|
||||
paths: secretPaths(Keys("ordinary"), Keys("copy")),
|
||||
seed: map[string]bool{"untracked": true},
|
||||
dependencies: []secretDependency{
|
||||
{dependency: Keys("untracked"), target: Keys("ordinary")},
|
||||
},
|
||||
inherited: []secretDependency{
|
||||
{dependency: Keys("untracked"), target: Keys("copy")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inheritance direction root escapes and numeric spellings",
|
||||
paths: secretPaths(Path{}, Keys("a/b", "01"), Keys("source", "value")),
|
||||
seed: secretSet(Path{}),
|
||||
dependencies: []secretDependency{
|
||||
{dependency: Keys("a/b", "01"), target: Keys("ordinary-copy")},
|
||||
},
|
||||
inherited: []secretDependency{
|
||||
{dependency: Keys("a/b", "01"), target: Keys("destination", "01")},
|
||||
{dependency: Keys("source"), target: Keys("destination")},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "inherited links do not trigger from classified descendants",
|
||||
paths: secretPaths(Keys("parent", "secret", "nested")),
|
||||
seed: secretSet(Keys("parent", "secret", "nested")),
|
||||
inherited: []secretDependency{
|
||||
{dependency: Keys("parent", "secret"), target: Keys("child", "secret")},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
want := referenceSecretDependencyClosure(test.paths, test.seed, test.dependencies, test.inherited)
|
||||
got := closeSecretDependencies(test.paths, test.seed, test.dependencies, test.inherited)
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("closure = %#v, want %#v", got, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func referenceSecretDependencyClosure(paths map[string]Path, seed map[string]bool, dependencies, inherited []secretDependency) map[string]bool {
|
||||
secret := make(map[string]bool, len(seed))
|
||||
for pointer, classified := range seed {
|
||||
if classified {
|
||||
secret[pointer] = true
|
||||
}
|
||||
}
|
||||
for {
|
||||
changed := false
|
||||
for _, dependency := range dependencies {
|
||||
if secretIntersectsPath(paths, secret, dependency.dependency) && !secret[dependency.target.String()] {
|
||||
secret[dependency.target.String()] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
for _, link := range inherited {
|
||||
if pathIsSecret(secret, link.dependency) && !secret[link.target.String()] {
|
||||
secret[link.target.String()] = true
|
||||
changed = true
|
||||
}
|
||||
if pathIsSecret(secret, link.target) && !secret[link.dependency.String()] {
|
||||
secret[link.dependency.String()] = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
return secret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func secretPaths(paths ...Path) map[string]Path {
|
||||
result := make(map[string]Path, len(paths))
|
||||
for _, path := range paths {
|
||||
result[path.String()] = path
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func secretSet(paths ...Path) map[string]bool {
|
||||
result := make(map[string]bool, len(paths))
|
||||
for _, path := range paths {
|
||||
result[path.String()] = true
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user