@opensoha/contracts 0.1.24 → 0.1.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/COMPATIBILITY.md +24 -0
  2. package/README.md +1 -1
  3. package/agent/environment/export.go +1 -0
  4. package/agent/environment/protocol_test.go +1 -1
  5. package/agent/guest/protocol_test.go +2 -2
  6. package/auth/catalog_generated.go +2 -2
  7. package/auth/gateway_permissions_test.go +48 -0
  8. package/auth/permission-catalog.json +300 -1
  9. package/compat/consumer-revisions.json +1 -1
  10. package/compat/json-schema-ecosystem-0.1.0.json +1 -1
  11. package/compat/openapi-loose-boundaries-0.1.0.json +1 -1
  12. package/delivery/document.schema.json +127 -9
  13. package/examples/relations_compat_test.go +18 -0
  14. package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-build-template.json +30 -0
  15. package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-deployment-template.json +30 -0
  16. package/fixtures/openapi/DeliveryDocument/invalid/migration-missing-guide-workflow-template.json +27 -0
  17. package/fixtures/openapi/DeliveryDocument/valid/migration-build-template.json +31 -0
  18. package/fixtures/openapi/DeliveryDocument/valid/migration-deployment-template.json +31 -0
  19. package/fixtures/openapi/DeliveryDocument/valid/migration-workflow-template.json +28 -0
  20. package/fixtures/openapi/DeliveryPlanInput/invalid/manual-plan.json +7 -0
  21. package/fixtures/openapi/DeliveryPlanInput/valid/manual-plan.json +6 -0
  22. package/fixtures/openapi/DeliveryTargetInput/invalid/candidate-on-runtime-deploy.json +9 -0
  23. package/fixtures/openapi/DeliveryTargetInput/invalid/config-update-missing-service.json +7 -0
  24. package/fixtures/openapi/DeliveryTargetInput/valid/template-configuration-only.json +8 -0
  25. package/fixtures/openapi/DeliveryTargetInput/valid/template-configuration-without-runtime-environment.json +1 -0
  26. package/fixtures/openapi/KubernetesAgentActionCheck/invalid/unproven-state.json +6 -0
  27. package/fixtures/openapi/KubernetesAgentActionCheck/valid/configured-denied.json +9 -0
  28. package/fixtures/openapi/KubernetesAgentActionProfile/invalid/wildcard.json +1 -0
  29. package/fixtures/openapi/KubernetesAgentActionProfile/valid/workbench.json +1 -0
  30. package/fixtures/openapi/KubernetesClusterRegistrationRequest/invalid/action-profile.json +3 -0
  31. package/fixtures/openapi/KubernetesClusterRegistrationRequest/valid/action-profile.json +5 -0
  32. package/fixtures/openapi/KubernetesImportCandidateListEnvelope/invalid/source-with-spec.json +33 -0
  33. package/fixtures/openapi/KubernetesImportCandidateListEnvelope/invalid/source-without-uid.json +29 -0
  34. package/fixtures/openapi/KubernetesImportCandidateListEnvelope/valid/stable-source.json +30 -0
  35. package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/condition-status-boolean.json +17 -0
  36. package/fixtures/openapi/KubernetesNodeListEnvelope/invalid/taint-effect-missing.json +16 -0
  37. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/legacy-fields-absent.json +11 -0
  38. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/no-taints.json +13 -0
  39. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/ready-cordoned-tainted.json +28 -0
  40. package/fixtures/openapi/KubernetesNodeListEnvelope/valid/unknown-fields-null.json +13 -0
  41. package/fixtures/openapi/KubernetesPod/invalid/pod-uid.json +9 -0
  42. package/fixtures/openapi/KubernetesPod/valid/pod-uid.json +9 -0
  43. package/fixtures/openapi/KubernetesPodDetail/invalid/pod-uid.json +6 -0
  44. package/fixtures/openapi/KubernetesPodDetail/valid/pod-uid.json +6 -0
  45. package/fixtures/openapi/KubernetesServiceImportRequest/invalid/empty-source-uid.json +48 -0
  46. package/fixtures/openapi/KubernetesServiceImportRequest/valid/pinned-workloads-and-cr.json +48 -0
  47. package/fixtures/openapi/KubernetesTopologyCoverage/invalid/empty-as-success.json +5 -0
  48. package/fixtures/openapi/KubernetesTopologyCoverage/valid/not-installed.json +6 -0
  49. package/fixtures/openapi/KubernetesTopologyGraphEdge/invalid/negative-weight.json +8 -0
  50. package/fixtures/openapi/KubernetesTopologyGraphEdge/valid/zero-weight.json +20 -0
  51. package/fixtures/openapi/KubernetesTopologyGraphNode/invalid/fake-flow-kind.json +42 -0
  52. package/fixtures/openapi/KubernetesTopologyGraphNode/valid/rule.json +42 -0
  53. package/fixtures/openapi/KubernetesTopologySnapshot/invalid/raw-source-field.json +33 -0
  54. package/fixtures/openapi/KubernetesTopologySnapshot/valid/projected-source.json +32 -0
  55. package/fixtures/openapi/KubernetesTopologySnapshotRequest/invalid/arbitrary-gvr.json +8 -0
  56. package/fixtures/openapi/KubernetesTopologySnapshotRequest/valid/bounded-sources.json +11 -0
  57. package/fixtures/openapi/KubernetesTopologySourceQuery/invalid/cluster-namespace.json +4 -0
  58. package/fixtures/openapi/KubernetesTopologySourceQuery/valid/all-namespaces.json +4 -0
  59. package/fixtures/openapi/ManifestExecutionTaskPayload/invalid/initialization.json +7 -0
  60. package/fixtures/openapi/ManifestExecutionTaskPayload/valid/initialization.json +7 -0
  61. package/fixtures/openapi/ManifestRootResource/invalid/root.json +1 -0
  62. package/fixtures/openapi/ManifestRootResource/valid/root.json +1 -0
  63. package/fixtures/openapi/ResourceMonitoringPanel/invalid/unknown-quality.json +7 -0
  64. package/fixtures/openapi/ResourceMonitoringPanel/valid/not-collected.json +7 -0
  65. package/fixtures/openapi/ResourceMonitoringPoint/invalid/string-zero.json +4 -0
  66. package/fixtures/openapi/ResourceMonitoringPoint/valid/missing.json +4 -0
  67. package/fixtures/openapi/ResourceMonitoringPoint/valid/zero.json +4 -0
  68. package/fixtures/openapi/ResourceMonitoringView/invalid/raw-query.json +12 -0
  69. package/fixtures/openapi/ResourceMonitoringView/valid/unconfigured.json +12 -0
  70. package/fixtures/openapi/ResourceRelationConfig/invalid/executable-rule.json +17 -0
  71. package/fixtures/openapi/ResourceRelationConfig/valid/unknown-kind.json +13 -0
  72. package/fixtures/openapi/ServiceDeploymentTemplateInput/invalid/migration-missing-guide-valid-yaml.json +38 -0
  73. package/fixtures/openapi/ServiceDeploymentTemplateInput/valid/migration-valid-yaml.json +39 -0
  74. package/fixtures/openapi/TemplateMigrationAssessment/invalid/initialization.json +9 -0
  75. package/fixtures/openapi/TemplateMigrationAssessment/valid/initialization.json +9 -0
  76. package/fixtures/openapi/TemplateMigrationPolicy/invalid/blank-guide.json +5 -0
  77. package/fixtures/openapi/TemplateMigrationPolicy/invalid/missing-guide.json +4 -0
  78. package/fixtures/openapi/TemplateMigrationPolicy/invalid/unknown-escape-hatch.json +6 -0
  79. package/fixtures/openapi/TemplateMigrationPolicy/valid/legacy-safe.json +4 -0
  80. package/fixtures/openapi/TemplateMigrationPolicy/valid/ordered-data-change.json +5 -0
  81. package/fixtures/openapi/TemplateUpdateBatchInput/invalid/duplicate-candidates.json +11 -0
  82. package/fixtures/openapi/TemplateUpdateBatchInput/invalid/empty-identities.json +10 -0
  83. package/fixtures/openapi/TemplateUpdateBatchInput/valid/staged-adoption.json +10 -0
  84. package/fixtures/openapi/TemplateUpdatePolicyInput/invalid/unknown-mode.json +8 -0
  85. package/fixtures/openapi/TemplateUpdatePolicyInput/valid/explicit-automatic.json +8 -0
  86. package/fixtures/openapi/TemplateUpdatePreviewInput/invalid/empty-identities.json +10 -0
  87. package/fixtures/openapi/TemplateUpdatePreviewInput/invalid/empty-targets.json +6 -0
  88. package/fixtures/openapi/TemplateUpdatePreviewInput/valid/cross-version-manual.json +14 -0
  89. package/gen/go/sohaapi/client.go +1 -1
  90. package/gen/go/sohaapi/client_test.go +2 -2
  91. package/gen/go/sohaapi/import_source_compat_test.go +12 -0
  92. package/gen/go/sohaapi/kubernetes_monitoring_test.go +67 -0
  93. package/gen/go/sohaapi/types.go +2210 -387
  94. package/gen/ts/sohaapi/index.ts +1862 -5
  95. package/go.mod +2 -2
  96. package/helmrelease/delivery.go +4 -1
  97. package/helmrelease/initialization.go +10 -0
  98. package/helmrelease/runtime/delivery.go +40 -10
  99. package/helmrelease/runtime/delivery_test.go +4 -0
  100. package/helmrelease/runtime/initialization.go +186 -0
  101. package/helmrelease/runtime/initialization_test.go +203 -0
  102. package/openapi/soha-api.json +5514 -1179
  103. package/openapi/soha-api.yaml +1080 -11
  104. package/openapi/spec_generated.go +3 -3
  105. package/openapi/spec_test.go +76 -0
  106. package/package.json +1 -1
  107. package/resource/agent_access.go +12 -0
  108. package/resource/agent_profiles.go +62 -0
  109. package/resource/agent_profiles_test.go +36 -0
  110. package/resource/kubernetes_kinds.go +73 -0
  111. package/resource/kubernetes_kinds_test.go +38 -0
  112. package/resource/models.go +25 -14
  113. package/resource/network_topology.go +166 -0
  114. package/resource/network_topology_projection.go +208 -0
  115. package/resource/network_topology_projection_test.go +53 -0
  116. package/resource/network_topology_test.go +51 -0
  117. package/resource/network_topology_validation.go +29 -0
  118. package/resource/node_drain.go +34 -0
  119. package/resource/relations.go +225 -0
  120. package/resource/relations_test.go +47 -0
  121. package/resource/resource_monitoring.go +99 -0
  122. package/resource/root_cr.go +38 -0
  123. package/resource/runtime/argocd_integration_test.go +1 -1
  124. package/resource/runtime/initialization.go +66 -0
  125. package/resource/runtime/initialization_test.go +80 -0
  126. package/resource/runtime/network_topology.go +232 -0
  127. package/resource/runtime/network_topology_test.go +62 -0
  128. package/resource/runtime/node_drain.go +124 -0
  129. package/resource/runtime/node_drain_test.go +95 -0
  130. package/resource/runtime/relations.go +161 -0
  131. package/resource/runtime/relations_test.go +100 -0
  132. package/resource/runtime/resource_edit.go +128 -0
  133. package/resource/runtime/resource_edit_test.go +183 -0
  134. package/resource/runtime/resource_monitoring.go +243 -0
  135. package/resource/runtime/resource_monitoring_test.go +88 -0
  136. package/resource/runtime/root_cr.go +79 -0
  137. package/resource/runtime/root_cr_test.go +118 -0
@@ -5,9 +5,9 @@ package openapi
5
5
  import _ "embed"
6
6
 
7
7
  const (
8
- Version = "0.1.24"
9
- YAMLSHA256 = "e1a2f7be61a7d7674f3e3928190a04ddf7ecb906170c5da9199519b50bc89616"
10
- JSONSHA256 = "cf9f707f6e35f29486eb7a190742124d27c2db82915dd98b192af5aa4a587596"
8
+ Version = "0.1.26"
9
+ YAMLSHA256 = "6d18163614e65c1f0f0439d928b65bcdd5937781ee6ae912a53526a592cbc2db"
10
+ JSONSHA256 = "37b9e04ece8f49eabdfe41a695ad9b28ce21d434d6a2e2b97922a9f202230c41"
11
11
  )
12
12
 
13
13
  //go:embed soha-api.yaml
@@ -4,6 +4,7 @@ import (
4
4
  "crypto/sha256"
5
5
  "encoding/json"
6
6
  "fmt"
7
+ "strings"
7
8
  "testing"
8
9
  )
9
10
 
@@ -36,3 +37,78 @@ func TestEmbeddedSpecsMatchMetadata(t *testing.T) {
36
37
  t.Fatalf("info.version = %q, want %q", document.Info.Version, Version)
37
38
  }
38
39
  }
40
+
41
+ func TestTopologySuccessResponsePreservesDescription(t *testing.T) {
42
+ var document struct {
43
+ Paths map[string]struct {
44
+ Get struct {
45
+ Responses map[string]map[string]json.RawMessage `json:"responses"`
46
+ } `json:"get"`
47
+ } `json:"paths"`
48
+ }
49
+ if err := json.Unmarshal(JSON(), &document); err != nil {
50
+ t.Fatal(err)
51
+ }
52
+ response := document.Paths["/clusters/{clusterID}/network/topology"].Get.Responses["200"]
53
+ if len(response) == 0 {
54
+ t.Fatal("missing topology success response")
55
+ }
56
+ for key := range response {
57
+ switch key {
58
+ case "description", "headers", "content", "links":
59
+ default:
60
+ if !strings.HasPrefix(key, "x-") {
61
+ t.Errorf("unexpected topology response field %q", key)
62
+ }
63
+ }
64
+ }
65
+ var description string
66
+ if err := json.Unmarshal(response["description"], &description); err != nil {
67
+ t.Fatal(err)
68
+ }
69
+ if !strings.Contains(description, "Kubernetes, Gateway API, Traefik and APISIX topology,") {
70
+ t.Fatalf("topology response description was truncated: %q", description)
71
+ }
72
+ }
73
+
74
+ func TestPodWebSocketMetadataPreservesHTTPRouteSurface(t *testing.T) {
75
+ var document struct {
76
+ Paths map[string]json.RawMessage `json:"paths"`
77
+ Streams map[string]struct {
78
+ Get struct {
79
+ Permissions struct {
80
+ Keys []string `json:"keys"`
81
+ } `json:"x-soha-permissions"`
82
+ Parameters []struct {
83
+ Name string `json:"name"`
84
+ Schema struct {
85
+ Type string `json:"type"`
86
+ MinLength int `json:"minLength"`
87
+ } `json:"schema"`
88
+ } `json:"parameters"`
89
+ } `json:"get"`
90
+ } `json:"x-soha-websocket-paths"`
91
+ }
92
+ if err := json.Unmarshal(JSON(), &document); err != nil {
93
+ t.Fatal(err)
94
+ }
95
+ for suffix, permission := range map[string]string{"logs/stream": "platform.pods.logs", "terminal": "platform.pods.exec"} {
96
+ path := "/clusters/{clusterID}/workloads/pods/{podName}/" + suffix
97
+ if _, exists := document.Paths[path]; exists {
98
+ t.Fatalf("WebSocket target entered the HTTP client route surface: %s", path)
99
+ }
100
+ stream, exists := document.Streams[path]
101
+ if !exists || len(stream.Get.Permissions.Keys) != 1 || stream.Get.Permissions.Keys[0] != permission {
102
+ t.Fatalf("missing WebSocket target permission: %s", path)
103
+ }
104
+ uidQuery := false
105
+ for _, parameter := range stream.Get.Parameters {
106
+ if parameter.Name == "expectedUid" && parameter.Schema.Type == "string" && parameter.Schema.MinLength == 1 {
107
+ uidQuery = true
108
+ }
109
+ }
110
+ if !uidQuery {
111
+ t.Fatalf("missing non-empty expectedUid query metadata: %s", path)
112
+ }
113
+ }
114
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opensoha/contracts",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
4
4
  "private": false,
5
5
  "description": "Shared OpenSoha API, agent, MCP, skill, event, and auth contracts.",
6
6
  "license": "Apache-2.0",
@@ -21,3 +21,15 @@ type PrometheusQuery struct {
21
21
  End int64 `json:"end,omitempty"`
22
22
  Step int64 `json:"step,omitempty"`
23
23
  }
24
+
25
+ // AgentActionCheck separates binary support, authenticated action policy, and Kubernetes authorization.
26
+ // RBAC scope is cluster-wide; target-specific checks and mutations remain authoritative.
27
+ type AgentActionCheck struct {
28
+ Action string `json:"action"`
29
+ Implementation string `json:"implementation"`
30
+ Configured string `json:"configured"`
31
+ KubernetesRBAC string `json:"kubernetesRBAC"`
32
+ CheckedAt string `json:"checkedAt,omitempty"`
33
+ ReasonCode string `json:"reasonCode,omitempty"`
34
+ Scope string `json:"scope,omitempty"`
35
+ }
@@ -0,0 +1,62 @@
1
+ package resource
2
+
3
+ import "fmt"
4
+
5
+ // KubernetesAgentProfileActions is an installation preset, never a runtime permission bypass.
6
+ // Legacy preserves installations generated before the workbench profile was introduced.
7
+ func KubernetesAgentProfileActions(profile string) ([]string, error) {
8
+ switch profile {
9
+ case "read-only":
10
+ return []string{"platform.custom_resources.list"}, nil
11
+ case "workbench":
12
+ return append([]string(nil), kubernetesWorkbenchActions[:]...), nil
13
+ case "legacy":
14
+ actions := make([]string, 0, len(kubernetesWorkbenchActions))
15
+ for _, action := range kubernetesWorkbenchActions {
16
+ if action != "platform.helm_releases.install" && action != "platform.helm_releases.values_update" && action != "platform.helm_releases.delete" && action != "platform.helm_releases.rollback" {
17
+ actions = append(actions, action)
18
+ }
19
+ }
20
+ return actions, nil
21
+ default:
22
+ return nil, fmt.Errorf("unknown Kubernetes Agent action profile")
23
+ }
24
+ }
25
+
26
+ // KubernetesAgentActions lists implemented platform actions, including separately opted-in CRD deletion.
27
+ func KubernetesAgentActions() []string {
28
+ actions := make([]string, 0, len(kubernetesWorkbenchActions))
29
+ for _, action := range kubernetesWorkbenchActions {
30
+ if action != "runtime.execution_tasks.cancel" {
31
+ actions = append(actions, action)
32
+ }
33
+ }
34
+ return append(actions, "platform.crds.delete")
35
+ }
36
+
37
+ var kubernetesWorkbenchActions = [...]string{
38
+ "platform.nodes.drain",
39
+ "platform.pods.exec",
40
+ "platform.deployments.restart",
41
+ "platform.deployments.scale",
42
+ "platform.deployments.image",
43
+ "platform.deployments.rollback",
44
+ "platform.statefulsets.restart",
45
+ "platform.statefulsets.scale",
46
+ "platform.daemonsets.restart",
47
+ "platform.resources.apply",
48
+ "platform.resources.create",
49
+ "platform.resources.delete",
50
+ "platform.custom_resources.list",
51
+ "platform.custom_resources.create",
52
+ "platform.custom_resources.apply",
53
+ "platform.custom_resources.delete",
54
+ "platform.port_forwards.create",
55
+ "platform.port_forwards.tunnel",
56
+ "platform.port_forwards.delete",
57
+ "platform.helm_releases.install",
58
+ "platform.helm_releases.values_update",
59
+ "platform.helm_releases.delete",
60
+ "platform.helm_releases.rollback",
61
+ "runtime.execution_tasks.cancel",
62
+ }
@@ -0,0 +1,36 @@
1
+ package resource
2
+
3
+ import (
4
+ "slices"
5
+ "testing"
6
+ )
7
+
8
+ func TestKubernetesAgentProfilesKeepPermissionsExplicit(t *testing.T) {
9
+ workbench, err := KubernetesAgentProfileActions("workbench")
10
+ if err != nil {
11
+ t.Fatal(err)
12
+ }
13
+ for _, action := range []string{"platform.nodes.drain", "platform.resources.apply", "platform.resources.create", "platform.resources.delete", "platform.helm_releases.rollback", "platform.port_forwards.tunnel"} {
14
+ if !slices.Contains(workbench, action) {
15
+ t.Fatalf("missing workbench action %s", action)
16
+ }
17
+ }
18
+ if slices.Contains(workbench, "*") || slices.Contains(workbench, "platform.crds.delete") {
19
+ t.Fatal("workbench must not opt into unrestricted or CRD deletion actions")
20
+ }
21
+ legacy, _ := KubernetesAgentProfileActions("legacy")
22
+ if slices.Contains(legacy, "platform.helm_releases.install") {
23
+ t.Fatal("legacy installation permissions expanded")
24
+ }
25
+ readOnly, _ := KubernetesAgentProfileActions("read-only")
26
+ if !slices.Equal(readOnly, []string{"platform.custom_resources.list"}) {
27
+ t.Fatalf("read-only contains writes: %v", readOnly)
28
+ }
29
+ if _, err := KubernetesAgentProfileActions("unknown"); err == nil {
30
+ t.Fatal("unknown profile accepted")
31
+ }
32
+ workbench[0] = "changed"
33
+ if next, _ := KubernetesAgentProfileActions("workbench"); next[0] == "changed" {
34
+ t.Fatal("profile leaked mutable storage")
35
+ }
36
+ }
@@ -0,0 +1,73 @@
1
+ package resource
2
+
3
+ import "strings"
4
+
5
+ // KubernetesResourceKind describes the fixed resource surface shared by both transports.
6
+ // GenericYAML excludes resources whose dedicated lifecycle has stronger authorization.
7
+ type KubernetesResourceKind struct {
8
+ Kind string
9
+ Group string
10
+ Version string
11
+ Resource string
12
+ Namespaced bool
13
+ GenericYAML bool
14
+ }
15
+
16
+ var kubernetesResourceKinds = [...]KubernetesResourceKind{
17
+ {Kind: "Namespace", Group: "", Version: "v1", Resource: "namespaces", Namespaced: false, GenericYAML: false},
18
+ {Kind: "Pod", Group: "", Version: "v1", Resource: "pods", Namespaced: true, GenericYAML: true},
19
+ {Kind: "Node", Group: "", Version: "v1", Resource: "nodes", Namespaced: false, GenericYAML: true},
20
+ {Kind: "Deployment", Group: "apps", Version: "v1", Resource: "deployments", Namespaced: true, GenericYAML: true},
21
+ {Kind: "StatefulSet", Group: "apps", Version: "v1", Resource: "statefulsets", Namespaced: true, GenericYAML: true},
22
+ {Kind: "DaemonSet", Group: "apps", Version: "v1", Resource: "daemonsets", Namespaced: true, GenericYAML: true},
23
+ {Kind: "ReplicaSet", Group: "apps", Version: "v1", Resource: "replicasets", Namespaced: true, GenericYAML: true},
24
+ {Kind: "Job", Group: "batch", Version: "v1", Resource: "jobs", Namespaced: true, GenericYAML: true},
25
+ {Kind: "CronJob", Group: "batch", Version: "v1", Resource: "cronjobs", Namespaced: true, GenericYAML: true},
26
+ {Kind: "ConfigMap", Group: "", Version: "v1", Resource: "configmaps", Namespaced: true, GenericYAML: true},
27
+ {Kind: "Secret", Group: "", Version: "v1", Resource: "secrets", Namespaced: true, GenericYAML: true},
28
+ {Kind: "ServiceAccount", Group: "", Version: "v1", Resource: "serviceaccounts", Namespaced: true, GenericYAML: true},
29
+ {Kind: "ReplicationController", Group: "", Version: "v1", Resource: "replicationcontrollers", Namespaced: true, GenericYAML: true},
30
+ {Kind: "Service", Group: "", Version: "v1", Resource: "services", Namespaced: true, GenericYAML: true},
31
+ {Kind: "PersistentVolumeClaim", Group: "", Version: "v1", Resource: "persistentvolumeclaims", Namespaced: true, GenericYAML: true},
32
+ {Kind: "PersistentVolume", Group: "", Version: "v1", Resource: "persistentvolumes", Namespaced: false, GenericYAML: true},
33
+ {Kind: "StorageClass", Group: "storage.k8s.io", Version: "v1", Resource: "storageclasses", Namespaced: false, GenericYAML: true},
34
+ {Kind: "Role", Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "roles", Namespaced: true, GenericYAML: true},
35
+ {Kind: "RoleBinding", Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "rolebindings", Namespaced: true, GenericYAML: true},
36
+ {Kind: "ResourceQuota", Group: "", Version: "v1", Resource: "resourcequotas", Namespaced: true, GenericYAML: true},
37
+ {Kind: "LimitRange", Group: "", Version: "v1", Resource: "limitranges", Namespaced: true, GenericYAML: true},
38
+ {Kind: "Lease", Group: "coordination.k8s.io", Version: "v1", Resource: "leases", Namespaced: true, GenericYAML: true},
39
+ {Kind: "HorizontalPodAutoscaler", Group: "autoscaling", Version: "v2", Resource: "horizontalpodautoscalers", Namespaced: true, GenericYAML: true},
40
+ {Kind: "PodDisruptionBudget", Group: "policy", Version: "v1", Resource: "poddisruptionbudgets", Namespaced: true, GenericYAML: true},
41
+ {Kind: "Ingress", Group: "networking.k8s.io", Version: "v1", Resource: "ingresses", Namespaced: true, GenericYAML: true},
42
+ {Kind: "EndpointSlice", Group: "discovery.k8s.io", Version: "v1", Resource: "endpointslices", Namespaced: true, GenericYAML: true},
43
+ {Kind: "NetworkPolicy", Group: "networking.k8s.io", Version: "v1", Resource: "networkpolicies", Namespaced: true, GenericYAML: true},
44
+ {Kind: "IngressClass", Group: "networking.k8s.io", Version: "v1", Resource: "ingressclasses", Namespaced: false, GenericYAML: true},
45
+ {Kind: "GatewayClass", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "gatewayclasses", Namespaced: false, GenericYAML: true},
46
+ {Kind: "Gateway", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "gateways", Namespaced: true, GenericYAML: true},
47
+ {Kind: "HTTPRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "httproutes", Namespaced: true, GenericYAML: true},
48
+ {Kind: "BackendTLSPolicy", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "backendtlspolicies", Namespaced: true, GenericYAML: true},
49
+ {Kind: "GRPCRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "grpcroutes", Namespaced: true, GenericYAML: true},
50
+ {Kind: "ReferenceGrant", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "referencegrants", Namespaced: true, GenericYAML: true},
51
+ {Kind: "PriorityClass", Group: "scheduling.k8s.io", Version: "v1", Resource: "priorityclasses", Namespaced: false, GenericYAML: true},
52
+ {Kind: "RuntimeClass", Group: "node.k8s.io", Version: "v1", Resource: "runtimeclasses", Namespaced: false, GenericYAML: true},
53
+ {Kind: "ClusterRole", Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterroles", Namespaced: false, GenericYAML: true},
54
+ {Kind: "ClusterRoleBinding", Group: "rbac.authorization.k8s.io", Version: "v1", Resource: "clusterrolebindings", Namespaced: false, GenericYAML: true},
55
+ {Kind: "CustomResourceDefinition", Group: "apiextensions.k8s.io", Version: "v1", Resource: "customresourcedefinitions", Namespaced: false, GenericYAML: false},
56
+ {Kind: "WorkloadCronJob", Group: "workloads.soha.io", Version: "v1alpha1", Resource: "workloadcronjobs", Namespaced: true, GenericYAML: false},
57
+ {Kind: "MutatingWebhookConfiguration", Group: "admissionregistration.k8s.io", Version: "v1", Resource: "mutatingwebhookconfigurations", Namespaced: false, GenericYAML: true},
58
+ {Kind: "ValidatingWebhookConfiguration", Group: "admissionregistration.k8s.io", Version: "v1", Resource: "validatingwebhookconfigurations", Namespaced: false, GenericYAML: true},
59
+ }
60
+
61
+ // KubernetesResourceKinds returns a copy so callers cannot alter the shared surface.
62
+ func KubernetesResourceKinds() []KubernetesResourceKind {
63
+ return append([]KubernetesResourceKind(nil), kubernetesResourceKinds[:]...)
64
+ }
65
+
66
+ func LookupKubernetesResourceKind(kind string) (KubernetesResourceKind, bool) {
67
+ for _, definition := range kubernetesResourceKinds {
68
+ if strings.EqualFold(strings.TrimSpace(kind), definition.Kind) {
69
+ return definition, true
70
+ }
71
+ }
72
+ return KubernetesResourceKind{}, false
73
+ }
@@ -0,0 +1,38 @@
1
+ package resource
2
+
3
+ import (
4
+ "strings"
5
+ "testing"
6
+ )
7
+
8
+ func TestKubernetesResourceKindsAreUniqueAndKeepDedicatedLifecycles(t *testing.T) {
9
+ seen := map[string]bool{}
10
+ for _, definition := range KubernetesResourceKinds() {
11
+ key := strings.ToLower(definition.Kind)
12
+ if seen[key] || definition.Version == "" || definition.Resource == "" {
13
+ t.Fatalf("invalid definition: %+v", definition)
14
+ }
15
+ seen[key] = true
16
+ if got, ok := LookupKubernetesResourceKind(" " + key + " "); !ok || got != definition {
17
+ t.Fatalf("lookup: %+v %v", got, ok)
18
+ }
19
+ }
20
+ for _, kind := range []string{"Namespace", "CustomResourceDefinition", "WorkloadCronJob"} {
21
+ if definition, ok := LookupKubernetesResourceKind(kind); !ok || definition.GenericYAML {
22
+ t.Fatalf("%s must retain its dedicated lifecycle", kind)
23
+ }
24
+ }
25
+ for _, kind := range []string{"HorizontalPodAutoscaler", "PodDisruptionBudget"} {
26
+ if definition, ok := LookupKubernetesResourceKind(kind); !ok || !definition.GenericYAML || !definition.Namespaced {
27
+ t.Fatalf("missing namespaced YAML surface: %s", kind)
28
+ }
29
+ }
30
+ if _, ok := LookupKubernetesResourceKind("Unknown"); ok {
31
+ t.Fatal("unknown kind accepted")
32
+ }
33
+ definitions := KubernetesResourceKinds()
34
+ definitions[0].Kind = "changed"
35
+ if KubernetesResourceKinds()[0].Kind == "changed" {
36
+ t.Fatal("caller changed the shared registry")
37
+ }
38
+ }
@@ -14,13 +14,14 @@ const (
14
14
  )
15
15
 
16
16
  type ResourceRef struct {
17
- ClusterID string `json:"clusterId"`
18
- APIVersion string `json:"apiVersion"`
19
- Kind string `json:"kind"`
20
- Name string `json:"name"`
21
- Namespace string `json:"namespace,omitempty"`
22
- ScopeMode ResourceScopeMode `json:"scopeMode"`
23
- UID string `json:"uid,omitempty"`
17
+ ClusterID string `json:"clusterId"`
18
+ APIVersion string `json:"apiVersion"`
19
+ Kind string `json:"kind"`
20
+ Name string `json:"name"`
21
+ Namespace string `json:"namespace,omitempty"`
22
+ ScopeMode ResourceScopeMode `json:"scopeMode"`
23
+ UID string `json:"uid,omitempty"`
24
+ ResourceVersion string `json:"resourceVersion,omitempty"`
24
25
  }
25
26
 
26
27
  type ResourceSearchItem struct {
@@ -103,6 +104,7 @@ type NamespaceUpsertInput struct {
103
104
  }
104
105
 
105
106
  type PodView struct {
107
+ UID string `json:"uid,omitempty"`
106
108
  Name string `json:"name"`
107
109
  Namespace string `json:"namespace"`
108
110
  Phase string `json:"phase"`
@@ -239,6 +241,7 @@ type NodeResourceSummaryView struct {
239
241
  }
240
242
 
241
243
  type PodDetailView struct {
244
+ UID string `json:"uid,omitempty"`
242
245
  Name string `json:"name"`
243
246
  Namespace string `json:"namespace"`
244
247
  Phase string `json:"phase"`
@@ -897,19 +900,27 @@ type NetworkTopologyTraceView struct {
897
900
  }
898
901
 
899
902
  type NetworkTopologyView struct {
900
- ClusterID string `json:"clusterId"`
901
- Namespace string `json:"namespace,omitempty"`
902
- Source string `json:"source"`
903
- GeneratedAt string `json:"generatedAt"`
904
- Summary NetworkTopologySummaryView `json:"summary"`
905
- Traces []NetworkTopologyTraceView `json:"traces,omitempty"`
906
- Warnings []string `json:"warnings,omitempty"`
903
+ GraphVersion string `json:"graphVersion,omitempty"`
904
+ Nodes []NetworkTopologyGraphNode `json:"nodes,omitempty"`
905
+ Edges []NetworkTopologyGraphEdge `json:"edges,omitempty"`
906
+ Coverage []NetworkTopologyCoverage `json:"coverage,omitempty"`
907
+ ObservedAt string `json:"observedAt,omitempty"`
908
+ Truncated bool `json:"truncated,omitempty"`
909
+ ClusterID string `json:"clusterId"`
910
+ Namespace string `json:"namespace,omitempty"`
911
+ Source string `json:"source"`
912
+ GeneratedAt string `json:"generatedAt"`
913
+ Summary NetworkTopologySummaryView `json:"summary"`
914
+ Traces []NetworkTopologyTraceView `json:"traces,omitempty"`
915
+ Warnings []string `json:"warnings,omitempty"`
907
916
  }
908
917
 
909
918
  type NodeView struct {
910
919
  Name string `json:"name"`
911
920
  Status string `json:"status"`
912
921
  Unschedulable bool `json:"unschedulable"`
922
+ Taints []NodeTaintView `json:"taints"`
923
+ Conditions []WorkloadConditionView `json:"conditions"`
913
924
  Roles []string `json:"roles,omitempty"`
914
925
  Version string `json:"version,omitempty"`
915
926
  InternalIP string `json:"internalIp,omitempty"`
@@ -0,0 +1,166 @@
1
+ package resource
2
+
3
+ const (
4
+ NetworkTopologySnapshotFeature = "network.topology.snapshot.v1"
5
+ NetworkTopologyGatewayL4Feature = "network.topology.gateway-l4.v1"
6
+ NetworkTopologyMaxQueries = 64
7
+ NetworkTopologyMaxSourceItems = 200
8
+ NetworkTopologyMaxSourceBytes = 1 << 20
9
+ NetworkTopologyMaxTotalBytes = 4 << 20
10
+ NetworkTopologyMaxObjectBytes = 64 << 10
11
+ NetworkTopologyDefaultNodes = 200
12
+ NetworkTopologyDefaultEdges = 400
13
+ NetworkTopologyMaxNodes = 500
14
+ NetworkTopologyMaxEdges = 1000
15
+ )
16
+
17
+ type NetworkTopologyGraphNode struct {
18
+ ID string `json:"id"`
19
+ Type string `json:"type"`
20
+ Label string `json:"label"`
21
+ Provider string `json:"provider"`
22
+ Protocol string `json:"protocol,omitempty"`
23
+ ResolutionState string `json:"resolutionState"`
24
+ Resource *ResourceRef `json:"resource,omitempty"`
25
+ Rule *NetworkTopologyRule `json:"rule,omitempty"`
26
+ Properties []NetworkTopologyProperty `json:"properties,omitempty"`
27
+ Conditions []NetworkTopologyCondition `json:"conditions,omitempty"`
28
+ Generation int64 `json:"generation,omitempty"`
29
+ }
30
+
31
+ type NetworkTopologyGraphEdge struct {
32
+ ID string `json:"id"`
33
+ Source string `json:"source"`
34
+ Target string `json:"target"`
35
+ Relation string `json:"relation"`
36
+ ResolutionState string `json:"resolutionState"`
37
+ Protocol string `json:"protocol,omitempty"`
38
+ Port string `json:"port,omitempty"`
39
+ Weight *int64 `json:"weight,omitempty"`
40
+ TotalWeight *int64 `json:"totalWeight,omitempty"`
41
+ MirrorPercent *float64 `json:"mirrorPercent,omitempty"`
42
+ RuleRef string `json:"ruleRef,omitempty"`
43
+ SourceRef *ResourceRef `json:"sourceRef,omitempty"`
44
+ Note string `json:"note,omitempty"`
45
+ }
46
+
47
+ type NetworkTopologyRule struct {
48
+ Name string `json:"name,omitempty"`
49
+ Index int `json:"index"`
50
+ MatchGroups [][]NetworkTopologyMatch `json:"matchGroups,omitempty"`
51
+ MatchSummary string `json:"matchSummary,omitempty"`
52
+ Priority *int64 `json:"priority,omitempty"`
53
+ TLSMode string `json:"tlsMode,omitempty"`
54
+ }
55
+
56
+ type NetworkTopologyMatch struct {
57
+ Type string `json:"type"`
58
+ Name string `json:"name,omitempty"`
59
+ Operator string `json:"operator,omitempty"`
60
+ Value string `json:"value,omitempty"`
61
+ }
62
+
63
+ type NetworkTopologyProperty struct {
64
+ Name string `json:"name"`
65
+ Value string `json:"value"`
66
+ }
67
+
68
+ type NetworkTopologyCondition struct {
69
+ Type string `json:"type"`
70
+ Status string `json:"status"`
71
+ Reason string `json:"reason,omitempty"`
72
+ ObservedGeneration *int64 `json:"observedGeneration,omitempty"`
73
+ LastTransitionTime string `json:"lastTransitionTime,omitempty"`
74
+ }
75
+
76
+ type NetworkTopologyCoverage struct {
77
+ Provider string `json:"provider"`
78
+ SourceID string `json:"sourceId"`
79
+ State string `json:"state"`
80
+ Namespace string `json:"namespace,omitempty"`
81
+ SupportedKinds []string `json:"supportedKinds,omitempty"`
82
+ ReasonCode string `json:"reasonCode,omitempty"`
83
+ CheckedAt string `json:"checkedAt,omitempty"`
84
+ ObservedAt string `json:"observedAt,omitempty"`
85
+ Truncated bool `json:"truncated,omitempty"`
86
+ }
87
+
88
+ // Snapshot queries name a fixed source, never a caller-supplied GVR or field path.
89
+ type NetworkTopologySourceQuery struct {
90
+ SourceID string `json:"sourceId"`
91
+ Namespace string `json:"namespace,omitempty"`
92
+ }
93
+
94
+ type NetworkTopologySnapshotRequest struct {
95
+ Queries []NetworkTopologySourceQuery `json:"queries"`
96
+ }
97
+
98
+ type NetworkTopologySourceObject struct {
99
+ Resource ResourceRef `json:"resource"`
100
+ Generation int64 `json:"generation,omitempty"`
101
+ // Content contains only the source's contract-allowlisted projection, not an original manifest.
102
+ Content string `json:"content"`
103
+ }
104
+
105
+ type NetworkTopologySourceResult struct {
106
+ Coverage NetworkTopologyCoverage `json:"coverage"`
107
+ Objects []NetworkTopologySourceObject `json:"objects"`
108
+ }
109
+
110
+ type NetworkTopologySnapshot struct {
111
+ Version string `json:"version"`
112
+ Sources []NetworkTopologySourceResult `json:"sources"`
113
+ }
114
+
115
+ type NetworkTopologySourceDefinition struct {
116
+ ID string
117
+ Provider string
118
+ FallbackVersion string
119
+ CRDResourceDefinition
120
+ }
121
+
122
+ var networkTopologySources = [...]NetworkTopologySourceDefinition{
123
+ {ID: "kubernetes.services", Provider: "kubernetes", CRDResourceDefinition: CRDResourceDefinition{Kind: "Service", Version: "v1", Resource: "services", Namespaced: true}},
124
+ {ID: "kubernetes.pods", Provider: "kubernetes", CRDResourceDefinition: CRDResourceDefinition{Kind: "Pod", Version: "v1", Resource: "pods", Namespaced: true}},
125
+ {ID: "kubernetes.endpointslices", Provider: "kubernetes", CRDResourceDefinition: CRDResourceDefinition{Kind: "EndpointSlice", Group: "discovery.k8s.io", Version: "v1", Resource: "endpointslices", Namespaced: true}},
126
+ {ID: "kubernetes.ingresses", Provider: "kubernetes", CRDResourceDefinition: CRDResourceDefinition{Kind: "Ingress", Group: "networking.k8s.io", Version: "v1", Resource: "ingresses", Namespaced: true}},
127
+ {ID: "kubernetes.ingressclasses", Provider: "kubernetes", CRDResourceDefinition: CRDResourceDefinition{Kind: "IngressClass", Group: "networking.k8s.io", Version: "v1", Resource: "ingressclasses"}},
128
+ {ID: "gateway.gateways", Provider: "gateway", CRDResourceDefinition: CRDResourceDefinition{Kind: "Gateway", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "gateways", Namespaced: true}},
129
+ {ID: "gateway.gatewayclasses", Provider: "gateway", CRDResourceDefinition: CRDResourceDefinition{Kind: "GatewayClass", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "gatewayclasses"}},
130
+ {ID: "gateway.httproutes", Provider: "gateway", CRDResourceDefinition: CRDResourceDefinition{Kind: "HTTPRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "httproutes", Namespaced: true}},
131
+ {ID: "gateway.grpcroutes", Provider: "gateway", CRDResourceDefinition: CRDResourceDefinition{Kind: "GRPCRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "grpcroutes", Namespaced: true}},
132
+ {ID: "gateway.tcproutes", Provider: "gateway", FallbackVersion: "v1alpha2", CRDResourceDefinition: CRDResourceDefinition{Kind: "TCPRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "tcproutes", Namespaced: true}},
133
+ {ID: "gateway.tlsroutes", Provider: "gateway", FallbackVersion: "v1alpha2", CRDResourceDefinition: CRDResourceDefinition{Kind: "TLSRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "tlsroutes", Namespaced: true}},
134
+ {ID: "gateway.udproutes", Provider: "gateway", FallbackVersion: "v1alpha2", CRDResourceDefinition: CRDResourceDefinition{Kind: "UDPRoute", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "udproutes", Namespaced: true}},
135
+ {ID: "gateway.referencegrants", Provider: "gateway", FallbackVersion: "v1beta1", CRDResourceDefinition: CRDResourceDefinition{Kind: "ReferenceGrant", Group: "gateway.networking.k8s.io", Version: "v1", Resource: "referencegrants", Namespaced: true}},
136
+ {ID: "traefik.ingressroutes", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "IngressRoute", Group: "traefik.io", Version: "v1alpha1", Resource: "ingressroutes", Namespaced: true}},
137
+ {ID: "traefik.ingressroutetcps", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "IngressRouteTCP", Group: "traefik.io", Version: "v1alpha1", Resource: "ingressroutetcps", Namespaced: true}},
138
+ {ID: "traefik.ingressrouteudps", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "IngressRouteUDP", Group: "traefik.io", Version: "v1alpha1", Resource: "ingressrouteudps", Namespaced: true}},
139
+ {ID: "traefik.traefikservices", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "TraefikService", Group: "traefik.io", Version: "v1alpha1", Resource: "traefikservices", Namespaced: true}},
140
+ {ID: "traefik.middlewares", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "Middleware", Group: "traefik.io", Version: "v1alpha1", Resource: "middlewares", Namespaced: true}},
141
+ {ID: "traefik.middlewaretcps", Provider: "traefik", CRDResourceDefinition: CRDResourceDefinition{Kind: "MiddlewareTCP", Group: "traefik.io", Version: "v1alpha1", Resource: "middlewaretcps", Namespaced: true}},
142
+ {ID: "apisix.apisixroutes", Provider: "apisix", CRDResourceDefinition: CRDResourceDefinition{Kind: "ApisixRoute", Group: "apisix.apache.org", Version: "v2", Resource: "apisixroutes", Namespaced: true}},
143
+ {ID: "apisix.apisixupstreams", Provider: "apisix", CRDResourceDefinition: CRDResourceDefinition{Kind: "ApisixUpstream", Group: "apisix.apache.org", Version: "v2", Resource: "apisixupstreams", Namespaced: true}},
144
+ {ID: "apisix.apisixtls", Provider: "apisix", CRDResourceDefinition: CRDResourceDefinition{Kind: "ApisixTls", Group: "apisix.apache.org", Version: "v2", Resource: "apisixtls", Namespaced: true}},
145
+ }
146
+
147
+ func NetworkTopologySources() []NetworkTopologySourceDefinition {
148
+ return append([]NetworkTopologySourceDefinition(nil), networkTopologySources[:]...)
149
+ }
150
+
151
+ func IsGatewayL4TopologySource(id string) bool {
152
+ return id == "gateway.tcproutes" || id == "gateway.tlsroutes" || id == "gateway.udproutes"
153
+ }
154
+
155
+ func (d NetworkTopologySourceDefinition) RequiresCustomResourceAccess() bool {
156
+ return d.Provider == "traefik" || d.Provider == "apisix" || IsGatewayL4TopologySource(d.ID)
157
+ }
158
+
159
+ func LookupNetworkTopologySource(id string) (NetworkTopologySourceDefinition, bool) {
160
+ for _, definition := range networkTopologySources {
161
+ if definition.ID == id {
162
+ return definition, true
163
+ }
164
+ }
165
+ return NetworkTopologySourceDefinition{}, false
166
+ }