Classify action routes and operation transports

This commit is contained in:
rcourtman
2026-07-12 13:55:19 +01:00
parent e6d40f3c11
commit c07927adbd
6 changed files with 229 additions and 62 deletions
+1 -1
View File
@@ -116,7 +116,7 @@ jobs:
run: python3 scripts/release_control/registry_audit.py --check
- name: Run canonical mutation registry audits
run: go test ./internal/mutationregistry ./internal/ai/tools -run 'Test(EveryRegisteredMutationHasDisposition|InfrastructureAPIRoutesResolveToRegistry|TransportCommandCatalogsResolveToRegistry|PatrolJobRegistrationResolvesToRegistry|RuntimeCandidateAuditNegativeFixtures|RegisteredModelMutationSchemasResolveToClosedRegistry|RetiredMutationAliasesCannotShadowExtensions)' -count=1
run: go test ./internal/mutationregistry ./internal/ai/tools -run 'Test(EveryRegisteredMutationHasDisposition|InfrastructureAPIRoutesResolveToRegistry|TransportCommandCatalogsResolveToRegistry|PatrolJobRegistrationResolvesToRegistry|RuntimeCandidateAuditNegativeFixtures|ActionRouteMethodAuthorityIsExactAndLookalikesFailClosed|NonAdmittingTransportMessagesCannotCarryDispatchAuthority|UnknownTransportLookalikeFailsClosed|RegisteredModelMutationSchemasResolveToClosedRegistry|RetiredMutationAliasesCannotShadowExtensions)' -count=1
- name: Run contract audit
run: python3 scripts/release_control/contract_audit.py --check
@@ -5778,6 +5778,18 @@ Production code contains no command or rollback execution algorithm; exported
approve/execute/rollback interfaces and HTTP endpoints are permanently inert
even when a command executor is injected.
The agent transport catalogue now classifies wire roles independently from
mutation identity. `docker_container_lifecycle` is the typed mutation request
for the existing `resource.docker.container-lifecycle` capability and may be
sent only after committed action-lifecycle authority.
`agent_operation_query` is query-only reconciliation;
`agent_operation_query_result` and `docker_container_lifecycle_result` are
non-admitting receipt/result messages. Query, result, receipt, and general
protocol roles are forbidden from carrying a mutation registry id or durable
authority reference, so response-shaped lookalikes cannot become dispatch
entry points. Task 10 remains the sole owner of `ActionResultV2` execution,
verification, evidence, and compensation truth.
Action planning and approval attribution are now server-owned across Assistant,
Patrol, and MCP projections. Trusted brokers use explicit service/policy actor
contexts; public `requestedBy` content cannot become audit authority. The agent
+67
View File
@@ -14,6 +14,73 @@ type RuntimeCandidate struct {
DurableAuthorityID string
}
type TransportRole string
const (
TransportRoleMutationRequest TransportRole = "mutation_request"
TransportRoleAdministrativeRequest TransportRole = "administrative_request"
TransportRoleOperationQuery TransportRole = "operation_query"
TransportRoleOperationResult TransportRole = "operation_result"
TransportRoleOperationReceipt TransportRole = "operation_receipt"
TransportRoleProtocol TransportRole = "protocol"
)
// TransportSurface classifies one actual wire-catalog member. Only mutation
// requests may bind lifecycle authority. Queries, results, receipts, and
// protocol messages are explicitly non-admitting and cannot carry a mutation
// registry identity or durable-authority reference.
type TransportSurface struct {
Name string
WireValue string
Role TransportRole
MutationID string
DurableAuthorityID string
}
func AuditTransportSurfaces(surfaces []TransportSurface) error {
seenNames := make(map[string]struct{}, len(surfaces))
seenWireValues := make(map[string]struct{}, len(surfaces))
for _, surface := range surfaces {
name := strings.TrimSpace(surface.Name)
wireValue := strings.TrimSpace(surface.WireValue)
if name == "" || wireValue == "" {
return fmt.Errorf("transport surface has empty name or wire value: %+v", surface)
}
if _, duplicate := seenNames[name]; duplicate {
return fmt.Errorf("transport surface name %q is classified more than once", name)
}
if _, duplicate := seenWireValues[wireValue]; duplicate {
return fmt.Errorf("transport wire value %q is classified more than once", wireValue)
}
seenNames[name] = struct{}{}
seenWireValues[wireValue] = struct{}{}
switch surface.Role {
case TransportRoleMutationRequest:
if err := AuditRuntimeCandidates([]RuntimeCandidate{{
Surface: "transport:" + wireValue,
MutationID: surface.MutationID,
Transport: true,
DurableAuthorityID: surface.DurableAuthorityID,
}}); err != nil {
return err
}
case TransportRoleAdministrativeRequest:
entry, ok := Lookup(surface.MutationID)
if !ok || entry.Disposition != DispositionAdministrativeException || surface.DurableAuthorityID != "" {
return fmt.Errorf("administrative transport %q has invalid registry authority", wireValue)
}
case TransportRoleOperationQuery, TransportRoleOperationResult, TransportRoleOperationReceipt, TransportRoleProtocol:
if strings.TrimSpace(surface.MutationID) != "" || strings.TrimSpace(surface.DurableAuthorityID) != "" {
return fmt.Errorf("non-admitting transport %q cannot carry mutation authority", wireValue)
}
default:
return fmt.Errorf("transport %q has unknown role %q", wireValue, surface.Role)
}
}
return nil
}
// AuditRuntimeCandidates requires every mechanically discovered mutation
// candidate to resolve to exactly one closed registry disposition. Transport
// candidates additionally name the lifecycle authority that must exist before
+1
View File
@@ -38,6 +38,7 @@
{"id":"resource.host.package-update","origin":"api","resource_class":"customer_infrastructure","resource_kind":"agent","capability":"install_os_updates","entrypoint":"host.package_updates","disposition":"lifecycle","lifecycle_executor":"hostUpdateActionExecutor","approval_floor":"policy_or_admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"task_10_truth_and_compensation","residual_owners":["task_10_truth_and_compensation"]},
{"id":"resource.proxmox.guest-lifecycle","origin":"api","resource_class":"customer_infrastructure","resource_kind":"vm_or_system-container","capability":"start_shutdown_reboot_stop","entrypoint":"proxmox.vm.lifecycle/proxmox.ct.lifecycle","disposition":"lifecycle","lifecycle_executor":"proxmoxGuestActionExecutor","approval_floor":"admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"task_10_truth_and_compensation","residual_owners":["task_10_truth_and_compensation"]},
{"id":"transport.agent.docker-container-lifecycle","origin":"transport","resource_class":"customer_infrastructure","resource_kind":"app-container","capability":"start_stop_restart","entrypoint":"agentexec docker_container_lifecycle","disposition":"lifecycle","lifecycle_executor":"dockerContainerActionExecutor","approval_floor":"admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"task_10_truth_and_compensation","residual_owners":["task_10_truth_and_compensation"]},
{"id":"transport.agent.host-package-cache-cleanup","origin":"transport","resource_class":"customer_infrastructure","resource_kind":"agent","capability":"clean_package_cache","entrypoint":"agentexec host_storage_cleanup","disposition":"lifecycle","lifecycle_executor":"hostStorageCleanupActionExecutor","approval_floor":"policy_or_admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"unsupported"},
{"id":"transport.agent.host-package-update","origin":"transport","resource_class":"customer_infrastructure","resource_kind":"agent","capability":"install_os_updates","entrypoint":"agentexec host_update","disposition":"lifecycle","lifecycle_executor":"hostUpdateActionExecutor","approval_floor":"policy_or_admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"task_10_truth_and_compensation","residual_owners":["task_10_truth_and_compensation"]},
{"id":"transport.agent.raw-command","origin":"transport","resource_class":"customer_infrastructure","resource_kind":"agent-target","capability":"executor_owned_command","entrypoint":"agentexec execute_command","disposition":"lifecycle","lifecycle_executor":"dockerContainerActionExecutor/proxmoxGuestActionExecutor","approval_floor":"admin","delivery":"committed_lifecycle_before_transport","verification":"required","rollback":"task_10_truth_and_compensation","residual_owners":["task_10_truth_and_compensation"]},
@@ -39,6 +39,7 @@ var generatedEntries = []Entry{
{ID: "transport.agent.deploy-cancel", Origin: Origin("transport"), ResourceClass: ResourceClass("pulse_administration"), ResourceKind: "agent-enrollment", Capability: "deploy_agent", Entrypoint: "agentexec deploy_cancel", Disposition: Disposition("administrative_exception"), LifecycleExecutor: "", Approval: ApprovalFloor("admin"), Delivery: DeliveryClass("administrative_transaction"), Verification: VerificationClass("administrative"), Rollback: RollbackClass("unsupported"), ResidualOwners: []string{"task_12_final_governance"}},
{ID: "transport.agent.deploy-install", Origin: Origin("transport"), ResourceClass: ResourceClass("pulse_administration"), ResourceKind: "agent-enrollment", Capability: "deploy_agent", Entrypoint: "agentexec deploy_install", Disposition: Disposition("administrative_exception"), LifecycleExecutor: "", Approval: ApprovalFloor("admin"), Delivery: DeliveryClass("administrative_transaction"), Verification: VerificationClass("administrative"), Rollback: RollbackClass("unsupported"), ResidualOwners: []string{"task_12_final_governance"}},
{ID: "transport.agent.deploy-preflight", Origin: Origin("transport"), ResourceClass: ResourceClass("pulse_administration"), ResourceKind: "agent-enrollment", Capability: "deploy_agent", Entrypoint: "agentexec deploy_preflight", Disposition: Disposition("administrative_exception"), LifecycleExecutor: "", Approval: ApprovalFloor("admin"), Delivery: DeliveryClass("administrative_transaction"), Verification: VerificationClass("administrative"), Rollback: RollbackClass("unsupported"), ResidualOwners: []string{"task_12_final_governance"}},
{ID: "transport.agent.docker-container-lifecycle", Origin: Origin("transport"), ResourceClass: ResourceClass("customer_infrastructure"), ResourceKind: "app-container", Capability: "start_stop_restart", Entrypoint: "agentexec docker_container_lifecycle", Disposition: Disposition("lifecycle"), LifecycleExecutor: "dockerContainerActionExecutor", Approval: ApprovalFloor("admin"), Delivery: DeliveryClass("committed_lifecycle_before_transport"), Verification: VerificationClass("required"), Rollback: RollbackClass("task_10_truth_and_compensation"), ResidualOwners: []string{"task_10_truth_and_compensation"}},
{ID: "transport.agent.host-package-cache-cleanup", Origin: Origin("transport"), ResourceClass: ResourceClass("customer_infrastructure"), ResourceKind: "agent", Capability: "clean_package_cache", Entrypoint: "agentexec host_storage_cleanup", Disposition: Disposition("lifecycle"), LifecycleExecutor: "hostStorageCleanupActionExecutor", Approval: ApprovalFloor("policy_or_admin"), Delivery: DeliveryClass("committed_lifecycle_before_transport"), Verification: VerificationClass("required"), Rollback: RollbackClass("unsupported"), ResidualOwners: []string(nil)},
{ID: "transport.agent.host-package-update", Origin: Origin("transport"), ResourceClass: ResourceClass("customer_infrastructure"), ResourceKind: "agent", Capability: "install_os_updates", Entrypoint: "agentexec host_update", Disposition: Disposition("lifecycle"), LifecycleExecutor: "hostUpdateActionExecutor", Approval: ApprovalFloor("policy_or_admin"), Delivery: DeliveryClass("committed_lifecycle_before_transport"), Verification: VerificationClass("required"), Rollback: RollbackClass("task_10_truth_and_compensation"), ResidualOwners: []string{"task_10_truth_and_compensation"}},
{ID: "transport.agent.raw-command", Origin: Origin("transport"), ResourceClass: ResourceClass("customer_infrastructure"), ResourceKind: "agent-target", Capability: "executor_owned_command", Entrypoint: "agentexec execute_command", Disposition: Disposition("lifecycle"), LifecycleExecutor: "dockerContainerActionExecutor/proxmoxGuestActionExecutor", Approval: ApprovalFloor("admin"), Delivery: DeliveryClass("committed_lifecycle_before_transport"), Verification: VerificationClass("required"), Rollback: RollbackClass("task_10_truth_and_compensation"), ResidualOwners: []string{"task_10_truth_and_compensation"}},
@@ -1,6 +1,7 @@
package mutationregistry
import (
"fmt"
"os"
"path/filepath"
"regexp"
@@ -9,6 +10,37 @@ import (
"testing"
)
type routeClassification struct {
MutationID string
}
var infrastructureRouteCatalog = map[string]routeClassification{
"POST /api/actions/plan": {MutationID: "action.api.plan"},
"GET /api/actions/pending": {},
"GET /api/actions": {},
"GET /api/actions/{id}": {},
"POST /api/actions/{id}/decision": {MutationID: "action.api.decision"},
"POST /api/actions/{id}/execute": {MutationID: "action.api.execute"},
"/api/agents/docker/report": {},
"/api/agents/docker/commands/": {},
"/api/agents/docker/runtimes/": {MutationID: "docker.api.runtime-stop"},
"/api/agents/docker/containers/update": {MutationID: "docker.api.update-container"},
"/api/updates/check": {},
"/api/updates/apply": {MutationID: "admin.pulse.self-update"},
"/api/updates/rollback": {MutationID: "admin.pulse.self-update"},
"/api/updates/status": {},
"/api/updates/stream": {},
"/api/updates/plan": {},
"/api/updates/history": {},
"/api/updates/history/entry": {},
"/api/ai/run-command": {MutationID: "legacy.api.run-command"},
"/api/ai/remediation/plans": {},
"/api/ai/remediation/plan": {},
"/api/ai/remediation/approve": {MutationID: "legacy.enterprise.remediation-approve"},
"/api/ai/remediation/execute": {MutationID: "legacy.enterprise.remediation-execute"},
"/api/ai/remediation/rollback": {MutationID: "legacy.enterprise.remediation-rollback"},
}
// TestInfrastructureAPIRoutesResolveToRegistry scans the real router sources,
// not a duplicate ID inventory. Its scope is deliberately limited to the
// infrastructure mutation namespaces below; settings/metadata CRUD is outside
@@ -20,31 +52,7 @@ func TestInfrastructureAPIRoutesResolveToRegistry(t *testing.T) {
"internal/api/router_routes_registration.go",
"internal/api/router_routes_ai_relay.go",
}
catalog := map[string]string{
"/api/actions/plan": "action.api.plan",
"/api/actions/pending": "",
"/api/actions/{id}/decision": "action.api.decision",
"/api/actions/{id}/execute": "action.api.execute",
"/api/agents/docker/report": "",
"/api/agents/docker/commands/": "",
"/api/agents/docker/runtimes/": "docker.api.runtime-stop",
"/api/agents/docker/containers/update": "docker.api.update-container",
"/api/updates/check": "",
"/api/updates/apply": "admin.pulse.self-update",
"/api/updates/rollback": "admin.pulse.self-update",
"/api/updates/status": "",
"/api/updates/stream": "",
"/api/updates/plan": "",
"/api/updates/history": "",
"/api/updates/history/entry": "",
"/api/ai/run-command": "legacy.api.run-command",
"/api/ai/remediation/plans": "",
"/api/ai/remediation/plan": "",
"/api/ai/remediation/approve": "legacy.enterprise.remediation-approve",
"/api/ai/remediation/execute": "legacy.enterprise.remediation-execute",
"/api/ai/remediation/rollback": "legacy.enterprise.remediation-rollback",
}
routeRE := regexp.MustCompile(`"(?:GET |POST |PUT |DELETE )?(/api/[^"]+)"`)
routeRE := regexp.MustCompile(`"(?:(GET|POST|PUT|DELETE) )?(/api/[^"]+)"`)
found := map[string]bool{}
for _, relative := range files {
data, err := os.ReadFile(filepath.Join(root, relative))
@@ -52,26 +60,27 @@ func TestInfrastructureAPIRoutesResolveToRegistry(t *testing.T) {
t.Fatal(err)
}
for _, match := range routeRE.FindAllStringSubmatch(string(data), -1) {
route := match[1]
method, route := match[1], match[2]
if !auditedInfrastructurePrefix(route) {
continue
}
found[route] = true
id, classified := catalog[route]
key := routeCatalogKey(method, route)
found[key] = true
classification, classified := infrastructureRouteCatalog[key]
if !classified {
t.Errorf("runtime infrastructure route %q is unclassified", route)
t.Errorf("runtime infrastructure route %q is unclassified", key)
continue
}
if id != "" {
if err := AuditRuntimeCandidates([]RuntimeCandidate{{Surface: "http:" + route, MutationID: id}}); err != nil {
if classification.MutationID != "" {
if err := AuditRuntimeCandidates([]RuntimeCandidate{{Surface: "http:" + key, MutationID: classification.MutationID}}); err != nil {
t.Error(err)
}
}
}
}
for route := range catalog {
if !found[route] {
t.Errorf("classified route %q is no longer registered", route)
for key := range infrastructureRouteCatalog {
if !found[key] {
t.Errorf("classified route %q is no longer registered", key)
}
}
// The shared Docker gateway contains a second mutation sub-route.
@@ -84,7 +93,17 @@ func TestInfrastructureAPIRoutesResolveToRegistry(t *testing.T) {
}
}
func routeCatalogKey(method, route string) string {
if strings.TrimSpace(method) == "" {
return route
}
return method + " " + route
}
func auditedInfrastructurePrefix(route string) bool {
if route == "/api/actions" {
return true
}
for _, prefix := range []string{"/api/actions/", "/api/agents/docker/", "/api/updates/", "/api/ai/remediation/", "/api/ai/run-command"} {
if strings.HasPrefix(route, prefix) {
return true
@@ -93,32 +112,90 @@ func auditedInfrastructurePrefix(route string) bool {
return false
}
func TestActionRouteMethodAuthorityIsExactAndLookalikesFailClosed(t *testing.T) {
tests := []struct {
method, route, mutationID string
classified bool
}{
{method: "GET", route: "/api/actions/{id}", classified: true},
{method: "POST", route: "/api/actions/{id}/execute", mutationID: "action.api.execute", classified: true},
{method: "DELETE", route: "/api/actions/{id}"},
{method: "POST", route: "/api/actions/{id}"},
{method: "GET", route: "/api/actions/{id}/execute"},
{method: "POST", route: "/api/actions/{id}/execute-copy"},
}
for _, tt := range tests {
key := routeCatalogKey(tt.method, tt.route)
classification, ok := infrastructureRouteCatalog[key]
if ok != tt.classified {
t.Errorf("route %q classified=%v, want %v", key, ok, tt.classified)
continue
}
if ok && classification.MutationID != tt.mutationID {
t.Errorf("route %q mutation=%q, want %q", key, classification.MutationID, tt.mutationID)
}
}
}
type transportClassification struct {
Role TransportRole
MutationID string
DurableAuthorityID string
}
func TestTransportCommandCatalogsResolveToRegistry(t *testing.T) {
root := repoRoot(t)
docker := scanNamedStringConstants(t, filepath.Join(root, "internal/monitoring/docker_commands.go"), `DockerCommandType\w+`)
dockerCatalog := map[string]string{
"DockerCommandTypeStop": "transport.docker.runtime-stop",
"DockerCommandTypeUpdateContainer": "transport.docker.update-container",
"DockerCommandTypeUpdateAll": "transport.docker.update-all",
"DockerCommandTypeCheckUpdates": "",
dockerCatalog := map[string]transportClassification{
"DockerCommandTypeStop": {Role: TransportRoleMutationRequest, MutationID: "transport.docker.runtime-stop"},
"DockerCommandTypeUpdateContainer": {Role: TransportRoleMutationRequest, MutationID: "transport.docker.update-container"},
"DockerCommandTypeUpdateAll": {Role: TransportRoleMutationRequest, MutationID: "transport.docker.update-all"},
"DockerCommandTypeCheckUpdates": {Role: TransportRoleProtocol},
}
auditConstantCatalog(t, docker, dockerCatalog, nil)
auditConstantCatalog(t, docker, dockerCatalog)
agent := scanNamedStringConstants(t, filepath.Join(root, "internal/agentexec/types.go"), `MsgType\w+`)
agentCatalog := map[string]string{
"MsgTypeAgentRegister": "", "MsgTypeAgentPing": "", "MsgTypeCommandResult": "",
"MsgTypeHostStorageCleanupResult": "", "MsgTypeHostUpdateResult": "", "MsgTypeRegistered": "", "MsgTypePong": "",
"MsgTypeExecuteCmd": "transport.agent.raw-command", "MsgTypeHostStorageCleanup": "transport.agent.host-package-cache-cleanup",
"MsgTypeReadFile": "", "MsgTypeHostUpdate": "transport.agent.host-package-update",
"MsgTypeDeployPreflight": "transport.agent.deploy-preflight", "MsgTypeDeployInstall": "transport.agent.deploy-install",
"MsgTypeDeployCancelJob": "transport.agent.deploy-cancel", "MsgTypeDeployProgress": "",
agentCatalog := map[string]transportClassification{
"MsgTypeAgentRegister": {Role: TransportRoleProtocol}, "MsgTypeAgentPing": {Role: TransportRoleProtocol},
"MsgTypeCommandResult": {Role: TransportRoleOperationResult},
"MsgTypeHostStorageCleanupResult": {Role: TransportRoleOperationResult}, "MsgTypeHostUpdateResult": {Role: TransportRoleOperationResult},
"MsgTypeDockerContainerLifecycleResult": {Role: TransportRoleOperationResult},
"MsgTypeOperationQueryResult": {Role: TransportRoleOperationReceipt},
"MsgTypeRegistered": {Role: TransportRoleProtocol}, "MsgTypePong": {Role: TransportRoleProtocol},
"MsgTypeExecuteCmd": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.raw-command", DurableAuthorityID: "assistant.resource-action"},
"MsgTypeHostStorageCleanup": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.host-package-cache-cleanup", DurableAuthorityID: "resource.host.package-cache-cleanup"},
"MsgTypeReadFile": {Role: TransportRoleProtocol},
"MsgTypeHostUpdate": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.host-package-update", DurableAuthorityID: "resource.host.package-update"},
"MsgTypeDockerContainerLifecycle": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.docker-container-lifecycle", DurableAuthorityID: "resource.docker.container-lifecycle"},
"MsgTypeOperationQuery": {Role: TransportRoleOperationQuery},
"MsgTypeDeployPreflight": {Role: TransportRoleAdministrativeRequest, MutationID: "transport.agent.deploy-preflight"},
"MsgTypeDeployInstall": {Role: TransportRoleAdministrativeRequest, MutationID: "transport.agent.deploy-install"},
"MsgTypeDeployCancelJob": {Role: TransportRoleAdministrativeRequest, MutationID: "transport.agent.deploy-cancel"},
"MsgTypeDeployProgress": {Role: TransportRoleOperationResult},
}
authority := map[string]string{
"MsgTypeExecuteCmd": "assistant.resource-action",
"MsgTypeHostStorageCleanup": "resource.host.package-cache-cleanup",
"MsgTypeHostUpdate": "resource.host.package-update",
auditConstantCatalog(t, agent, agentCatalog)
}
func TestNonAdmittingTransportMessagesCannotCarryDispatchAuthority(t *testing.T) {
for _, role := range []TransportRole{TransportRoleOperationQuery, TransportRoleOperationResult, TransportRoleOperationReceipt, TransportRoleProtocol} {
err := AuditTransportSurfaces([]TransportSurface{{
Name: "lookalike", WireValue: "docker_container_lifecycle_result_retry", Role: role,
MutationID: "transport.agent.docker-container-lifecycle", DurableAuthorityID: "resource.docker.container-lifecycle",
}})
if err == nil {
t.Errorf("non-admitting role %q accepted dispatch authority", role)
}
}
}
func TestUnknownTransportLookalikeFailsClosed(t *testing.T) {
actual := map[string]string{"MsgTypeDockerContainerLifecycle": "docker_container_lifecycle", "MsgTypeDockerContainerLifecycleRetry": "docker_container_lifecycle_retry"}
catalog := map[string]transportClassification{
"MsgTypeDockerContainerLifecycle": {Role: TransportRoleMutationRequest, MutationID: "transport.agent.docker-container-lifecycle", DurableAuthorityID: "resource.docker.container-lifecycle"},
}
if unknown := unclassifiedTransportConstants(actual, catalog); len(unknown) != 1 || unknown[0] != `MsgTypeDockerContainerLifecycleRetry="docker_container_lifecycle_retry"` {
t.Fatalf("unknown lookalike classification = %v", unknown)
}
auditConstantCatalog(t, agent, agentCatalog, authority)
}
func TestPatrolJobRegistrationResolvesToRegistry(t *testing.T) {
@@ -147,21 +224,19 @@ func scanNamedStringConstants(t *testing.T, path, namePattern string) map[string
return out
}
func auditConstantCatalog(t *testing.T, actual, catalog, authority map[string]string) {
func auditConstantCatalog(t *testing.T, actual map[string]string, catalog map[string]transportClassification) {
t.Helper()
var surfaces []TransportSurface
for name, value := range actual {
id, ok := catalog[name]
classification, ok := catalog[name]
if !ok {
t.Errorf("transport command %s=%q is unclassified", name, value)
continue
}
if id == "" {
continue
}
candidate := RuntimeCandidate{Surface: "transport:" + value, MutationID: id, Transport: true, DurableAuthorityID: authority[name]}
if err := AuditRuntimeCandidates([]RuntimeCandidate{candidate}); err != nil {
t.Error(err)
}
surfaces = append(surfaces, TransportSurface{Name: name, WireValue: value, Role: classification.Role, MutationID: classification.MutationID, DurableAuthorityID: classification.DurableAuthorityID})
}
if err := AuditTransportSurfaces(surfaces); err != nil {
t.Error(err)
}
var stale []string
for name := range catalog {
@@ -174,3 +249,14 @@ func auditConstantCatalog(t *testing.T, actual, catalog, authority map[string]st
t.Errorf("classified transport constants no longer exist: %v", stale)
}
}
func unclassifiedTransportConstants(actual map[string]string, catalog map[string]transportClassification) []string {
var unknown []string
for name, value := range actual {
if _, ok := catalog[name]; !ok {
unknown = append(unknown, fmt.Sprintf(`%s=%q`, name, value))
}
}
sort.Strings(unknown)
return unknown
}