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'`,
|
Validate: `go test ./internal/engine -run 'Secret|Sensitivity|Inherited|Effective|Explain'`,
|
||||||
engine race tests, and the everyday check. No new predicate order/count guarantee.
|
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
|
Roadmap candidate 6, isolated algorithm. Add private
|
||||||
`internal/engine/secret_propagation.go` and focused tests.
|
`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