1
This commit is contained in:
+2644
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -58,6 +59,14 @@ func main() {
|
||||
if err := runUpdateLoop(ctx, os.Args[2:]); err != nil {
|
||||
log.Fatalf("update-loop failed: %v", err)
|
||||
}
|
||||
case "monitor-loop":
|
||||
if err := runMonitorLoop(ctx, os.Args[2:]); err != nil {
|
||||
log.Fatalf("monitor-loop failed: %v", err)
|
||||
}
|
||||
case "monitor-once":
|
||||
if err := runMonitorOnce(ctx, os.Args[2:]); err != nil {
|
||||
log.Fatalf("monitor-once failed: %v", err)
|
||||
}
|
||||
case "install-updater":
|
||||
if err := runInstallUpdater(ctx, os.Args[2:]); err != nil {
|
||||
log.Fatalf("install-updater failed: %v", err)
|
||||
@@ -288,6 +297,9 @@ func runInstall(ctx context.Context, args []string) error {
|
||||
return err
|
||||
}
|
||||
fmt.Print(result.Unit)
|
||||
if result.MonitorUnit != "" {
|
||||
fmt.Print(result.MonitorUnit)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -304,7 +316,7 @@ func runInstall(ctx context.Context, args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("updater_service=%s unit=%s binary=%s started=%t\n", serviceResult.UnitName, serviceResult.UnitPath, serviceResult.BinaryPath, serviceResult.Started)
|
||||
fmt.Printf("updater_service=%s unit=%s binary=%s started=%t monitor_service=%s\n", serviceResult.UnitName, serviceResult.UnitPath, serviceResult.BinaryPath, serviceResult.Started, serviceResult.MonitorUnitName)
|
||||
}
|
||||
fmt.Println("next: approve the join request in the platform admin panel, then the node-agent will finish bootstrap and start heartbeats")
|
||||
return nil
|
||||
@@ -429,6 +441,75 @@ func runUpdateLoop(ctx context.Context, args []string) error {
|
||||
return (hostagent.DockerManager{}).RunUpdateLoop(ctx, cfg)
|
||||
}
|
||||
|
||||
func runMonitorLoop(ctx context.Context, args []string) error {
|
||||
cfg, err := parseMonitor(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return hostagent.RunMonitorLoop(ctx, cfg)
|
||||
}
|
||||
|
||||
func runMonitorOnce(ctx context.Context, args []string) error {
|
||||
cfg, err := parseMonitor(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.MaxRuns = 1
|
||||
result := hostagent.RunMonitorOnce(ctx, cfg)
|
||||
if err := json.NewEncoder(os.Stdout).Encode(result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseMonitor(args []string) (hostagent.MonitorConfig, error) {
|
||||
fs := flag.NewFlagSet("monitor-loop", flag.ContinueOnError)
|
||||
cfg := hostagent.MonitorConfig{}
|
||||
var intervalSeconds int
|
||||
var initialDelaySeconds int
|
||||
var maxRuns int
|
||||
var restartCooldownSeconds int
|
||||
var staleRestartingSeconds int
|
||||
var tmpMinAgeMinutes int
|
||||
watchContainers := repeatedFlag{}
|
||||
fs.StringVar(&cfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL used for monitor status reports.")
|
||||
fs.StringVar(&cfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.")
|
||||
fs.StringVar(&cfg.NodeID, "node-id", getenv("RAP_NODE_ID", ""), "Already enrolled node ID.")
|
||||
fs.StringVar(&cfg.StateDir, "state-dir", getenv("RAP_NODE_STATE_DIR", hostagent.DefaultStateDir), "Host path containing node-agent identity.json.")
|
||||
fs.StringVar(&cfg.Product, "product", getenv("RAP_MONITOR_PRODUCT", hostagent.DefaultMonitorProduct), "Status product name.")
|
||||
fs.StringVar(&cfg.CurrentVersion, "current-version", getenv("RAP_HOST_AGENT_VERSION", agent.Version), "Current rap-host-agent version.")
|
||||
fs.StringVar(&cfg.DockerBinary, "docker-binary", getenv("RAP_DOCKER_BINARY", "docker"), "Docker CLI binary.")
|
||||
fs.StringVar(&cfg.DiskPath, "disk-path", getenv("RAP_MONITOR_DISK_PATH", "/"), "Filesystem path used for disk usage checks.")
|
||||
fs.StringVar(&cfg.TmpDir, "tmp-dir", getenv("RAP_MONITOR_TMP_DIR", "/tmp"), "Temporary directory cleaned under pressure.")
|
||||
fs.StringVar(&cfg.StatusFile, "status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written after every run.")
|
||||
fs.IntVar(&intervalSeconds, "interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.")
|
||||
fs.IntVar(&initialDelaySeconds, "initial-delay-seconds", getenvInt("RAP_MONITOR_INITIAL_DELAY_SECONDS", 0), "Seconds to wait before first monitor check.")
|
||||
fs.IntVar(&maxRuns, "max-runs", getenvInt("RAP_MONITOR_MAX_RUNS", 0), "Maximum monitor iterations. Use 0 to run until stopped.")
|
||||
fs.IntVar(&cfg.DiskWarnPercent, "disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.")
|
||||
fs.IntVar(&cfg.DiskCleanupPercent, "disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.")
|
||||
fs.IntVar(&cfg.DiskCriticalPercent, "disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.")
|
||||
fs.IntVar(&restartCooldownSeconds, "restart-cooldown-seconds", getenvInt("RAP_MONITOR_RESTART_COOLDOWN_SECONDS", hostagent.DefaultMonitorRestartCooldownSec), "Minimum seconds between repeated restarts of the same target.")
|
||||
fs.IntVar(&staleRestartingSeconds, "stale-restarting-seconds", getenvInt("RAP_MONITOR_STALE_RESTARTING_SECONDS", hostagent.DefaultMonitorStaleRestartingSec), "Seconds after which docker restarting state is considered stuck.")
|
||||
fs.IntVar(&tmpMinAgeMinutes, "tmp-min-age-minutes", getenvInt("RAP_MONITOR_TMP_MIN_AGE_MINUTES", hostagent.DefaultMonitorTmpMinAgeMinutes), "Minimum age for /tmp rap-* and go-build* cleanup.")
|
||||
fs.BoolVar(&cfg.RestartContainers, "restart-containers", getenvBool("RAP_MONITOR_RESTART_CONTAINERS", true), "Start/restart watched containers when they are stopped, unhealthy, or stuck restarting.")
|
||||
fs.BoolVar(&cfg.CleanupDocker, "cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.")
|
||||
fs.Var(&watchContainers, "watch-container", "Docker container to watch and heal; may be repeated.")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return hostagent.MonitorConfig{}, err
|
||||
}
|
||||
cfg.WatchContainers = watchContainers
|
||||
cfg.Interval = time.Duration(intervalSeconds) * time.Second
|
||||
cfg.InitialDelay = time.Duration(initialDelaySeconds) * time.Second
|
||||
cfg.MaxRuns = maxRuns
|
||||
cfg.RestartCooldown = time.Duration(restartCooldownSeconds) * time.Second
|
||||
cfg.StaleRestartingAfter = time.Duration(staleRestartingSeconds) * time.Second
|
||||
cfg.TmpMinAge = time.Duration(tmpMinAgeMinutes) * time.Minute
|
||||
cfg.Logf = func(format string, args ...any) {
|
||||
fmt.Printf(format+"\n", args...)
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func firstNonEmptyLocal(values ...string) string {
|
||||
for _, value := range values {
|
||||
if strings.TrimSpace(value) != "" {
|
||||
@@ -444,6 +525,8 @@ func runInstallUpdater(ctx context.Context, args []string) error {
|
||||
service := hostagent.UpdateServiceConfig{}
|
||||
var dryRun bool
|
||||
var selfUpdater bool
|
||||
var monitorEnabled bool
|
||||
monitorContainers := repeatedFlag{}
|
||||
fs.StringVar(&runtimeCfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL.")
|
||||
fs.StringVar(&runtimeCfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.")
|
||||
fs.StringVar(&runtimeCfg.ContainerName, "container-name", getenv("RAP_NODE_AGENT_CONTAINER", hostagent.DefaultContainerName), "Docker container name to update.")
|
||||
@@ -456,6 +539,14 @@ func runInstallUpdater(ctx context.Context, args []string) error {
|
||||
fs.IntVar(&service.HealthTimeoutSec, "health-timeout-seconds", getenvInt("RAP_UPDATE_HEALTH_TIMEOUT_SECONDS", 30), "Updated container running-state timeout in seconds.")
|
||||
fs.StringVar(&service.BinaryInstallPath, "binary-path", getenv("RAP_HOST_AGENT_BINARY_PATH", hostagent.DefaultHostAgentInstallPath), "Persistent host path for rap-host-agent binary used by the service.")
|
||||
fs.BoolVar(&selfUpdater, "self-updater-enabled", getenvBool("RAP_HOST_AGENT_SELF_UPDATE_ENABLED", true), "Install and start one global host-agent binary self-updater service.")
|
||||
fs.BoolVar(&monitorEnabled, "monitor-enabled", getenvBool("RAP_HOST_AGENT_MONITOR_ENABLED", true), "Install and start the local host monitor service.")
|
||||
fs.IntVar(&service.MonitorIntervalSec, "monitor-interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.")
|
||||
fs.StringVar(&service.MonitorStatusFile, "monitor-status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written by the monitor.")
|
||||
fs.IntVar(&service.MonitorDiskWarn, "monitor-disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.")
|
||||
fs.IntVar(&service.MonitorDiskCleanup, "monitor-disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.")
|
||||
fs.IntVar(&service.MonitorDiskCritical, "monitor-disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.")
|
||||
fs.BoolVar(&service.MonitorCleanupDocker, "monitor-cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.")
|
||||
fs.Var(&monitorContainers, "monitor-container", "Extra Docker container watched by monitor; may be repeated.")
|
||||
fs.BoolVar(&dryRun, "dry-run", false, "Print the systemd unit without installing it.")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
@@ -465,6 +556,8 @@ func runInstallUpdater(ctx context.Context, args []string) error {
|
||||
service.DryRun = dryRun
|
||||
service.InstallSelfUpdater = selfUpdater
|
||||
service.SelfUpdateVersion = agent.Version
|
||||
service.InstallMonitor = monitorEnabled
|
||||
service.MonitorContainers = monitorContainers
|
||||
result, err := (hostagent.DockerManager{}).InstallUpdateService(ctx, service)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -474,9 +567,12 @@ func runInstallUpdater(ctx context.Context, args []string) error {
|
||||
if result.SelfUnit != "" {
|
||||
fmt.Print(result.SelfUnit)
|
||||
}
|
||||
if result.MonitorUnit != "" {
|
||||
fmt.Print(result.MonitorUnit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("updater_service=%s unit=%s binary=%s started=%t self_updater=%s\n", result.UnitName, result.UnitPath, result.BinaryPath, result.Started, result.SelfUnitName)
|
||||
fmt.Printf("updater_service=%s unit=%s binary=%s started=%t self_updater=%s monitor_service=%s\n", result.UnitName, result.UnitPath, result.BinaryPath, result.Started, result.SelfUnitName, result.MonitorUnitName)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -572,6 +668,7 @@ func parseInstall(args []string) (installCommandConfig, error) {
|
||||
var installToken string
|
||||
var autoUpdateEnabled bool
|
||||
autoUpdate := hostagent.UpdateServiceConfig{}
|
||||
monitorContainers := repeatedFlag{}
|
||||
fs.StringVar(&cfg.BackendURL, "backend-url", getenv("RAP_BACKEND_URL", ""), "Control Plane API base URL.")
|
||||
fs.StringVar(&cfg.ClusterID, "cluster-id", getenv("RAP_CLUSTER_ID", ""), "Cluster ID.")
|
||||
fs.StringVar(&cfg.JoinToken, "join-token", getenv("RAP_JOIN_TOKEN", ""), "One-time join token for first enrollment.")
|
||||
@@ -591,6 +688,7 @@ func parseInstall(args []string) (installCommandConfig, error) {
|
||||
fs.BoolVar(&dryRun, "dry-run", false, "Print the docker command with secrets redacted.")
|
||||
fs.BoolVar(&autoUpdateEnabled, "auto-update-enabled", getenvBool("RAP_AUTO_UPDATE_ENABLED", true), "Install and start the local update-loop service.")
|
||||
fs.BoolVar(&autoUpdate.InstallSelfUpdater, "host-agent-self-update-enabled", getenvBool("RAP_HOST_AGENT_SELF_UPDATE_ENABLED", true), "Install and start one global host-agent binary self-updater service.")
|
||||
fs.BoolVar(&autoUpdate.InstallMonitor, "host-agent-monitor-enabled", getenvBool("RAP_HOST_AGENT_MONITOR_ENABLED", true), "Install and start the local host monitor service.")
|
||||
fs.StringVar(&autoUpdate.CurrentVersion, "auto-update-current-version", getenv("RAP_NODE_AGENT_VERSION", agent.Version), "Initial node-agent version used by update-loop before the first successful update.")
|
||||
fs.StringVar(&autoUpdate.SelfUpdateVersion, "host-agent-current-version", getenv("RAP_HOST_AGENT_VERSION", agent.Version), "Initial host-agent binary version used by the self-updater.")
|
||||
fs.StringVar(&autoUpdate.Channel, "auto-update-channel", getenv("RAP_UPDATE_CHANNEL", ""), "Optional update channel override for update-loop.")
|
||||
@@ -599,6 +697,12 @@ func parseInstall(args []string) (installCommandConfig, error) {
|
||||
fs.Float64Var(&autoUpdate.Jitter, "auto-update-jitter", getenvFloat("RAP_UPDATE_JITTER", 0.15), "Update-loop interval jitter, 0..1.")
|
||||
fs.IntVar(&autoUpdate.HealthTimeoutSec, "auto-update-health-timeout-seconds", getenvInt("RAP_UPDATE_HEALTH_TIMEOUT_SECONDS", 30), "Updated container running-state timeout in seconds.")
|
||||
fs.StringVar(&autoUpdate.BinaryInstallPath, "auto-update-binary-path", getenv("RAP_HOST_AGENT_BINARY_PATH", hostagent.DefaultHostAgentInstallPath), "Persistent host path for rap-host-agent binary used by the service.")
|
||||
fs.IntVar(&autoUpdate.MonitorIntervalSec, "monitor-interval-seconds", getenvInt("RAP_MONITOR_INTERVAL_SECONDS", hostagent.DefaultMonitorIntervalSeconds), "Seconds between monitor checks.")
|
||||
fs.StringVar(&autoUpdate.MonitorStatusFile, "monitor-status-file", getenv("RAP_MONITOR_STATUS_FILE", ""), "Optional JSON status file written by the monitor.")
|
||||
fs.IntVar(&autoUpdate.MonitorDiskWarn, "monitor-disk-warn-percent", getenvInt("RAP_MONITOR_DISK_WARN_PERCENT", hostagent.DefaultMonitorDiskWarnPercent), "Disk used percent that reports warning.")
|
||||
fs.IntVar(&autoUpdate.MonitorDiskCleanup, "monitor-disk-cleanup-percent", getenvInt("RAP_MONITOR_DISK_CLEANUP_PERCENT", hostagent.DefaultMonitorDiskCleanupPercent), "Disk used percent that triggers cleanup.")
|
||||
fs.IntVar(&autoUpdate.MonitorDiskCritical, "monitor-disk-critical-percent", getenvInt("RAP_MONITOR_DISK_CRITICAL_PERCENT", hostagent.DefaultMonitorDiskCriticalPercent), "Disk used percent that reports failure after cleanup.")
|
||||
fs.BoolVar(&autoUpdate.MonitorCleanupDocker, "monitor-cleanup-docker", getenvBool("RAP_MONITOR_CLEANUP_DOCKER", true), "Run safe docker prune cleanup when disk is above cleanup threshold.")
|
||||
fs.BoolVar(&cfg.WorkloadSupervisionEnabled, "workload-supervision-enabled", getenvBool("RAP_WORKLOAD_SUPERVISION_ENABLED", false), "Enable node-agent workload status reporting.")
|
||||
fs.BoolVar(&cfg.MeshSyntheticRuntimeEnabled, "mesh-synthetic-runtime-enabled", getenvBool("RAP_MESH_SYNTHETIC_RUNTIME_ENABLED", false), "Enable synthetic mesh runtime.")
|
||||
fs.BoolVar(&cfg.MeshProductionForwardingEnabled, "mesh-production-forwarding-enabled", getenvBool("RAP_MESH_PRODUCTION_FORWARDING_ENABLED", false), "Enable production forwarding gate; runtime still fail-closed if unavailable.")
|
||||
@@ -622,12 +726,14 @@ func parseInstall(args []string) (installCommandConfig, error) {
|
||||
fs.Var(&extraEnv, "env", "Extra KEY=VALUE env passed to node-agent container; may be repeated.")
|
||||
fs.Var(&extraRunArg, "docker-run-arg", "Extra raw docker run argument; may be repeated.")
|
||||
fs.Var(&imageArtifactURL, "image-artifact-url", "Docker image tar artifact URL to docker load before running; may be repeated.")
|
||||
fs.Var(&monitorContainers, "monitor-container", "Extra Docker container watched by monitor; may be repeated.")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return installCommandConfig{}, err
|
||||
}
|
||||
cfg.ExtraEnv = extraEnv
|
||||
cfg.AdditionalDockerRunArgs = extraRunArg
|
||||
cfg.ImageArtifactURLs = append(cfg.ImageArtifactURLs, imageArtifactURL...)
|
||||
autoUpdate.MonitorContainers = monitorContainers
|
||||
if strings.TrimSpace(profileURL) != "" || strings.TrimSpace(installToken) != "" {
|
||||
profile, err := hostagent.FetchDockerInstallProfile(context.Background(), hostagent.ProfileRequest{
|
||||
URL: profileURL,
|
||||
@@ -738,6 +844,8 @@ func usage() {
|
||||
rap-host-agent install-updater -backend-url URL -cluster-id ID -state-dir DIR -container-name NAME
|
||||
rap-host-agent update-host-agent -backend-url URL -cluster-id ID -state-dir DIR
|
||||
rap-host-agent update-host-agent-loop -backend-url URL -cluster-id ID -state-dir DIR
|
||||
rap-host-agent monitor-loop -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME
|
||||
rap-host-agent monitor-once -backend-url URL -cluster-id ID -state-dir DIR --watch-container NAME
|
||||
rap-host-agent update -backend-url URL -cluster-id ID -node-id ID [-container-name NAME]
|
||||
rap-host-agent update-loop -backend-url URL -cluster-id ID -node-id ID [-container-name NAME]
|
||||
rap-host-agent status [-container-name NAME]`)
|
||||
|
||||
@@ -222,6 +222,11 @@ type NodeVPNAssignmentLeaseRenewRequest struct {
|
||||
TTLSeconds int `json:"ttl_seconds"`
|
||||
}
|
||||
|
||||
type NodeVPNAssignmentLeaseAcquireRequest struct {
|
||||
TTLSeconds int `json:"ttl_seconds"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type MeshLinkObservationRequest struct {
|
||||
SourceNodeID string `json:"source_node_id"`
|
||||
TargetNodeID string `json:"target_node_id"`
|
||||
@@ -658,6 +663,17 @@ func (c *Client) ReportNodeVPNAssignmentStatus(ctx context.Context, clusterID, n
|
||||
return c.postJSON(ctx, path, request, nil)
|
||||
}
|
||||
|
||||
func (c *Client) AcquireNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID string, request NodeVPNAssignmentLeaseAcquireRequest) (*NodeVPNAssignmentLease, error) {
|
||||
var response struct {
|
||||
Lease NodeVPNAssignmentLease `json:"lease"`
|
||||
}
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/acquire", clusterID, nodeID, vpnConnectionID)
|
||||
if err := c.postJSON(ctx, path, request, &response); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.Lease, nil
|
||||
}
|
||||
|
||||
func (c *Client) RenewNodeVPNAssignmentLease(ctx context.Context, clusterID, nodeID, vpnConnectionID, leaseID string, request NodeVPNAssignmentLeaseRenewRequest) error {
|
||||
path := fmt.Sprintf("/clusters/%s/nodes/%s/vpn/assignments/%s/lease/%s/renew", clusterID, nodeID, vpnConnectionID, leaseID)
|
||||
return c.postJSON(ctx, path, request, nil)
|
||||
|
||||
@@ -40,6 +40,10 @@ type Config struct {
|
||||
MeshSyntheticConfigPath string
|
||||
MeshPeerEndpointsJSON string
|
||||
MeshSyntheticRoutesJSON string
|
||||
RemoteWorkspaceRealAdapterEnabled bool
|
||||
RemoteWorkspaceRealAdapterCommand string
|
||||
RemoteWorkspaceRealAdapterArgsJSON string
|
||||
RemoteWorkspaceRealAdapterWorkDir string
|
||||
}
|
||||
|
||||
func Load(args []string, env map[string]string) (Config, error) {
|
||||
@@ -73,6 +77,10 @@ func Load(args []string, env map[string]string) (Config, error) {
|
||||
fs.StringVar(&cfg.MeshSyntheticConfigPath, "mesh-synthetic-config", getEnv(env, "RAP_MESH_SYNTHETIC_CONFIG", ""), "Path to scoped synthetic mesh config snapshot. Preferred over debug JSON env.")
|
||||
fs.StringVar(&cfg.MeshPeerEndpointsJSON, "mesh-peer-endpoints-json", getEnv(env, "RAP_MESH_PEER_ENDPOINTS_JSON", ""), "JSON object mapping peer node_id to synthetic mesh endpoint URL.")
|
||||
fs.StringVar(&cfg.MeshSyntheticRoutesJSON, "mesh-synthetic-routes-json", getEnv(env, "RAP_MESH_SYNTHETIC_ROUTES_JSON", ""), "JSON array of synthetic mesh routes for test-only runtime.")
|
||||
fs.BoolVar(&cfg.RemoteWorkspaceRealAdapterEnabled, "remote-workspace-real-adapter-enabled", getEnvBool(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", false), "Request future real remote workspace adapter supervision. Disabled until the real runtime stage is implemented.")
|
||||
fs.StringVar(&cfg.RemoteWorkspaceRealAdapterCommand, "remote-workspace-real-adapter-command", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", ""), "Future real remote workspace adapter command path. Redacted from status payloads.")
|
||||
fs.StringVar(&cfg.RemoteWorkspaceRealAdapterArgsJSON, "remote-workspace-real-adapter-args-json", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", ""), "Future real remote workspace adapter args JSON. Redacted from status payloads.")
|
||||
fs.StringVar(&cfg.RemoteWorkspaceRealAdapterWorkDir, "remote-workspace-real-adapter-workdir", getEnv(env, "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR", ""), "Future real remote workspace adapter working directory. Redacted from status payloads.")
|
||||
heartbeatSeconds := getEnvInt(env, "RAP_HEARTBEAT_INTERVAL_SECONDS", 15)
|
||||
fs.DurationVar(&cfg.HeartbeatInterval, "heartbeat-interval", time.Duration(heartbeatSeconds)*time.Second, "Heartbeat interval.")
|
||||
enrollmentPollIntervalSeconds := getEnvInt(env, "RAP_ENROLLMENT_POLL_INTERVAL_SECONDS", 5)
|
||||
@@ -100,6 +108,9 @@ func Load(args []string, env map[string]string) (Config, error) {
|
||||
cfg.MeshSyntheticConfigPath = strings.TrimSpace(cfg.MeshSyntheticConfigPath)
|
||||
cfg.MeshPeerEndpointsJSON = strings.TrimSpace(cfg.MeshPeerEndpointsJSON)
|
||||
cfg.MeshSyntheticRoutesJSON = strings.TrimSpace(cfg.MeshSyntheticRoutesJSON)
|
||||
cfg.RemoteWorkspaceRealAdapterCommand = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterCommand)
|
||||
cfg.RemoteWorkspaceRealAdapterArgsJSON = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterArgsJSON)
|
||||
cfg.RemoteWorkspaceRealAdapterWorkDir = strings.TrimSpace(cfg.RemoteWorkspaceRealAdapterWorkDir)
|
||||
if cfg.BackendURL == "" {
|
||||
return Config{}, errors.New("backend URL is required")
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) {
|
||||
"RAP_MESH_SYNTHETIC_CONFIG": "/tmp/rap-node/mesh-synthetic.json",
|
||||
"RAP_MESH_PEER_ENDPOINTS_JSON": `{"node-b":"http://127.0.0.1:19002"}`,
|
||||
"RAP_MESH_SYNTHETIC_ROUTES_JSON": `[{"route_id":"route-1"}]`,
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED": "true",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND": " /opt/rap/bin/rdp-worker ",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON": ` ["--future-probe"] `,
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR": " /var/lib/rap-node-agent/rdp-worker ",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("load config: %v", err)
|
||||
@@ -85,6 +89,12 @@ func TestLoadConfigFromEnvAndArgs(t *testing.T) {
|
||||
if cfg.MeshPeerEndpointsJSON == "" || cfg.MeshSyntheticRoutesJSON == "" {
|
||||
t.Fatalf("mesh live synthetic config was not loaded: %+v", cfg)
|
||||
}
|
||||
if !cfg.RemoteWorkspaceRealAdapterEnabled ||
|
||||
cfg.RemoteWorkspaceRealAdapterCommand != "/opt/rap/bin/rdp-worker" ||
|
||||
cfg.RemoteWorkspaceRealAdapterArgsJSON != `["--future-probe"]` ||
|
||||
cfg.RemoteWorkspaceRealAdapterWorkDir != "/var/lib/rap-node-agent/rdp-worker" {
|
||||
t.Fatalf("unexpected remote workspace real adapter config: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigDefaultsEnrollmentPollingToNoTimeout(t *testing.T) {
|
||||
@@ -98,6 +108,12 @@ func TestLoadConfigDefaultsEnrollmentPollingToNoTimeout(t *testing.T) {
|
||||
if cfg.EnrollmentPollTimeout != 0 {
|
||||
t.Fatalf("EnrollmentPollTimeout = %s, want no timeout", cfg.EnrollmentPollTimeout)
|
||||
}
|
||||
if cfg.RemoteWorkspaceRealAdapterEnabled ||
|
||||
cfg.RemoteWorkspaceRealAdapterCommand != "" ||
|
||||
cfg.RemoteWorkspaceRealAdapterArgsJSON != "" ||
|
||||
cfg.RemoteWorkspaceRealAdapterWorkDir != "" {
|
||||
t.Fatalf("real adapter config should default disabled and empty: %+v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigRejectsNegativeProductionObservationSinkCapacity(t *testing.T) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
//go:build !windows
|
||||
|
||||
package hostagent
|
||||
|
||||
import "syscall"
|
||||
|
||||
func diskUsage(path string) (DiskUsage, error) {
|
||||
var stat syscall.Statfs_t
|
||||
if err := syscall.Statfs(path, &stat); err != nil {
|
||||
return DiskUsage{}, err
|
||||
}
|
||||
total := stat.Blocks * uint64(stat.Bsize)
|
||||
free := stat.Bavail * uint64(stat.Bsize)
|
||||
used := total - free
|
||||
percent := 0
|
||||
if total > 0 {
|
||||
percent = int((used*100 + total - 1) / total)
|
||||
}
|
||||
return DiskUsage{
|
||||
Path: path,
|
||||
TotalBytes: total,
|
||||
FreeBytes: free,
|
||||
UsedBytes: used,
|
||||
UsedPercent: percent,
|
||||
AvailablePercent: 100 - percent,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build windows
|
||||
|
||||
package hostagent
|
||||
|
||||
import "fmt"
|
||||
|
||||
func diskUsage(path string) (DiskUsage, error) {
|
||||
return DiskUsage{Path: path}, fmt.Errorf("disk usage monitor is not implemented on windows")
|
||||
}
|
||||
@@ -0,0 +1,494 @@
|
||||
package hostagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/example/remote-access-platform/agents/rap-node-agent/internal/state"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultMonitorProduct = "rap-host-agent"
|
||||
DefaultMonitorPhase = "host_monitor"
|
||||
DefaultMonitorIntervalSeconds = 60
|
||||
DefaultMonitorDiskWarnPercent = 80
|
||||
DefaultMonitorDiskCleanupPercent = 85
|
||||
DefaultMonitorDiskCriticalPercent = 95
|
||||
DefaultMonitorRestartCooldownSec = 300
|
||||
DefaultMonitorTmpMinAgeMinutes = 240
|
||||
DefaultMonitorStaleRestartingSec = 180
|
||||
DefaultMonitorDockerBinary = "docker"
|
||||
DefaultMonitorDiskPath = "/"
|
||||
DefaultMonitorTmpDir = "/tmp"
|
||||
DefaultMonitorStatusSchemaVersion = "rap.host_monitor_status.v1"
|
||||
DefaultMonitorRemediationSucceeded = "remediated"
|
||||
)
|
||||
|
||||
type MonitorConfig struct {
|
||||
BackendURL string
|
||||
ClusterID string
|
||||
NodeID string
|
||||
StateDir string
|
||||
Product string
|
||||
CurrentVersion string
|
||||
Interval time.Duration
|
||||
InitialDelay time.Duration
|
||||
MaxRuns int
|
||||
DockerBinary string
|
||||
WatchContainers []string
|
||||
RestartContainers bool
|
||||
RestartCooldown time.Duration
|
||||
StaleRestartingAfter time.Duration
|
||||
DiskPath string
|
||||
TmpDir string
|
||||
DiskWarnPercent int
|
||||
DiskCleanupPercent int
|
||||
DiskCriticalPercent int
|
||||
TmpMinAge time.Duration
|
||||
CleanupDocker bool
|
||||
StatusFile string
|
||||
Runner CommandRunner
|
||||
Logf func(format string, args ...any)
|
||||
restartHistory map[string]time.Time
|
||||
}
|
||||
|
||||
type DiskUsage struct {
|
||||
Path string `json:"path"`
|
||||
TotalBytes uint64 `json:"total_bytes"`
|
||||
FreeBytes uint64 `json:"free_bytes"`
|
||||
UsedBytes uint64 `json:"used_bytes"`
|
||||
UsedPercent int `json:"used_percent"`
|
||||
AvailablePercent int `json:"available_percent"`
|
||||
}
|
||||
|
||||
type MonitorContainerStatus struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Running bool `json:"running"`
|
||||
Restarting bool `json:"restarting"`
|
||||
ExitCode int `json:"exit_code,omitempty"`
|
||||
Health string `json:"health,omitempty"`
|
||||
RestartCount int `json:"restart_count,omitempty"`
|
||||
StartedAt string `json:"started_at,omitempty"`
|
||||
FinishedAt string `json:"finished_at,omitempty"`
|
||||
LastAction string `json:"last_action,omitempty"`
|
||||
LastActionOK bool `json:"last_action_ok,omitempty"`
|
||||
LastActionError string `json:"last_action_error,omitempty"`
|
||||
}
|
||||
|
||||
type MonitorAction struct {
|
||||
Kind string `json:"kind"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Success bool `json:"success"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
type MonitorResult struct {
|
||||
SchemaVersion string `json:"schema_version"`
|
||||
Status string `json:"status"`
|
||||
ObservedAt time.Time `json:"observed_at"`
|
||||
Disk *DiskUsage `json:"disk,omitempty"`
|
||||
Containers []MonitorContainerStatus `json:"containers,omitempty"`
|
||||
Actions []MonitorAction `json:"actions,omitempty"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
type monitorDockerInspect struct {
|
||||
Name string `json:"Name"`
|
||||
RestartCount int `json:"RestartCount"`
|
||||
State struct {
|
||||
Status string `json:"Status"`
|
||||
Running bool `json:"Running"`
|
||||
Restarting bool `json:"Restarting"`
|
||||
ExitCode int `json:"ExitCode"`
|
||||
Error string `json:"Error"`
|
||||
StartedAt string `json:"StartedAt"`
|
||||
FinishedAt string `json:"FinishedAt"`
|
||||
Health *struct {
|
||||
Status string `json:"Status"`
|
||||
} `json:"Health"`
|
||||
} `json:"State"`
|
||||
}
|
||||
|
||||
func RunMonitorLoop(ctx context.Context, cfg MonitorConfig) error {
|
||||
cfg = normalizeMonitorConfig(cfg)
|
||||
if cfg.InitialDelay > 0 {
|
||||
if err := sleepContext(ctx, cfg.InitialDelay); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
runs := 0
|
||||
restartHistory := map[string]time.Time{}
|
||||
for {
|
||||
cfg.restartHistory = restartHistory
|
||||
result := RunMonitorOnce(ctx, cfg)
|
||||
logMonitorResult(cfg, result)
|
||||
if err := writeMonitorStatusFile(cfg.StatusFile, result); err != nil && cfg.Logf != nil {
|
||||
cfg.Logf("monitor status-file failed: %v", err)
|
||||
}
|
||||
if err := reportMonitorStatus(ctx, cfg, result); err != nil && cfg.Logf != nil {
|
||||
cfg.Logf("monitor report failed: %v", err)
|
||||
}
|
||||
runs++
|
||||
if cfg.MaxRuns > 0 && runs >= cfg.MaxRuns {
|
||||
return nil
|
||||
}
|
||||
if err := sleepContext(ctx, cfg.Interval); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RunMonitorOnce(ctx context.Context, cfg MonitorConfig) MonitorResult {
|
||||
cfg = normalizeMonitorConfig(cfg)
|
||||
result := MonitorResult{
|
||||
SchemaVersion: DefaultMonitorStatusSchemaVersion,
|
||||
Status: "ok",
|
||||
ObservedAt: time.Now().UTC(),
|
||||
}
|
||||
if usage, err := diskUsage(cfg.DiskPath); err != nil {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("disk usage %s: %v", cfg.DiskPath, err))
|
||||
} else {
|
||||
result.Disk = &usage
|
||||
if usage.UsedPercent >= cfg.DiskWarnPercent {
|
||||
result.Status = "warning"
|
||||
}
|
||||
if usage.UsedPercent >= cfg.DiskCleanupPercent {
|
||||
runCleanup(ctx, cfg, &result, fmt.Sprintf("disk_used_%d_percent", usage.UsedPercent))
|
||||
if refreshed, err := diskUsage(cfg.DiskPath); err == nil {
|
||||
result.Disk = &refreshed
|
||||
}
|
||||
}
|
||||
if result.Disk != nil && result.Disk.UsedPercent >= cfg.DiskCriticalPercent {
|
||||
result.Status = "failed"
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("disk %s critical: %d%% used", cfg.DiskPath, result.Disk.UsedPercent))
|
||||
}
|
||||
}
|
||||
for _, name := range uniqueTrimmed(cfg.WatchContainers) {
|
||||
status := inspectMonitorContainer(ctx, cfg, name)
|
||||
if cfg.RestartContainers {
|
||||
remediateMonitorContainer(ctx, cfg, &status, &result)
|
||||
}
|
||||
if !status.Running || status.Health == "unhealthy" || status.Restarting || status.LastActionError != "" {
|
||||
if result.Status == "ok" {
|
||||
result.Status = "warning"
|
||||
}
|
||||
}
|
||||
result.Containers = append(result.Containers, status)
|
||||
}
|
||||
for _, action := range result.Actions {
|
||||
if !action.Success {
|
||||
result.Status = "failed"
|
||||
if action.Error != "" {
|
||||
result.Errors = append(result.Errors, action.Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func normalizeMonitorConfig(cfg MonitorConfig) MonitorConfig {
|
||||
cfg.BackendURL = strings.TrimRight(strings.TrimSpace(cfg.BackendURL), "/")
|
||||
cfg.ClusterID = strings.TrimSpace(cfg.ClusterID)
|
||||
cfg.NodeID = strings.TrimSpace(cfg.NodeID)
|
||||
cfg.StateDir = strings.TrimSpace(cfg.StateDir)
|
||||
cfg.Product = firstNonEmpty(cfg.Product, DefaultMonitorProduct)
|
||||
if cfg.Interval <= 0 {
|
||||
cfg.Interval = time.Duration(DefaultMonitorIntervalSeconds) * time.Second
|
||||
}
|
||||
if cfg.DockerBinary == "" {
|
||||
cfg.DockerBinary = DefaultMonitorDockerBinary
|
||||
}
|
||||
if cfg.DiskPath == "" {
|
||||
cfg.DiskPath = DefaultMonitorDiskPath
|
||||
}
|
||||
if cfg.TmpDir == "" {
|
||||
cfg.TmpDir = DefaultMonitorTmpDir
|
||||
}
|
||||
if cfg.DiskWarnPercent == 0 {
|
||||
cfg.DiskWarnPercent = DefaultMonitorDiskWarnPercent
|
||||
}
|
||||
if cfg.DiskCleanupPercent == 0 {
|
||||
cfg.DiskCleanupPercent = DefaultMonitorDiskCleanupPercent
|
||||
}
|
||||
if cfg.DiskCriticalPercent == 0 {
|
||||
cfg.DiskCriticalPercent = DefaultMonitorDiskCriticalPercent
|
||||
}
|
||||
if cfg.RestartCooldown == 0 {
|
||||
cfg.RestartCooldown = time.Duration(DefaultMonitorRestartCooldownSec) * time.Second
|
||||
}
|
||||
if cfg.StaleRestartingAfter == 0 {
|
||||
cfg.StaleRestartingAfter = time.Duration(DefaultMonitorStaleRestartingSec) * time.Second
|
||||
}
|
||||
if cfg.TmpMinAge == 0 {
|
||||
cfg.TmpMinAge = time.Duration(DefaultMonitorTmpMinAgeMinutes) * time.Minute
|
||||
}
|
||||
if cfg.Runner == nil {
|
||||
cfg.Runner = ExecRunner{}
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func inspectMonitorContainer(ctx context.Context, cfg MonitorConfig, name string) MonitorContainerStatus {
|
||||
out := MonitorContainerStatus{Name: name}
|
||||
raw, err := cfg.Runner.Run(ctx, cfg.DockerBinary, "inspect", name)
|
||||
if err != nil {
|
||||
out.LastActionError = strings.TrimSpace(err.Error())
|
||||
return out
|
||||
}
|
||||
var inspected []monitorDockerInspect
|
||||
if err := json.Unmarshal([]byte(raw), &inspected); err != nil {
|
||||
out.LastActionError = fmt.Sprintf("parse docker inspect: %v", err)
|
||||
return out
|
||||
}
|
||||
if len(inspected) == 0 {
|
||||
out.LastActionError = "docker inspect returned no containers"
|
||||
return out
|
||||
}
|
||||
item := inspected[0]
|
||||
out.Name = strings.TrimPrefix(firstNonEmpty(item.Name, name), "/")
|
||||
out.Status = item.State.Status
|
||||
out.Running = item.State.Running
|
||||
out.Restarting = item.State.Restarting
|
||||
out.ExitCode = item.State.ExitCode
|
||||
out.RestartCount = item.RestartCount
|
||||
out.StartedAt = item.State.StartedAt
|
||||
out.FinishedAt = item.State.FinishedAt
|
||||
if item.State.Health != nil {
|
||||
out.Health = strings.TrimSpace(item.State.Health.Status)
|
||||
}
|
||||
if item.State.Error != "" {
|
||||
out.LastActionError = item.State.Error
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func remediateMonitorContainer(ctx context.Context, cfg MonitorConfig, status *MonitorContainerStatus, result *MonitorResult) {
|
||||
if status.Name == "" {
|
||||
return
|
||||
}
|
||||
action := ""
|
||||
reason := ""
|
||||
switch {
|
||||
case status.LastActionError != "" && status.Status == "":
|
||||
action = "start"
|
||||
reason = "inspect_failed_or_missing"
|
||||
case status.Health == "unhealthy":
|
||||
action = "restart"
|
||||
reason = "health_unhealthy"
|
||||
case status.Restarting && restartingIsStale(status.StartedAt, status.FinishedAt, cfg.StaleRestartingAfter):
|
||||
action = "restart"
|
||||
reason = "restarting_stale"
|
||||
case !status.Running && status.Status != "":
|
||||
action = "start"
|
||||
reason = "not_running"
|
||||
default:
|
||||
return
|
||||
}
|
||||
if cfg.restartHistory != nil {
|
||||
if last, ok := cfg.restartHistory[status.Name]; ok && time.Since(last) < cfg.RestartCooldown {
|
||||
result.Actions = append(result.Actions, MonitorAction{
|
||||
Kind: "docker_" + action + "_skipped",
|
||||
Target: status.Name,
|
||||
Reason: "restart_cooldown",
|
||||
Success: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
args := []string{action, status.Name}
|
||||
_, err := cfg.Runner.Run(ctx, cfg.DockerBinary, args...)
|
||||
monitorAction := MonitorAction{Kind: "docker_" + action, Target: status.Name, Reason: reason, Success: err == nil}
|
||||
status.LastAction = action
|
||||
status.LastActionOK = err == nil
|
||||
if err != nil {
|
||||
monitorAction.Error = strings.TrimSpace(err.Error())
|
||||
status.LastActionError = monitorAction.Error
|
||||
} else {
|
||||
if cfg.restartHistory != nil {
|
||||
cfg.restartHistory[status.Name] = time.Now()
|
||||
}
|
||||
status.LastActionError = ""
|
||||
status.Running = true
|
||||
status.Restarting = false
|
||||
status.Status = DefaultMonitorRemediationSucceeded
|
||||
}
|
||||
result.Actions = append(result.Actions, monitorAction)
|
||||
}
|
||||
|
||||
func restartingIsStale(startedAt, finishedAt string, threshold time.Duration) bool {
|
||||
for _, value := range []string{finishedAt, startedAt} {
|
||||
parsed, err := time.Parse(time.RFC3339Nano, strings.TrimSpace(value))
|
||||
if err == nil && !parsed.IsZero() {
|
||||
return time.Since(parsed) >= threshold
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func runCleanup(ctx context.Context, cfg MonitorConfig, result *MonitorResult, reason string) {
|
||||
if cfg.CleanupDocker {
|
||||
for _, args := range [][]string{
|
||||
{"builder", "prune", "-af"},
|
||||
{"image", "prune", "-f"},
|
||||
{"container", "prune", "-f"},
|
||||
} {
|
||||
_, err := cfg.Runner.Run(ctx, cfg.DockerBinary, args...)
|
||||
action := MonitorAction{Kind: "docker_" + strings.Join(args[:len(args)-1], "_"), Reason: reason, Success: err == nil}
|
||||
if err != nil {
|
||||
action.Error = strings.TrimSpace(err.Error())
|
||||
}
|
||||
result.Actions = append(result.Actions, action)
|
||||
}
|
||||
}
|
||||
removed, err := cleanupTmpBuildDirs(cfg.TmpDir, cfg.TmpMinAge)
|
||||
action := MonitorAction{Kind: "tmp_cleanup", Target: cfg.TmpDir, Reason: reason, Success: err == nil}
|
||||
if err != nil {
|
||||
action.Error = err.Error()
|
||||
} else {
|
||||
action.Target = fmt.Sprintf("%s removed=%d", cfg.TmpDir, removed)
|
||||
}
|
||||
result.Actions = append(result.Actions, action)
|
||||
}
|
||||
|
||||
func cleanupTmpBuildDirs(tmpDir string, minAge time.Duration) (int, error) {
|
||||
tmpDir = filepath.Clean(strings.TrimSpace(tmpDir))
|
||||
if tmpDir == "" || tmpDir == "." || tmpDir == string(filepath.Separator) {
|
||||
return 0, fmt.Errorf("unsafe tmp dir: %q", tmpDir)
|
||||
}
|
||||
entries, err := os.ReadDir(tmpDir)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
now := time.Now()
|
||||
removed := 0
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, "rap-") && !strings.HasPrefix(name, "go-build") {
|
||||
continue
|
||||
}
|
||||
info, err := entry.Info()
|
||||
if err != nil || now.Sub(info.ModTime()) < minAge {
|
||||
continue
|
||||
}
|
||||
if err := os.RemoveAll(filepath.Join(tmpDir, name)); err != nil {
|
||||
return removed, err
|
||||
}
|
||||
removed++
|
||||
}
|
||||
return removed, nil
|
||||
}
|
||||
|
||||
func reportMonitorStatus(ctx context.Context, cfg MonitorConfig, result MonitorResult) error {
|
||||
cfg = normalizeMonitorConfig(cfg)
|
||||
nodeID, clusterID, err := resolveMonitorIdentity(cfg)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNodeIdentityNotReady) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if cfg.BackendURL == "" || clusterID == "" || nodeID == "" {
|
||||
return nil
|
||||
}
|
||||
payload := map[string]any{
|
||||
"schema_version": result.SchemaVersion,
|
||||
"monitor_status": result.Status,
|
||||
"disk": result.Disk,
|
||||
"containers": result.Containers,
|
||||
"actions": result.Actions,
|
||||
"errors": result.Errors,
|
||||
}
|
||||
errText := ""
|
||||
if len(result.Errors) > 0 {
|
||||
errText = strings.Join(result.Errors, "; ")
|
||||
}
|
||||
req := NodeUpdateStatusRequest{
|
||||
Product: cfg.Product,
|
||||
CurrentVersion: cfg.CurrentVersion,
|
||||
Phase: DefaultMonitorPhase,
|
||||
Status: result.Status,
|
||||
Payload: payload,
|
||||
ObservedAt: result.ObservedAt,
|
||||
}
|
||||
if errText != "" {
|
||||
req.ErrorMessage = &errText
|
||||
}
|
||||
return ReportNodeUpdateStatus(ctx, cfg.BackendURL, clusterID, nodeID, req)
|
||||
}
|
||||
|
||||
func resolveMonitorIdentity(cfg MonitorConfig) (string, string, error) {
|
||||
nodeID := strings.TrimSpace(cfg.NodeID)
|
||||
clusterID := strings.TrimSpace(cfg.ClusterID)
|
||||
if nodeID != "" {
|
||||
return nodeID, clusterID, nil
|
||||
}
|
||||
if strings.TrimSpace(cfg.StateDir) == "" {
|
||||
return "", clusterID, ErrNodeIdentityNotReady
|
||||
}
|
||||
identity, err := state.Load(filepath.Join(cfg.StateDir, state.FileName))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "", clusterID, ErrNodeIdentityNotReady
|
||||
}
|
||||
return "", clusterID, err
|
||||
}
|
||||
nodeID = strings.TrimSpace(identity.NodeID)
|
||||
if nodeID == "" {
|
||||
return "", clusterID, ErrNodeIdentityNotReady
|
||||
}
|
||||
if clusterID == "" {
|
||||
clusterID = strings.TrimSpace(identity.ClusterID)
|
||||
}
|
||||
return nodeID, clusterID, nil
|
||||
}
|
||||
|
||||
func writeMonitorStatusFile(path string, result MonitorResult) error {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmp := path + ".tmp"
|
||||
if err := os.WriteFile(tmp, payload, 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmp, path)
|
||||
}
|
||||
|
||||
func logMonitorResult(cfg MonitorConfig, result MonitorResult) {
|
||||
if cfg.Logf == nil {
|
||||
return
|
||||
}
|
||||
cfg.Logf("monitor status=%s containers=%d actions=%d errors=%d", result.Status, len(result.Containers), len(result.Actions), len(result.Errors))
|
||||
}
|
||||
|
||||
func uniqueTrimmed(values []string) []string {
|
||||
seen := map[string]struct{}{}
|
||||
out := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
out = append(out, value)
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package hostagent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type monitorRunner struct {
|
||||
inspect map[string]string
|
||||
calls []string
|
||||
}
|
||||
|
||||
func (r *monitorRunner) Run(_ context.Context, name string, args ...string) (string, error) {
|
||||
call := strings.TrimSpace(name + " " + strings.Join(args, " "))
|
||||
r.calls = append(r.calls, call)
|
||||
if len(args) >= 2 && args[0] == "inspect" {
|
||||
out, ok := r.inspect[args[1]]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("not found")
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func TestRunMonitorOnceStartsExitedContainer(t *testing.T) {
|
||||
runner := &monitorRunner{inspect: map[string]string{
|
||||
"rap-node-agent": `[{"Name":"/rap-node-agent","State":{"Status":"exited","Running":false,"ExitCode":137,"StartedAt":"2026-05-13T00:00:00Z","FinishedAt":"2026-05-13T00:01:00Z"}}]`,
|
||||
}}
|
||||
result := RunMonitorOnce(context.Background(), MonitorConfig{
|
||||
WatchContainers: []string{"rap-node-agent"},
|
||||
RestartContainers: true,
|
||||
Runner: runner,
|
||||
DiskPath: t.TempDir(),
|
||||
DiskCleanupPercent: 101,
|
||||
DiskWarnPercent: 101,
|
||||
DiskCriticalPercent: 101,
|
||||
})
|
||||
if len(result.Actions) != 1 || result.Actions[0].Kind != "docker_start" || !result.Actions[0].Success {
|
||||
t.Fatalf("unexpected actions: %+v", result.Actions)
|
||||
}
|
||||
if !containsCall(runner.calls, "docker start rap-node-agent") {
|
||||
t.Fatalf("start call missing: %+v", runner.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunMonitorOnceRestartsUnhealthyContainer(t *testing.T) {
|
||||
runner := &monitorRunner{inspect: map[string]string{
|
||||
"rap-backend": `[{"Name":"/rap-backend","State":{"Status":"running","Running":true,"StartedAt":"2026-05-13T00:00:00Z","Health":{"Status":"unhealthy"}}}]`,
|
||||
}}
|
||||
result := RunMonitorOnce(context.Background(), MonitorConfig{
|
||||
WatchContainers: []string{"rap-backend"},
|
||||
RestartContainers: true,
|
||||
Runner: runner,
|
||||
DiskPath: t.TempDir(),
|
||||
DiskCleanupPercent: 101,
|
||||
DiskWarnPercent: 101,
|
||||
DiskCriticalPercent: 101,
|
||||
})
|
||||
if len(result.Actions) != 1 || result.Actions[0].Kind != "docker_restart" || !result.Actions[0].Success {
|
||||
t.Fatalf("unexpected actions: %+v", result.Actions)
|
||||
}
|
||||
if !containsCall(runner.calls, "docker restart rap-backend") {
|
||||
t.Fatalf("restart call missing: %+v", runner.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestartingIsStale(t *testing.T) {
|
||||
if !restartingIsStale(time.Now().Add(-10*time.Minute).UTC().Format(time.RFC3339Nano), "", time.Minute) {
|
||||
t.Fatalf("old restarting container should be stale")
|
||||
}
|
||||
if restartingIsStale(time.Now().UTC().Format(time.RFC3339Nano), "", time.Hour) {
|
||||
t.Fatalf("fresh restarting container should not be stale")
|
||||
}
|
||||
}
|
||||
|
||||
func containsCall(calls []string, want string) bool {
|
||||
for _, call := range calls {
|
||||
if call == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -31,6 +31,14 @@ type UpdateServiceConfig struct {
|
||||
DryRun bool
|
||||
InstallSelfUpdater bool
|
||||
SelfUpdateVersion string
|
||||
InstallMonitor bool
|
||||
MonitorIntervalSec int
|
||||
MonitorContainers []string
|
||||
MonitorStatusFile string
|
||||
MonitorDiskWarn int
|
||||
MonitorDiskCleanup int
|
||||
MonitorDiskCritical int
|
||||
MonitorCleanupDocker bool
|
||||
}
|
||||
|
||||
type UpdateServiceResult struct {
|
||||
@@ -43,6 +51,9 @@ type UpdateServiceResult struct {
|
||||
SelfUnitName string
|
||||
SelfUnitPath string
|
||||
SelfUnit string
|
||||
MonitorUnitName string
|
||||
MonitorUnitPath string
|
||||
MonitorUnit string
|
||||
}
|
||||
|
||||
func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServiceConfig) (UpdateServiceResult, error) {
|
||||
@@ -59,6 +70,9 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi
|
||||
if cfg.HealthTimeoutSec == 0 {
|
||||
cfg.HealthTimeoutSec = 30
|
||||
}
|
||||
if cfg.MonitorIntervalSec == 0 {
|
||||
cfg.MonitorIntervalSec = DefaultMonitorIntervalSeconds
|
||||
}
|
||||
cfg.BinaryInstallPath = firstNonEmpty(cfg.BinaryInstallPath, DefaultHostAgentInstallPath)
|
||||
cfg.UnitDir = firstNonEmpty(cfg.UnitDir, DefaultSystemdUnitDir)
|
||||
unitName := "rap-host-agent-updater-" + safeUnitSlug(cfg.RuntimeConfig.ContainerName) + ".service"
|
||||
@@ -82,6 +96,15 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi
|
||||
result.SelfUnitName = selfUnitName
|
||||
result.SelfUnitPath = selfUnitPath
|
||||
}
|
||||
if cfg.InstallMonitor {
|
||||
monitorUnit, monitorUnitName, monitorUnitPath, err := buildHostAgentMonitorUnit(cfg)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.MonitorUnit = monitorUnit
|
||||
result.MonitorUnitName = monitorUnitName
|
||||
result.MonitorUnitPath = monitorUnitPath
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
if runtime.GOOS != "linux" && cfg.UnitDir == DefaultSystemdUnitDir {
|
||||
@@ -108,6 +131,18 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi
|
||||
result.SelfUnitName = selfUnitName
|
||||
result.SelfUnitPath = selfUnitPath
|
||||
}
|
||||
if cfg.InstallMonitor {
|
||||
monitorUnit, monitorUnitName, monitorUnitPath, err := buildHostAgentMonitorUnit(cfg)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if err := os.WriteFile(monitorUnitPath, []byte(monitorUnit), 0o644); err != nil {
|
||||
return result, err
|
||||
}
|
||||
result.MonitorUnit = monitorUnit
|
||||
result.MonitorUnitName = monitorUnitName
|
||||
result.MonitorUnitPath = monitorUnitPath
|
||||
}
|
||||
result.Installed = true
|
||||
if cfg.ManageSystemd {
|
||||
runner := m.Runner
|
||||
@@ -125,6 +160,11 @@ func (m DockerManager) InstallUpdateService(ctx context.Context, cfg UpdateServi
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
if cfg.InstallMonitor && result.MonitorUnitName != "" {
|
||||
if _, err := runner.Run(ctx, "systemctl", "enable", "--now", result.MonitorUnitName); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
result.Started = true
|
||||
}
|
||||
return result, nil
|
||||
@@ -223,6 +263,64 @@ WantedBy=multi-user.target
|
||||
`, systemdJoin(args)), unitName, unitPath, nil
|
||||
}
|
||||
|
||||
func buildHostAgentMonitorUnit(cfg UpdateServiceConfig) (string, string, string, error) {
|
||||
runtimeCfg := cfg.RuntimeConfig.Normalize()
|
||||
if runtimeCfg.BackendURL == "" || runtimeCfg.ClusterID == "" || runtimeCfg.StateDir == "" {
|
||||
return "", "", "", fmt.Errorf("backend-url, cluster-id, and state-dir are required for host monitor")
|
||||
}
|
||||
containers := uniqueTrimmed(append([]string{runtimeCfg.ContainerName}, cfg.MonitorContainers...))
|
||||
if len(containers) == 0 {
|
||||
return "", "", "", fmt.Errorf("at least one monitor container is required")
|
||||
}
|
||||
unitName := "rap-host-agent-monitor-" + safeUnitSlug(runtimeCfg.ContainerName) + ".service"
|
||||
unitPath := filepath.Join(firstNonEmpty(cfg.UnitDir, DefaultSystemdUnitDir), unitName)
|
||||
args := []string{
|
||||
cfg.BinaryInstallPath,
|
||||
"monitor-loop",
|
||||
"--backend-url", runtimeCfg.BackendURL,
|
||||
"--cluster-id", runtimeCfg.ClusterID,
|
||||
"--state-dir", runtimeCfg.StateDir,
|
||||
"--current-version", firstNonEmpty(cfg.SelfUpdateVersion, cfg.CurrentVersion),
|
||||
"--interval-seconds", fmt.Sprintf("%d", firstNonZero(cfg.MonitorIntervalSec, DefaultMonitorIntervalSeconds)),
|
||||
"--disk-warn-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskWarn, DefaultMonitorDiskWarnPercent)),
|
||||
"--disk-cleanup-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskCleanup, DefaultMonitorDiskCleanupPercent)),
|
||||
"--disk-critical-percent", fmt.Sprintf("%d", firstNonZero(cfg.MonitorDiskCritical, DefaultMonitorDiskCriticalPercent)),
|
||||
}
|
||||
if cfg.MonitorCleanupDocker {
|
||||
args = append(args, "--cleanup-docker")
|
||||
}
|
||||
if strings.TrimSpace(cfg.MonitorStatusFile) != "" {
|
||||
args = append(args, "--status-file", strings.TrimSpace(cfg.MonitorStatusFile))
|
||||
}
|
||||
for _, container := range containers {
|
||||
args = append(args, "--watch-container", container)
|
||||
}
|
||||
return fmt.Sprintf(`[Unit]
|
||||
Description=RAP host-agent monitor for %s
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=%s
|
||||
Restart=always
|
||||
RestartSec=30
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`, runtimeCfg.ContainerName, systemdJoin(args)), unitName, unitPath, nil
|
||||
}
|
||||
|
||||
func firstNonZero(values ...int) int {
|
||||
for _, value := range values {
|
||||
if value != 0 {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func installHostAgentBinary(sourcePath, targetPath string) error {
|
||||
sourcePath = strings.TrimSpace(sourcePath)
|
||||
targetPath = strings.TrimSpace(targetPath)
|
||||
|
||||
@@ -33,6 +33,9 @@ func TestInstallUpdateServiceWritesSystemdUnit(t *testing.T) {
|
||||
ManageSystemd: false,
|
||||
InstallSelfUpdater: true,
|
||||
SelfUpdateVersion: "0.1.0-host",
|
||||
InstallMonitor: true,
|
||||
MonitorContainers: []string{"rap-test-backend"},
|
||||
MonitorCleanupDocker: true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("install update service: %v", err)
|
||||
@@ -73,6 +76,25 @@ func TestInstallUpdateServiceWritesSystemdUnit(t *testing.T) {
|
||||
if text := string(selfUnit); !strings.Contains(text, "update-host-agent-loop") || !strings.Contains(text, "--current-version 0.1.0-host") {
|
||||
t.Fatalf("unexpected self unit:\n%s", text)
|
||||
}
|
||||
if result.MonitorUnitName == "" || result.MonitorUnitPath == "" {
|
||||
t.Fatalf("monitor result = %+v", result)
|
||||
}
|
||||
monitorUnit, err := os.ReadFile(result.MonitorUnitPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read monitor unit: %v", err)
|
||||
}
|
||||
monitorText := string(monitorUnit)
|
||||
for _, want := range []string{
|
||||
"monitor-loop",
|
||||
"--watch-container rap-node-agent-node-a",
|
||||
"--watch-container rap-test-backend",
|
||||
"--cleanup-docker",
|
||||
"Restart=always",
|
||||
} {
|
||||
if !strings.Contains(monitorText, want) {
|
||||
t.Fatalf("monitor unit missing %q:\n%s", want, monitorText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowsHostAgentUpdateScriptTargetsWindowsService(t *testing.T) {
|
||||
|
||||
@@ -313,6 +313,9 @@ func (m DockerManager) ApplyUpdate(ctx context.Context, req UpdateRequest) (Upda
|
||||
cfg.ClusterID = firstNonEmpty(cfg.ClusterID, req.ClusterID)
|
||||
cfg.ContainerName = req.ContainerName
|
||||
cfg.Image = artifactImage(*plan.Artifact, cfg.Image)
|
||||
if artifactDockerVPNGatewayEnabled(*plan.Artifact) {
|
||||
cfg.DockerVPNGatewayEnabled = true
|
||||
}
|
||||
cfg.ImageArtifactURLs = artifactURLsForBackend(*plan.Artifact, req.BackendURL)
|
||||
cfg.ImageArtifactSHA256 = plan.Artifact.SHA256
|
||||
cfg.ImageArtifactSizeBytes = plan.Artifact.SizeBytes
|
||||
@@ -681,6 +684,20 @@ func artifactImage(artifact ReleaseArtifact, fallback string) string {
|
||||
return firstNonEmpty(fallback, DefaultImage)
|
||||
}
|
||||
|
||||
func artifactDockerVPNGatewayEnabled(artifact ReleaseArtifact) bool {
|
||||
if len(artifact.Metadata) == 0 {
|
||||
return false
|
||||
}
|
||||
var metadata struct {
|
||||
DockerVPNGatewayEnabled bool `json:"docker_vpn_gateway_enabled"`
|
||||
VPNGatewayEnabled bool `json:"vpn_gateway_enabled"`
|
||||
}
|
||||
if err := json.Unmarshal(artifact.Metadata, &metadata); err != nil {
|
||||
return false
|
||||
}
|
||||
return metadata.DockerVPNGatewayEnabled || metadata.VPNGatewayEnabled
|
||||
}
|
||||
|
||||
func artifactURLs(artifact ReleaseArtifact) []string {
|
||||
out := make([]string, 0, 1+len(artifact.URLs))
|
||||
for _, raw := range append([]string{artifact.URL}, artifact.URLs...) {
|
||||
|
||||
@@ -596,6 +596,18 @@ func TestArtifactImageDerivesDockerTagFromProductAndVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestArtifactDockerVPNGatewayEnabledFromMetadata(t *testing.T) {
|
||||
if !artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"docker_vpn_gateway_enabled":true}`)}) {
|
||||
t.Fatal("expected docker vpn gateway metadata to enable gateway runtime")
|
||||
}
|
||||
if !artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"vpn_gateway_enabled":true}`)}) {
|
||||
t.Fatal("expected legacy vpn gateway metadata to enable gateway runtime")
|
||||
}
|
||||
if artifactDockerVPNGatewayEnabled(ReleaseArtifact{Metadata: json.RawMessage(`{"docker_vpn_gateway_enabled":false}`)}) {
|
||||
t.Fatal("expected disabled metadata to remain disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func serverArtifactURL(r *http.Request) string {
|
||||
scheme := "http"
|
||||
if r.TLS != nil {
|
||||
|
||||
@@ -41,6 +41,9 @@ type RemoteWorkspaceFrameProbeSink struct {
|
||||
mailboxAfterSequenceReadTotal int64
|
||||
mailboxReturnedTotal int64
|
||||
mailboxSkippedTotal int64
|
||||
mailboxPreflightTotal int64
|
||||
mailboxPreflightAckTotal int64
|
||||
mailboxPreflightCheckpointTotal int64
|
||||
mailboxConsumerReadTotal int64
|
||||
mailboxConsumerAckTotal int64
|
||||
mailboxConsumerResetTotal int64
|
||||
@@ -57,6 +60,31 @@ type RemoteWorkspaceFrameProbeSink struct {
|
||||
lastMailboxAfterSequence int64
|
||||
lastMailboxSkippedCount int
|
||||
lastMailboxReturnedCount int
|
||||
lastMailboxPreflightAt string
|
||||
lastMailboxPreflightAdapterSessionID string
|
||||
lastMailboxPreflightConsumerID string
|
||||
lastMailboxPreflightResumeFrom string
|
||||
lastMailboxPreflightResumeSequence int64
|
||||
lastMailboxPreflightAfterSequence int64
|
||||
lastMailboxPreflightAvailableCount int
|
||||
lastMailboxPreflightReturnedCount int
|
||||
lastMailboxPreflightSkippedCount int
|
||||
lastMailboxPreflightFirstSequence int64
|
||||
lastMailboxPreflightLastSequence int64
|
||||
lastMailboxPreflightFirstRetained int64
|
||||
lastMailboxPreflightLastRetained int64
|
||||
lastMailboxPreflightMailboxDropped int64
|
||||
lastMailboxPreflightDiagnosticState string
|
||||
lastMailboxPreflightStaleCursor bool
|
||||
lastMailboxPreflightMissingDropped int
|
||||
lastMailboxPreflightRecommendedAction string
|
||||
lastMailboxPreflightActionHints []string
|
||||
lastMailboxPreflightActionReason string
|
||||
lastMailboxPreflightActionContext map[string]any
|
||||
lastMailboxPreflightOperatorSummary string
|
||||
lastMailboxPreflightOperatorStatus string
|
||||
lastMailboxPreflightOperatorSeverity string
|
||||
lastMailboxPreflightOperatorFields map[string]any
|
||||
lastMailboxConsumerID string
|
||||
lastMailboxConsumerAdapterSessionID string
|
||||
lastMailboxConsumerReadAt string
|
||||
@@ -104,6 +132,11 @@ type remoteWorkspaceAdapterProbeSession struct {
|
||||
MailboxAfterSequenceRead int64
|
||||
MailboxReturnedTotal int64
|
||||
MailboxSkippedTotal int64
|
||||
MailboxPreflightTotal int64
|
||||
MailboxPreflightAckTotal int64
|
||||
MailboxPreflightCheckpointTotal int64
|
||||
MailboxPreflightOperatorStatusCounts map[string]int64
|
||||
MailboxPreflightOperatorSeverityCounts map[string]int64
|
||||
MailboxConsumers map[string]*remoteWorkspaceAdapterMailboxConsumerState
|
||||
MailboxConsumerReadTotal int64
|
||||
MailboxConsumerAckTotal int64
|
||||
@@ -125,6 +158,30 @@ type remoteWorkspaceAdapterProbeSession struct {
|
||||
LastMailboxAfterSequence int64
|
||||
LastMailboxSkippedCount int
|
||||
LastMailboxReturnedCount int
|
||||
LastMailboxPreflightAt time.Time
|
||||
LastMailboxPreflightConsumerID string
|
||||
LastMailboxPreflightResumeFrom string
|
||||
LastMailboxPreflightResumeSequence int64
|
||||
LastMailboxPreflightAfterSequence int64
|
||||
LastMailboxPreflightAvailableCount int
|
||||
LastMailboxPreflightReturnedCount int
|
||||
LastMailboxPreflightSkippedCount int
|
||||
LastMailboxPreflightFirstSequence int64
|
||||
LastMailboxPreflightLastSequence int64
|
||||
LastMailboxPreflightFirstRetained int64
|
||||
LastMailboxPreflightLastRetained int64
|
||||
LastMailboxPreflightMailboxDropped int64
|
||||
LastMailboxPreflightDiagnosticState string
|
||||
LastMailboxPreflightStaleCursor bool
|
||||
LastMailboxPreflightMissingDropped int
|
||||
LastMailboxPreflightRecommendedAction string
|
||||
LastMailboxPreflightActionHints []string
|
||||
LastMailboxPreflightActionReason string
|
||||
LastMailboxPreflightActionContext map[string]any
|
||||
LastMailboxPreflightOperatorSummary string
|
||||
LastMailboxPreflightOperatorStatus string
|
||||
LastMailboxPreflightOperatorSeverity string
|
||||
LastMailboxPreflightOperatorFields map[string]any
|
||||
LastChannelID string
|
||||
LastResourceID string
|
||||
LastRouteID string
|
||||
@@ -263,6 +320,7 @@ type RemoteWorkspaceAdapterMailboxPreflightSnapshot struct {
|
||||
Limit int `json:"limit"`
|
||||
MailboxDepth int `json:"mailbox_depth"`
|
||||
MailboxEnqueued int64 `json:"mailbox_enqueued_total"`
|
||||
MailboxDropped int64 `json:"mailbox_dropped_total"`
|
||||
MailboxReadTotal int64 `json:"mailbox_read_total"`
|
||||
ConsumerReadTotal int64 `json:"consumer_read_total"`
|
||||
ConsumerAckTotal int64 `json:"consumer_ack_total"`
|
||||
@@ -274,6 +332,19 @@ type RemoteWorkspaceAdapterMailboxPreflightSnapshot struct {
|
||||
ExpectedSkippedCount int `json:"expected_skipped_count"`
|
||||
FirstExpectedSequence int64 `json:"first_expected_sequence,omitempty"`
|
||||
LastExpectedSequence int64 `json:"last_expected_sequence,omitempty"`
|
||||
FirstRetainedSequence int64 `json:"first_retained_sequence,omitempty"`
|
||||
LastRetainedSequence int64 `json:"last_retained_sequence,omitempty"`
|
||||
DiagnosticState string `json:"diagnostic_state"`
|
||||
StaleCursor bool `json:"stale_cursor"`
|
||||
MissingDroppedCount int `json:"missing_dropped_count"`
|
||||
RecommendedAction string `json:"recommended_action"`
|
||||
ActionHints []string `json:"action_hints"`
|
||||
ActionReason string `json:"action_reason"`
|
||||
ActionContext map[string]any `json:"action_context"`
|
||||
OperatorSummary string `json:"operator_summary"`
|
||||
OperatorStatus string `json:"operator_status"`
|
||||
OperatorSeverity string `json:"operator_severity"`
|
||||
OperatorSummaryFields map[string]any `json:"operator_summary_fields"`
|
||||
}
|
||||
|
||||
type RemoteWorkspaceAdapterSessionSnapshot struct {
|
||||
@@ -651,6 +722,8 @@ func (s *RemoteWorkspaceFrameProbeSink) ensureSessionLocked(delivery RemoteWorks
|
||||
CreatedAt: now,
|
||||
LastActivityAt: now,
|
||||
MailboxConsumers: map[string]*remoteWorkspaceAdapterMailboxConsumerState{},
|
||||
MailboxPreflightOperatorStatusCounts: map[string]int64{},
|
||||
MailboxPreflightOperatorSeverityCounts: map[string]int64{},
|
||||
}
|
||||
s.sessions[sessionID] = session
|
||||
s.sessionCreatedTotal++
|
||||
@@ -1180,7 +1253,74 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe
|
||||
firstExpected = session.Mailbox[startIndex].Sequence
|
||||
lastExpected = session.Mailbox[startIndex+returned-1].Sequence
|
||||
}
|
||||
return RemoteWorkspaceAdapterMailboxPreflightSnapshot{
|
||||
var firstRetained int64
|
||||
var lastRetained int64
|
||||
if len(session.Mailbox) > 0 {
|
||||
firstRetained = session.Mailbox[0].Sequence
|
||||
lastRetained = session.Mailbox[len(session.Mailbox)-1].Sequence
|
||||
}
|
||||
diagnosticState := "ready"
|
||||
staleCursor := false
|
||||
missingDropped := 0
|
||||
recommendedAction := "resume_from_cursor"
|
||||
actionHints := []string{"resume_from_requested_cursor"}
|
||||
actionReason := "cursor_window_available"
|
||||
if firstRetained > 0 && resumeSequence < firstRetained-1 {
|
||||
diagnosticState = "stale_cursor_gap"
|
||||
staleCursor = true
|
||||
missingDropped = int(firstRetained - resumeSequence - 1)
|
||||
recommendedAction = "reset_consumer_and_resync"
|
||||
actionHints = []string{"reset_consumer_cursor", "request_full_adapter_resync", "resume_from_checkpoint_after_resync"}
|
||||
actionReason = "consumer_cursor_before_first_retained_sequence"
|
||||
} else if returned == 0 {
|
||||
diagnosticState = "caught_up"
|
||||
recommendedAction = "wait_for_new_mailbox_events"
|
||||
actionHints = []string{"keep_consumer_cursor", "long_poll_after_sequence"}
|
||||
actionReason = "cursor_caught_up_to_retained_mailbox"
|
||||
}
|
||||
actionContext := map[string]any{
|
||||
"consumer_id": consumerID,
|
||||
"resume_from": resumeFrom,
|
||||
"resume_sequence": resumeSequence,
|
||||
"first_retained_sequence": firstRetained,
|
||||
"last_retained_sequence": lastRetained,
|
||||
"mailbox_depth": len(session.Mailbox),
|
||||
"mailbox_dropped_total": session.MailboxDropped,
|
||||
"missing_dropped_count": missingDropped,
|
||||
"expected_available_count": available,
|
||||
"expected_returned_count": returned,
|
||||
"expected_skipped_count": startIndex,
|
||||
"consumer_checkpoint_sequence": consumer.CheckpointSequence,
|
||||
"consumer_ack_sequence": consumer.AckSequence,
|
||||
}
|
||||
operatorSummary := "consumer cursor can resume from requested window"
|
||||
operatorStatus := "ready_to_resume"
|
||||
operatorSeverity := "ok"
|
||||
if diagnosticState == "stale_cursor_gap" {
|
||||
operatorSummary = "stale cursor gap: reset consumer and resync before resume"
|
||||
operatorStatus = "resync_required"
|
||||
operatorSeverity = "warn"
|
||||
} else if diagnosticState == "caught_up" {
|
||||
operatorSummary = "consumer cursor is caught up; wait for new mailbox events"
|
||||
operatorStatus = "caught_up"
|
||||
operatorSeverity = "info"
|
||||
}
|
||||
operatorSummaryFields := map[string]any{
|
||||
"diagnostic_state": diagnosticState,
|
||||
"recommended_action": recommendedAction,
|
||||
"action_reason": actionReason,
|
||||
"operator_status": operatorStatus,
|
||||
"operator_severity": operatorSeverity,
|
||||
"resume_from": resumeFrom,
|
||||
"resume_sequence": resumeSequence,
|
||||
"first_retained_sequence": firstRetained,
|
||||
"last_retained_sequence": lastRetained,
|
||||
"missing_dropped_count": missingDropped,
|
||||
"expected_available_count": available,
|
||||
"expected_returned_count": returned,
|
||||
"expected_skipped_count": startIndex,
|
||||
}
|
||||
snapshot := RemoteWorkspaceAdapterMailboxPreflightSnapshot{
|
||||
SchemaVersion: "rap.remote_workspace_adapter_mailbox_preflight.v1",
|
||||
AdapterRuntimeID: RemoteWorkspaceFrameProbeSinkRuntimeID,
|
||||
AdapterSessionID: adapterSessionID,
|
||||
@@ -1193,6 +1333,7 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe
|
||||
Limit: limit,
|
||||
MailboxDepth: len(session.Mailbox),
|
||||
MailboxEnqueued: session.MailboxEnqueued,
|
||||
MailboxDropped: session.MailboxDropped,
|
||||
MailboxReadTotal: session.MailboxRead,
|
||||
ConsumerReadTotal: session.MailboxConsumerReadTotal,
|
||||
ConsumerAckTotal: session.MailboxConsumerAckTotal,
|
||||
@@ -1204,7 +1345,236 @@ func (s *RemoteWorkspaceFrameProbeSink) PreflightAdapterSessionMailboxConsumerRe
|
||||
ExpectedSkippedCount: startIndex,
|
||||
FirstExpectedSequence: firstExpected,
|
||||
LastExpectedSequence: lastExpected,
|
||||
}, nil
|
||||
FirstRetainedSequence: firstRetained,
|
||||
LastRetainedSequence: lastRetained,
|
||||
DiagnosticState: diagnosticState,
|
||||
StaleCursor: staleCursor,
|
||||
MissingDroppedCount: missingDropped,
|
||||
RecommendedAction: recommendedAction,
|
||||
ActionHints: actionHints,
|
||||
ActionReason: actionReason,
|
||||
ActionContext: actionContext,
|
||||
OperatorSummary: operatorSummary,
|
||||
OperatorStatus: operatorStatus,
|
||||
OperatorSeverity: operatorSeverity,
|
||||
OperatorSummaryFields: operatorSummaryFields,
|
||||
}
|
||||
s.recordAdapterSessionMailboxPreflightLocked(session, snapshot, now)
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
func (s *RemoteWorkspaceFrameProbeSink) recordAdapterSessionMailboxPreflightLocked(session *remoteWorkspaceAdapterProbeSession, snapshot RemoteWorkspaceAdapterMailboxPreflightSnapshot, now time.Time) {
|
||||
s.mailboxPreflightTotal++
|
||||
if snapshot.ResumeFrom == "ack" {
|
||||
s.mailboxPreflightAckTotal++
|
||||
}
|
||||
if snapshot.ResumeFrom == "checkpoint" {
|
||||
s.mailboxPreflightCheckpointTotal++
|
||||
}
|
||||
s.lastMailboxPreflightAt = now.Format(time.RFC3339Nano)
|
||||
s.lastMailboxPreflightAdapterSessionID = snapshot.AdapterSessionID
|
||||
s.lastMailboxPreflightConsumerID = snapshot.ConsumerID
|
||||
s.lastMailboxPreflightResumeFrom = snapshot.ResumeFrom
|
||||
s.lastMailboxPreflightResumeSequence = snapshot.ResumeSequence
|
||||
s.lastMailboxPreflightAfterSequence = snapshot.AfterSequence
|
||||
s.lastMailboxPreflightAvailableCount = snapshot.ExpectedAvailableCount
|
||||
s.lastMailboxPreflightReturnedCount = snapshot.ExpectedReturnedCount
|
||||
s.lastMailboxPreflightSkippedCount = snapshot.ExpectedSkippedCount
|
||||
s.lastMailboxPreflightFirstSequence = snapshot.FirstExpectedSequence
|
||||
s.lastMailboxPreflightLastSequence = snapshot.LastExpectedSequence
|
||||
s.lastMailboxPreflightFirstRetained = snapshot.FirstRetainedSequence
|
||||
s.lastMailboxPreflightLastRetained = snapshot.LastRetainedSequence
|
||||
s.lastMailboxPreflightMailboxDropped = snapshot.MailboxDropped
|
||||
s.lastMailboxPreflightDiagnosticState = snapshot.DiagnosticState
|
||||
s.lastMailboxPreflightStaleCursor = snapshot.StaleCursor
|
||||
s.lastMailboxPreflightMissingDropped = snapshot.MissingDroppedCount
|
||||
s.lastMailboxPreflightRecommendedAction = snapshot.RecommendedAction
|
||||
s.lastMailboxPreflightActionHints = append([]string(nil), snapshot.ActionHints...)
|
||||
s.lastMailboxPreflightActionReason = snapshot.ActionReason
|
||||
s.lastMailboxPreflightActionContext = cloneStringAnyMap(snapshot.ActionContext)
|
||||
s.lastMailboxPreflightOperatorSummary = snapshot.OperatorSummary
|
||||
s.lastMailboxPreflightOperatorStatus = snapshot.OperatorStatus
|
||||
s.lastMailboxPreflightOperatorSeverity = snapshot.OperatorSeverity
|
||||
s.lastMailboxPreflightOperatorFields = cloneStringAnyMap(snapshot.OperatorSummaryFields)
|
||||
if session == nil {
|
||||
return
|
||||
}
|
||||
session.MailboxPreflightTotal++
|
||||
if snapshot.ResumeFrom == "ack" {
|
||||
session.MailboxPreflightAckTotal++
|
||||
}
|
||||
if snapshot.ResumeFrom == "checkpoint" {
|
||||
session.MailboxPreflightCheckpointTotal++
|
||||
}
|
||||
incrementStringInt64Map(&session.MailboxPreflightOperatorStatusCounts, snapshot.OperatorStatus)
|
||||
incrementStringInt64Map(&session.MailboxPreflightOperatorSeverityCounts, snapshot.OperatorSeverity)
|
||||
session.LastMailboxPreflightAt = now
|
||||
session.LastMailboxPreflightConsumerID = snapshot.ConsumerID
|
||||
session.LastMailboxPreflightResumeFrom = snapshot.ResumeFrom
|
||||
session.LastMailboxPreflightResumeSequence = snapshot.ResumeSequence
|
||||
session.LastMailboxPreflightAfterSequence = snapshot.AfterSequence
|
||||
session.LastMailboxPreflightAvailableCount = snapshot.ExpectedAvailableCount
|
||||
session.LastMailboxPreflightReturnedCount = snapshot.ExpectedReturnedCount
|
||||
session.LastMailboxPreflightSkippedCount = snapshot.ExpectedSkippedCount
|
||||
session.LastMailboxPreflightFirstSequence = snapshot.FirstExpectedSequence
|
||||
session.LastMailboxPreflightLastSequence = snapshot.LastExpectedSequence
|
||||
session.LastMailboxPreflightFirstRetained = snapshot.FirstRetainedSequence
|
||||
session.LastMailboxPreflightLastRetained = snapshot.LastRetainedSequence
|
||||
session.LastMailboxPreflightMailboxDropped = snapshot.MailboxDropped
|
||||
session.LastMailboxPreflightDiagnosticState = snapshot.DiagnosticState
|
||||
session.LastMailboxPreflightStaleCursor = snapshot.StaleCursor
|
||||
session.LastMailboxPreflightMissingDropped = snapshot.MissingDroppedCount
|
||||
session.LastMailboxPreflightRecommendedAction = snapshot.RecommendedAction
|
||||
session.LastMailboxPreflightActionHints = append([]string(nil), snapshot.ActionHints...)
|
||||
session.LastMailboxPreflightActionReason = snapshot.ActionReason
|
||||
session.LastMailboxPreflightActionContext = cloneStringAnyMap(snapshot.ActionContext)
|
||||
session.LastMailboxPreflightOperatorSummary = snapshot.OperatorSummary
|
||||
session.LastMailboxPreflightOperatorStatus = snapshot.OperatorStatus
|
||||
session.LastMailboxPreflightOperatorSeverity = snapshot.OperatorSeverity
|
||||
session.LastMailboxPreflightOperatorFields = cloneStringAnyMap(snapshot.OperatorSummaryFields)
|
||||
}
|
||||
|
||||
func cloneStringAnyMap(source map[string]any) map[string]any {
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
clone := make(map[string]any, len(source))
|
||||
for key, value := range source {
|
||||
clone[key] = value
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneStringInt64Map(source map[string]int64) map[string]int64 {
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
clone := make(map[string]int64, len(source))
|
||||
for key, value := range source {
|
||||
clone[key] = value
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
||||
func incrementStringInt64Map(target *map[string]int64, key string) {
|
||||
key = strings.TrimSpace(key)
|
||||
if key == "" || target == nil {
|
||||
return
|
||||
}
|
||||
if *target == nil {
|
||||
*target = map[string]int64{}
|
||||
}
|
||||
(*target)[key]++
|
||||
}
|
||||
|
||||
func remoteWorkspacePreflightAttentionStatus(statusCounts map[string]int64, severityCounts map[string]int64) string {
|
||||
resyncCount := statusCounts["resync_required"]
|
||||
warnCount := severityCounts["warn"]
|
||||
if resyncCount > 1 || warnCount > 1 {
|
||||
return "repeated_resync_required"
|
||||
}
|
||||
if resyncCount > 0 || warnCount > 0 {
|
||||
return "needs_attention"
|
||||
}
|
||||
if statusCounts["ready_to_resume"] > 0 || statusCounts["caught_up"] > 0 || severityCounts["ok"] > 0 || severityCounts["info"] > 0 {
|
||||
return "clean"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
func remoteWorkspacePreflightAttentionReason(status string, statusCounts map[string]int64, severityCounts map[string]int64) string {
|
||||
switch status {
|
||||
case "repeated_resync_required":
|
||||
return "resync_required_preflight_repeated"
|
||||
case "needs_attention":
|
||||
if statusCounts["resync_required"] > 0 {
|
||||
return "resync_required_preflight_observed"
|
||||
}
|
||||
if severityCounts["warn"] > 0 {
|
||||
return "warn_preflight_observed"
|
||||
}
|
||||
return "attention_preflight_observed"
|
||||
case "clean":
|
||||
return "no_resync_required_preflight_observed"
|
||||
default:
|
||||
return "no_preflight_observed"
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspacePreflightRemediationChecklist(operatorStatus string, actionHints []string) []map[string]any {
|
||||
hints := map[string]bool{}
|
||||
for _, hint := range actionHints {
|
||||
hints[hint] = true
|
||||
}
|
||||
if operatorStatus == "resync_required" {
|
||||
return []map[string]any{
|
||||
{
|
||||
"step": "reset_consumer_cursor",
|
||||
"required": true,
|
||||
"satisfied": false,
|
||||
"source_hint": hints["reset_consumer_cursor"],
|
||||
},
|
||||
{
|
||||
"step": "request_full_adapter_resync",
|
||||
"required": true,
|
||||
"satisfied": false,
|
||||
"source_hint": hints["request_full_adapter_resync"],
|
||||
},
|
||||
{
|
||||
"step": "resume_from_checkpoint_after_resync",
|
||||
"required": true,
|
||||
"satisfied": false,
|
||||
"source_hint": hints["resume_from_checkpoint_after_resync"],
|
||||
},
|
||||
}
|
||||
}
|
||||
if operatorStatus == "ready_to_resume" {
|
||||
return []map[string]any{{
|
||||
"step": "resume_from_requested_cursor",
|
||||
"required": true,
|
||||
"satisfied": true,
|
||||
"source_hint": hints["resume_from_requested_cursor"],
|
||||
}}
|
||||
}
|
||||
return []map[string]any{{
|
||||
"step": "wait_for_new_mailbox_events",
|
||||
"required": true,
|
||||
"satisfied": false,
|
||||
"source_hint": hints["long_poll_after_sequence"] || hints["keep_consumer_cursor"],
|
||||
}}
|
||||
}
|
||||
|
||||
func remoteWorkspacePreflightRemediationChecklistSummary(checklist []map[string]any) map[string]any {
|
||||
total := len(checklist)
|
||||
required := 0
|
||||
satisfied := 0
|
||||
for _, item := range checklist {
|
||||
itemRequired, _ := item["required"].(bool)
|
||||
itemSatisfied, _ := item["satisfied"].(bool)
|
||||
if itemRequired {
|
||||
required++
|
||||
if itemSatisfied {
|
||||
satisfied++
|
||||
}
|
||||
}
|
||||
}
|
||||
pending := required - satisfied
|
||||
if pending < 0 {
|
||||
pending = 0
|
||||
}
|
||||
status := "not_required"
|
||||
if required > 0 && pending == 0 {
|
||||
status = "ready"
|
||||
} else if pending > 0 {
|
||||
status = "action_required"
|
||||
}
|
||||
return map[string]any{
|
||||
"status": status,
|
||||
"total_count": total,
|
||||
"required_count": required,
|
||||
"satisfied_count": satisfied,
|
||||
"pending_count": pending,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RemoteWorkspaceFrameProbeSink) evictOldestMailboxConsumerLocked(session *remoteWorkspaceAdapterProbeSession) bool {
|
||||
@@ -1270,11 +1640,36 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi
|
||||
"consumer_capacity": DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity,
|
||||
"mailbox_read_total": s.mailboxReadTotal,
|
||||
"mailbox_resume_total": s.mailboxResumeReadTotal,
|
||||
"mailbox_preflight_total": s.mailboxPreflightTotal,
|
||||
}
|
||||
if session == nil {
|
||||
if s.sequence == 0 {
|
||||
readiness["no_session_summary"] = map[string]any{
|
||||
"schema_version": "rap.remote_workspace_adapter_no_session_summary.v1",
|
||||
"summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"},
|
||||
"summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true},
|
||||
"status": "idle",
|
||||
"diagnostic_state": "waiting_for_session",
|
||||
"active_session_count": len(s.sessions),
|
||||
"terminal_session_count": len(s.terminalSessions),
|
||||
}
|
||||
}
|
||||
if s.sequence > 0 {
|
||||
readiness["last_adapter_session_id"] = s.last.AdapterSessionID
|
||||
readiness["last_session_state"] = s.last.SessionState
|
||||
lastSessionState := s.last.SessionState
|
||||
if terminal, ok := s.terminalSessions[s.last.AdapterSessionID]; ok {
|
||||
lastSessionState = terminal.State
|
||||
readiness["terminal_session_summary"] = map[string]any{
|
||||
"schema_version": "rap.remote_workspace_adapter_terminal_session_summary.v1",
|
||||
"summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"},
|
||||
"summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true},
|
||||
"adapter_session_id": s.last.AdapterSessionID,
|
||||
"session_state": terminal.State,
|
||||
"reason": terminal.Reason,
|
||||
"controlled_at": terminal.ControlledAt.Format(time.RFC3339Nano),
|
||||
}
|
||||
}
|
||||
readiness["last_session_state"] = lastSessionState
|
||||
readiness["diagnostic_state"] = "last_session_terminal_or_expired"
|
||||
}
|
||||
return readiness
|
||||
@@ -1299,6 +1694,13 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi
|
||||
readiness["mailbox_enqueued_total"] = session.MailboxEnqueued
|
||||
readiness["mailbox_read_total"] = session.MailboxRead
|
||||
readiness["mailbox_resume_read_total"] = session.MailboxResumeRead
|
||||
readiness["mailbox_preflight_total"] = session.MailboxPreflightTotal
|
||||
readiness["mailbox_preflight_operator_status_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts)
|
||||
readiness["mailbox_preflight_operator_severity_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts)
|
||||
preflightAttentionStatus := remoteWorkspacePreflightAttentionStatus(session.MailboxPreflightOperatorStatusCounts, session.MailboxPreflightOperatorSeverityCounts)
|
||||
preflightAttentionReason := remoteWorkspacePreflightAttentionReason(preflightAttentionStatus, session.MailboxPreflightOperatorStatusCounts, session.MailboxPreflightOperatorSeverityCounts)
|
||||
readiness["preflight_attention_status"] = preflightAttentionStatus
|
||||
readiness["preflight_attention_reason"] = preflightAttentionReason
|
||||
readiness["mailbox_after_sequence_read_total"] = session.MailboxAfterSequenceRead
|
||||
readiness["mailbox_returned_total"] = session.MailboxReturnedTotal
|
||||
readiness["mailbox_skipped_total"] = session.MailboxSkippedTotal
|
||||
@@ -1315,6 +1717,66 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi
|
||||
readiness["last_after_sequence"] = session.LastMailboxAfterSequence
|
||||
readiness["last_returned_count"] = session.LastMailboxReturnedCount
|
||||
readiness["last_skipped_count"] = session.LastMailboxSkippedCount
|
||||
readiness["last_preflight_consumer_id"] = session.LastMailboxPreflightConsumerID
|
||||
readiness["last_preflight_resume_from"] = session.LastMailboxPreflightResumeFrom
|
||||
readiness["last_preflight_resume_sequence"] = session.LastMailboxPreflightResumeSequence
|
||||
readiness["last_preflight_available_count"] = session.LastMailboxPreflightAvailableCount
|
||||
readiness["last_preflight_returned_count"] = session.LastMailboxPreflightReturnedCount
|
||||
readiness["last_preflight_skipped_count"] = session.LastMailboxPreflightSkippedCount
|
||||
readiness["last_preflight_diagnostic_state"] = session.LastMailboxPreflightDiagnosticState
|
||||
readiness["last_preflight_stale_cursor"] = session.LastMailboxPreflightStaleCursor
|
||||
readiness["last_preflight_missing_dropped_count"] = session.LastMailboxPreflightMissingDropped
|
||||
readiness["last_preflight_recommended_action"] = session.LastMailboxPreflightRecommendedAction
|
||||
readiness["last_preflight_action_hints"] = append([]string(nil), session.LastMailboxPreflightActionHints...)
|
||||
readiness["last_preflight_action_reason"] = session.LastMailboxPreflightActionReason
|
||||
readiness["last_preflight_action_context"] = cloneStringAnyMap(session.LastMailboxPreflightActionContext)
|
||||
readiness["last_preflight_operator_summary"] = session.LastMailboxPreflightOperatorSummary
|
||||
readiness["last_preflight_operator_status"] = session.LastMailboxPreflightOperatorStatus
|
||||
readiness["last_preflight_operator_severity"] = session.LastMailboxPreflightOperatorSeverity
|
||||
readiness["last_preflight_operator_summary_fields"] = cloneStringAnyMap(session.LastMailboxPreflightOperatorFields)
|
||||
if session.MailboxPreflightTotal > 0 {
|
||||
remediationChecklist := remoteWorkspacePreflightRemediationChecklist(session.LastMailboxPreflightOperatorStatus, session.LastMailboxPreflightActionHints)
|
||||
remediationChecklistSummary := remoteWorkspacePreflightRemediationChecklistSummary(remediationChecklist)
|
||||
readiness["last_preflight"] = map[string]any{
|
||||
"diagnostics_schema_version": "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1",
|
||||
"diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"},
|
||||
"diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true},
|
||||
"observed_at": session.LastMailboxPreflightAt.Format(time.RFC3339Nano),
|
||||
"consumer_id": session.LastMailboxPreflightConsumerID,
|
||||
"resume_from": session.LastMailboxPreflightResumeFrom,
|
||||
"resume_sequence": session.LastMailboxPreflightResumeSequence,
|
||||
"after_sequence": session.LastMailboxPreflightAfterSequence,
|
||||
"available_count": session.LastMailboxPreflightAvailableCount,
|
||||
"returned_count": session.LastMailboxPreflightReturnedCount,
|
||||
"skipped_count": session.LastMailboxPreflightSkippedCount,
|
||||
"first_sequence": session.LastMailboxPreflightFirstSequence,
|
||||
"last_sequence": session.LastMailboxPreflightLastSequence,
|
||||
"first_retained_sequence": session.LastMailboxPreflightFirstRetained,
|
||||
"last_retained_sequence": session.LastMailboxPreflightLastRetained,
|
||||
"mailbox_dropped_total": session.LastMailboxPreflightMailboxDropped,
|
||||
"diagnostic_state": session.LastMailboxPreflightDiagnosticState,
|
||||
"stale_cursor": session.LastMailboxPreflightStaleCursor,
|
||||
"missing_dropped_count": session.LastMailboxPreflightMissingDropped,
|
||||
"recommended_action": session.LastMailboxPreflightRecommendedAction,
|
||||
"action_hints": append([]string(nil), session.LastMailboxPreflightActionHints...),
|
||||
"action_reason": session.LastMailboxPreflightActionReason,
|
||||
"action_context": cloneStringAnyMap(session.LastMailboxPreflightActionContext),
|
||||
"remediation_checklist": remediationChecklist,
|
||||
"remediation_checklist_status": remediationChecklistSummary["status"],
|
||||
"remediation_checklist_counts": remediationChecklistSummary,
|
||||
"operator_summary": session.LastMailboxPreflightOperatorSummary,
|
||||
"operator_status": session.LastMailboxPreflightOperatorStatus,
|
||||
"operator_severity": session.LastMailboxPreflightOperatorSeverity,
|
||||
"operator_summary_fields": cloneStringAnyMap(session.LastMailboxPreflightOperatorFields),
|
||||
"mailbox_preflight_total": session.MailboxPreflightTotal,
|
||||
"mailbox_preflight_ack_total": session.MailboxPreflightAckTotal,
|
||||
"mailbox_preflight_checkpoint_total": session.MailboxPreflightCheckpointTotal,
|
||||
"preflight_attention_status": preflightAttentionStatus,
|
||||
"preflight_attention_reason": preflightAttentionReason,
|
||||
"operator_status_counts": cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts),
|
||||
"operator_severity_counts": cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts),
|
||||
}
|
||||
}
|
||||
if !session.LastActivityAt.IsZero() {
|
||||
readiness["last_activity_at"] = session.LastActivityAt.Format(time.RFC3339Nano)
|
||||
}
|
||||
@@ -1327,6 +1789,9 @@ func remoteWorkspaceAdapterRuntimeReadinessLocked(s *RemoteWorkspaceFrameProbeSi
|
||||
if !session.LastMailboxConsumerAckAt.IsZero() {
|
||||
readiness["last_consumer_ack_at"] = session.LastMailboxConsumerAckAt.Format(time.RFC3339Nano)
|
||||
}
|
||||
if !session.LastMailboxPreflightAt.IsZero() {
|
||||
readiness["last_preflight_at"] = session.LastMailboxPreflightAt.Format(time.RFC3339Nano)
|
||||
}
|
||||
return readiness
|
||||
}
|
||||
|
||||
@@ -1445,6 +1910,9 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any {
|
||||
report["mailbox_after_sequence_read_total"] = s.mailboxAfterSequenceReadTotal
|
||||
report["mailbox_returned_total"] = s.mailboxReturnedTotal
|
||||
report["mailbox_skipped_total"] = s.mailboxSkippedTotal
|
||||
report["mailbox_preflight_total"] = s.mailboxPreflightTotal
|
||||
report["mailbox_preflight_ack_total"] = s.mailboxPreflightAckTotal
|
||||
report["mailbox_preflight_checkpoint_total"] = s.mailboxPreflightCheckpointTotal
|
||||
report["mailbox_consumer_capacity"] = DefaultRemoteWorkspaceAdapterMailboxConsumerCapacity
|
||||
report["mailbox_consumer_count"] = countMailboxConsumersLocked(s.sessions)
|
||||
report["mailbox_consumer_read_total"] = s.mailboxConsumerReadTotal
|
||||
@@ -1467,6 +1935,30 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any {
|
||||
report["last_mailbox_resume_sequence"] = s.lastMailboxResumeSequence
|
||||
report["last_mailbox_resume_consumer_id"] = s.lastMailboxResumeConsumerID
|
||||
}
|
||||
if s.mailboxPreflightTotal > 0 {
|
||||
report["last_mailbox_preflight_at"] = s.lastMailboxPreflightAt
|
||||
report["last_mailbox_preflight_adapter_session_id"] = s.lastMailboxPreflightAdapterSessionID
|
||||
report["last_mailbox_preflight_consumer_id"] = s.lastMailboxPreflightConsumerID
|
||||
report["last_mailbox_preflight_resume_from"] = s.lastMailboxPreflightResumeFrom
|
||||
report["last_mailbox_preflight_resume_sequence"] = s.lastMailboxPreflightResumeSequence
|
||||
report["last_mailbox_preflight_after_sequence"] = s.lastMailboxPreflightAfterSequence
|
||||
report["last_mailbox_preflight_available_count"] = s.lastMailboxPreflightAvailableCount
|
||||
report["last_mailbox_preflight_returned_count"] = s.lastMailboxPreflightReturnedCount
|
||||
report["last_mailbox_preflight_skipped_count"] = s.lastMailboxPreflightSkippedCount
|
||||
report["last_mailbox_preflight_first_sequence"] = s.lastMailboxPreflightFirstSequence
|
||||
report["last_mailbox_preflight_last_sequence"] = s.lastMailboxPreflightLastSequence
|
||||
report["last_mailbox_preflight_diagnostic_state"] = s.lastMailboxPreflightDiagnosticState
|
||||
report["last_mailbox_preflight_stale_cursor"] = s.lastMailboxPreflightStaleCursor
|
||||
report["last_mailbox_preflight_missing_dropped_count"] = s.lastMailboxPreflightMissingDropped
|
||||
report["last_mailbox_preflight_recommended_action"] = s.lastMailboxPreflightRecommendedAction
|
||||
report["last_mailbox_preflight_action_hints"] = append([]string(nil), s.lastMailboxPreflightActionHints...)
|
||||
report["last_mailbox_preflight_action_reason"] = s.lastMailboxPreflightActionReason
|
||||
report["last_mailbox_preflight_action_context"] = cloneStringAnyMap(s.lastMailboxPreflightActionContext)
|
||||
report["last_mailbox_preflight_operator_summary"] = s.lastMailboxPreflightOperatorSummary
|
||||
report["last_mailbox_preflight_operator_status"] = s.lastMailboxPreflightOperatorStatus
|
||||
report["last_mailbox_preflight_operator_severity"] = s.lastMailboxPreflightOperatorSeverity
|
||||
report["last_mailbox_preflight_operator_summary_fields"] = cloneStringAnyMap(s.lastMailboxPreflightOperatorFields)
|
||||
}
|
||||
if s.mailboxConsumerReadTotal > 0 {
|
||||
report["last_mailbox_consumer_id"] = s.lastMailboxConsumerID
|
||||
report["last_mailbox_consumer_read_at"] = s.lastMailboxConsumerReadAt
|
||||
@@ -1520,6 +2012,11 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any {
|
||||
report["current_session_mailbox_after_sequence_read_total"] = session.MailboxAfterSequenceRead
|
||||
report["current_session_mailbox_returned_total"] = session.MailboxReturnedTotal
|
||||
report["current_session_mailbox_skipped_total"] = session.MailboxSkippedTotal
|
||||
report["current_session_mailbox_preflight_total"] = session.MailboxPreflightTotal
|
||||
report["current_session_mailbox_preflight_ack_total"] = session.MailboxPreflightAckTotal
|
||||
report["current_session_mailbox_preflight_checkpoint_total"] = session.MailboxPreflightCheckpointTotal
|
||||
report["current_session_mailbox_preflight_operator_status_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorStatusCounts)
|
||||
report["current_session_mailbox_preflight_operator_severity_counts"] = cloneStringInt64Map(session.MailboxPreflightOperatorSeverityCounts)
|
||||
report["current_session_mailbox_consumer_count"] = len(session.MailboxConsumers)
|
||||
report["current_session_mailbox_consumer_read_total"] = session.MailboxConsumerReadTotal
|
||||
report["current_session_mailbox_consumer_ack_total"] = session.MailboxConsumerAckTotal
|
||||
@@ -1549,6 +2046,29 @@ func (s *RemoteWorkspaceFrameProbeSink) Report(now time.Time) map[string]any {
|
||||
report["current_session_last_mailbox_resume_sequence"] = session.LastMailboxResumeSequence
|
||||
report["current_session_last_mailbox_resume_consumer_id"] = session.LastMailboxResumeConsumerID
|
||||
}
|
||||
if session.MailboxPreflightTotal > 0 {
|
||||
report["current_session_last_mailbox_preflight_at"] = session.LastMailboxPreflightAt.Format(time.RFC3339Nano)
|
||||
report["current_session_last_mailbox_preflight_consumer_id"] = session.LastMailboxPreflightConsumerID
|
||||
report["current_session_last_mailbox_preflight_resume_from"] = session.LastMailboxPreflightResumeFrom
|
||||
report["current_session_last_mailbox_preflight_resume_sequence"] = session.LastMailboxPreflightResumeSequence
|
||||
report["current_session_last_mailbox_preflight_after_sequence"] = session.LastMailboxPreflightAfterSequence
|
||||
report["current_session_last_mailbox_preflight_available_count"] = session.LastMailboxPreflightAvailableCount
|
||||
report["current_session_last_mailbox_preflight_returned_count"] = session.LastMailboxPreflightReturnedCount
|
||||
report["current_session_last_mailbox_preflight_skipped_count"] = session.LastMailboxPreflightSkippedCount
|
||||
report["current_session_last_mailbox_preflight_first_sequence"] = session.LastMailboxPreflightFirstSequence
|
||||
report["current_session_last_mailbox_preflight_last_sequence"] = session.LastMailboxPreflightLastSequence
|
||||
report["current_session_last_mailbox_preflight_diagnostic_state"] = session.LastMailboxPreflightDiagnosticState
|
||||
report["current_session_last_mailbox_preflight_stale_cursor"] = session.LastMailboxPreflightStaleCursor
|
||||
report["current_session_last_mailbox_preflight_missing_dropped_count"] = session.LastMailboxPreflightMissingDropped
|
||||
report["current_session_last_mailbox_preflight_recommended_action"] = session.LastMailboxPreflightRecommendedAction
|
||||
report["current_session_last_mailbox_preflight_action_hints"] = append([]string(nil), session.LastMailboxPreflightActionHints...)
|
||||
report["current_session_last_mailbox_preflight_action_reason"] = session.LastMailboxPreflightActionReason
|
||||
report["current_session_last_mailbox_preflight_action_context"] = cloneStringAnyMap(session.LastMailboxPreflightActionContext)
|
||||
report["current_session_last_mailbox_preflight_operator_summary"] = session.LastMailboxPreflightOperatorSummary
|
||||
report["current_session_last_mailbox_preflight_operator_status"] = session.LastMailboxPreflightOperatorStatus
|
||||
report["current_session_last_mailbox_preflight_operator_severity"] = session.LastMailboxPreflightOperatorSeverity
|
||||
report["current_session_last_mailbox_preflight_operator_summary_fields"] = cloneStringAnyMap(session.LastMailboxPreflightOperatorFields)
|
||||
}
|
||||
if !session.LastBackpressureAt.IsZero() {
|
||||
report["current_session_last_backpressure_at"] = session.LastBackpressureAt.Format(time.RFC3339Nano)
|
||||
report["current_session_last_backpressure_reason"] = session.LastReason
|
||||
|
||||
@@ -1643,6 +1643,44 @@ func TestRemoteWorkspaceAdapterSessionControlEndpointClosesSession(t *testing.T)
|
||||
report["last_session_control_state"] != "closed" {
|
||||
t.Fatalf("control report = %+v", report)
|
||||
}
|
||||
readiness, ok := report["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from control report = %+v", report)
|
||||
}
|
||||
if readiness["schema_version"] != "rap.remote_workspace_adapter_runtime_readiness.v1" ||
|
||||
readiness["status"] != "idle" ||
|
||||
readiness["diagnostic_state"] != "last_session_terminal_or_expired" ||
|
||||
readiness["ready"] != false ||
|
||||
readiness["active_session_count"] != 0 ||
|
||||
readiness["last_adapter_session_id"] != "rap-rw-adapter-session-aaaaaaaaaaaaaaaaaaaaaaaa" ||
|
||||
readiness["last_session_state"] != "closed" {
|
||||
t.Fatalf("invalid no-active-session readiness after close = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["adapter_session_id"]; ok {
|
||||
t.Fatalf("adapter_session_id should be absent without active session = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["last_preflight"]; ok {
|
||||
t.Fatalf("last_preflight should be absent without active session = %+v", readiness)
|
||||
}
|
||||
terminalSummary, ok := readiness["terminal_session_summary"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("terminal session summary missing after close = %+v", readiness)
|
||||
}
|
||||
if terminalSummary["adapter_session_id"] != "rap-rw-adapter-session-aaaaaaaaaaaaaaaaaaaaaaaa" ||
|
||||
terminalSummary["schema_version"] != "rap.remote_workspace_adapter_terminal_session_summary.v1" ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "adapter_session_id") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "session_state") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "reason") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "controlled_at") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "adapter_session_id") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "session_state") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "reason") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "controlled_at") ||
|
||||
terminalSummary["session_state"] != "closed" ||
|
||||
terminalSummary["reason"] != "unit test close" ||
|
||||
terminalSummary["controlled_at"] == "" {
|
||||
t.Fatalf("invalid terminal session summary after close = %+v", terminalSummary)
|
||||
}
|
||||
|
||||
resp, err = http.Post(controlURL, "application/json", bytes.NewReader([]byte(`{"action":"close","reason":"repeat close"}`)))
|
||||
if err != nil {
|
||||
@@ -1665,6 +1703,255 @@ func TestRemoteWorkspaceAdapterSessionControlEndpointClosesSession(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterReadinessBeforeAnySessionHasNoTerminalSummary(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
report := sink.Report(time.Now().UTC())
|
||||
readiness, ok := report["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from report = %+v", report)
|
||||
}
|
||||
if readiness["schema_version"] != "rap.remote_workspace_adapter_runtime_readiness.v1" ||
|
||||
readiness["status"] != "idle" ||
|
||||
readiness["diagnostic_state"] != "waiting_for_session" ||
|
||||
readiness["ready"] != false ||
|
||||
readiness["active_session_count"] != 0 ||
|
||||
readiness["terminal_session_count"] != 0 {
|
||||
t.Fatalf("invalid empty readiness = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["last_adapter_session_id"]; ok {
|
||||
t.Fatalf("last_adapter_session_id should be absent before any session = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["last_session_state"]; ok {
|
||||
t.Fatalf("last_session_state should be absent before any session = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["terminal_session_summary"]; ok {
|
||||
t.Fatalf("terminal_session_summary should be absent before terminal history = %+v", readiness)
|
||||
}
|
||||
noSessionSummary, ok := readiness["no_session_summary"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("no_session_summary should be present before any session = %+v", readiness)
|
||||
}
|
||||
if noSessionSummary["schema_version"] != "rap.remote_workspace_adapter_no_session_summary.v1" ||
|
||||
!stringAnySliceContains(noSessionSummary["summary_contract"], "status") ||
|
||||
!stringAnySliceContains(noSessionSummary["summary_contract"], "diagnostic_state") ||
|
||||
!stringAnySliceContains(noSessionSummary["summary_contract"], "active_session_count") ||
|
||||
!stringAnySliceContains(noSessionSummary["summary_contract"], "terminal_session_count") ||
|
||||
!boolMapValue(noSessionSummary["summary_features"], "status") ||
|
||||
!boolMapValue(noSessionSummary["summary_features"], "diagnostic_state") ||
|
||||
!boolMapValue(noSessionSummary["summary_features"], "active_session_count") ||
|
||||
!boolMapValue(noSessionSummary["summary_features"], "terminal_session_count") ||
|
||||
noSessionSummary["status"] != "idle" ||
|
||||
noSessionSummary["diagnostic_state"] != "waiting_for_session" ||
|
||||
noSessionSummary["active_session_count"] != 0 ||
|
||||
noSessionSummary["terminal_session_count"] != 0 {
|
||||
t.Fatalf("invalid no-session summary before any session = %+v", noSessionSummary)
|
||||
}
|
||||
if _, ok := readiness["last_preflight"]; ok {
|
||||
t.Fatalf("last_preflight should be absent before any session = %+v", readiness)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterReadinessSummaryExclusivity(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
freshReport := sink.Report(time.Now().UTC())
|
||||
freshReadiness, ok := freshReport["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("fresh readiness missing from report = %+v", freshReport)
|
||||
}
|
||||
if _, ok := freshReadiness["no_session_summary"]; !ok {
|
||||
t.Fatalf("fresh readiness should include no_session_summary = %+v", freshReadiness)
|
||||
}
|
||||
if _, ok := freshReadiness["terminal_session_summary"]; ok {
|
||||
t.Fatalf("fresh readiness should not include terminal_session_summary = %+v", freshReadiness)
|
||||
}
|
||||
|
||||
sessionID := "rap-rw-adapter-session-d1d1d1d1d1d1d1d1d1d1d1d1"
|
||||
delivery := RemoteWorkspaceFrameBatchDelivery{
|
||||
ClusterID: "cluster-1",
|
||||
ChannelID: "channel-rw",
|
||||
ResourceID: "workspace-exclusivity",
|
||||
ServiceClass: FabricServiceClassRemoteWorkspace,
|
||||
ChannelClass: FabricServiceChannelInteractive,
|
||||
AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1",
|
||||
AdapterSessionID: sessionID,
|
||||
Frames: []RemoteWorkspaceFrameProbeRecord{{
|
||||
Channel: "display",
|
||||
Direction: "adapter_to_client",
|
||||
Droppable: true,
|
||||
}},
|
||||
}
|
||||
if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil {
|
||||
t.Fatalf("accept frame batch: %v", err)
|
||||
}
|
||||
activeReport := sink.Report(time.Now().UTC())
|
||||
activeReadiness, ok := activeReport["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("active readiness missing from report = %+v", activeReport)
|
||||
}
|
||||
if activeReadiness["adapter_session_id"] != sessionID ||
|
||||
activeReadiness["active_session_count"] != 1 {
|
||||
t.Fatalf("invalid active readiness = %+v", activeReadiness)
|
||||
}
|
||||
if _, ok := activeReadiness["no_session_summary"]; ok {
|
||||
t.Fatalf("active readiness should not include no_session_summary = %+v", activeReadiness)
|
||||
}
|
||||
if _, ok := activeReadiness["terminal_session_summary"]; ok {
|
||||
t.Fatalf("active readiness should not include terminal_session_summary = %+v", activeReadiness)
|
||||
}
|
||||
|
||||
server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler())
|
||||
defer server.Close()
|
||||
body := bytes.NewReader([]byte(`{"action":"close","reason":"unit summary exclusivity close"}`))
|
||||
resp, err := http.Post(server.URL+"/mesh/v1/remote-workspace/adapter-sessions/"+sessionID+"/control", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("post control: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("status = %d body=%s", resp.StatusCode, string(raw))
|
||||
}
|
||||
terminalReport := sink.Report(time.Now().UTC())
|
||||
terminalReadiness, ok := terminalReport["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("terminal readiness missing from report = %+v", terminalReport)
|
||||
}
|
||||
if _, ok := terminalReadiness["terminal_session_summary"]; !ok {
|
||||
t.Fatalf("terminal readiness should include terminal_session_summary = %+v", terminalReadiness)
|
||||
}
|
||||
if _, ok := terminalReadiness["no_session_summary"]; ok {
|
||||
t.Fatalf("terminal readiness should not include no_session_summary = %+v", terminalReadiness)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterSessionControlTerminalReadinessStates(t *testing.T) {
|
||||
tests := []struct {
|
||||
action string
|
||||
sessionID string
|
||||
wantState string
|
||||
wantClosed int64
|
||||
wantExpired int64
|
||||
wantReset int64
|
||||
wantPrevState string
|
||||
}{
|
||||
{
|
||||
action: "expire",
|
||||
sessionID: "rap-rw-adapter-session-b0b0b0b0b0b0b0b0b0b0b0b0",
|
||||
wantState: "expired",
|
||||
wantClosed: 1,
|
||||
wantExpired: 1,
|
||||
wantPrevState: "probe_bound",
|
||||
},
|
||||
{
|
||||
action: "reset",
|
||||
sessionID: "rap-rw-adapter-session-c0c0c0c0c0c0c0c0c0c0c0c0",
|
||||
wantState: "reset",
|
||||
wantClosed: 1,
|
||||
wantReset: 1,
|
||||
wantPrevState: "probe_bound",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.action, func(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
delivery := RemoteWorkspaceFrameBatchDelivery{
|
||||
ClusterID: "cluster-1",
|
||||
ChannelID: "channel-rw",
|
||||
ResourceID: "workspace-" + tt.action,
|
||||
ServiceClass: FabricServiceClassRemoteWorkspace,
|
||||
ChannelClass: FabricServiceChannelInteractive,
|
||||
AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1",
|
||||
AdapterSessionID: tt.sessionID,
|
||||
Frames: []RemoteWorkspaceFrameProbeRecord{{
|
||||
Channel: "display",
|
||||
Direction: "adapter_to_client",
|
||||
Droppable: true,
|
||||
}},
|
||||
}
|
||||
if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil {
|
||||
t.Fatalf("accept frame batch: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler())
|
||||
defer server.Close()
|
||||
|
||||
body := bytes.NewReader([]byte(fmt.Sprintf(`{"action":%q,"reason":"unit terminal readiness"}`, tt.action)))
|
||||
resp, err := http.Post(server.URL+"/mesh/v1/remote-workspace/adapter-sessions/"+tt.sessionID+"/control", "application/json", body)
|
||||
if err != nil {
|
||||
t.Fatalf("post control: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("status = %d body=%s", resp.StatusCode, string(raw))
|
||||
}
|
||||
var result RemoteWorkspaceAdapterSessionControlResult
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
t.Fatalf("decode control result: %v", err)
|
||||
}
|
||||
if !result.Accepted ||
|
||||
result.Action != tt.action ||
|
||||
result.AdapterSessionID != tt.sessionID ||
|
||||
result.PreviousState != tt.wantPrevState ||
|
||||
result.SessionState != tt.wantState ||
|
||||
result.ActiveSessions != 0 {
|
||||
t.Fatalf("control result = %+v", result)
|
||||
}
|
||||
|
||||
report := sink.Report(time.Now().UTC())
|
||||
if report["active_session_count"] != 0 ||
|
||||
report["session_closed_total"] != tt.wantClosed ||
|
||||
report["session_expired_total"] != tt.wantExpired ||
|
||||
report["session_reset_total"] != tt.wantReset ||
|
||||
report["last_controlled_adapter_session_id"] != tt.sessionID ||
|
||||
report["last_session_control_action"] != tt.action ||
|
||||
report["last_session_control_state"] != tt.wantState {
|
||||
t.Fatalf("terminal control report = %+v", report)
|
||||
}
|
||||
readiness, ok := report["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from report = %+v", report)
|
||||
}
|
||||
if readiness["status"] != "idle" ||
|
||||
readiness["diagnostic_state"] != "last_session_terminal_or_expired" ||
|
||||
readiness["ready"] != false ||
|
||||
readiness["active_session_count"] != 0 ||
|
||||
readiness["last_adapter_session_id"] != tt.sessionID ||
|
||||
readiness["last_session_state"] != tt.wantState {
|
||||
t.Fatalf("invalid terminal readiness = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["adapter_session_id"]; ok {
|
||||
t.Fatalf("adapter_session_id should be absent without active session = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["last_preflight"]; ok {
|
||||
t.Fatalf("last_preflight should be absent without active session = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["no_session_summary"]; ok {
|
||||
t.Fatalf("no_session_summary should be absent for terminal session history = %+v", readiness)
|
||||
}
|
||||
terminalSummary, ok := readiness["terminal_session_summary"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("terminal session summary missing = %+v", readiness)
|
||||
}
|
||||
if terminalSummary["adapter_session_id"] != tt.sessionID ||
|
||||
terminalSummary["schema_version"] != "rap.remote_workspace_adapter_terminal_session_summary.v1" ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "adapter_session_id") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "session_state") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "reason") ||
|
||||
!stringAnySliceContains(terminalSummary["summary_contract"], "controlled_at") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "adapter_session_id") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "session_state") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "reason") ||
|
||||
!boolMapValue(terminalSummary["summary_features"], "controlled_at") ||
|
||||
terminalSummary["session_state"] != tt.wantState ||
|
||||
terminalSummary["reason"] != "unit terminal readiness" ||
|
||||
terminalSummary["controlled_at"] == "" {
|
||||
t.Fatalf("invalid terminal session summary = %+v", terminalSummary)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterSessionControlRejectsInvalidRequests(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler())
|
||||
@@ -3064,6 +3351,19 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightIsReadOnly(t *testing.T) {
|
||||
}
|
||||
if preflight.ResumeFrom != "checkpoint" ||
|
||||
preflight.ResumeSequence != 2 ||
|
||||
preflight.DiagnosticState != "ready" ||
|
||||
preflight.RecommendedAction != "resume_from_cursor" ||
|
||||
preflight.ActionReason != "cursor_window_available" ||
|
||||
preflight.OperatorSummary != "consumer cursor can resume from requested window" ||
|
||||
preflight.OperatorStatus != "ready_to_resume" ||
|
||||
preflight.OperatorSeverity != "ok" ||
|
||||
anyInt64(preflight.ActionContext["resume_sequence"]) != 2 ||
|
||||
anyInt64(preflight.ActionContext["first_retained_sequence"]) != 1 ||
|
||||
preflight.OperatorSummaryFields["diagnostic_state"] != "ready" ||
|
||||
preflight.OperatorSummaryFields["recommended_action"] != "resume_from_cursor" ||
|
||||
preflight.OperatorSummaryFields["operator_status"] != "ready_to_resume" ||
|
||||
preflight.OperatorSummaryFields["operator_severity"] != "ok" ||
|
||||
!stringSliceContains(preflight.ActionHints, "resume_from_requested_cursor") ||
|
||||
preflight.ExpectedAvailableCount != 1 ||
|
||||
preflight.ExpectedReturnedCount != 1 ||
|
||||
preflight.ExpectedSkippedCount != 2 ||
|
||||
@@ -3079,6 +3379,547 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightIsReadOnly(t *testing.T) {
|
||||
reportAfter["current_session_mailbox_consumer_ack_total"] != reportBefore["current_session_mailbox_consumer_ack_total"] {
|
||||
t.Fatalf("preflight mutated report before=%+v after=%+v", reportBefore, reportAfter)
|
||||
}
|
||||
if reportAfter["mailbox_preflight_total"] != int64(2) ||
|
||||
reportAfter["mailbox_preflight_ack_total"] != int64(1) ||
|
||||
reportAfter["mailbox_preflight_checkpoint_total"] != int64(1) ||
|
||||
reportAfter["last_mailbox_preflight_adapter_session_id"] != sessionID ||
|
||||
reportAfter["last_mailbox_preflight_consumer_id"] != "rdp-worker-probe" ||
|
||||
reportAfter["last_mailbox_preflight_resume_from"] != "checkpoint" ||
|
||||
reportAfter["last_mailbox_preflight_resume_sequence"] != int64(2) ||
|
||||
reportAfter["last_mailbox_preflight_available_count"] != 1 ||
|
||||
reportAfter["last_mailbox_preflight_returned_count"] != 1 ||
|
||||
reportAfter["last_mailbox_preflight_skipped_count"] != 2 ||
|
||||
reportAfter["current_session_mailbox_preflight_total"] != int64(2) ||
|
||||
reportAfter["current_session_mailbox_preflight_ack_total"] != int64(1) ||
|
||||
reportAfter["current_session_mailbox_preflight_checkpoint_total"] != int64(1) ||
|
||||
mapInt64Value(reportAfter["current_session_mailbox_preflight_operator_status_counts"], "ready_to_resume") != 2 ||
|
||||
mapInt64Value(reportAfter["current_session_mailbox_preflight_operator_severity_counts"], "ok") != 2 ||
|
||||
reportAfter["current_session_last_mailbox_preflight_resume_from"] != "checkpoint" ||
|
||||
reportAfter["current_session_last_mailbox_preflight_resume_sequence"] != int64(2) ||
|
||||
reportAfter["current_session_last_mailbox_preflight_returned_count"] != 1 ||
|
||||
reportAfter["current_session_last_mailbox_preflight_recommended_action"] != "resume_from_cursor" ||
|
||||
reportAfter["last_mailbox_preflight_operator_summary"] != "consumer cursor can resume from requested window" ||
|
||||
reportAfter["last_mailbox_preflight_operator_status"] != "ready_to_resume" ||
|
||||
reportAfter["last_mailbox_preflight_operator_severity"] != "ok" ||
|
||||
reportAfter["current_session_last_mailbox_preflight_operator_summary"] != "consumer cursor can resume from requested window" ||
|
||||
reportAfter["current_session_last_mailbox_preflight_operator_status"] != "ready_to_resume" ||
|
||||
reportAfter["current_session_last_mailbox_preflight_operator_severity"] != "ok" {
|
||||
t.Fatalf("invalid preflight telemetry report = %+v", reportAfter)
|
||||
}
|
||||
readiness, ok := reportAfter["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from report = %+v", reportAfter)
|
||||
}
|
||||
if readiness["mailbox_preflight_total"] != int64(2) ||
|
||||
readiness["last_preflight_consumer_id"] != "rdp-worker-probe" ||
|
||||
readiness["last_preflight_resume_from"] != "checkpoint" ||
|
||||
readiness["last_preflight_resume_sequence"] != int64(2) ||
|
||||
readiness["last_preflight_returned_count"] != 1 ||
|
||||
readiness["last_preflight_skipped_count"] != 2 ||
|
||||
readiness["last_preflight_recommended_action"] != "resume_from_cursor" ||
|
||||
readiness["last_preflight_action_reason"] != "cursor_window_available" ||
|
||||
readiness["last_preflight_operator_summary"] != "consumer cursor can resume from requested window" ||
|
||||
readiness["last_preflight_operator_status"] != "ready_to_resume" ||
|
||||
readiness["last_preflight_operator_severity"] != "ok" ||
|
||||
mapInt64Value(readiness["mailbox_preflight_operator_status_counts"], "ready_to_resume") != 2 ||
|
||||
mapInt64Value(readiness["mailbox_preflight_operator_severity_counts"], "ok") != 2 ||
|
||||
readiness["preflight_attention_status"] != "clean" ||
|
||||
readiness["preflight_attention_reason"] != "no_resync_required_preflight_observed" {
|
||||
t.Fatalf("invalid preflight readiness = %+v", readiness)
|
||||
}
|
||||
lastPreflight, ok := readiness["last_preflight"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("last preflight rollup missing from readiness = %+v", readiness)
|
||||
}
|
||||
if lastPreflight["consumer_id"] != "rdp-worker-probe" ||
|
||||
lastPreflight["diagnostics_schema_version"] != "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "retained_window") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "remediation_checklist") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "attention") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "operator_counts") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "retained_window") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "remediation_checklist") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "attention") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "operator_counts") ||
|
||||
lastPreflight["resume_from"] != "checkpoint" ||
|
||||
lastPreflight["operator_status"] != "ready_to_resume" ||
|
||||
lastPreflight["operator_severity"] != "ok" ||
|
||||
lastPreflight["recommended_action"] != "resume_from_cursor" ||
|
||||
!preflightChecklistContains(lastPreflight["remediation_checklist"], "resume_from_requested_cursor", true, true) ||
|
||||
lastPreflight["remediation_checklist_status"] != "ready" ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "required_count")) != 1 ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "satisfied_count")) != 1 ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "pending_count")) != 0 ||
|
||||
mapInt64Value(lastPreflight["operator_status_counts"], "ready_to_resume") != 2 ||
|
||||
mapInt64Value(lastPreflight["operator_severity_counts"], "ok") != 2 ||
|
||||
lastPreflight["preflight_attention_status"] != "clean" ||
|
||||
lastPreflight["preflight_attention_reason"] != "no_resync_required_preflight_observed" ||
|
||||
anyInt64(lastPreflight["resume_sequence"]) != 2 ||
|
||||
anyInt64(lastPreflight["first_retained_sequence"]) != 1 ||
|
||||
anyInt64(lastPreflight["last_retained_sequence"]) != 3 ||
|
||||
anyInt64(lastPreflight["mailbox_dropped_total"]) != 0 ||
|
||||
anyInt64(lastPreflight["mailbox_preflight_total"]) != 2 {
|
||||
t.Fatalf("invalid last preflight rollup = %+v", lastPreflight)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterSessionReadinessBeforePreflight(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
sessionID := "rap-rw-adapter-session-a0a0a0a0a0a0a0a0a0a0a0a0"
|
||||
delivery := RemoteWorkspaceFrameBatchDelivery{
|
||||
ClusterID: "cluster-1",
|
||||
ChannelID: "channel-rw",
|
||||
ResourceID: "workspace-before-preflight",
|
||||
ServiceClass: FabricServiceClassRemoteWorkspace,
|
||||
ChannelClass: FabricServiceChannelInteractive,
|
||||
AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1",
|
||||
AdapterSessionID: sessionID,
|
||||
Frames: []RemoteWorkspaceFrameProbeRecord{{
|
||||
Channel: "display",
|
||||
Direction: "adapter_to_client",
|
||||
Droppable: true,
|
||||
}},
|
||||
}
|
||||
if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil {
|
||||
t.Fatalf("accept frame batch: %v", err)
|
||||
}
|
||||
|
||||
report := sink.Report(time.Now().UTC())
|
||||
if report["mailbox_preflight_total"] != int64(0) ||
|
||||
report["current_session_mailbox_preflight_total"] != int64(0) {
|
||||
t.Fatalf("unexpected preflight totals before preflight = %+v", report)
|
||||
}
|
||||
readiness, ok := report["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from report = %+v", report)
|
||||
}
|
||||
if readiness["adapter_session_id"] != sessionID ||
|
||||
readiness["mailbox_preflight_total"] != int64(0) ||
|
||||
readiness["preflight_attention_status"] != "unknown" ||
|
||||
readiness["preflight_attention_reason"] != "no_preflight_observed" {
|
||||
t.Fatalf("invalid no-preflight readiness = %+v", readiness)
|
||||
}
|
||||
if _, ok := readiness["last_preflight"]; ok {
|
||||
t.Fatalf("last preflight rollup should be absent before preflight = %+v", readiness["last_preflight"])
|
||||
}
|
||||
if readiness["last_preflight_diagnostic_state"] != "" ||
|
||||
readiness["last_preflight_recommended_action"] != "" ||
|
||||
len(readiness["last_preflight_action_hints"].([]string)) != 0 {
|
||||
t.Fatalf("last preflight flat fields should be empty before preflight = %+v", readiness)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterSessionMailboxPreflightReportsStaleCursorGap(t *testing.T) {
|
||||
sink := NewRemoteWorkspaceFrameProbeSink()
|
||||
sessionID := "rap-rw-adapter-session-adadadadadadadadadadadad"
|
||||
delivery := RemoteWorkspaceFrameBatchDelivery{
|
||||
ClusterID: "cluster-1",
|
||||
ChannelID: "channel-rw",
|
||||
ResourceID: "workspace-stale-0",
|
||||
ServiceClass: FabricServiceClassRemoteWorkspace,
|
||||
ChannelClass: FabricServiceChannelInteractive,
|
||||
AdapterContractID: "rap.rdp_worker.remote_workspace_adapter_contract_probe.v1",
|
||||
AdapterSessionID: sessionID,
|
||||
Frames: []RemoteWorkspaceFrameProbeRecord{{
|
||||
Channel: "display",
|
||||
Direction: "adapter_to_client",
|
||||
Droppable: true,
|
||||
}},
|
||||
}
|
||||
if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil {
|
||||
t.Fatalf("accept initial frame batch: %v", err)
|
||||
}
|
||||
server := httptest.NewServer(Server{RemoteWorkspaceFrameSink: sink}.Handler())
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox?consumer_id=rdp-worker-probe&ack_sequence=1&limit=1")
|
||||
if err != nil {
|
||||
t.Fatalf("seed ack cursor: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
for i := 1; i <= DefaultRemoteWorkspaceAdapterMailboxCapacity+2; i++ {
|
||||
delivery.ResourceID = fmt.Sprintf("workspace-stale-%d", i)
|
||||
if _, err := sink.AcceptRemoteWorkspaceFrameBatchProbe(context.Background(), delivery); err != nil {
|
||||
t.Fatalf("accept overflow frame batch %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox/preflight?consumer_id=rdp-worker-probe&resume_from=ack&limit=3")
|
||||
if err != nil {
|
||||
t.Fatalf("get stale preflight: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var preflight RemoteWorkspaceAdapterMailboxPreflightSnapshot
|
||||
if err := json.NewDecoder(resp.Body).Decode(&preflight); err != nil {
|
||||
t.Fatalf("decode stale preflight: %v", err)
|
||||
}
|
||||
if preflight.ResumeFrom != "ack" ||
|
||||
preflight.ResumeSequence != 1 ||
|
||||
preflight.MailboxDepth != DefaultRemoteWorkspaceAdapterMailboxCapacity ||
|
||||
preflight.MailboxDropped != 3 ||
|
||||
preflight.ExpectedAvailableCount != DefaultRemoteWorkspaceAdapterMailboxCapacity ||
|
||||
preflight.ExpectedReturnedCount != 3 ||
|
||||
preflight.ExpectedSkippedCount != 0 ||
|
||||
preflight.FirstExpectedSequence != 4 ||
|
||||
preflight.LastExpectedSequence != 6 ||
|
||||
preflight.FirstRetainedSequence != 4 ||
|
||||
preflight.LastRetainedSequence != 19 ||
|
||||
preflight.DiagnosticState != "stale_cursor_gap" ||
|
||||
!preflight.StaleCursor ||
|
||||
preflight.MissingDroppedCount != 2 ||
|
||||
preflight.RecommendedAction != "reset_consumer_and_resync" ||
|
||||
preflight.ActionReason != "consumer_cursor_before_first_retained_sequence" ||
|
||||
preflight.OperatorSummary != "stale cursor gap: reset consumer and resync before resume" ||
|
||||
preflight.OperatorStatus != "resync_required" ||
|
||||
preflight.OperatorSeverity != "warn" ||
|
||||
anyInt64(preflight.ActionContext["resume_sequence"]) != 1 ||
|
||||
anyInt64(preflight.ActionContext["first_retained_sequence"]) != 4 ||
|
||||
anyInt64(preflight.ActionContext["missing_dropped_count"]) != 2 ||
|
||||
preflight.OperatorSummaryFields["diagnostic_state"] != "stale_cursor_gap" ||
|
||||
preflight.OperatorSummaryFields["recommended_action"] != "reset_consumer_and_resync" ||
|
||||
preflight.OperatorSummaryFields["operator_status"] != "resync_required" ||
|
||||
preflight.OperatorSummaryFields["operator_severity"] != "warn" ||
|
||||
anyInt64(preflight.OperatorSummaryFields["missing_dropped_count"]) != 2 ||
|
||||
!stringSliceContains(preflight.ActionHints, "reset_consumer_cursor") ||
|
||||
!stringSliceContains(preflight.ActionHints, "request_full_adapter_resync") ||
|
||||
!stringSliceContains(preflight.ActionHints, "resume_from_checkpoint_after_resync") {
|
||||
t.Fatalf("stale preflight = %+v", preflight)
|
||||
}
|
||||
resp, err = http.Get(server.URL + "/mesh/v1/remote-workspace/adapter-sessions/" + sessionID + "/mailbox/preflight?consumer_id=rdp-worker-probe&resume_from=ack&limit=3")
|
||||
if err != nil {
|
||||
t.Fatalf("get repeated stale preflight: %v", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
report := sink.Report(time.Now().UTC())
|
||||
if report["last_mailbox_preflight_diagnostic_state"] != "stale_cursor_gap" ||
|
||||
report["last_mailbox_preflight_stale_cursor"] != true ||
|
||||
report["last_mailbox_preflight_missing_dropped_count"] != 2 ||
|
||||
report["last_mailbox_preflight_recommended_action"] != "reset_consumer_and_resync" ||
|
||||
report["last_mailbox_preflight_action_reason"] != "consumer_cursor_before_first_retained_sequence" ||
|
||||
report["last_mailbox_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" ||
|
||||
report["last_mailbox_preflight_operator_status"] != "resync_required" ||
|
||||
report["last_mailbox_preflight_operator_severity"] != "warn" ||
|
||||
report["current_session_last_mailbox_preflight_diagnostic_state"] != "stale_cursor_gap" ||
|
||||
report["current_session_last_mailbox_preflight_stale_cursor"] != true ||
|
||||
report["current_session_last_mailbox_preflight_missing_dropped_count"] != 2 ||
|
||||
report["current_session_last_mailbox_preflight_recommended_action"] != "reset_consumer_and_resync" ||
|
||||
report["current_session_last_mailbox_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" ||
|
||||
report["current_session_last_mailbox_preflight_operator_status"] != "resync_required" ||
|
||||
report["current_session_last_mailbox_preflight_operator_severity"] != "warn" ||
|
||||
mapInt64Value(report["current_session_mailbox_preflight_operator_status_counts"], "resync_required") != 2 ||
|
||||
mapInt64Value(report["current_session_mailbox_preflight_operator_severity_counts"], "warn") != 2 {
|
||||
t.Fatalf("stale preflight report = %+v", report)
|
||||
}
|
||||
readiness, ok := report["adapter_runtime_readiness"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("adapter runtime readiness missing from report = %+v", report)
|
||||
}
|
||||
if readiness["last_preflight_diagnostic_state"] != "stale_cursor_gap" ||
|
||||
readiness["last_preflight_stale_cursor"] != true ||
|
||||
readiness["last_preflight_missing_dropped_count"] != 2 ||
|
||||
readiness["last_preflight_recommended_action"] != "reset_consumer_and_resync" ||
|
||||
readiness["last_preflight_action_reason"] != "consumer_cursor_before_first_retained_sequence" ||
|
||||
readiness["last_preflight_operator_summary"] != "stale cursor gap: reset consumer and resync before resume" ||
|
||||
readiness["last_preflight_operator_status"] != "resync_required" ||
|
||||
readiness["last_preflight_operator_severity"] != "warn" ||
|
||||
mapInt64Value(readiness["mailbox_preflight_operator_status_counts"], "resync_required") != 2 ||
|
||||
mapInt64Value(readiness["mailbox_preflight_operator_severity_counts"], "warn") != 2 ||
|
||||
readiness["preflight_attention_status"] != "repeated_resync_required" ||
|
||||
readiness["preflight_attention_reason"] != "resync_required_preflight_repeated" {
|
||||
t.Fatalf("stale preflight readiness = %+v", readiness)
|
||||
}
|
||||
lastPreflight, ok := readiness["last_preflight"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("stale last preflight rollup missing from readiness = %+v", readiness)
|
||||
}
|
||||
if lastPreflight["diagnostic_state"] != "stale_cursor_gap" ||
|
||||
lastPreflight["diagnostics_schema_version"] != "rap.remote_workspace_adapter_mailbox_preflight_diagnostics.v1" ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "retained_window") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "remediation_checklist") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "attention") ||
|
||||
!stringAnySliceContains(lastPreflight["diagnostics_contract"], "operator_counts") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "retained_window") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "remediation_checklist") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "attention") ||
|
||||
!boolMapValue(lastPreflight["diagnostics_features"], "operator_counts") ||
|
||||
lastPreflight["operator_status"] != "resync_required" ||
|
||||
lastPreflight["operator_severity"] != "warn" ||
|
||||
lastPreflight["recommended_action"] != "reset_consumer_and_resync" ||
|
||||
lastPreflight["action_reason"] != "consumer_cursor_before_first_retained_sequence" ||
|
||||
!preflightChecklistContains(lastPreflight["remediation_checklist"], "reset_consumer_cursor", true, false) ||
|
||||
!preflightChecklistContains(lastPreflight["remediation_checklist"], "request_full_adapter_resync", true, false) ||
|
||||
!preflightChecklistContains(lastPreflight["remediation_checklist"], "resume_from_checkpoint_after_resync", true, false) ||
|
||||
lastPreflight["remediation_checklist_status"] != "action_required" ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "required_count")) != 3 ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "satisfied_count")) != 0 ||
|
||||
anyInt64(preflightChecklistCountsValue(lastPreflight["remediation_checklist_counts"], "pending_count")) != 3 ||
|
||||
mapInt64Value(lastPreflight["operator_status_counts"], "resync_required") != 2 ||
|
||||
mapInt64Value(lastPreflight["operator_severity_counts"], "warn") != 2 ||
|
||||
lastPreflight["preflight_attention_status"] != "repeated_resync_required" ||
|
||||
lastPreflight["preflight_attention_reason"] != "resync_required_preflight_repeated" ||
|
||||
anyInt64(lastPreflight["missing_dropped_count"]) != 2 ||
|
||||
anyInt64(lastPreflight["first_retained_sequence"]) != 4 ||
|
||||
anyInt64(lastPreflight["last_retained_sequence"]) != 19 ||
|
||||
anyInt64(lastPreflight["mailbox_dropped_total"]) != 3 ||
|
||||
anyInt64(lastPreflight["resume_sequence"]) != 1 {
|
||||
t.Fatalf("invalid stale last preflight rollup = %+v", lastPreflight)
|
||||
}
|
||||
}
|
||||
|
||||
func preflightChecklistCountsValue(value any, key string) any {
|
||||
switch counts := value.(type) {
|
||||
case map[string]any:
|
||||
return counts[key]
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapInt64Value(value any, key string) int64 {
|
||||
switch items := value.(type) {
|
||||
case map[string]int64:
|
||||
return items[key]
|
||||
case map[string]any:
|
||||
return anyInt64(items[key])
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func boolMapValue(value any, key string) bool {
|
||||
switch items := value.(type) {
|
||||
case map[string]bool:
|
||||
return items[key]
|
||||
case map[string]any:
|
||||
item, _ := items[key].(bool)
|
||||
return item
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func preflightDiagnosticsContractCompatible(rollup map[string]any) bool {
|
||||
for _, feature := range []string{"retained_window", "remediation_checklist", "attention", "operator_counts"} {
|
||||
if !stringAnySliceContains(rollup["diagnostics_contract"], feature) || !boolMapValue(rollup["diagnostics_features"], feature) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func terminalSessionSummaryContractCompatible(summary map[string]any) bool {
|
||||
for _, feature := range []string{"adapter_session_id", "session_state", "reason", "controlled_at"} {
|
||||
if !stringAnySliceContains(summary["summary_contract"], feature) || !boolMapValue(summary["summary_features"], feature) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func noSessionSummaryContractCompatible(summary map[string]any) bool {
|
||||
for _, feature := range []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"} {
|
||||
if !stringAnySliceContains(summary["summary_contract"], feature) || !boolMapValue(summary["summary_features"], feature) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func stringAnySliceContains(value any, want string) bool {
|
||||
switch items := value.(type) {
|
||||
case []string:
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func preflightChecklistContains(value any, step string, required bool, satisfied bool) bool {
|
||||
switch items := value.(type) {
|
||||
case []map[string]any:
|
||||
for _, item := range items {
|
||||
if item["step"] == step && item["required"] == required && item["satisfied"] == satisfied && item["source_hint"] == true {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, raw := range items {
|
||||
item, ok := raw.(map[string]any)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if item["step"] == step && item["required"] == required && item["satisfied"] == satisfied && item["source_hint"] == true {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func stringSliceContains(items []string, want string) bool {
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func anyInt64(value any) int64 {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return int64(v)
|
||||
case int64:
|
||||
return v
|
||||
case float64:
|
||||
return int64(v)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspacePreflightDiagnosticsContractCompatibility(t *testing.T) {
|
||||
compatible := map[string]any{
|
||||
"diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"},
|
||||
"diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true},
|
||||
}
|
||||
if !preflightDiagnosticsContractCompatible(compatible) {
|
||||
t.Fatalf("expected contract/features to be compatible")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rollup map[string]any
|
||||
}{
|
||||
{
|
||||
name: "missing contract item",
|
||||
rollup: map[string]any{
|
||||
"diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention"},
|
||||
"diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing feature flag",
|
||||
rollup: map[string]any{
|
||||
"diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"},
|
||||
"diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "false feature flag",
|
||||
rollup: map[string]any{
|
||||
"diagnostics_contract": []string{"retained_window", "remediation_checklist", "attention", "operator_counts"},
|
||||
"diagnostics_features": map[string]bool{"retained_window": true, "remediation_checklist": true, "attention": true, "operator_counts": false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if preflightDiagnosticsContractCompatible(tt.rollup) {
|
||||
t.Fatalf("expected incompatible contract/features for %+v", tt.rollup)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceTerminalSessionSummaryContractCompatibility(t *testing.T) {
|
||||
compatible := map[string]any{
|
||||
"summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"},
|
||||
"summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true},
|
||||
}
|
||||
if !terminalSessionSummaryContractCompatible(compatible) {
|
||||
t.Fatalf("expected summary contract/features to be compatible")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
summary map[string]any
|
||||
}{
|
||||
{
|
||||
name: "missing contract item",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"adapter_session_id", "session_state", "reason"},
|
||||
"summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing feature flag",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"},
|
||||
"summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "false feature flag",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"adapter_session_id", "session_state", "reason", "controlled_at"},
|
||||
"summary_features": map[string]bool{"adapter_session_id": true, "session_state": true, "reason": true, "controlled_at": false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if terminalSessionSummaryContractCompatible(tt.summary) {
|
||||
t.Fatalf("expected incompatible summary contract/features for %+v", tt.summary)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceNoSessionSummaryContractCompatibility(t *testing.T) {
|
||||
compatible := map[string]any{
|
||||
"summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"},
|
||||
"summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true},
|
||||
}
|
||||
if !noSessionSummaryContractCompatible(compatible) {
|
||||
t.Fatalf("expected no-session summary contract/features to be compatible")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
summary map[string]any
|
||||
}{
|
||||
{
|
||||
name: "missing contract item",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"status", "diagnostic_state", "active_session_count"},
|
||||
"summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing feature flag",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"},
|
||||
"summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "false feature flag",
|
||||
summary: map[string]any{
|
||||
"summary_contract": []string{"status", "diagnostic_state", "active_session_count", "terminal_session_count"},
|
||||
"summary_features": map[string]bool{"status": true, "diagnostic_state": true, "active_session_count": true, "terminal_session_count": false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if noSessionSummaryContractCompatible(tt.summary) {
|
||||
t.Fatalf("expected incompatible no-session summary contract/features for %+v", tt.summary)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspaceAdapterSessionMailboxPreflightRejectsInvalidRequests(t *testing.T) {
|
||||
@@ -3145,6 +3986,57 @@ func TestRemoteWorkspaceAdapterSessionMailboxPreflightRejectsInvalidRequests(t *
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteWorkspacePreflightAttentionReasonSummaries(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
statusCounts map[string]int64
|
||||
severityCounts map[string]int64
|
||||
wantStatus string
|
||||
wantReason string
|
||||
}{
|
||||
{
|
||||
name: "clean ready",
|
||||
statusCounts: map[string]int64{"ready_to_resume": 1},
|
||||
severityCounts: map[string]int64{"ok": 1},
|
||||
wantStatus: "clean",
|
||||
wantReason: "no_resync_required_preflight_observed",
|
||||
},
|
||||
{
|
||||
name: "single resync",
|
||||
statusCounts: map[string]int64{"resync_required": 1},
|
||||
severityCounts: map[string]int64{"warn": 1},
|
||||
wantStatus: "needs_attention",
|
||||
wantReason: "resync_required_preflight_observed",
|
||||
},
|
||||
{
|
||||
name: "repeated resync",
|
||||
statusCounts: map[string]int64{"resync_required": 2},
|
||||
severityCounts: map[string]int64{"warn": 2},
|
||||
wantStatus: "repeated_resync_required",
|
||||
wantReason: "resync_required_preflight_repeated",
|
||||
},
|
||||
{
|
||||
name: "none observed",
|
||||
statusCounts: map[string]int64{},
|
||||
severityCounts: map[string]int64{},
|
||||
wantStatus: "unknown",
|
||||
wantReason: "no_preflight_observed",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
status := remoteWorkspacePreflightAttentionStatus(tt.statusCounts, tt.severityCounts)
|
||||
if status != tt.wantStatus {
|
||||
t.Fatalf("status=%q want %q", status, tt.wantStatus)
|
||||
}
|
||||
reason := remoteWorkspacePreflightAttentionReason(status, tt.statusCounts, tt.severityCounts)
|
||||
if reason != tt.wantReason {
|
||||
t.Fatalf("reason=%q want %q", reason, tt.wantReason)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFabricServiceChannelVPNPacketIngressHonorsDisabledBackendRelayPolicy(t *testing.T) {
|
||||
publicKey, privateKey, err := ed25519.GenerateKey(nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -14,6 +14,14 @@ type Supervisor interface {
|
||||
|
||||
type StubSupervisor struct {
|
||||
Version string
|
||||
RemoteWorkspaceRealAdapter RemoteWorkspaceRealAdapterConfig
|
||||
}
|
||||
|
||||
type RemoteWorkspaceRealAdapterConfig struct {
|
||||
EnabledRequested bool
|
||||
Command string
|
||||
ArgsJSON string
|
||||
WorkDir string
|
||||
}
|
||||
|
||||
func (s StubSupervisor) Apply(_ context.Context, desired []client.DesiredWorkload) ([]client.WorkloadStatusRequest, error) {
|
||||
@@ -85,6 +93,7 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
payload["backend_relay_steady_state"] = false
|
||||
payload["channels"] = remoteWorkspaceAdapterChannels()
|
||||
payload["frame_batch_contract"] = remoteWorkspaceFrameBatchContract()
|
||||
payload["real_adapter_supervision"] = remoteWorkspaceRealAdapterSupervisionContract(s.RemoteWorkspaceRealAdapter)
|
||||
payload["traffic"] = "none"
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "running",
|
||||
@@ -93,6 +102,20 @@ func (s StubSupervisor) applyOne(workload client.DesiredWorkload) client.Workloa
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
if serviceType == "rdp-worker" && runtimeMode == "native" && boolConfig(workload.Config, "real_adapter_supervision") {
|
||||
payload["reason"] = "remote_workspace_real_adapter_supervision_disabled"
|
||||
payload["execution_mode"] = "real_adapter_supervision_disabled"
|
||||
payload["service_class"] = "remote_workspace"
|
||||
payload["traffic"] = "blocked"
|
||||
payload["payload_traffic"] = "none"
|
||||
payload["real_adapter_supervision"] = remoteWorkspaceRealAdapterSupervisionContract(s.RemoteWorkspaceRealAdapter)
|
||||
return client.WorkloadStatusRequest{
|
||||
ReportedState: "degraded",
|
||||
RuntimeMode: runtimeMode,
|
||||
Version: version,
|
||||
StatusPayload: payload,
|
||||
}
|
||||
}
|
||||
payload["reason"] = "service_runtime_not_implemented"
|
||||
payload["traffic"] = "blocked"
|
||||
return client.WorkloadStatusRequest{
|
||||
@@ -152,6 +175,166 @@ func remoteWorkspaceFrameBatchContract() map[string]any {
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceRealAdapterSupervisionContract(configs ...RemoteWorkspaceRealAdapterConfig) map[string]any {
|
||||
var config RemoteWorkspaceRealAdapterConfig
|
||||
if len(configs) > 0 {
|
||||
config = configs[0]
|
||||
}
|
||||
return map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_supervision.v1",
|
||||
"enabled": false,
|
||||
"activation_state": "disabled_until_real_runtime_stage",
|
||||
"execution_mode": "real_adapter_supervision_disabled",
|
||||
"payload_traffic": "none",
|
||||
"process_model": "external_rdp_worker_process",
|
||||
"config_projection": remoteWorkspaceRealAdapterConfigProjection(config),
|
||||
"activation_decision": remoteWorkspaceRealAdapterActivationDecision(config),
|
||||
"process_supervisor_preconditions": remoteWorkspaceRealAdapterProcessSupervisorPreconditions(config),
|
||||
"process_health_probe": remoteWorkspaceRealAdapterProcessHealthProbe(),
|
||||
"features": map[string]any{
|
||||
"config_projection": true,
|
||||
"activation_decision": true,
|
||||
"missing_gates": true,
|
||||
"process_health_probe": true,
|
||||
"process_health_probe_disabled": true,
|
||||
"process_supervisor_preconditions": true,
|
||||
"process_supervisor_start_disabled": true,
|
||||
"raw_values_redacted": true,
|
||||
},
|
||||
"config_env": []string{
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR",
|
||||
},
|
||||
"status_contract": []string{
|
||||
"schema_version",
|
||||
"enabled",
|
||||
"activation_state",
|
||||
"execution_mode",
|
||||
"payload_traffic",
|
||||
"process_model",
|
||||
"config_projection",
|
||||
"activation_decision",
|
||||
"process_supervisor_preconditions",
|
||||
"process_health_probe",
|
||||
"features",
|
||||
"config_env",
|
||||
"status_contract",
|
||||
},
|
||||
"guardrails": []string{
|
||||
"contract_probe_remains_default",
|
||||
"no_payload_forwarding_until_real_runtime_stage",
|
||||
"backend_relay_not_steady_state",
|
||||
"fabric_service_channel_required",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceRealAdapterProcessHealthProbe() map[string]any {
|
||||
return map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1",
|
||||
"health_probe_enabled": false,
|
||||
"reason": "disabled_until_real_runtime_stage",
|
||||
"payload_traffic": "none",
|
||||
"probe_model": "external_process_health",
|
||||
"required_signals": []string{
|
||||
"process_started",
|
||||
"process_exit_status",
|
||||
"adapter_control_channel_ready",
|
||||
"fabric_service_channel_bound",
|
||||
"payload_forwarding_contract_ready",
|
||||
},
|
||||
"missing_signals": []string{
|
||||
"process_started",
|
||||
"process_exit_status",
|
||||
"adapter_control_channel_ready",
|
||||
"fabric_service_channel_bound",
|
||||
"payload_forwarding_contract_ready",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceRealAdapterProcessSupervisorPreconditions(config RemoteWorkspaceRealAdapterConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1",
|
||||
"process_start_allowed": false,
|
||||
"reason": "disabled_until_real_runtime_stage",
|
||||
"command_config_present": strings.TrimSpace(config.Command) != "",
|
||||
"workdir_config_present": strings.TrimSpace(config.WorkDir) != "",
|
||||
"args_config_present": strings.TrimSpace(config.ArgsJSON) != "",
|
||||
"required_checks": []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"command_config_validated",
|
||||
"workdir_config_validated",
|
||||
"process_identity_policy_bound",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"payload_forwarding_contract_enabled",
|
||||
"health_probe_contract_enabled",
|
||||
},
|
||||
"missing_checks": []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"command_config_validated",
|
||||
"workdir_config_validated",
|
||||
"process_identity_policy_bound",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"payload_forwarding_contract_enabled",
|
||||
"health_probe_contract_enabled",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceRealAdapterActivationDecision(config RemoteWorkspaceRealAdapterConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1",
|
||||
"decision": "blocked",
|
||||
"reason": "real_runtime_stage_not_enabled",
|
||||
"enabled_requested": config.EnabledRequested,
|
||||
"activation_allowed": false,
|
||||
"payload_traffic": "none",
|
||||
"required_gates": []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"adapter_process_supervisor_enabled",
|
||||
"payload_forwarding_contract_enabled",
|
||||
},
|
||||
"missing_gates": []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"adapter_process_supervisor_enabled",
|
||||
"payload_forwarding_contract_enabled",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceRealAdapterConfigProjection(config RemoteWorkspaceRealAdapterConfig) map[string]any {
|
||||
return map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1",
|
||||
"enabled_requested": config.EnabledRequested,
|
||||
"activation_allowed": false,
|
||||
"command_present": strings.TrimSpace(config.Command) != "",
|
||||
"args_json_present": strings.TrimSpace(config.ArgsJSON) != "",
|
||||
"args_json_shape": remoteWorkspaceArgsJSONShape(config.ArgsJSON),
|
||||
"workdir_present": strings.TrimSpace(config.WorkDir) != "",
|
||||
"raw_values_redacted": true,
|
||||
}
|
||||
}
|
||||
|
||||
func remoteWorkspaceArgsJSONShape(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return "absent"
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(trimmed, "["):
|
||||
return "json_array"
|
||||
case strings.HasPrefix(trimmed, "{"):
|
||||
return "json_object"
|
||||
default:
|
||||
return "opaque"
|
||||
}
|
||||
}
|
||||
|
||||
func serviceTrafficMode(serviceType string) string {
|
||||
switch serviceType {
|
||||
case "core-mesh":
|
||||
|
||||
@@ -130,4 +130,469 @@ func TestStubSupervisorRunsRDPWorkerAdapterContractProbeOnly(t *testing.T) {
|
||||
frameBatch["service_class"] != "remote_workspace" {
|
||||
t.Fatalf("unexpected frame batch contract: %#v", frameBatch)
|
||||
}
|
||||
realAdapter, ok := statuses[0].StatusPayload["real_adapter_supervision"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("real_adapter_supervision = %#v", statuses[0].StatusPayload["real_adapter_supervision"])
|
||||
}
|
||||
if realAdapter["schema_version"] != "rap.remote_workspace_real_adapter_supervision.v1" ||
|
||||
realAdapter["enabled"] != false ||
|
||||
realAdapter["activation_state"] != "disabled_until_real_runtime_stage" ||
|
||||
realAdapter["payload_traffic"] != "none" {
|
||||
t.Fatalf("unexpected real adapter supervision contract: %#v", realAdapter)
|
||||
}
|
||||
if !realAdapterSupervisionContractCompatible(realAdapter) {
|
||||
t.Fatalf("real adapter supervision contract is not compatible: %#v", realAdapter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorKeepsContractProbePrecedenceWhenRealAdapterAlsoRequested(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{
|
||||
Version: "test",
|
||||
RemoteWorkspaceRealAdapter: RemoteWorkspaceRealAdapterConfig{
|
||||
EnabledRequested: true,
|
||||
Command: "/opt/rap/bin/rdp-worker",
|
||||
ArgsJSON: `["--future-probe"]`,
|
||||
WorkDir: "/var/lib/rap-node-agent/rdp-worker",
|
||||
},
|
||||
}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "rdp-worker",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"adapter_contract_probe": true,
|
||||
"real_adapter_supervision": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "running" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
payload := statuses[0].StatusPayload
|
||||
if payload["execution_mode"] != "contract_probe" ||
|
||||
payload["reason"] != "remote_workspace_adapter_contract_probe_ready" ||
|
||||
payload["traffic"] != "none" {
|
||||
t.Fatalf("contract probe did not retain precedence: %#v", payload)
|
||||
}
|
||||
realAdapter, ok := payload["real_adapter_supervision"].(map[string]any)
|
||||
if !ok || !realAdapterSupervisionContractCompatible(realAdapter) {
|
||||
t.Fatalf("real_adapter_supervision = %#v", payload["real_adapter_supervision"])
|
||||
}
|
||||
decision := realAdapter["activation_decision"].(map[string]any)
|
||||
if realAdapter["enabled"] != false ||
|
||||
decision["decision"] != "blocked" ||
|
||||
decision["reason"] != "real_runtime_stage_not_enabled" ||
|
||||
decision["payload_traffic"] != "none" {
|
||||
t.Fatalf("unexpected activation decision under contract-probe precedence: %#v", realAdapter)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStubSupervisorKeepsRealAdapterSupervisionDisabled(t *testing.T) {
|
||||
statuses, err := (StubSupervisor{
|
||||
Version: "test",
|
||||
RemoteWorkspaceRealAdapter: RemoteWorkspaceRealAdapterConfig{
|
||||
EnabledRequested: true,
|
||||
Command: "/opt/rap/bin/rdp-worker",
|
||||
ArgsJSON: `["--future-probe"]`,
|
||||
WorkDir: "/var/lib/rap-node-agent/rdp-worker",
|
||||
},
|
||||
}).Apply(context.Background(), []client.DesiredWorkload{
|
||||
{
|
||||
ServiceType: "rdp-worker",
|
||||
DesiredState: "enabled",
|
||||
RuntimeMode: "native",
|
||||
Config: map[string]any{
|
||||
"real_adapter_supervision": true,
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("apply desired workload: %v", err)
|
||||
}
|
||||
if statuses[0].ReportedState != "degraded" {
|
||||
t.Fatalf("ReportedState = %q", statuses[0].ReportedState)
|
||||
}
|
||||
if statuses[0].StatusPayload["reason"] != "remote_workspace_real_adapter_supervision_disabled" ||
|
||||
statuses[0].StatusPayload["execution_mode"] != "real_adapter_supervision_disabled" ||
|
||||
statuses[0].StatusPayload["traffic"] != "blocked" ||
|
||||
statuses[0].StatusPayload["payload_traffic"] != "none" {
|
||||
t.Fatalf("unexpected real adapter disabled payload: %#v", statuses[0].StatusPayload)
|
||||
}
|
||||
realAdapter, ok := statuses[0].StatusPayload["real_adapter_supervision"].(map[string]any)
|
||||
if !ok || !realAdapterSupervisionContractCompatible(realAdapter) {
|
||||
t.Fatalf("real adapter supervision contract = %#v", statuses[0].StatusPayload["real_adapter_supervision"])
|
||||
}
|
||||
projection, ok := realAdapter["config_projection"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("config_projection = %#v", realAdapter["config_projection"])
|
||||
}
|
||||
if realAdapter["enabled"] != false ||
|
||||
projection["enabled_requested"] != true ||
|
||||
projection["activation_allowed"] != false ||
|
||||
projection["command_present"] != true ||
|
||||
projection["args_json_present"] != true ||
|
||||
projection["args_json_shape"] != "json_array" ||
|
||||
projection["workdir_present"] != true ||
|
||||
projection["raw_values_redacted"] != true {
|
||||
t.Fatalf("unexpected config projection: %#v", projection)
|
||||
}
|
||||
decision, ok := realAdapter["activation_decision"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("activation_decision = %#v", realAdapter["activation_decision"])
|
||||
}
|
||||
if decision["decision"] != "blocked" ||
|
||||
decision["reason"] != "real_runtime_stage_not_enabled" ||
|
||||
decision["enabled_requested"] != true ||
|
||||
decision["activation_allowed"] != false ||
|
||||
decision["payload_traffic"] != "none" {
|
||||
t.Fatalf("unexpected activation decision: %#v", decision)
|
||||
}
|
||||
features, ok := realAdapter["features"].(map[string]any)
|
||||
if !ok ||
|
||||
features["config_projection"] != true ||
|
||||
features["activation_decision"] != true ||
|
||||
features["process_supervisor_preconditions"] != true ||
|
||||
features["process_supervisor_start_disabled"] != true ||
|
||||
features["missing_gates"] != true ||
|
||||
features["raw_values_redacted"] != true {
|
||||
t.Fatalf("unexpected real adapter features: %#v", realAdapter["features"])
|
||||
}
|
||||
preconditions, ok := realAdapter["process_supervisor_preconditions"].(map[string]any)
|
||||
if !ok ||
|
||||
preconditions["schema_version"] != "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" ||
|
||||
preconditions["process_start_allowed"] != false ||
|
||||
preconditions["command_config_present"] != true ||
|
||||
preconditions["args_config_present"] != true ||
|
||||
preconditions["workdir_config_present"] != true {
|
||||
t.Fatalf("unexpected process supervisor preconditions: %#v", realAdapter["process_supervisor_preconditions"])
|
||||
}
|
||||
healthProbe, ok := realAdapter["process_health_probe"].(map[string]any)
|
||||
if !ok ||
|
||||
healthProbe["schema_version"] != "rap.remote_workspace_real_adapter_process_health_probe.v1" ||
|
||||
healthProbe["health_probe_enabled"] != false ||
|
||||
healthProbe["payload_traffic"] != "none" {
|
||||
t.Fatalf("unexpected process health probe: %#v", realAdapter["process_health_probe"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealAdapterSupervisionContractCompatibility(t *testing.T) {
|
||||
compatible := remoteWorkspaceRealAdapterSupervisionContract()
|
||||
if !realAdapterSupervisionContractCompatible(compatible) {
|
||||
t.Fatalf("expected real adapter supervision contract to be compatible")
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
contract map[string]any
|
||||
}{
|
||||
{
|
||||
name: "enabled",
|
||||
contract: map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_supervision.v1",
|
||||
"enabled": true,
|
||||
"activation_state": "disabled_until_real_runtime_stage",
|
||||
"payload_traffic": "none",
|
||||
"config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true},
|
||||
"activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}},
|
||||
"process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}},
|
||||
"process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}},
|
||||
"features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true},
|
||||
"config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR"},
|
||||
"status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"},
|
||||
"guardrails": []string{"contract_probe_remains_default", "no_payload_forwarding_until_real_runtime_stage", "backend_relay_not_steady_state", "fabric_service_channel_required"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing env",
|
||||
contract: map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_supervision.v1",
|
||||
"enabled": false,
|
||||
"activation_state": "disabled_until_real_runtime_stage",
|
||||
"payload_traffic": "none",
|
||||
"config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true},
|
||||
"activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}},
|
||||
"process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}},
|
||||
"process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}},
|
||||
"features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true},
|
||||
"config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED"},
|
||||
"status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"},
|
||||
"guardrails": []string{"contract_probe_remains_default", "no_payload_forwarding_until_real_runtime_stage", "backend_relay_not_steady_state", "fabric_service_channel_required"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing guardrail",
|
||||
contract: map[string]any{
|
||||
"schema_version": "rap.remote_workspace_real_adapter_supervision.v1",
|
||||
"enabled": false,
|
||||
"activation_state": "disabled_until_real_runtime_stage",
|
||||
"payload_traffic": "none",
|
||||
"config_projection": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_config_projection.v1", "activation_allowed": false, "raw_values_redacted": true},
|
||||
"activation_decision": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_activation_decision.v1", "decision": "blocked", "reason": "real_runtime_stage_not_enabled", "activation_allowed": false, "payload_traffic": "none", "required_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}, "missing_gates": []string{"real_runtime_stage_enabled", "fabric_service_channel_runtime_ready", "adapter_process_supervisor_enabled", "payload_forwarding_contract_enabled"}},
|
||||
"process_supervisor_preconditions": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1", "process_start_allowed": false, "reason": "disabled_until_real_runtime_stage", "required_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}, "missing_checks": []string{"real_runtime_stage_enabled", "command_config_validated", "workdir_config_validated", "process_identity_policy_bound", "fabric_service_channel_runtime_ready", "payload_forwarding_contract_enabled", "health_probe_contract_enabled"}},
|
||||
"process_health_probe": map[string]any{"schema_version": "rap.remote_workspace_real_adapter_process_health_probe.v1", "health_probe_enabled": false, "reason": "disabled_until_real_runtime_stage", "payload_traffic": "none", "probe_model": "external_process_health", "required_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}, "missing_signals": []string{"process_started", "process_exit_status", "adapter_control_channel_ready", "fabric_service_channel_bound", "payload_forwarding_contract_ready"}},
|
||||
"features": map[string]any{"config_projection": true, "activation_decision": true, "missing_gates": true, "process_health_probe": true, "process_health_probe_disabled": true, "process_supervisor_preconditions": true, "process_supervisor_start_disabled": true, "raw_values_redacted": true},
|
||||
"config_env": []string{"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON", "RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR"},
|
||||
"status_contract": []string{"schema_version", "enabled", "activation_state", "execution_mode", "payload_traffic", "process_model", "config_projection", "activation_decision", "process_supervisor_preconditions", "process_health_probe", "features", "config_env", "status_contract"},
|
||||
"guardrails": []string{"contract_probe_remains_default"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if realAdapterSupervisionContractCompatible(tt.contract) {
|
||||
t.Fatalf("expected incompatible contract for %+v", tt.contract)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealAdapterConfigProjectionCompatibility(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config RemoteWorkspaceRealAdapterConfig
|
||||
enabledRequested bool
|
||||
commandPresent bool
|
||||
argsJSONPresent bool
|
||||
argsJSONShape string
|
||||
workdirPresent bool
|
||||
}{
|
||||
{
|
||||
name: "default empty",
|
||||
argsJSONShape: "absent",
|
||||
},
|
||||
{
|
||||
name: "requested array args",
|
||||
config: RemoteWorkspaceRealAdapterConfig{
|
||||
EnabledRequested: true,
|
||||
Command: "/opt/rap/bin/rdp-worker",
|
||||
ArgsJSON: `["--future-probe"]`,
|
||||
WorkDir: "/var/lib/rap-node-agent/rdp-worker",
|
||||
},
|
||||
enabledRequested: true,
|
||||
commandPresent: true,
|
||||
argsJSONPresent: true,
|
||||
argsJSONShape: "json_array",
|
||||
workdirPresent: true,
|
||||
},
|
||||
{
|
||||
name: "object args shape",
|
||||
config: RemoteWorkspaceRealAdapterConfig{
|
||||
ArgsJSON: `{"arg":"value"}`,
|
||||
},
|
||||
argsJSONPresent: true,
|
||||
argsJSONShape: "json_object",
|
||||
},
|
||||
{
|
||||
name: "opaque args shape",
|
||||
config: RemoteWorkspaceRealAdapterConfig{
|
||||
ArgsJSON: "--future-probe",
|
||||
},
|
||||
argsJSONPresent: true,
|
||||
argsJSONShape: "opaque",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
contract := remoteWorkspaceRealAdapterSupervisionContract(tt.config)
|
||||
if !realAdapterSupervisionContractCompatible(contract) {
|
||||
t.Fatalf("contract is not compatible: %#v", contract)
|
||||
}
|
||||
projection := contract["config_projection"].(map[string]any)
|
||||
if projection["enabled_requested"] != tt.enabledRequested ||
|
||||
projection["activation_allowed"] != false ||
|
||||
projection["command_present"] != tt.commandPresent ||
|
||||
projection["args_json_present"] != tt.argsJSONPresent ||
|
||||
projection["args_json_shape"] != tt.argsJSONShape ||
|
||||
projection["workdir_present"] != tt.workdirPresent ||
|
||||
projection["raw_values_redacted"] != true {
|
||||
t.Fatalf("unexpected config projection: %#v", projection)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealAdapterProjectionAndActivationDecisionStayAligned(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config RemoteWorkspaceRealAdapterConfig
|
||||
enabledRequested bool
|
||||
}{
|
||||
{name: "default"},
|
||||
{
|
||||
name: "requested",
|
||||
config: RemoteWorkspaceRealAdapterConfig{
|
||||
EnabledRequested: true,
|
||||
Command: "/opt/rap/bin/rdp-worker",
|
||||
ArgsJSON: `["--future-probe"]`,
|
||||
WorkDir: "/var/lib/rap-node-agent/rdp-worker",
|
||||
},
|
||||
enabledRequested: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
contract := remoteWorkspaceRealAdapterSupervisionContract(tt.config)
|
||||
projection := contract["config_projection"].(map[string]any)
|
||||
decision := contract["activation_decision"].(map[string]any)
|
||||
if projection["enabled_requested"] != decision["enabled_requested"] ||
|
||||
projection["enabled_requested"] != tt.enabledRequested ||
|
||||
projection["activation_allowed"] != false ||
|
||||
decision["activation_allowed"] != false ||
|
||||
contract["enabled"] != false ||
|
||||
contract["payload_traffic"] != decision["payload_traffic"] {
|
||||
t.Fatalf("projection and activation decision are not aligned: contract=%#v", contract)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func realAdapterSupervisionContractCompatible(contract map[string]any) bool {
|
||||
if contract["schema_version"] != "rap.remote_workspace_real_adapter_supervision.v1" ||
|
||||
contract["enabled"] != false ||
|
||||
contract["activation_state"] != "disabled_until_real_runtime_stage" ||
|
||||
contract["payload_traffic"] != "none" {
|
||||
return false
|
||||
}
|
||||
projection, ok := contract["config_projection"].(map[string]any)
|
||||
if !ok ||
|
||||
projection["schema_version"] != "rap.remote_workspace_real_adapter_config_projection.v1" ||
|
||||
projection["activation_allowed"] != false ||
|
||||
projection["raw_values_redacted"] != true {
|
||||
return false
|
||||
}
|
||||
decision, ok := contract["activation_decision"].(map[string]any)
|
||||
if !ok ||
|
||||
decision["schema_version"] != "rap.remote_workspace_real_adapter_activation_decision.v1" ||
|
||||
decision["decision"] != "blocked" ||
|
||||
decision["reason"] != "real_runtime_stage_not_enabled" ||
|
||||
decision["activation_allowed"] != false ||
|
||||
decision["payload_traffic"] != "none" {
|
||||
return false
|
||||
}
|
||||
for _, item := range []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"adapter_process_supervisor_enabled",
|
||||
"payload_forwarding_contract_enabled",
|
||||
} {
|
||||
if !anyStringSliceContains(decision["required_gates"], item) || !anyStringSliceContains(decision["missing_gates"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
preconditions, ok := contract["process_supervisor_preconditions"].(map[string]any)
|
||||
if !ok ||
|
||||
preconditions["schema_version"] != "rap.remote_workspace_real_adapter_process_supervisor_preconditions.v1" ||
|
||||
preconditions["process_start_allowed"] != false ||
|
||||
preconditions["reason"] != "disabled_until_real_runtime_stage" {
|
||||
return false
|
||||
}
|
||||
for _, item := range []string{
|
||||
"real_runtime_stage_enabled",
|
||||
"command_config_validated",
|
||||
"workdir_config_validated",
|
||||
"process_identity_policy_bound",
|
||||
"fabric_service_channel_runtime_ready",
|
||||
"payload_forwarding_contract_enabled",
|
||||
"health_probe_contract_enabled",
|
||||
} {
|
||||
if !anyStringSliceContains(preconditions["required_checks"], item) || !anyStringSliceContains(preconditions["missing_checks"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
healthProbe, ok := contract["process_health_probe"].(map[string]any)
|
||||
if !ok ||
|
||||
healthProbe["schema_version"] != "rap.remote_workspace_real_adapter_process_health_probe.v1" ||
|
||||
healthProbe["health_probe_enabled"] != false ||
|
||||
healthProbe["reason"] != "disabled_until_real_runtime_stage" ||
|
||||
healthProbe["payload_traffic"] != "none" ||
|
||||
healthProbe["probe_model"] != "external_process_health" {
|
||||
return false
|
||||
}
|
||||
for _, item := range []string{
|
||||
"process_started",
|
||||
"process_exit_status",
|
||||
"adapter_control_channel_ready",
|
||||
"fabric_service_channel_bound",
|
||||
"payload_forwarding_contract_ready",
|
||||
} {
|
||||
if !anyStringSliceContains(healthProbe["required_signals"], item) || !anyStringSliceContains(healthProbe["missing_signals"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
features, ok := contract["features"].(map[string]any)
|
||||
if !ok ||
|
||||
features["config_projection"] != true ||
|
||||
features["activation_decision"] != true ||
|
||||
features["missing_gates"] != true ||
|
||||
features["process_health_probe"] != true ||
|
||||
features["process_health_probe_disabled"] != true ||
|
||||
features["process_supervisor_preconditions"] != true ||
|
||||
features["process_supervisor_start_disabled"] != true ||
|
||||
features["raw_values_redacted"] != true {
|
||||
return false
|
||||
}
|
||||
for _, item := range []string{
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ENABLED",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_COMMAND",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_ARGS_JSON",
|
||||
"RAP_REMOTE_WORKSPACE_REAL_ADAPTER_WORKDIR",
|
||||
} {
|
||||
if !anyStringSliceContains(contract["config_env"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, item := range []string{
|
||||
"schema_version",
|
||||
"enabled",
|
||||
"activation_state",
|
||||
"execution_mode",
|
||||
"payload_traffic",
|
||||
"process_model",
|
||||
"config_projection",
|
||||
"activation_decision",
|
||||
"process_supervisor_preconditions",
|
||||
"process_health_probe",
|
||||
"features",
|
||||
"config_env",
|
||||
"status_contract",
|
||||
} {
|
||||
if !anyStringSliceContains(contract["status_contract"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, item := range []string{
|
||||
"contract_probe_remains_default",
|
||||
"no_payload_forwarding_until_real_runtime_stage",
|
||||
"backend_relay_not_steady_state",
|
||||
"fabric_service_channel_required",
|
||||
} {
|
||||
if !anyStringSliceContains(contract["guardrails"], item) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func anyStringSliceContains(value any, want string) bool {
|
||||
switch items := value.(type) {
|
||||
case []string:
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, item := range items {
|
||||
if item == want {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -184,6 +184,9 @@ func (g *Gateway) Snapshot() map[string]any {
|
||||
if !lastRuntimeActivityAt.IsZero() {
|
||||
out["last_runtime_activity_at"] = lastRuntimeActivityAt.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
if platform := gatewayPlatformSnapshot(g.InterfaceName, g.RouteCIDR); len(platform) > 0 {
|
||||
out["platform"] = platform
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ const (
|
||||
iffNoPI = 0x1000
|
||||
tunSetIFF = 0x400454ca
|
||||
ifNameSize = 16
|
||||
gatewayTunMTU = "1000"
|
||||
gatewayTCPMSS = "900"
|
||||
)
|
||||
|
||||
type tunDevice struct {
|
||||
@@ -86,6 +88,9 @@ func configureGatewayInterface(name, addressCIDR, routeCIDR string) error {
|
||||
if err := runCommand("ip", "addr", "replace", addressCIDR, "dev", name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runCommand("ip", "link", "set", "dev", name, "mtu", gatewayTunMTU); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := runCommand("ip", "link", "set", name, "up"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,11 +123,10 @@ func ensureMasqueradeRules(routeCIDR string) error {
|
||||
}
|
||||
|
||||
func ensureMSSClampRule(interfaceName string) error {
|
||||
err := ensureIPTablesRule("mangle", "FORWARD", "-i", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--clamp-mss-to-pmtu")
|
||||
if err == nil {
|
||||
return nil
|
||||
if err := ensureIPTablesRule("mangle", "FORWARD", "-i", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--set-mss", gatewayTCPMSS); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ensureIPTablesRule("mangle", "FORWARD", "-o", interfaceName, "-p", "tcp", "--tcp-flags", "SYN,RST", "SYN", "-j", "TCPMSS", "--set-mss", gatewayTCPMSS)
|
||||
}
|
||||
|
||||
func defaultIPv4Interface() (string, error) {
|
||||
@@ -204,3 +208,47 @@ func runCommand(name string, args ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func gatewayPlatformSnapshot(interfaceName, routeCIDR string) map[string]any {
|
||||
out := map[string]any{
|
||||
"os": "linux",
|
||||
"interface": interfaceName,
|
||||
"route_cidr": routeCIDR,
|
||||
}
|
||||
if value, err := readTrimmedFile("/proc/sys/net/ipv4/ip_forward"); err == nil {
|
||||
out["ipv4_forward"] = value
|
||||
}
|
||||
for _, key := range []string{"all", "default", interfaceName} {
|
||||
if strings.TrimSpace(key) == "" {
|
||||
continue
|
||||
}
|
||||
if value, err := readTrimmedFile(fmt.Sprintf("/proc/sys/net/ipv4/conf/%s/rp_filter", key)); err == nil {
|
||||
out["rp_filter_"+key] = value
|
||||
}
|
||||
}
|
||||
if interfaceName != "" {
|
||||
out["forward_in_rule"] = iptablesRulePresent("filter", "FORWARD", "-i", interfaceName, "-j", "ACCEPT")
|
||||
out["forward_out_established_rule"] = iptablesRulePresent("filter", "FORWARD", "-o", interfaceName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT")
|
||||
}
|
||||
if routeCIDR != "" {
|
||||
out["masquerade_rule"] = iptablesRulePresent("nat", "POSTROUTING", "-s", routeCIDR, "-j", "MASQUERADE")
|
||||
if egress, err := defaultIPv4Interface(); err == nil && egress != "" {
|
||||
out["default_egress"] = egress
|
||||
out["egress_masquerade_rule"] = iptablesRulePresent("nat", "POSTROUTING", "-s", routeCIDR, "-o", egress, "-j", "MASQUERADE")
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func readTrimmedFile(path string) (string, error) {
|
||||
payload, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(payload)), nil
|
||||
}
|
||||
|
||||
func iptablesRulePresent(table, chain string, rule ...string) bool {
|
||||
checkArgs := append([]string{"-t", table, "-C", chain}, rule...)
|
||||
return exec.Command("iptables", checkArgs...).Run() == nil
|
||||
}
|
||||
|
||||
@@ -21,3 +21,11 @@ func (d *tunDevice) Write(packet []byte) (int, error) {
|
||||
func (d *tunDevice) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func gatewayPlatformSnapshot(interfaceName, routeCIDR string) map[string]any {
|
||||
return map[string]any{
|
||||
"os": "unsupported",
|
||||
"interface": interfaceName,
|
||||
"route_cidr": routeCIDR,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2160,6 +2160,7 @@ type IssueFabricServiceChannelLeaseInput struct {
|
||||
Failover json.RawMessage
|
||||
Metadata json.RawMessage
|
||||
TTL time.Duration
|
||||
BackendFallbackAllowed *bool
|
||||
}
|
||||
|
||||
type UpdateFabricServiceChannelPoolPolicyInput struct {
|
||||
@@ -2531,6 +2532,14 @@ type RenewNodeVPNAssignmentLeaseInput struct {
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type AcquireNodeVPNAssignmentLeaseInput struct {
|
||||
ClusterID string
|
||||
VPNConnectionID string
|
||||
OwnerNodeID string
|
||||
TTL time.Duration
|
||||
Metadata json.RawMessage
|
||||
}
|
||||
|
||||
type ReleaseVPNConnectionLeaseInput struct {
|
||||
ActorUserID string
|
||||
ClusterID string
|
||||
|
||||
@@ -147,6 +147,7 @@ func (m *Module) RegisterRoutes(router chi.Router) {
|
||||
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/release", m.releaseVPNConnectionLease)
|
||||
r.Post("/{clusterID}/vpn-connections/{vpnConnectionID}/leases/{leaseID}/fence", m.fenceVPNConnectionLease)
|
||||
r.Get("/{clusterID}/nodes/{nodeID}/vpn/assignments", m.listNodeVPNAssignments)
|
||||
r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/lease/acquire", m.acquireNodeVPNAssignmentLease)
|
||||
r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/lease/{leaseID}/renew", m.renewNodeVPNAssignmentLease)
|
||||
r.Post("/{clusterID}/nodes/{nodeID}/vpn/assignments/{vpnConnectionID}/status", m.reportNodeVPNAssignmentStatus)
|
||||
r.Get("/{clusterID}/vpn-connections/{vpnConnectionID}/tunnel/stats", m.getVPNPacketStats)
|
||||
@@ -2072,6 +2073,35 @@ func (m *Module) listNodeVPNAssignments(w http.ResponseWriter, r *http.Request)
|
||||
httpx.WriteJSON(w, http.StatusOK, map[string]any{"vpn_assignments": items})
|
||||
}
|
||||
|
||||
func (m *Module) acquireNodeVPNAssignmentLease(w http.ResponseWriter, r *http.Request) {
|
||||
var payload struct {
|
||||
TTLSeconds int `json:"ttl_seconds"`
|
||||
Metadata json.RawMessage `json:"metadata"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
httpx.WriteError(w, http.StatusBadRequest, "invalid vpn node lease acquire payload")
|
||||
return
|
||||
}
|
||||
item, err := m.service.AcquireNodeVPNAssignmentLease(r.Context(), AcquireNodeVPNAssignmentLeaseInput{
|
||||
ClusterID: chi.URLParam(r, "clusterID"),
|
||||
VPNConnectionID: chi.URLParam(r, "vpnConnectionID"),
|
||||
OwnerNodeID: chi.URLParam(r, "nodeID"),
|
||||
TTL: time.Duration(payload.TTLSeconds) * time.Second,
|
||||
Metadata: payload.Metadata,
|
||||
})
|
||||
if writeServiceError(w, err) {
|
||||
return
|
||||
}
|
||||
httpx.WriteJSON(w, http.StatusCreated, map[string]any{"lease": NodeVPNAssignmentLease{
|
||||
LeaseID: item.ID,
|
||||
OwnerNodeID: item.OwnerNodeID,
|
||||
LeaseGeneration: item.LeaseGeneration,
|
||||
Status: item.Status,
|
||||
RenewedAt: item.RenewedAt,
|
||||
ExpiresAt: item.ExpiresAt,
|
||||
}})
|
||||
}
|
||||
|
||||
func (m *Module) renewNodeVPNAssignmentLease(w http.ResponseWriter, r *http.Request) {
|
||||
var payload struct {
|
||||
TTLSeconds int `json:"ttl_seconds"`
|
||||
|
||||
@@ -4758,7 +4758,6 @@ func (s *PostgresStore) vpnEntryEndpointCandidates(ctx context.Context, clusterI
|
||||
}
|
||||
|
||||
func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.RawMessage, metadata json.RawMessage) []map[string]any {
|
||||
localGatewayShortcut := heartbeatCapabilityEnabled(capabilities, "vpn_local_gateway_shortcut")
|
||||
var payload struct {
|
||||
MeshEndpointReport struct {
|
||||
PeerEndpoint string `json:"peer_endpoint"`
|
||||
@@ -4823,9 +4822,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
|
||||
if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" {
|
||||
item["api_base_url"] = apiBaseURL
|
||||
}
|
||||
if localGatewayShortcut {
|
||||
item["local_gateway_shortcut"] = true
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
if len(out) == 0 {
|
||||
@@ -4847,9 +4843,6 @@ func vpnEntryEndpointCandidatesFromHeartbeat(nodeID string, capabilities json.Ra
|
||||
if apiBaseURL := vpnEntryAPIBaseURL(address); apiBaseURL != "" {
|
||||
item["api_base_url"] = apiBaseURL
|
||||
}
|
||||
if localGatewayShortcut {
|
||||
item["local_gateway_shortcut"] = true
|
||||
}
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
@@ -5129,10 +5122,15 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
|
||||
cfg["vpn_fabric_route"] = map[string]any{
|
||||
"schema_version": "rap.vpn_fabric_route.v1",
|
||||
"status": status,
|
||||
"preferred_data_plane": "fabric_mesh",
|
||||
"fallback_data_plane": "backend_relay",
|
||||
"backend_relay_fallback": true,
|
||||
"selection_mode": "entry_to_fastest_exit",
|
||||
"preferred_data_plane": "fabric_service_channel",
|
||||
"fallback_data_plane": "none",
|
||||
"backend_relay_fallback": false,
|
||||
"selection_mode": "farm_authoritative_entry_to_exit",
|
||||
"route_authority": "fabric_farm",
|
||||
"vpn_builds_routes": false,
|
||||
"vpn_builds_tunnels": false,
|
||||
"farm_builds_routes": true,
|
||||
"farm_builds_tunnels": true,
|
||||
"entry_pool_node_ids": entryPool,
|
||||
"exit_pool_node_ids": exitPool,
|
||||
"selected_entry_node_id": selectedEntry,
|
||||
@@ -5147,20 +5145,28 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
|
||||
"tunnel_type": "universal_ip_packet",
|
||||
"application_protocol_agnostic": true,
|
||||
"packet_forwarding_channel": "vpn_packet",
|
||||
"control_plane_packet_relay_mode": "lab_fallback_only",
|
||||
"control_plane_packet_relay_mode": "fabric_service_channel_only",
|
||||
"route_authority": "fabric_farm",
|
||||
"backend_relay_allowed": false,
|
||||
"requires_fabric_service_channel": true,
|
||||
"vpn_builds_routes": false,
|
||||
"vpn_builds_tunnels": false,
|
||||
"farm_builds_routes": true,
|
||||
"farm_builds_tunnels": true,
|
||||
"traffic_contract": map[string]any{
|
||||
"all_ip_traffic": true,
|
||||
"protocol_specific_routing": false,
|
||||
"diagnostics_only_protocol_summaries": true,
|
||||
},
|
||||
"route_selection": map[string]any{
|
||||
"mode": "lowest_latency_healthy_route",
|
||||
"mode": "farm_authoritative_lowest_latency_healthy_route",
|
||||
"selected_entry_node_id": selectedEntry,
|
||||
"selected_exit_node_id": selectedExit,
|
||||
"route_candidates": routeCandidates,
|
||||
},
|
||||
"failover": map[string]any{
|
||||
"enabled": true,
|
||||
"owner": "fabric_farm",
|
||||
"client_topology_hidden": true,
|
||||
"preserve_vpn_connection_id": true,
|
||||
"alternate_route_count": alternateVPNRouteCount(routeCandidates, selectedEntry, selectedExit),
|
||||
@@ -5178,8 +5184,8 @@ func enrichVPNClientFabricRoute(item VPNClientConnection, preferredEntryNodeID,
|
||||
"drop_policy": "drop_only_when_all_routes_unavailable_or_queue_full",
|
||||
"bulk_and_realtime": "same_packet_path",
|
||||
"flow_isolation": "hash_by_ip_protocol_and_ports",
|
||||
"target_dataplane": "entry_node_to_exit_node_fabric",
|
||||
"temporary_fallback": "backend_http_packet_relay",
|
||||
"target_dataplane": "fabric_farm_entry_to_exit_service_channel",
|
||||
"temporary_fallback": "none",
|
||||
},
|
||||
}
|
||||
out, err := json.Marshal(cfg)
|
||||
|
||||
@@ -52,7 +52,7 @@ func TestEnrichVPNClientFabricRoutePrefersPlacementEntryAndActiveExit(t *testing
|
||||
if !ok {
|
||||
t.Fatalf("missing vpn_fabric_route in %#v", cfg)
|
||||
}
|
||||
if route["preferred_data_plane"] != "fabric_mesh" || route["fallback_data_plane"] != "backend_relay" {
|
||||
if route["preferred_data_plane"] != "fabric_service_channel" || route["fallback_data_plane"] != "none" || route["backend_relay_fallback"] != false {
|
||||
t.Fatalf("unexpected data-plane route contract: %#v", route)
|
||||
}
|
||||
if route["selected_entry_node_id"] != "entry-2" || route["selected_exit_node_id"] != "exit-active" {
|
||||
@@ -158,8 +158,8 @@ func TestEnrichVPNClientEntryEndpointCandidatesAddsReportedEntryAPI(t *testing.T
|
||||
if candidate["node_id"] != "entry-1" || candidate["api_base_url"] != "http://entry.example.test:19131/api/v1" {
|
||||
t.Fatalf("unexpected endpoint candidate: %#v", candidate)
|
||||
}
|
||||
if candidate["local_gateway_shortcut"] != true {
|
||||
t.Fatalf("local gateway shortcut missing: %#v", candidate)
|
||||
if _, ok := candidate["local_gateway_shortcut"]; ok {
|
||||
t.Fatalf("local gateway shortcut must not be advertised in farm-owned VPN mode: %#v", candidate)
|
||||
}
|
||||
if candidate["selected_entry"] != true || candidate["source"] != "node_latest_heartbeat.mesh_endpoint_report.endpoint_candidates" {
|
||||
t.Fatalf("unexpected endpoint metadata: %#v", candidate)
|
||||
|
||||
@@ -4015,8 +4015,8 @@ func (s *Service) IssueFabricServiceChannelLease(ctx context.Context, input Issu
|
||||
if ttl <= 0 {
|
||||
ttl = time.Minute
|
||||
}
|
||||
if ttl > 5*time.Minute {
|
||||
ttl = 5 * time.Minute
|
||||
if ttl > 6*time.Hour {
|
||||
ttl = 6 * time.Hour
|
||||
}
|
||||
now := s.now().UTC()
|
||||
expiresAt := now.Add(ttl)
|
||||
@@ -4031,6 +4031,9 @@ func (s *Service) IssueFabricServiceChannelLease(ctx context.Context, input Issu
|
||||
return FabricServiceChannelLease{}, err
|
||||
}
|
||||
poolPolicy := fabricServiceChannelPoolPolicyFromCluster(cluster)
|
||||
if input.BackendFallbackAllowed != nil {
|
||||
poolPolicy.BackendFallbackAllowed = *input.BackendFallbackAllowed
|
||||
}
|
||||
entryNodeIDs := fabricServiceChannelEffectivePool(input.EntryNodeIDs, poolPolicy.EntryPoolNodeIDs)
|
||||
exitNodeIDs := fabricServiceChannelEffectivePool(input.ExitNodeIDs, poolPolicy.ExitPoolNodeIDs)
|
||||
if len(entryNodeIDs) == 0 || len(exitNodeIDs) == 0 {
|
||||
@@ -7303,8 +7306,10 @@ func (s *Service) GetNodeSyntheticMeshConfig(ctx context.Context, input GetNodeS
|
||||
if feedback, ok := serviceChannelFeedback[route.RouteID]; ok && feedback.Fenced {
|
||||
replacementDecision := s.serviceChannelRouteReplacementDecision(input, route, intents, serviceChannelFeedback, cfg.ConfigVersion)
|
||||
routePathDecisions = append(routePathDecisions, replacementDecision)
|
||||
if replacementDecision.DecisionSource != "service_channel_feedback_no_alternate_keep_primary" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
reportedPeers, reportedCandidates, err := s.reportedEndpointConfig(ctx, input.ClusterID, input.NodeID, route.Hops, localPerspective)
|
||||
if err != nil {
|
||||
return NodeSyntheticMeshConfig{}, err
|
||||
@@ -8700,6 +8705,98 @@ func (s *Service) RenewNodeVPNAssignmentLease(ctx context.Context, input RenewNo
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *Service) AcquireNodeVPNAssignmentLease(ctx context.Context, input AcquireNodeVPNAssignmentLeaseInput) (VPNConnectionLease, error) {
|
||||
input.ClusterID = strings.TrimSpace(input.ClusterID)
|
||||
input.VPNConnectionID = strings.TrimSpace(input.VPNConnectionID)
|
||||
input.OwnerNodeID = strings.TrimSpace(input.OwnerNodeID)
|
||||
if input.ClusterID == "" || input.VPNConnectionID == "" || input.OwnerNodeID == "" {
|
||||
return VPNConnectionLease{}, ErrInvalidPayload
|
||||
}
|
||||
conn, err := s.store.GetVPNConnection(ctx, input.ClusterID, input.VPNConnectionID)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return VPNConnectionLease{}, ErrInvalidVPNConnection
|
||||
}
|
||||
if err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
}
|
||||
if conn.Mode != VPNConnectionModeSingleActive || conn.DesiredState != VPNConnectionDesiredEnabled {
|
||||
return VPNConnectionLease{}, errors.New("vpn connection must be enabled single_active before lease acquisition")
|
||||
}
|
||||
if err := s.ensureVPNLeaseOwnerEligible(ctx, input.ClusterID, input.VPNConnectionID, input.OwnerNodeID); err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
}
|
||||
assignments, err := s.store.ListNodeVPNAssignments(ctx, input.ClusterID, input.OwnerNodeID)
|
||||
if err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
}
|
||||
visibleCandidate := false
|
||||
for _, assignment := range assignments {
|
||||
if assignment.VPNConnectionID != input.VPNConnectionID {
|
||||
continue
|
||||
}
|
||||
if assignment.DesiredState != "" && assignment.DesiredState != VPNConnectionDesiredEnabled {
|
||||
return VPNConnectionLease{}, ErrVPNLeaseOwnerNotAllowed
|
||||
}
|
||||
if assignment.AssignmentReason == "active_owner" &&
|
||||
assignment.ActiveLease != nil &&
|
||||
assignment.ActiveLease.OwnerNodeID == input.OwnerNodeID {
|
||||
return VPNConnectionLease{
|
||||
ID: assignment.ActiveLease.LeaseID,
|
||||
VPNConnectionID: assignment.VPNConnectionID,
|
||||
ClusterID: assignment.ClusterID,
|
||||
OwnerNodeID: assignment.ActiveLease.OwnerNodeID,
|
||||
LeaseGeneration: assignment.ActiveLease.LeaseGeneration,
|
||||
Status: assignment.ActiveLease.Status,
|
||||
RenewedAt: assignment.ActiveLease.RenewedAt,
|
||||
ExpiresAt: assignment.ActiveLease.ExpiresAt,
|
||||
}, nil
|
||||
}
|
||||
if assignment.AssignmentReason == "eligible_candidate" {
|
||||
visibleCandidate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !visibleCandidate {
|
||||
return VPNConnectionLease{}, ErrVPNLeaseOwnerNotAllowed
|
||||
}
|
||||
if input.TTL <= 0 {
|
||||
input.TTL = 2 * time.Minute
|
||||
}
|
||||
input.Metadata = defaultJSON(input.Metadata, `{}`)
|
||||
if !json.Valid(input.Metadata) {
|
||||
return VPNConnectionLease{}, errors.New("lease metadata must be valid json")
|
||||
}
|
||||
token, err := generateFencingToken()
|
||||
if err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
}
|
||||
item, err := s.store.AcquireVPNConnectionLease(ctx, AcquireVPNConnectionLeaseInput{
|
||||
ClusterID: input.ClusterID,
|
||||
VPNConnectionID: input.VPNConnectionID,
|
||||
OwnerNodeID: input.OwnerNodeID,
|
||||
TTL: input.TTL,
|
||||
Metadata: input.Metadata,
|
||||
}, s.now().Add(input.TTL), token)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return VPNConnectionLease{}, ErrInvalidVPNLease
|
||||
}
|
||||
if errors.Is(err, ErrVPNLeaseAlreadyActive) {
|
||||
return VPNConnectionLease{}, ErrVPNLeaseAlreadyActive
|
||||
}
|
||||
if err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
}
|
||||
_ = s.store.RecordAudit(ctx, ClusterAuditEvent{
|
||||
ClusterID: &input.ClusterID,
|
||||
EventType: "vpn_connection.lease_acquired_by_node",
|
||||
TargetType: "vpn_connection",
|
||||
TargetID: &input.VPNConnectionID,
|
||||
Payload: json.RawMessage(`{"node_agent_runtime_requested":true}`),
|
||||
CreatedAt: s.now(),
|
||||
})
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *Service) ReleaseVPNConnectionLease(ctx context.Context, input ReleaseVPNConnectionLeaseInput) (VPNConnectionLease, error) {
|
||||
if err := s.ensurePlatformAdmin(ctx, input.ActorUserID); err != nil {
|
||||
return VPNConnectionLease{}, err
|
||||
@@ -8910,6 +9007,7 @@ func (s *Service) attachVPNFabricServiceChannelLeases(ctx context.Context, profi
|
||||
if len(exitPool) == 0 {
|
||||
exitPool = dedupeStrings(append([]string{route.SelectedExitNodeID, connection.ExitNodeID}, connection.AllowedNodeIDs...))
|
||||
}
|
||||
backendFallbackAllowed := false
|
||||
lease, err := s.IssueFabricServiceChannelLease(ctx, IssueFabricServiceChannelLeaseInput{
|
||||
ClusterID: profile.ClusterID,
|
||||
OrganizationID: profile.OrganizationID,
|
||||
@@ -8921,7 +9019,8 @@ func (s *Service) attachVPNFabricServiceChannelLeases(ctx context.Context, profi
|
||||
PreferredEntryNodeID: route.SelectedEntryNodeID,
|
||||
PreferredExitNodeID: route.SelectedExitNodeID,
|
||||
AllowedChannels: []string{"vpn_packet", "fabric_control", FabricChannelBulk, FabricChannelControl},
|
||||
TTL: time.Minute,
|
||||
TTL: 6 * time.Hour,
|
||||
BackendFallbackAllowed: &backendFallbackAllowed,
|
||||
})
|
||||
if err != nil {
|
||||
profile.Connections[i].ClientConfig = attachVPNFabricServiceChannelError(connection.ClientConfig, err)
|
||||
@@ -8996,8 +9095,10 @@ func enrichVPNDataplaneSession(profile VPNClientProfile, connection VPNClientCon
|
||||
"vpn_connection_id": connection.ID,
|
||||
"entry_node_id": route.SelectedEntryNodeID,
|
||||
"exit_node_id": route.SelectedExitNodeID,
|
||||
"preferred_transport": "fabric_packet_quic_v1",
|
||||
"fallback_transport": "backend_http_packet_relay",
|
||||
"preferred_transport": "fabric_service_channel_v1",
|
||||
"fallback_transport": "none",
|
||||
"route_authority": "fabric_farm",
|
||||
"backend_relay_allowed": false,
|
||||
"packet_contract": map[string]any{
|
||||
"tunnel_type": "universal_ip_packet",
|
||||
"application_protocol_agnostic": true,
|
||||
@@ -9089,10 +9190,12 @@ func vpnConcreteEntryCandidatesFromClientConfig(cfg map[string]any) []map[string
|
||||
func vpnDataplaneTransportCandidates(route vpnClientFabricRoute, entryCandidates []map[string]any) []map[string]any {
|
||||
candidates := []map[string]any{
|
||||
{
|
||||
"type": "fabric_packet_quic_v1",
|
||||
"type": "fabric_service_channel_v1",
|
||||
"status": "contract_ready_listener_pending",
|
||||
"entry_node_id": route.SelectedEntryNodeID,
|
||||
"exit_node_id": route.SelectedExitNodeID,
|
||||
"route_authority": "fabric_farm",
|
||||
"backend_relay_allowed": false,
|
||||
"entry_candidates": entryCandidates,
|
||||
"application_protocols": []string{"ip"},
|
||||
},
|
||||
@@ -9100,11 +9203,6 @@ func vpnDataplaneTransportCandidates(route vpnClientFabricRoute, entryCandidates
|
||||
if direct := vpnDirectHTTPEntryTransportCandidate(route, entryCandidates); direct != nil {
|
||||
candidates = append(candidates, direct)
|
||||
}
|
||||
candidates = append(candidates, map[string]any{
|
||||
"type": "backend_http_packet_relay",
|
||||
"status": "active_fallback",
|
||||
"description": "current safe dataplane until entry listener is available",
|
||||
})
|
||||
return candidates
|
||||
}
|
||||
|
||||
@@ -9112,7 +9210,6 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi
|
||||
var selected []map[string]any
|
||||
hasPublic := false
|
||||
hasHTTP := false
|
||||
hasLocalGatewayShortcut := false
|
||||
for _, candidate := range entryCandidates {
|
||||
nodeID, _ := candidate["node_id"].(string)
|
||||
if route.SelectedEntryNodeID != "" && nodeID != route.SelectedEntryNodeID {
|
||||
@@ -9132,9 +9229,6 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi
|
||||
if strings.EqualFold(reachability, "public") {
|
||||
hasPublic = true
|
||||
}
|
||||
if value, ok := candidate["local_gateway_shortcut"].(bool); ok && value {
|
||||
hasLocalGatewayShortcut = true
|
||||
}
|
||||
selected = append(selected, candidate)
|
||||
}
|
||||
if len(selected) == 0 {
|
||||
@@ -9148,13 +9242,8 @@ func vpnDirectHTTPEntryTransportCandidate(route vpnClientFabricRoute, entryCandi
|
||||
}
|
||||
safeClientSwitch := hasPublic
|
||||
if route.SelectedEntryNodeID != "" && route.SelectedEntryNodeID == route.SelectedExitNodeID {
|
||||
if hasPublic && hasLocalGatewayShortcut {
|
||||
status = "available_local_gateway_shortcut"
|
||||
safeClientSwitch = true
|
||||
} else {
|
||||
status = "available_local_gateway_shortcut_pending"
|
||||
safeClientSwitch = false
|
||||
}
|
||||
status = "available_farm_local_route"
|
||||
safeClientSwitch = hasPublic
|
||||
}
|
||||
return map[string]any{
|
||||
"type": "entry_direct_http_v1",
|
||||
@@ -9275,9 +9364,13 @@ func vpnFabricRouteIntentPolicy(sourceNodeID, destinationNodeID string, expiresA
|
||||
"route_version": version,
|
||||
"policy_version": version,
|
||||
"peer_directory_version": version,
|
||||
"backend_relay_fallback": true,
|
||||
"data_plane_preference": "fabric_mesh",
|
||||
"route_owner": "vpn_client_profile",
|
||||
"backend_relay_fallback": false,
|
||||
"data_plane_preference": "fabric_service_channel",
|
||||
"route_owner": "fabric_farm",
|
||||
"vpn_builds_routes": false,
|
||||
"vpn_builds_tunnels": false,
|
||||
"farm_builds_routes": true,
|
||||
"farm_builds_tunnels": true,
|
||||
"route_refresh_required": true,
|
||||
"route_refresh_threshold": "24h",
|
||||
}
|
||||
@@ -11387,11 +11480,11 @@ func (s *Service) serviceChannelRouteReplacementDecision(input GetNodeSyntheticM
|
||||
SourceNodeID: fencedRoute.SourceNodeID,
|
||||
DestinationNodeID: fencedRoute.DestinationNodeID,
|
||||
OriginalHops: append([]string{}, fencedRoute.Hops...),
|
||||
EffectiveHops: []string{},
|
||||
DecisionSource: "service_channel_feedback_no_alternate",
|
||||
EffectiveHops: append([]string{}, fencedRoute.Hops...),
|
||||
DecisionSource: "service_channel_feedback_no_alternate_keep_primary",
|
||||
Generation: generation,
|
||||
PathScore: 0,
|
||||
ScoreReasons: []string{"service_channel_fenced_route", "no_unfenced_alternate_route"},
|
||||
PathScore: serviceChannelReplacementRouteScore(fencedRoute),
|
||||
ScoreReasons: []string{"service_channel_fenced_route", "no_unfenced_alternate_route", "primary_route_retained_until_rebuild"},
|
||||
ControlPlaneOnly: true,
|
||||
ProductionForwarding: false,
|
||||
ExpiresAt: fencedRoute.ExpiresAt.UTC(),
|
||||
@@ -11399,10 +11492,10 @@ func (s *Service) serviceChannelRouteReplacementDecision(input GetNodeSyntheticM
|
||||
applyServiceChannelFeedbackCorrelationToDecision(&decision, routeFeedback)
|
||||
if serviceChannelFeedbackRequestsRebuild(routeFeedback) {
|
||||
decision.RebuildRequestID = serviceChannelRebuildRequestID(fencedRoute.RouteID, input.NodeID, generation)
|
||||
decision.RebuildStatus = "pending_degraded_fallback"
|
||||
decision.RebuildStatus = "requested"
|
||||
decision.RebuildReason = "service_channel_feedback_rebuild_requested"
|
||||
decision.RebuildAttempt = routeFeedback.ConsecutiveFailures
|
||||
decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_rebuild_requested", "backend_relay_degraded_fallback_until_rebuild")
|
||||
decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_rebuild_requested")
|
||||
if routeFeedback.DegradedFallbackRecommended {
|
||||
decision.ScoreReasons = append(decision.ScoreReasons, "service_channel_degraded_fallback_recommended")
|
||||
}
|
||||
|
||||
@@ -732,7 +732,7 @@ func TestGetVPNClientProfileEnsuresFabricVPNPacketRouteIntents(t *testing.T) {
|
||||
if !ok {
|
||||
t.Fatalf("missing vpn_dataplane_session in %#v", cfg)
|
||||
}
|
||||
if session["preferred_transport"] != "fabric_packet_quic_v1" || session["fallback_transport"] != "backend_http_packet_relay" {
|
||||
if session["preferred_transport"] != "fabric_service_channel_v1" || session["fallback_transport"] != "none" || session["backend_relay_allowed"] != false {
|
||||
t.Fatalf("unexpected dataplane session transports: %#v", session)
|
||||
}
|
||||
if session["entry_node_id"] != "entry-1" || session["exit_node_id"] != "exit-1" {
|
||||
@@ -811,7 +811,7 @@ func TestGetVPNClientProfileForwardsPreferredExit(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVPNDirectHTTPEntryTransportWaitsForLocalGatewayShortcutWhenEntryIsExit(t *testing.T) {
|
||||
func TestVPNDirectHTTPEntryTransportUsesFarmLocalRouteWhenEntryIsExit(t *testing.T) {
|
||||
candidate := vpnDirectHTTPEntryTransportCandidate(vpnClientFabricRoute{
|
||||
SelectedEntryNodeID: "node-1",
|
||||
SelectedExitNodeID: "node-1",
|
||||
@@ -823,12 +823,12 @@ func TestVPNDirectHTTPEntryTransportWaitsForLocalGatewayShortcutWhenEntryIsExit(
|
||||
if candidate == nil {
|
||||
t.Fatal("candidate is nil")
|
||||
}
|
||||
if candidate["safe_client_switch"] != false || candidate["status"] != "available_local_gateway_shortcut_pending" {
|
||||
t.Fatalf("unexpected local shortcut guard: %#v", candidate)
|
||||
if candidate["safe_client_switch"] != true || candidate["status"] != "available_farm_local_route" {
|
||||
t.Fatalf("unexpected farm local route guard: %#v", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVPNDirectHTTPEntryTransportAllowsLocalGatewayShortcutWhenReported(t *testing.T) {
|
||||
func TestVPNDirectHTTPEntryTransportIgnoresLegacyLocalGatewayShortcut(t *testing.T) {
|
||||
candidate := vpnDirectHTTPEntryTransportCandidate(vpnClientFabricRoute{
|
||||
SelectedEntryNodeID: "node-1",
|
||||
SelectedExitNodeID: "node-1",
|
||||
@@ -841,8 +841,8 @@ func TestVPNDirectHTTPEntryTransportAllowsLocalGatewayShortcutWhenReported(t *te
|
||||
if candidate == nil {
|
||||
t.Fatal("candidate is nil")
|
||||
}
|
||||
if candidate["safe_client_switch"] != true || candidate["status"] != "available_local_gateway_shortcut" {
|
||||
t.Fatalf("unexpected local shortcut candidate: %#v", candidate)
|
||||
if candidate["safe_client_switch"] != true || candidate["status"] != "available_farm_local_route" {
|
||||
t.Fatalf("unexpected farm route candidate: %#v", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3152,6 +3152,68 @@ func TestListNodeVPNAssignmentsDoesNotRequirePlatformAdmin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquireNodeVPNAssignmentLeaseAllowsEligibleCandidateWithoutPlatformAdmin(t *testing.T) {
|
||||
store := &fakeRepository{
|
||||
platformRole: "user",
|
||||
vpnConnection: VPNConnection{
|
||||
ID: "vpn-1",
|
||||
ClusterID: "cluster-1",
|
||||
Mode: VPNConnectionModeSingleActive,
|
||||
DesiredState: VPNConnectionDesiredEnabled,
|
||||
},
|
||||
nodeVPNAssignments: []NodeVPNAssignment{
|
||||
{
|
||||
VPNConnectionID: "vpn-1",
|
||||
ClusterID: "cluster-1",
|
||||
OrganizationID: "org-1",
|
||||
DesiredState: VPNConnectionDesiredEnabled,
|
||||
AssignmentReason: "eligible_candidate",
|
||||
},
|
||||
},
|
||||
}
|
||||
service := NewService(store)
|
||||
|
||||
lease, err := service.AcquireNodeVPNAssignmentLease(context.Background(), AcquireNodeVPNAssignmentLeaseInput{
|
||||
ClusterID: "cluster-1",
|
||||
VPNConnectionID: "vpn-1",
|
||||
OwnerNodeID: "node-1",
|
||||
TTL: time.Minute,
|
||||
Metadata: json.RawMessage(`{"reason":"test"}`),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("acquire node vpn assignment lease: %v", err)
|
||||
}
|
||||
if lease.OwnerNodeID != "node-1" || lease.VPNConnectionID != "vpn-1" || lease.Status != VPNLeaseStatusActive {
|
||||
t.Fatalf("unexpected lease: %+v", lease)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAcquireNodeVPNAssignmentLeaseRejectsInvisibleAssignment(t *testing.T) {
|
||||
store := &fakeRepository{
|
||||
platformRole: "user",
|
||||
vpnConnection: VPNConnection{
|
||||
ID: "vpn-1",
|
||||
ClusterID: "cluster-1",
|
||||
Mode: VPNConnectionModeSingleActive,
|
||||
DesiredState: VPNConnectionDesiredEnabled,
|
||||
},
|
||||
nodeVPNAssignments: []NodeVPNAssignment{
|
||||
{VPNConnectionID: "other-vpn", ClusterID: "cluster-1", AssignmentReason: "eligible_candidate"},
|
||||
},
|
||||
}
|
||||
service := NewService(store)
|
||||
|
||||
_, err := service.AcquireNodeVPNAssignmentLease(context.Background(), AcquireNodeVPNAssignmentLeaseInput{
|
||||
ClusterID: "cluster-1",
|
||||
VPNConnectionID: "vpn-1",
|
||||
OwnerNodeID: "node-1",
|
||||
TTL: time.Minute,
|
||||
})
|
||||
if !errors.Is(err, ErrVPNLeaseOwnerNotAllowed) {
|
||||
t.Fatalf("err = %v, want ErrVPNLeaseOwnerNotAllowed", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenewNodeVPNAssignmentLeaseAllowsActiveOwnerWithoutPlatformAdmin(t *testing.T) {
|
||||
store := &fakeRepository{
|
||||
platformRole: "user",
|
||||
@@ -6051,18 +6113,24 @@ func TestGetNodeSyntheticMeshConfigReportsRebuildPendingWhenNoAlternateExists(t
|
||||
if err != nil {
|
||||
t.Fatalf("synthetic config: %v", err)
|
||||
}
|
||||
if containsRouteID(cfg.Routes, "route-bad") {
|
||||
t.Fatalf("fenced route should be withheld while rebuild is pending: %+v", cfg.Routes)
|
||||
if !containsRouteID(cfg.Routes, "route-bad") {
|
||||
t.Fatalf("fenced route should be retained until an alternate exists: %+v", cfg.Routes)
|
||||
}
|
||||
if cfg.RoutePathDecisions == nil || cfg.RoutePathDecisions.RebuildRequestCount != 1 || cfg.RoutePathDecisions.DegradedDecisionCount != 1 {
|
||||
if cfg.RoutePathDecisions == nil || cfg.RoutePathDecisions.RebuildRequestCount != 1 || cfg.RoutePathDecisions.DegradedDecisionCount != 0 {
|
||||
t.Fatalf("expected rebuild/degraded decision counts: %+v", cfg.RoutePathDecisions)
|
||||
}
|
||||
decision := cfg.RoutePathDecisions.Decisions[0]
|
||||
if decision.DecisionSource != "service_channel_feedback_no_alternate" ||
|
||||
decision.RebuildStatus != "pending_degraded_fallback" ||
|
||||
var decision RoutePathDecision
|
||||
for _, item := range cfg.RoutePathDecisions.Decisions {
|
||||
if item.DecisionSource == "service_channel_feedback_no_alternate_keep_primary" {
|
||||
decision = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if decision.DecisionSource != "service_channel_feedback_no_alternate_keep_primary" ||
|
||||
decision.RebuildStatus != "requested" ||
|
||||
decision.RebuildRequestID == "" ||
|
||||
decision.RebuildAttempt != 3 ||
|
||||
!containsString(decision.ScoreReasons, "backend_relay_degraded_fallback_until_rebuild") {
|
||||
!containsString(decision.ScoreReasons, "primary_route_retained_until_rebuild") {
|
||||
t.Fatalf("unexpected rebuild decision: %+v", decision)
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -22,7 +22,7 @@ android {
|
||||
return (value == null ? "" : value.toString()).replace("\\", "\\\\").replace("\"", "\\\"")
|
||||
}
|
||||
|
||||
def defaultBackendUrl = project.findProperty("RAP_ANDROID_DEFAULT_BACKEND_URL") ?: "http://vpn.cin.su:19191/api/v1"
|
||||
def defaultBackendUrl = project.findProperty("RAP_ANDROID_DEFAULT_BACKEND_URL") ?: "https://vpn.cin.su/api/v1"
|
||||
def defaultClusterId = project.findProperty("RAP_ANDROID_DEFAULT_CLUSTER_ID") ?: "cfc0743d-d960-49fb-9de8-96e063d5e4aa"
|
||||
def defaultOrganizationId = project.findProperty("RAP_ANDROID_DEFAULT_ORGANIZATION_ID") ?: "125ff8b2-5ac1-4406-9bbb-ebbe18f7c7ed"
|
||||
|
||||
@@ -30,8 +30,8 @@ android {
|
||||
applicationId "su.cin.rapvpn"
|
||||
minSdk 26
|
||||
targetSdk 35
|
||||
versionCode 159
|
||||
versionName "0.2.159"
|
||||
versionCode 182
|
||||
versionName "0.2.182"
|
||||
buildConfigField "String", "DEFAULT_BACKEND_URL", "\"${normalizeGradleString(defaultBackendUrl)}\""
|
||||
buildConfigField "String", "DEFAULT_CLUSTER_ID", "\"${normalizeGradleString(defaultClusterId)}\""
|
||||
buildConfigField "String", "DEFAULT_ORGANIZATION_ID", "\"${normalizeGradleString(defaultOrganizationId)}\""
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Locale;
|
||||
public class MainActivity extends Activity {
|
||||
private static final String APP_VERSION = BuildConfig.VERSION_NAME;
|
||||
private static final String DEFAULT_BACKEND_URL = BuildConfig.DEFAULT_BACKEND_URL;
|
||||
private static final String PUBLIC_FABRIC_BACKEND_URL = "https://vpn.cin.su/api/v1";
|
||||
private static final String DEFAULT_CLUSTER_ID = BuildConfig.DEFAULT_CLUSTER_ID;
|
||||
private static final String DEFAULT_ORGANIZATION_ID = BuildConfig.DEFAULT_ORGANIZATION_ID;
|
||||
private static final String PREF_SELECTED_EXIT_NODE_ID = "selected_exit_node_id";
|
||||
@@ -659,6 +660,16 @@ public class MainActivity extends Activity {
|
||||
if (candidate.isEmpty()) {
|
||||
return DEFAULT_BACKEND_URL;
|
||||
}
|
||||
String lower = candidate.toLowerCase(Locale.US);
|
||||
if ("http://vpn.cin.su:19191/api/v1".equals(lower)
|
||||
|| "http://vpn.cin.su/api/v1".equals(lower)
|
||||
|| "https://vpn.cin.su:443/api/v1".equals(lower)
|
||||
|| "http://94.141.118.222:19191/api/v1".equals(lower)
|
||||
|| "http://195.123.240.88:19131/api/v1".equals(lower)
|
||||
|| "http://192.168.200.61:18080/api/v1".equals(lower)
|
||||
|| "http://docker-test.cin.su:18080/api/v1".equals(lower)) {
|
||||
return PUBLIC_FABRIC_BACKEND_URL;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
|
||||
@@ -356,7 +356,7 @@ final class RapApiClient {
|
||||
return new byte[0];
|
||||
}
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IllegalStateException("HTTP " + response.code());
|
||||
throw new IllegalStateException(describeHttpFailure(response));
|
||||
}
|
||||
ResponseBody body = response.body();
|
||||
return body == null ? new byte[0] : body.bytes();
|
||||
@@ -377,15 +377,34 @@ final class RapApiClient {
|
||||
Request request = builder.build();
|
||||
try (Response response = httpClient.newCall(request).execute()) {
|
||||
if (!response.isSuccessful()) {
|
||||
throw new IllegalStateException("HTTP " + response.code());
|
||||
throw new IllegalStateException(describeHttpFailure(response));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String clientPacketPath(String clusterId, String vpnConnectionId, String suffix) {
|
||||
private String describeHttpFailure(Response response) {
|
||||
StringBuilder message = new StringBuilder("HTTP ").append(response.code());
|
||||
ResponseBody body = response.body();
|
||||
if (body != null) {
|
||||
try {
|
||||
String text = body.string();
|
||||
if (text != null && !text.trim().isEmpty()) {
|
||||
text = text.replace('\n', ' ').replace('\r', ' ').trim();
|
||||
if (text.length() > 240) {
|
||||
text = text.substring(0, 240);
|
||||
}
|
||||
message.append(": ").append(text);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return message.toString();
|
||||
}
|
||||
|
||||
private String clientPacketPath(String clusterId, String vpnConnectionId, String suffix) throws IOException {
|
||||
String path = fabricServiceChannel.packetPathForBase(baseUrl, clusterId, vpnConnectionId, false);
|
||||
if (path.isEmpty()) {
|
||||
path = "/clusters/" + clusterId + "/vpn-connections/" + vpnConnectionId + "/tunnel/client/packets";
|
||||
throw new IOException("fabric service channel lease required for VPN packet dataplane");
|
||||
}
|
||||
return path + (suffix == null ? "" : suffix);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONObject;
|
||||
@@ -38,6 +39,7 @@ import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -51,6 +53,7 @@ public class RapDiagnosticService extends Service {
|
||||
private static final String CHANNEL_ID = "rap-vpn-diagnostics";
|
||||
private static final String APP_VERSION = BuildConfig.VERSION_NAME;
|
||||
private static final String DEFAULT_BACKEND_URL = BuildConfig.DEFAULT_BACKEND_URL;
|
||||
private static final String PUBLIC_FABRIC_BACKEND_URL = "https://vpn.cin.su/api/v1";
|
||||
private static final String INTERNAL_BACKEND_URL = "http://192.168.200.61:18080/api/v1";
|
||||
private static final String DEFAULT_CLUSTER_ID = BuildConfig.DEFAULT_CLUSTER_ID;
|
||||
private static final String DEFAULT_ORGANIZATION_ID = BuildConfig.DEFAULT_ORGANIZATION_ID;
|
||||
@@ -60,14 +63,22 @@ public class RapDiagnosticService extends Service {
|
||||
private static final String PREF_REFRESH_TOKEN = "refresh_token";
|
||||
private static final String PREF_USER_ID = "user_id";
|
||||
private static final String PREF_DEVICE_ID = "device_id";
|
||||
private static final String PREF_DIAGNOSTIC_DEVICE_ID = "diagnostic_device_id";
|
||||
private static final String PREF_PROFILE_JSON = "profile_json";
|
||||
private static final String PREF_VPN_CONNECTION_ID = "vpn_connection_id";
|
||||
private static final long COMMAND_STALE_MS = 45000;
|
||||
private static final long COMMAND_ORPHAN_MS = 60000;
|
||||
private static final long POLL_FORCE_MS = 45000;
|
||||
private volatile boolean running;
|
||||
private Thread worker;
|
||||
private Thread supervisor;
|
||||
private String serviceState = "";
|
||||
private String lastCommandType = "";
|
||||
private String lastCommandResult = "";
|
||||
private String lastCommandPollResult = "";
|
||||
private String lastReceivedCommandID = "";
|
||||
private String lastReceivedCommandType = "";
|
||||
private long lastReceivedCommandAt = 0;
|
||||
private long lastCommandAt = 0;
|
||||
private long lastHeartbeatAt = 0;
|
||||
private long lastCommandPollAt = 0;
|
||||
@@ -129,6 +140,16 @@ public class RapDiagnosticService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
static void restart(android.content.Context context) {
|
||||
Intent intent = new Intent(context, RapDiagnosticService.class);
|
||||
intent.setAction(ACTION_RESTART);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
context.startForegroundService(intent);
|
||||
} else {
|
||||
context.startService(intent);
|
||||
}
|
||||
}
|
||||
|
||||
private void startWorker() {
|
||||
if (worker != null && worker.isAlive()) {
|
||||
long age = System.currentTimeMillis() - lastWorkerProgressAt;
|
||||
@@ -211,8 +232,12 @@ public class RapDiagnosticService extends Service {
|
||||
if (clusterId == null || clusterId.trim().isEmpty()) {
|
||||
clusterId = DEFAULT_CLUSTER_ID;
|
||||
}
|
||||
String deviceId = prefs.getString(PREF_DEVICE_ID, "");
|
||||
String deviceId = diagnosticDeviceId(prefs);
|
||||
if (backendUrl.isEmpty() || clusterId.isEmpty() || deviceId.isEmpty()) {
|
||||
serviceState = "waiting for config backend=" + !backendUrl.isEmpty()
|
||||
+ " cluster=" + !clusterId.isEmpty()
|
||||
+ " device=" + !deviceId.isEmpty();
|
||||
writeLocalDiagnosticHeartbeat();
|
||||
Thread.sleep(3000);
|
||||
continue;
|
||||
}
|
||||
@@ -259,12 +284,33 @@ public class RapDiagnosticService extends Service {
|
||||
commandPollStartedAt = 0;
|
||||
serviceState = "diagnostic poll watchdog released stale poll";
|
||||
}
|
||||
if (commandPollInProgress.get() && commandPollStartedAt == 0 && lastCommandPollAt > 0 && now - lastCommandPollAt > 45000) {
|
||||
commandPollInProgress.set(false);
|
||||
serviceState = "diagnostic poll watchdog released orphan poll flag age_ms=" + (now - lastCommandPollAt);
|
||||
}
|
||||
if (!commandPollInProgress.get() && !commandInProgress.get() && lastCommandPollAt > 0 && now - lastCommandPollAt > POLL_FORCE_MS) {
|
||||
serviceState = "diagnostic poll watchdog forcing command poll age_ms=" + (now - lastCommandPollAt);
|
||||
lastWorkerProgressAt = now;
|
||||
}
|
||||
long commandAge = commandInProgress.get() && commandStartedAt > 0 ? now - commandStartedAt : 0;
|
||||
if (commandAge > 120000) {
|
||||
if (commandAge > COMMAND_STALE_MS) {
|
||||
commandInProgress.set(false);
|
||||
commandStartedAt = 0;
|
||||
lastCommandType = lastReceivedCommandType.isEmpty() ? "command_timeout" : lastReceivedCommandType;
|
||||
lastCommandResult = "command watchdog timed out age_ms=" + commandAge
|
||||
+ " id=" + lastReceivedCommandID
|
||||
+ " type=" + lastReceivedCommandType;
|
||||
lastCommandAt = now;
|
||||
serviceState = "diagnostic command watchdog released stale command age_ms=" + commandAge;
|
||||
}
|
||||
if (commandInProgress.get() && commandStartedAt == 0 && lastCommandAt > 0 && now - lastCommandAt > COMMAND_ORPHAN_MS) {
|
||||
commandInProgress.set(false);
|
||||
serviceState = "diagnostic command watchdog released orphan command flag age_ms=" + (now - lastCommandAt);
|
||||
}
|
||||
if (commandInProgress.get() && commandStartedAt == 0 && lastCommandPollAt > 0 && now - lastCommandPollAt > COMMAND_ORPHAN_MS) {
|
||||
commandInProgress.set(false);
|
||||
serviceState = "diagnostic command watchdog released poll-stalled command flag age_ms=" + (now - lastCommandPollAt);
|
||||
}
|
||||
}
|
||||
|
||||
private void startHeartbeatWorker(String backendUrl, String clusterId, String deviceId, SharedPreferences prefs) {
|
||||
@@ -308,9 +354,14 @@ public class RapDiagnosticService extends Service {
|
||||
JSONObject commandEnvelope = nextCommandWithFallback(backendUrl, clusterId, deviceId);
|
||||
lastWorkerProgressAt = System.currentTimeMillis();
|
||||
if (commandEnvelope != null) {
|
||||
lastCommandPollResult = describeCommandEnvelope(commandEnvelope);
|
||||
rememberReceivedCommand(commandEnvelope);
|
||||
startCommandWorker(backendUrl, clusterId, deviceId, commandEnvelope);
|
||||
} else {
|
||||
lastCommandPollResult = "no_content";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
lastCommandPollResult = "error: " + e.getClass().getSimpleName();
|
||||
serviceState = "command poll error: " + e.getMessage();
|
||||
lastWorkerProgressAt = System.currentTimeMillis();
|
||||
} finally {
|
||||
@@ -323,11 +374,15 @@ public class RapDiagnosticService extends Service {
|
||||
|
||||
private void startCommandWorker(String backendUrl, String clusterId, String deviceId, JSONObject commandEnvelope) {
|
||||
if (!commandInProgress.compareAndSet(false, true)) {
|
||||
lastCommandPollResult = "worker_busy " + describeCommandEnvelope(commandEnvelope);
|
||||
return;
|
||||
}
|
||||
Thread commandWorker = new Thread(() -> {
|
||||
try {
|
||||
commandStartedAt = System.currentTimeMillis();
|
||||
lastCommandType = lastReceivedCommandType.isEmpty() ? "command_running" : lastReceivedCommandType;
|
||||
lastCommandResult = "running id=" + lastReceivedCommandID + " type=" + lastReceivedCommandType;
|
||||
lastCommandAt = commandStartedAt;
|
||||
lastWorkerProgressAt = System.currentTimeMillis();
|
||||
RapApiClient commandClient = controlClient(backendUrl);
|
||||
controlNetworkMode = commandClient.networkMode();
|
||||
@@ -363,6 +418,9 @@ public class RapDiagnosticService extends Service {
|
||||
params = payload;
|
||||
}
|
||||
String result;
|
||||
lastCommandType = type;
|
||||
lastCommandResult = "running id=" + (command == null ? "" : command.optString("id", "")) + " type=" + type;
|
||||
lastCommandAt = System.currentTimeMillis();
|
||||
if ("start_vpn".equals(type)) {
|
||||
result = startVPNFromSavedProfile();
|
||||
} else if ("stop_vpn".equals(type)) {
|
||||
@@ -411,6 +469,8 @@ public class RapDiagnosticService extends Service {
|
||||
result = "remote_assist_end accepted";
|
||||
} else if ("full_vpn_test".equals(type)) {
|
||||
result = runFullVPNTest(client, clusterId, params);
|
||||
} else if ("install_profile".equals(type) || "apply_profile".equals(type)) {
|
||||
result = installProfileFromCommand(params);
|
||||
} else if ("refresh_profile".equals(type)) {
|
||||
result = refreshProfile();
|
||||
} else {
|
||||
@@ -418,9 +478,15 @@ public class RapDiagnosticService extends Service {
|
||||
}
|
||||
if (isRecoverableVPNProbe(type) && looksLikeVPNStall(result)) {
|
||||
String firstResult = result;
|
||||
Thread.sleep(1500);
|
||||
String fastRetry = runVPNProbeCommand(type, params);
|
||||
if (!looksLikeVPNStall(fastRetry)) {
|
||||
result = firstResult + " | fast_retry=" + fastRetry;
|
||||
} else {
|
||||
String recovery = controlledRestartVPNRuntime(client, clusterId);
|
||||
Thread.sleep(4000);
|
||||
result = firstResult + " | recovery=" + recovery + " | retry=" + runVPNProbeCommand(type, params);
|
||||
result = firstResult + " | fast_retry=" + fastRetry + " | recovery=" + recovery + " | retry=" + runVPNProbeCommand(type, params);
|
||||
}
|
||||
}
|
||||
lastCommandType = type;
|
||||
lastCommandResult = result;
|
||||
@@ -623,7 +689,9 @@ public class RapDiagnosticService extends Service {
|
||||
return "restart failed: queue reset failed: " + e.getMessage();
|
||||
}
|
||||
Thread.sleep(300);
|
||||
return startVPNFromSavedProfile();
|
||||
String refresh = refreshProfile();
|
||||
String start = startVPNFromSavedProfile();
|
||||
return start + " profile_refresh=" + refresh;
|
||||
} catch (Exception e) {
|
||||
return "restart failed: " + e.getClass().getSimpleName() + ": " + e.getMessage();
|
||||
}
|
||||
@@ -642,6 +710,10 @@ public class RapDiagnosticService extends Service {
|
||||
}
|
||||
serviceState = "upgrade restart " + lastVersion + " -> " + APP_VERSION;
|
||||
try {
|
||||
String refresh = refreshProfile();
|
||||
if (refresh.startsWith("refresh_profile failed")) {
|
||||
lastCommandResult = "vpn runtime profile refresh before upgrade restart failed: " + refresh;
|
||||
}
|
||||
try {
|
||||
client.resetVPNPacketQueues(clusterId, connectionId);
|
||||
} catch (Exception ignored) {
|
||||
@@ -650,7 +722,7 @@ public class RapDiagnosticService extends Service {
|
||||
startVPNFromSavedProfile();
|
||||
prefs.edit().putString("vpn_runtime_app_version", APP_VERSION).apply();
|
||||
lastCommandType = "auto_upgrade_restart";
|
||||
lastCommandResult = "vpn runtime reinitialized after app upgrade " + lastVersion + " -> " + APP_VERSION;
|
||||
lastCommandResult = "vpn runtime reinitialized after app upgrade " + lastVersion + " -> " + APP_VERSION + " profile_refresh=" + refresh;
|
||||
lastCommandAt = System.currentTimeMillis();
|
||||
} catch (Exception e) {
|
||||
lastCommandType = "auto_upgrade_restart";
|
||||
@@ -664,10 +736,23 @@ public class RapDiagnosticService extends Service {
|
||||
try {
|
||||
String refreshToken = new SecureTokenStore(this).get(PREF_REFRESH_TOKEN);
|
||||
if (refreshToken.isEmpty()) {
|
||||
return "refresh_profile skipped: refresh token missing";
|
||||
String savedUserId = prefs.getString(PREF_USER_ID, "");
|
||||
if (savedUserId == null || savedUserId.trim().isEmpty()) {
|
||||
return "refresh_profile skipped: refresh token and saved user missing";
|
||||
}
|
||||
return refreshProfileForUser(prefs, savedUserId.trim(), null);
|
||||
}
|
||||
RapApiClient client = new RapApiClient(normalizeBackendUrl(prefs.getString("backend_url", "")), this, true);
|
||||
RapApiClient.AuthContext auth = client.refresh(refreshToken);
|
||||
new SecureTokenStore(this).put(PREF_REFRESH_TOKEN, auth.refreshToken);
|
||||
return refreshProfileForUser(prefs, auth.userId, auth.deviceId);
|
||||
} catch (Exception e) {
|
||||
return "refresh_profile failed: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private String refreshProfileForUser(SharedPreferences prefs, String userId, String trustedDeviceId) throws Exception {
|
||||
String backendUrl = normalizeBackendUrl(prefs.getString("backend_url", DEFAULT_BACKEND_URL));
|
||||
String organizationId = prefs.getString("organization_id", DEFAULT_ORGANIZATION_ID);
|
||||
String clusterId = prefs.getString("cluster_id", DEFAULT_CLUSTER_ID);
|
||||
if (clusterId == null || clusterId.trim().isEmpty()) {
|
||||
@@ -677,31 +762,109 @@ public class RapDiagnosticService extends Service {
|
||||
organizationId = DEFAULT_ORGANIZATION_ID;
|
||||
}
|
||||
String exitNodeId = prefs.getString(PREF_SELECTED_EXIT_NODE_ID, "");
|
||||
String profileJson = client.vpnClientProfile(clusterId, organizationId, auth.userId, exitNodeId);
|
||||
RapApiClient client = new RapApiClient(backendUrl, this, true);
|
||||
String profileJson = client.vpnClientProfile(clusterId, organizationId, userId, exitNodeId);
|
||||
JSONObject root = new JSONObject(profileJson);
|
||||
JSONObject profile = root.getJSONObject("vpn_client_profile");
|
||||
String connectionId = profile.getJSONArray("connections").getJSONObject(0).getString("id");
|
||||
prefs.edit()
|
||||
.putString(PREF_USER_ID, auth.userId)
|
||||
.putString(PREF_DEVICE_ID, auth.deviceId)
|
||||
SharedPreferences.Editor editor = prefs.edit()
|
||||
.putString("backend_url", backendUrl)
|
||||
.putString("cluster_id", clusterId)
|
||||
.putString("organization_id", organizationId)
|
||||
.putString(PREF_USER_ID, userId)
|
||||
.putString(PREF_PROFILE_JSON, profileJson)
|
||||
.putString(PREF_VPN_CONNECTION_ID, connectionId)
|
||||
.apply();
|
||||
new SecureTokenStore(this).put(PREF_REFRESH_TOKEN, auth.refreshToken);
|
||||
.putString(PREF_VPN_CONNECTION_ID, connectionId);
|
||||
if (trustedDeviceId != null && !trustedDeviceId.trim().isEmpty()) {
|
||||
editor.putString(PREF_DEVICE_ID, trustedDeviceId.trim());
|
||||
}
|
||||
editor.apply();
|
||||
return "refresh_profile ok " + connectionId;
|
||||
}
|
||||
|
||||
private String installProfileFromCommand(JSONObject params) {
|
||||
try {
|
||||
String backendUrl = normalizeBackendUrl(params.optString("backend_url", DEFAULT_BACKEND_URL));
|
||||
String clusterId = params.optString("cluster_id", DEFAULT_CLUSTER_ID).trim();
|
||||
String organizationId = params.optString("organization_id", DEFAULT_ORGANIZATION_ID).trim();
|
||||
String userId = params.optString("user_id", "").trim();
|
||||
String trustedDeviceId = params.optString("trusted_device_id", "").trim();
|
||||
String selectedExitNodeId = params.optString("selected_exit_node_id", params.optString("exit_node_id", "")).trim();
|
||||
String profileJson = params.optString("profile_json", "").trim();
|
||||
JSONObject root;
|
||||
if (profileJson.isEmpty()) {
|
||||
JSONObject profile = params.optJSONObject("vpn_client_profile");
|
||||
if (profile == null) {
|
||||
profile = params.optJSONObject("profile");
|
||||
}
|
||||
if (profile == null) {
|
||||
return "install_profile skipped: profile missing";
|
||||
}
|
||||
root = new JSONObject();
|
||||
root.put("vpn_client_profile", profile);
|
||||
profileJson = root.toString();
|
||||
} else {
|
||||
root = new JSONObject(profileJson);
|
||||
if (!root.has("vpn_client_profile")) {
|
||||
JSONObject wrapped = new JSONObject();
|
||||
wrapped.put("vpn_client_profile", root);
|
||||
root = wrapped;
|
||||
profileJson = wrapped.toString();
|
||||
}
|
||||
}
|
||||
JSONObject profile = root.getJSONObject("vpn_client_profile");
|
||||
if (clusterId.isEmpty()) {
|
||||
clusterId = profile.optString("cluster_id", DEFAULT_CLUSTER_ID);
|
||||
}
|
||||
if (organizationId.isEmpty()) {
|
||||
organizationId = profile.optString("organization_id", DEFAULT_ORGANIZATION_ID);
|
||||
}
|
||||
if (userId.isEmpty()) {
|
||||
userId = profile.optString("user_id", "");
|
||||
}
|
||||
JSONObject connection = profile.getJSONArray("connections").getJSONObject(0);
|
||||
String connectionId = params.optString("vpn_connection_id", connection.optString("id", "")).trim();
|
||||
JSONObject config = connection.optJSONObject("client_config");
|
||||
if (selectedExitNodeId.isEmpty() && config != null) {
|
||||
JSONObject route = config.optJSONObject("vpn_fabric_route");
|
||||
if (route != null) {
|
||||
selectedExitNodeId = route.optString("selected_exit_node_id", "");
|
||||
}
|
||||
}
|
||||
SharedPreferences.Editor editor = getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putString("backend_url", backendUrl)
|
||||
.putString("cluster_id", clusterId)
|
||||
.putString("organization_id", organizationId)
|
||||
.putString(PREF_USER_ID, userId)
|
||||
.putString(PREF_PROFILE_JSON, profileJson)
|
||||
.putString(PREF_VPN_CONNECTION_ID, connectionId);
|
||||
if (!trustedDeviceId.isEmpty()) {
|
||||
editor.putString(PREF_DEVICE_ID, trustedDeviceId);
|
||||
}
|
||||
if (!selectedExitNodeId.isEmpty()) {
|
||||
editor.putString(PREF_SELECTED_EXIT_NODE_ID, selectedExitNodeId);
|
||||
}
|
||||
editor.apply();
|
||||
Intent stopIntent = new Intent(this, RapVpnService.class);
|
||||
stopIntent.setAction(RapVpnService.ACTION_STOP);
|
||||
startService(stopIntent);
|
||||
Thread.sleep(300);
|
||||
return "install_profile ok " + connectionId + " | " + startVPNFromSavedProfile();
|
||||
} catch (Exception e) {
|
||||
return "refresh_profile failed: " + e.getMessage();
|
||||
return "install_profile failed: " + e.getClass().getSimpleName() + ": " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private JSONObject statusPayload(String event) throws Exception {
|
||||
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
String deviceId = diagnosticDeviceId(prefs);
|
||||
JSONObject payload = new JSONObject();
|
||||
payload.put("event", event);
|
||||
payload.put("app_version", APP_VERSION);
|
||||
payload.put("service", "diagnostic");
|
||||
payload.put("user_id", prefs.getString(PREF_USER_ID, ""));
|
||||
payload.put("device_id", prefs.getString(PREF_DEVICE_ID, ""));
|
||||
payload.put("device_id", deviceId);
|
||||
payload.put("trusted_device_id", prefs.getString(PREF_DEVICE_ID, ""));
|
||||
payload.put("diagnostic_device_id", prefs.getString(PREF_DIAGNOSTIC_DEVICE_ID, ""));
|
||||
payload.put("organization_id", prefs.getString("organization_id", ""));
|
||||
payload.put("vpn_connection_id", prefs.getString(PREF_VPN_CONNECTION_ID, ""));
|
||||
payload.put("backend_url", prefs.getString("backend_url", ""));
|
||||
@@ -716,15 +879,86 @@ public class RapDiagnosticService extends Service {
|
||||
payload.put("last_command_at", lastCommandAt);
|
||||
payload.put("last_heartbeat_at", lastHeartbeatAt);
|
||||
payload.put("last_command_poll_at", lastCommandPollAt);
|
||||
payload.put("last_command_poll_result", lastCommandPollResult);
|
||||
payload.put("last_received_command_id", lastReceivedCommandID);
|
||||
payload.put("last_received_command_type", lastReceivedCommandType);
|
||||
payload.put("last_received_command_at", lastReceivedCommandAt);
|
||||
payload.put("heartbeat_in_progress", heartbeatInProgress.get());
|
||||
payload.put("heartbeat_started_at", heartbeatStartedAt);
|
||||
payload.put("command_poll_in_progress", commandPollInProgress.get());
|
||||
payload.put("command_poll_started_at", commandPollStartedAt);
|
||||
payload.put("command_in_progress", commandInProgress.get());
|
||||
payload.put("command_started_at", commandStartedAt);
|
||||
payload.put("browser_test", browserTestSnapshot());
|
||||
return payload;
|
||||
}
|
||||
|
||||
private String describeCommandEnvelope(JSONObject envelope) {
|
||||
if (envelope == null) {
|
||||
return "no_content";
|
||||
}
|
||||
JSONObject command = envelope.optJSONObject("vpn_client_diagnostic_command");
|
||||
JSONObject payload = command == null ? envelope.optJSONObject("payload") : command.optJSONObject("payload");
|
||||
String id = command == null ? "" : command.optString("id", "");
|
||||
String type = payload == null ? "" : payload.optString("type", "");
|
||||
String value = "received";
|
||||
if (!type.isEmpty()) {
|
||||
value += " " + type;
|
||||
}
|
||||
if (!id.isEmpty()) {
|
||||
value += " " + id;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void rememberReceivedCommand(JSONObject envelope) {
|
||||
JSONObject command = envelope == null ? null : envelope.optJSONObject("vpn_client_diagnostic_command");
|
||||
JSONObject payload = command == null ? (envelope == null ? null : envelope.optJSONObject("payload")) : command.optJSONObject("payload");
|
||||
lastReceivedCommandID = command == null ? "" : command.optString("id", "");
|
||||
lastReceivedCommandType = payload == null ? "" : payload.optString("type", "");
|
||||
lastReceivedCommandAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private String diagnosticDeviceId(SharedPreferences prefs) {
|
||||
String trusted = prefs.getString(PREF_DEVICE_ID, "");
|
||||
if (trusted != null && !trusted.trim().isEmpty()) {
|
||||
return trusted.trim();
|
||||
}
|
||||
String cached = prefs.getString(PREF_DIAGNOSTIC_DEVICE_ID, "");
|
||||
if (cached != null && !cached.trim().isEmpty()) {
|
||||
return cached.trim();
|
||||
}
|
||||
String androidId = "";
|
||||
try {
|
||||
androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
String seed = androidId == null || androidId.trim().isEmpty()
|
||||
? UUID.randomUUID().toString()
|
||||
: androidId.trim();
|
||||
String generated = "diag-" + seed.replaceAll("[^A-Za-z0-9_-]", "").toLowerCase();
|
||||
if (generated.length() > 80) {
|
||||
generated = generated.substring(0, 80);
|
||||
}
|
||||
prefs.edit().putString(PREF_DIAGNOSTIC_DEVICE_ID, generated).apply();
|
||||
return generated;
|
||||
}
|
||||
|
||||
private String normalizeBackendUrl(String value) {
|
||||
String candidate = value == null ? "" : value.trim().replaceAll("/+$", "");
|
||||
if (candidate.isEmpty()) {
|
||||
return DEFAULT_BACKEND_URL;
|
||||
}
|
||||
String lower = candidate.toLowerCase();
|
||||
if ("http://vpn.cin.su:19191/api/v1".equals(lower)
|
||||
|| "http://vpn.cin.su/api/v1".equals(lower)
|
||||
|| "https://vpn.cin.su:443/api/v1".equals(lower)
|
||||
|| "http://94.141.118.222:19191/api/v1".equals(lower)
|
||||
|| "http://195.123.240.88:19131/api/v1".equals(lower)
|
||||
|| "http://192.168.200.61:18080/api/v1".equals(lower)
|
||||
|| "http://docker-test.cin.su:18080/api/v1".equals(lower)) {
|
||||
return PUBLIC_FABRIC_BACKEND_URL;
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
@@ -791,7 +1025,7 @@ public class RapDiagnosticService extends Service {
|
||||
if (!connectionId.isEmpty()) {
|
||||
report.put("packet_stats", client.vpnPacketStats(clusterId, connectionId));
|
||||
}
|
||||
client.reportVPNDiagnosticStatus(clusterId, getSharedPreferences(PREFS, MODE_PRIVATE).getString(PREF_DEVICE_ID, ""), report);
|
||||
client.reportVPNDiagnosticStatus(clusterId, diagnosticDeviceId(getSharedPreferences(PREFS, MODE_PRIVATE)), report);
|
||||
}
|
||||
if (!connectionId.isEmpty()) {
|
||||
result.append(" | stats=").append(compact(client.vpnPacketStats(clusterId, connectionId).toString(), 900));
|
||||
@@ -818,7 +1052,7 @@ public class RapDiagnosticService extends Service {
|
||||
|
||||
private String runVPNDownloadTest(String target) {
|
||||
try {
|
||||
Network vpn = vpnNetwork();
|
||||
Network vpn = waitForVPNNetwork(5000);
|
||||
if (vpn == null) {
|
||||
return "vpn_download_test " + target + " -> vpn network not found";
|
||||
}
|
||||
@@ -1009,6 +1243,13 @@ public class RapDiagnosticService extends Service {
|
||||
payload.put("diagnostic_local_heartbeat_at", runtime.getLong("diagnostic_local_heartbeat_at", 0));
|
||||
payload.put("diagnostic_local_state", runtime.getString("diagnostic_local_state", ""));
|
||||
payload.put("diagnostic_local_app_version", runtime.getString("diagnostic_local_app_version", ""));
|
||||
payload.put("diagnostic_watchdog_started_at", runtime.getLong("diagnostic_watchdog_started_at", 0));
|
||||
payload.put("diagnostic_watchdog_last_ensure_at", runtime.getLong("diagnostic_watchdog_last_ensure_at", 0));
|
||||
payload.put("diagnostic_watchdog_last_heartbeat_age_ms", runtime.getLong("diagnostic_watchdog_last_heartbeat_age_ms", 0));
|
||||
payload.put("diagnostic_watchdog_last_action", runtime.getString("diagnostic_watchdog_last_action", ""));
|
||||
payload.put("diagnostic_watchdog_last_error", runtime.getString("diagnostic_watchdog_last_error", ""));
|
||||
payload.put("diagnostic_watchdog_ensure_requests", runtime.getLong("diagnostic_watchdog_ensure_requests", 0));
|
||||
payload.put("diagnostic_watchdog_restart_requests", runtime.getLong("diagnostic_watchdog_restart_requests", 0));
|
||||
payload.put("uplink_read", runtime.getLong("uplink_read", 0));
|
||||
payload.put("uplink_sent", runtime.getLong("uplink_sent", 0));
|
||||
payload.put("downlink_received", runtime.getLong("downlink_received", 0));
|
||||
@@ -1044,8 +1285,10 @@ public class RapDiagnosticService extends Service {
|
||||
payload.put("errors", runtime.getLong("errors", 0));
|
||||
payload.put("uplink", runtimePrefix(runtime, "uplink"));
|
||||
payload.put("uplink_sender", runtimePrefix(runtime, "uplink_sender"));
|
||||
payload.put("uplink_tcp", runtimePrefix(runtime, "uplink_tcp"));
|
||||
payload.put("downlink", runtimePrefix(runtime, "downlink"));
|
||||
payload.put("downlink_writer", runtimePrefix(runtime, "downlink_writer"));
|
||||
payload.put("downlink_tcp", runtimePrefix(runtime, "downlink_tcp"));
|
||||
payload.put("relay", runtimePrefix(runtime, "relay"));
|
||||
payload.put("uplink_worker_count", runtime.getInt("uplink_worker_count", 0));
|
||||
payload.put("uplink_queue_depth_total", runtime.getInt("uplink_queue_depth_total", 0));
|
||||
@@ -1266,7 +1509,7 @@ public class RapDiagnosticService extends Service {
|
||||
}
|
||||
long started = System.currentTimeMillis();
|
||||
try {
|
||||
Network vpn = vpnNetwork();
|
||||
Network vpn = waitForVPNNetwork(5000);
|
||||
if (vpn == null) {
|
||||
return "vpn_tcp_connect " + host + ":" + port + " -> vpn network not found";
|
||||
}
|
||||
@@ -1283,6 +1526,24 @@ public class RapDiagnosticService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private Network waitForVPNNetwork(int timeoutMs) {
|
||||
long deadline = System.currentTimeMillis() + Math.max(0, timeoutMs);
|
||||
Network vpn;
|
||||
do {
|
||||
vpn = vpnNetwork();
|
||||
if (vpn != null) {
|
||||
return vpn;
|
||||
}
|
||||
try {
|
||||
Thread.sleep(150);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
} while (System.currentTimeMillis() < deadline);
|
||||
return null;
|
||||
}
|
||||
|
||||
private FetchResult fetchVPNURL(Network vpn, URL url, int connectTimeoutMs, int readTimeoutMs, int maxBytes) throws Exception {
|
||||
long started = System.currentTimeMillis();
|
||||
HttpURLConnection connection = (HttpURLConnection) vpn.openConnection(url);
|
||||
|
||||
@@ -26,7 +26,9 @@ import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -48,10 +50,12 @@ public class RapVpnService extends VpnService {
|
||||
private static final String CHANNEL_ID = "rap-vpn";
|
||||
private static final String TAG = "RapVpnService";
|
||||
private static final String PREFS = "rap-vpn-runtime";
|
||||
private static final int DEFAULT_VPN_MTU = 1420;
|
||||
private static final int DEFAULT_VPN_MTU = 1000;
|
||||
private static final int VPN_TCP_MSS_CLAMP = 900;
|
||||
private static final boolean PACKET_WEBSOCKET_DATAPLANE_ENABLED = false;
|
||||
private static final int VPN_BATCH_MAX_PACKETS = 512;
|
||||
private static final int VPN_BATCH_MAX_BYTES = 1024 * 1024;
|
||||
private static final int UPLINK_WORKER_MAX_COUNT = 4;
|
||||
private static final int UPLINK_WORKER_MAX_COUNT = 1;
|
||||
private static final int UPLINK_QUEUE_CAPACITY = 32768;
|
||||
private static final int PRIORITY_QUEUE_CAPACITY = 4096;
|
||||
private static final int UPLINK_SEND_RETRY_COUNT = 2;
|
||||
@@ -72,7 +76,12 @@ public class RapVpnService extends VpnService {
|
||||
private static final int RUNTIME_WATCHDOG_STALE_SYNACK_MS = 15000;
|
||||
private static final int RUNTIME_WATCHDOG_RECOVERY_COOLDOWN_MS = 60000;
|
||||
private static final int RUNTIME_WATCHDOG_HARD_RESTART_COOLDOWN_MS = 180000;
|
||||
private static final int DIAGNOSTIC_WATCHDOG_INTERVAL_MS = 5000;
|
||||
private static final int DIAGNOSTIC_STALE_RESTART_MS = 30000;
|
||||
private static final int DIAGNOSTIC_RESTART_COOLDOWN_MS = 15000;
|
||||
private static final int VPN_START_WARMUP_TIMEOUT_MS = 6000;
|
||||
private static final int VPN_TCP_WARMUP_CONNECT_TIMEOUT_MS = 2500;
|
||||
private static final int VPN_TCP_WARMUP_PASSES = 3;
|
||||
private static final String[] DEFAULT_DNS_PROBE_DOMAINS = new String[]{
|
||||
"speedtest.rt.ru",
|
||||
"2ip.ru",
|
||||
@@ -97,6 +106,11 @@ public class RapVpnService extends VpnService {
|
||||
"rline-host.qms.ru",
|
||||
"timernet-host.qms.ru"
|
||||
};
|
||||
private static final String[] DEFAULT_TCP_WARMUP_TARGETS = new String[]{
|
||||
"192.168.200.61:18080",
|
||||
"192.168.200.95:3389",
|
||||
"188.40.167.82:80"
|
||||
};
|
||||
private static final String PREF_NAME = "rap-vpn";
|
||||
private static final String PREF_PROFILE_JSON = "profile_json";
|
||||
private static final String PREF_BACKEND_URL = "backend_url";
|
||||
@@ -112,6 +126,7 @@ public class RapVpnService extends VpnService {
|
||||
private Thread downlinkThread;
|
||||
private Thread downlinkWriterThread;
|
||||
private Thread runtimeWatchdogThread;
|
||||
private Thread diagnosticWatchdogThread;
|
||||
private BlockingQueue<byte[]>[] uplinkQueues;
|
||||
private BlockingQueue<byte[]>[] downlinkQueues;
|
||||
private BlockingQueue<byte[]> uplinkPriorityQueue;
|
||||
@@ -171,6 +186,8 @@ public class RapVpnService extends VpnService {
|
||||
private final AtomicLong runtimeWatchdogRecoveries = new AtomicLong();
|
||||
private final AtomicLong tcpHandshakeStalls = new AtomicLong();
|
||||
private final AtomicLong runtimeWatchdogHardRestarts = new AtomicLong();
|
||||
private final AtomicLong diagnosticEnsureRequests = new AtomicLong();
|
||||
private final AtomicLong diagnosticRestartRequests = new AtomicLong();
|
||||
private final AtomicBoolean hardRuntimeRestartInProgress = new AtomicBoolean();
|
||||
private volatile boolean relaxedUplinkSourceValidation;
|
||||
private volatile boolean relaxedDownlinkDestinationValidation;
|
||||
@@ -180,6 +197,7 @@ public class RapVpnService extends VpnService {
|
||||
private volatile int activePacketRelayIndex;
|
||||
private volatile VpnPacketWebSocketRelay packetWebSocketRelay;
|
||||
private volatile FabricServiceChannel activeFabricServiceChannel = new FabricServiceChannel();
|
||||
private volatile String lastUplinkSendErrorMessage = "";
|
||||
private final Object packetRelaySwitchLock = new Object();
|
||||
private final Map<String, byte[]> clientSourceNat = new LinkedHashMap<String, byte[]>(4096, 0.75f, true) {
|
||||
@Override
|
||||
@@ -198,7 +216,7 @@ public class RapVpnService extends VpnService {
|
||||
private volatile long lastRuntimeWatchdogHardRestartAt;
|
||||
private volatile long lastDiagnosticEnsureAt;
|
||||
private volatile long lastDiagnosticStatusEnsureAt;
|
||||
private volatile boolean nextDiagnosticEnsureMayRestart;
|
||||
private volatile long lastDiagnosticRestartAt;
|
||||
private static final int ADDRESS_MISMATCH_TOLERANCE_PACKETS = 16;
|
||||
|
||||
@Override
|
||||
@@ -257,8 +275,17 @@ public class RapVpnService extends VpnService {
|
||||
List<String> packetRelayUrls = activePacketRelayUrlsByProfile == null || activePacketRelayUrlsByProfile.isEmpty()
|
||||
? singletonUrl(activePacketRelayUrlByProfile)
|
||||
: new ArrayList<>(activePacketRelayUrlsByProfile);
|
||||
if (!activeFabricServiceChannel.enabled) {
|
||||
shutdownReason = "fabric service channel lease required";
|
||||
writeRuntimeStatus("error", "vpn not started: fabric service channel lease required", 0, 0, 0, 0);
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
if (packetRelayUrls.isEmpty()) {
|
||||
packetRelayUrls.add(backendUrl);
|
||||
shutdownReason = "missing farm entry endpoint";
|
||||
writeRuntimeStatus("error", "vpn not started: missing farm entry endpoint", 0, 0, 0, 0);
|
||||
stopSelf();
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
startPacketRelay(backendUrl, packetRelayUrls, clusterId, vpnConnectionId);
|
||||
if (!running) {
|
||||
@@ -284,6 +311,7 @@ public class RapVpnService extends VpnService {
|
||||
private void ensureDiagnosticServiceRunning() {
|
||||
try {
|
||||
RapDiagnosticService.start(this);
|
||||
diagnosticEnsureRequests.incrementAndGet();
|
||||
writeRuntimeDetail("diagnostic_start", "diagnostic service start requested by vpn runtime", "control", 0, 0, "", -1);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "diagnostic service start failed", e);
|
||||
@@ -295,8 +323,15 @@ public class RapVpnService extends VpnService {
|
||||
try {
|
||||
SharedPreferences runtime = getSharedPreferences(PREFS, MODE_PRIVATE);
|
||||
long lastLocalHeartbeat = runtime.getLong("diagnostic_local_heartbeat_at", 0);
|
||||
long age = lastLocalHeartbeat <= 0 ? Long.MAX_VALUE : System.currentTimeMillis() - lastLocalHeartbeat;
|
||||
boolean restart = nextDiagnosticEnsureMayRestart && age > 45000;
|
||||
long now = System.currentTimeMillis();
|
||||
long age = lastLocalHeartbeat <= 0 ? Long.MAX_VALUE : now - lastLocalHeartbeat;
|
||||
boolean restart = age > DIAGNOSTIC_STALE_RESTART_MS
|
||||
&& now - lastDiagnosticRestartAt >= DIAGNOSTIC_RESTART_COOLDOWN_MS;
|
||||
diagnosticEnsureRequests.incrementAndGet();
|
||||
if (restart) {
|
||||
diagnosticRestartRequests.incrementAndGet();
|
||||
lastDiagnosticRestartAt = now;
|
||||
}
|
||||
Intent intent = new Intent(this, RapDiagnosticService.class);
|
||||
intent.setAction(restart ? RapDiagnosticService.ACTION_RESTART : RapDiagnosticService.ACTION_START);
|
||||
if (Build.VERSION.SDK_INT >= 26) {
|
||||
@@ -304,7 +339,13 @@ public class RapVpnService extends VpnService {
|
||||
} else {
|
||||
startService(intent);
|
||||
}
|
||||
nextDiagnosticEnsureMayRestart = true;
|
||||
runtime.edit()
|
||||
.putLong("diagnostic_watchdog_last_ensure_at", now)
|
||||
.putLong("diagnostic_watchdog_last_heartbeat_age_ms", age)
|
||||
.putString("diagnostic_watchdog_last_action", restart ? "restart" : "start")
|
||||
.putLong("diagnostic_watchdog_ensure_requests", diagnosticEnsureRequests.get())
|
||||
.putLong("diagnostic_watchdog_restart_requests", diagnosticRestartRequests.get())
|
||||
.apply();
|
||||
writeRuntimeDetail(
|
||||
restart ? "diagnostic_restart" : "diagnostic_start",
|
||||
(restart ? "diagnostic service restart requested age_ms=" : "diagnostic service start requested age_ms=") + age,
|
||||
@@ -315,6 +356,11 @@ public class RapVpnService extends VpnService {
|
||||
-1);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "diagnostic service health ensure failed", e);
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putLong("diagnostic_watchdog_last_ensure_at", System.currentTimeMillis())
|
||||
.putString("diagnostic_watchdog_last_action", "failed")
|
||||
.putString("diagnostic_watchdog_last_error", e.getClass().getSimpleName() + ": " + e.getMessage())
|
||||
.apply();
|
||||
writeRuntimeDetail("diagnostic_start_failed", e.getMessage(), "control", 0, 1, e.getClass().getSimpleName(), -1);
|
||||
}
|
||||
}
|
||||
@@ -775,6 +821,19 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isIPv6URLHost(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
URI uri = URI.create(value.trim());
|
||||
String host = uri.getHost();
|
||||
return host != null && host.contains(":");
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeRuntimeConfig(VpnClientConfig config, boolean forceFullTunnel, boolean fastPathMode) {
|
||||
try {
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
@@ -935,11 +994,15 @@ public class RapVpnService extends VpnService {
|
||||
+ " connection=" + present(vpnConnectionId), 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
List<String> relayUrls = dedupeRelayUrls(candidateUrls, backendUrl);
|
||||
List<String> relayUrls = activeFabricServiceChannel.enabled ? dedupeFabricRelayUrls(candidateUrls, backendUrl) : dedupeRelayUrls(candidateUrls, backendUrl);
|
||||
String selectedRelayUrl = relayUrls.isEmpty() ? "" : relayUrls.get(0);
|
||||
if (selectedRelayUrl == null || selectedRelayUrl.isEmpty()) {
|
||||
if ((selectedRelayUrl == null || selectedRelayUrl.isEmpty()) && !activeFabricServiceChannel.enabled) {
|
||||
selectedRelayUrl = backendUrl;
|
||||
}
|
||||
if (selectedRelayUrl == null || selectedRelayUrl.isEmpty()) {
|
||||
writeRuntimeStatus("error", "relay not started: missing fabric farm entry endpoint", 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
activePacketRelayUrlByProfile = selectedRelayUrl;
|
||||
activePacketRelayUrlsByProfile = new ArrayList<>(relayUrls);
|
||||
activePacketRelayIndex = Math.max(0, relayUrls.indexOf(selectedRelayUrl));
|
||||
@@ -971,14 +1034,18 @@ public class RapVpnService extends VpnService {
|
||||
downlinkQueues[i] = new ArrayBlockingQueue<>(downlinkPerFlowCapacity);
|
||||
}
|
||||
configureBackendBypass(selectedRelayUrl);
|
||||
if (PACKET_WEBSOCKET_DATAPLANE_ENABLED) {
|
||||
startPacketWebSocketRelay(selectedRelayUrl, clusterId, vpnConnectionId);
|
||||
} else {
|
||||
writeRuntimeDetail("http_packet_batch", "packet websocket disabled; using confirmed HTTP batches", "relay", 0, 0, "", -1);
|
||||
}
|
||||
Log.i(TAG, "packet relay starting: backend=" + selectedRelayUrl + " cluster=" + clusterId + " vpn_connection=" + vpnConnectionId);
|
||||
writeRuntimeStatus("relay", "relay starting " + vpnConnectionId, 0, 0, 0, 0);
|
||||
writeRuntimeDetail("running", "packet relay active", "relay", 0, 0, "");
|
||||
final String resetRelayUrl = selectedRelayUrl;
|
||||
Thread resetThread = new Thread(() -> {
|
||||
final String legacyResetRelayUrl = selectedRelayUrl;
|
||||
Thread resetThread = activeFabricServiceChannel.enabled ? null : new Thread(() -> {
|
||||
try {
|
||||
RapApiClient uplinkClient = packetRelayClientForUrl(resetRelayUrl);
|
||||
RapApiClient uplinkClient = packetRelayClientForUrl(legacyResetRelayUrl);
|
||||
JSONObject reset = uplinkClient.resetVPNPacketQueues(clusterId, vpnConnectionId);
|
||||
Log.i(TAG, "packet relay queues reset: " + reset.toString());
|
||||
writeRuntimeStatus("relay_reset", reset.toString(), 0, 0, 0, 0);
|
||||
@@ -996,7 +1063,12 @@ public class RapVpnService extends VpnService {
|
||||
downlinkThread = new Thread(() -> runDownlinkWithRestart(clusterId, vpnConnectionId), "rap-vpn-downlink-receiver");
|
||||
downlinkWriterThread = new Thread(this::pumpDownlinkQueueToTun, "rap-vpn-downlink-writer");
|
||||
runtimeWatchdogThread = new Thread(() -> runRuntimeWatchdog(clusterId, vpnConnectionId), "rap-vpn-runtime-watchdog");
|
||||
diagnosticWatchdogThread = new Thread(this::runDiagnosticServiceWatchdog, "rap-vpn-diagnostic-watchdog");
|
||||
if (resetThread != null) {
|
||||
resetThread.start();
|
||||
} else {
|
||||
writeRuntimeStatus("farm_dataplane", "backend relay queue reset skipped; farm owns vpn packet routes", 0, 0, 0, 0);
|
||||
}
|
||||
uplinkThread.start();
|
||||
for (Thread senderThread : uplinkSenderThreads) {
|
||||
senderThread.start();
|
||||
@@ -1004,6 +1076,7 @@ public class RapVpnService extends VpnService {
|
||||
downlinkThread.start();
|
||||
downlinkWriterThread.start();
|
||||
runtimeWatchdogThread.start();
|
||||
diagnosticWatchdogThread.start();
|
||||
}
|
||||
|
||||
private List<String> singletonUrl(String value) {
|
||||
@@ -1057,6 +1130,33 @@ public class RapVpnService extends VpnService {
|
||||
return out;
|
||||
}
|
||||
|
||||
private List<String> dedupeFabricRelayUrls(List<String> candidateUrls, String backendUrl) {
|
||||
List<String> ipv4OrHost = new ArrayList<>();
|
||||
List<String> ipv6 = new ArrayList<>();
|
||||
boolean backendIsPrivate = isPrivateURLHost(backendUrl);
|
||||
if (candidateUrls != null) {
|
||||
for (String url : candidateUrls) {
|
||||
String normalized = normalizeHTTPBaseUrl(url);
|
||||
if (!backendIsPrivate && isPrivateURLHost(normalized)) {
|
||||
continue;
|
||||
}
|
||||
if (isIPv6URLHost(normalized)) {
|
||||
addUniqueUrl(ipv6, normalized);
|
||||
} else {
|
||||
addUniqueUrl(ipv4OrHost, normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> out = new ArrayList<>();
|
||||
for (String url : ipv4OrHost) {
|
||||
addUniqueUrl(out, url);
|
||||
}
|
||||
for (String url : ipv6) {
|
||||
addUniqueUrl(out, url);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void addUniqueUrl(List<String> urls, String value) {
|
||||
if (urls == null || value == null) {
|
||||
return;
|
||||
@@ -1132,6 +1232,9 @@ public class RapVpnService extends VpnService {
|
||||
if (next == null || next.isEmpty() || next.equals(normalizedFailed)) {
|
||||
continue;
|
||||
}
|
||||
if (isIPv6URLHost(next) && hasNonIPv6RelayUrl(urls)) {
|
||||
continue;
|
||||
}
|
||||
activePacketRelayIndex = nextIndex;
|
||||
activePacketRelayUrlByProfile = next;
|
||||
configureBackendBypass(next);
|
||||
@@ -1145,6 +1248,18 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasNonIPv6RelayUrl(List<String> urls) {
|
||||
if (urls == null) {
|
||||
return false;
|
||||
}
|
||||
for (String url : urls) {
|
||||
if (url != null && !url.isEmpty() && !isIPv6URLHost(url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String selectReachablePacketRelayUrl(List<String> relayUrls, String clusterId, String vpnConnectionId) {
|
||||
if (relayUrls == null || relayUrls.isEmpty()) {
|
||||
return "";
|
||||
@@ -1190,11 +1305,13 @@ public class RapVpnService extends VpnService {
|
||||
interruptAndJoin(downlinkThread);
|
||||
interruptAndJoin(downlinkWriterThread);
|
||||
interruptAndJoin(runtimeWatchdogThread);
|
||||
interruptAndJoin(diagnosticWatchdogThread);
|
||||
uplinkThread = null;
|
||||
uplinkSenderThreads = null;
|
||||
downlinkThread = null;
|
||||
downlinkWriterThread = null;
|
||||
runtimeWatchdogThread = null;
|
||||
diagnosticWatchdogThread = null;
|
||||
uplinkWorkerCount = 0;
|
||||
downlinkFlowQueueCount = 0;
|
||||
uplinkQueues = null;
|
||||
@@ -1411,10 +1528,6 @@ public class RapVpnService extends VpnService {
|
||||
return;
|
||||
}
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastDiagnosticEnsureAt >= 10000) {
|
||||
lastDiagnosticEnsureAt = now;
|
||||
ensureDiagnosticServiceHealthy();
|
||||
}
|
||||
int stale = staleTCPHandshakeCount();
|
||||
if (stale <= 0) {
|
||||
continue;
|
||||
@@ -1440,6 +1553,29 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
private void runDiagnosticServiceWatchdog() {
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putLong("diagnostic_watchdog_started_at", System.currentTimeMillis())
|
||||
.putString("diagnostic_watchdog_last_action", "started")
|
||||
.apply();
|
||||
while (running) {
|
||||
try {
|
||||
ensureDiagnosticServiceHealthy();
|
||||
Thread.sleep(DIAGNOSTIC_WATCHDOG_INTERVAL_MS);
|
||||
} catch (InterruptedException e) {
|
||||
if (!running) {
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
|
||||
.putLong("diagnostic_watchdog_last_ensure_at", System.currentTimeMillis())
|
||||
.putString("diagnostic_watchdog_last_action", "failed")
|
||||
.putString("diagnostic_watchdog_last_error", e.getClass().getSimpleName() + ": " + e.getMessage())
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldHardRestartRuntime(long now) {
|
||||
if (runtimeWatchdogRecoveries.get() < 2) {
|
||||
return false;
|
||||
@@ -1636,14 +1772,13 @@ public class RapVpnService extends VpnService {
|
||||
System.arraycopy(packet, 0, copy, 0, length);
|
||||
if (!hasIPv4Source(copy, length)) {
|
||||
long mismatch = uplinkSourceMismatchPackets.incrementAndGet();
|
||||
String natKey = natKeyForOutboundReturn(copy, length);
|
||||
if (natKey.isEmpty() || !rewriteIPv4SourceToVPN(copy, length, natKey)) {
|
||||
Log.w(TAG, "vpn uplink source is not vpn address; dropping " + packetSummary(copy, length));
|
||||
writeRuntimeDetail("source_drop", packetSummary(copy, length), "uplink", -1, mismatch, "SOURCE_MISMATCH");
|
||||
recordUplinkDrop(length);
|
||||
return;
|
||||
}
|
||||
writeRuntimeDetail("source_nat", packetSummary(copy, length), "uplink", -1, mismatch, "SOURCE_NAT");
|
||||
if (clampIPv4TCPMSS(copy, length, VPN_TCP_MSS_CLAMP)) {
|
||||
writeRuntimeDetail("tcp_mss_clamp", packetSummary(copy, length), "uplink_tcp", -1, -1, "");
|
||||
}
|
||||
recordOutboundTCPHandshake(copy, length);
|
||||
if (handleLocalDnsQuery(copy, length)) {
|
||||
@@ -1989,6 +2124,7 @@ public class RapVpnService extends VpnService {
|
||||
return;
|
||||
}
|
||||
String key = tcpFlowKey(flow.srcIp, flow.srcPort, flow.dstPort);
|
||||
writeRuntimeDetail("tcp_syn_ack", key, "downlink_tcp", downlinkReceivedPackets.get(), tcpHandshakeStalls.get(), "");
|
||||
synchronized (pendingTcpHandshakes) {
|
||||
if (pendingTcpHandshakes.containsKey(key)) {
|
||||
pendingTcpHandshakes.put(key, -System.currentTimeMillis());
|
||||
@@ -2014,7 +2150,11 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
|
||||
private boolean isTCPPriorityPacket(byte[] packet, int length) {
|
||||
if (packet == null || length < 40 || ((packet[0] >> 4) & 0x0f) != 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean clampIPv4TCPMSS(byte[] packet, int length, int maxMss) {
|
||||
if (packet == null || length < 40 || maxMss <= 0 || ((packet[0] >> 4) & 0x0f) != 4) {
|
||||
return false;
|
||||
}
|
||||
int ihl = (packet[0] & 0x0f) * 4;
|
||||
@@ -2025,17 +2165,47 @@ public class RapVpnService extends VpnService {
|
||||
if (totalLength <= 0 || totalLength > length) {
|
||||
totalLength = length;
|
||||
}
|
||||
int tcpHeaderLength = ((packet[ihl + 12] >> 4) & 0x0f) * 4;
|
||||
if (tcpHeaderLength < 20 || ihl + tcpHeaderLength > totalLength) {
|
||||
int tcpOffset = ihl;
|
||||
int tcpHeaderLength = ((packet[tcpOffset + 12] >> 4) & 0x0f) * 4;
|
||||
if (tcpHeaderLength < 20 || tcpOffset + tcpHeaderLength > totalLength) {
|
||||
return false;
|
||||
}
|
||||
int flags = packet[ihl + 13] & 0xff;
|
||||
int flags = packet[tcpOffset + 13] & 0xff;
|
||||
boolean syn = (flags & 0x02) != 0;
|
||||
boolean fin = (flags & 0x01) != 0;
|
||||
boolean rst = (flags & 0x04) != 0;
|
||||
boolean ack = (flags & 0x10) != 0;
|
||||
int payloadLength = totalLength - ihl - tcpHeaderLength;
|
||||
return syn || fin || rst || (ack && payloadLength == 0);
|
||||
if (!syn || ack || tcpHeaderLength <= 20) {
|
||||
return false;
|
||||
}
|
||||
int option = tcpOffset + 20;
|
||||
int end = tcpOffset + tcpHeaderLength;
|
||||
while (option < end) {
|
||||
int kind = packet[option] & 0xff;
|
||||
if (kind == 0) {
|
||||
break;
|
||||
}
|
||||
if (kind == 1) {
|
||||
option++;
|
||||
continue;
|
||||
}
|
||||
if (option + 1 >= end) {
|
||||
break;
|
||||
}
|
||||
int optionLength = packet[option + 1] & 0xff;
|
||||
if (optionLength < 2 || option + optionLength > end) {
|
||||
break;
|
||||
}
|
||||
if (kind == 2 && optionLength == 4) {
|
||||
int current = u16(packet, option + 2);
|
||||
if (current > maxMss) {
|
||||
putU16(packet, option + 2, maxMss);
|
||||
normalizeIPv4PacketChecksums(packet, length);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
option += optionLength;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private String tcpFlowKey(String remoteIp, int remotePort, int localPort) {
|
||||
@@ -2246,7 +2416,8 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
recordUplinkDrop(Math.max(0, batchBytes - 4));
|
||||
writeRuntimeStatus("degraded", "uplink send failed after retry; continuing", 0, sentPackets, 0, errors);
|
||||
writeRuntimeDetail("error", "uplink send failed after retry batch=" + batch.size(), "uplink_sender", sentPackets, errors, "SEND_RETRY_EXHAUSTED", workerIndex);
|
||||
String retryError = lastUplinkSendErrorMessage == null || lastUplinkSendErrorMessage.isEmpty() ? "" : " last_error=" + lastUplinkSendErrorMessage;
|
||||
writeRuntimeDetail("error", "uplink send failed after retry batch=" + batch.size() + retryError, "uplink_sender", sentPackets, errors, "SEND_RETRY_EXHAUSTED", workerIndex);
|
||||
continue;
|
||||
}
|
||||
sentPackets += batch.size();
|
||||
@@ -2289,6 +2460,7 @@ public class RapVpnService extends VpnService {
|
||||
|
||||
private boolean sendUplinkBatchWithRetry(String clusterId, String vpnConnectionId, List<byte[]> batch, int workerIndex) {
|
||||
Exception lastError = null;
|
||||
lastUplinkSendErrorMessage = "";
|
||||
int relayAttempts = Math.max(1, activePacketRelayUrlsByProfile == null ? 1 : activePacketRelayUrlsByProfile.size());
|
||||
for (int relayAttempt = 0; relayAttempt < relayAttempts && running; relayAttempt++) {
|
||||
String relayUrl = currentPacketRelayUrl();
|
||||
@@ -2308,7 +2480,8 @@ public class RapVpnService extends VpnService {
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
lastError = e;
|
||||
writeRuntimeDetail("retry", "uplink send retry worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " error=" + e.getClass().getSimpleName(), "uplink_sender", -1, -1, e.getClass().getSimpleName(), workerIndex);
|
||||
lastUplinkSendErrorMessage = compactException(e);
|
||||
writeRuntimeDetail("retry", "uplink send retry worker=" + workerIndex + " relay=" + relayUrl + " attempt=" + attempt + " error=" + lastUplinkSendErrorMessage, "uplink_sender", -1, -1, e.getClass().getSimpleName(), workerIndex);
|
||||
sleepQuietly(UPLINK_SEND_RETRY_SLEEP_MS * (attempt + 1L));
|
||||
}
|
||||
}
|
||||
@@ -2323,6 +2496,9 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
|
||||
private boolean sendUplinkBatchOverWebSocket(String relayUrl, String clusterId, String vpnConnectionId, List<byte[]> batch, int workerIndex) {
|
||||
if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) {
|
||||
return false;
|
||||
}
|
||||
VpnPacketWebSocketRelay relay = packetWebSocketRelay;
|
||||
if (relay == null || relayUrl == null || !relayUrl.equals(relay.baseUrl())) {
|
||||
return false;
|
||||
@@ -2555,6 +2731,7 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
addDefaultDnsProbeDomains(domains);
|
||||
int resolved = 0;
|
||||
int tcpOk = 0;
|
||||
String last = "";
|
||||
long warmUntil = System.currentTimeMillis() + 30000;
|
||||
int pass = 0;
|
||||
@@ -2579,7 +2756,14 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
sleepQuietly(120);
|
||||
}
|
||||
if (passResolved >= Math.min(3, domains.size()) && pass >= 2) {
|
||||
if (pass <= VPN_TCP_WARMUP_PASSES) {
|
||||
int passTcpOk = runTCPWarmupPass(vpn);
|
||||
tcpOk += passTcpOk;
|
||||
if (passTcpOk > 0) {
|
||||
last = "tcp_warmup_ok=" + passTcpOk;
|
||||
}
|
||||
}
|
||||
if (passResolved >= Math.min(3, domains.size()) && tcpOk > 0 && pass >= 2) {
|
||||
break;
|
||||
}
|
||||
sleepQuietly(750);
|
||||
@@ -2588,15 +2772,60 @@ public class RapVpnService extends VpnService {
|
||||
return;
|
||||
}
|
||||
if (resolved > 0) {
|
||||
writeRuntimeStatus("ready", "vpn ready; dns warmup ok " + resolved + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 0);
|
||||
writeRuntimeStatus("ready", "vpn ready; warmup dns=" + resolved + " tcp=" + tcpOk + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 0);
|
||||
} else {
|
||||
writeRuntimeStatus("warming", "vpn started; dns warmup pending " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 1);
|
||||
writeRuntimeStatus("warming", "vpn started; warmup pending dns=0 tcp=" + tcpOk + " " + dnsInfo + " " + last, 0, 0, downlinkReceivedPackets.get(), 1);
|
||||
}
|
||||
Log.i(TAG, "vpn readiness warmup complete: connection=" + vpnConnectionId + " resolved=" + resolved + " " + dnsInfo + " " + last);
|
||||
Log.i(TAG, "vpn readiness warmup complete: connection=" + vpnConnectionId + " resolved=" + resolved + " tcp=" + tcpOk + " " + dnsInfo + " " + last);
|
||||
}, "rap-vpn-readiness-warmup");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private int runTCPWarmupPass(Network vpn) {
|
||||
int ok = 0;
|
||||
for (String target : DEFAULT_TCP_WARMUP_TARGETS) {
|
||||
if (!running) {
|
||||
return ok;
|
||||
}
|
||||
TCPWarmupTarget parsed = parseTCPWarmupTarget(target);
|
||||
if (parsed == null) {
|
||||
continue;
|
||||
}
|
||||
long started = System.currentTimeMillis();
|
||||
try (Socket socket = new Socket()) {
|
||||
if (vpn != null) {
|
||||
vpn.bindSocket(socket);
|
||||
}
|
||||
socket.connect(new InetSocketAddress(parsed.host, parsed.port), VPN_TCP_WARMUP_CONNECT_TIMEOUT_MS);
|
||||
ok++;
|
||||
writeRuntimeDetail("tcp_warmup", target + " ms=" + (System.currentTimeMillis() - started), "readiness", ok, 0, "", -1);
|
||||
} catch (Exception e) {
|
||||
writeRuntimeDetail("tcp_warmup_failed", target + " " + e.getClass().getSimpleName(), "readiness", ok, 1, e.getClass().getSimpleName(), -1);
|
||||
}
|
||||
sleepQuietly(120);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
private TCPWarmupTarget parseTCPWarmupTarget(String target) {
|
||||
if (target == null) {
|
||||
return null;
|
||||
}
|
||||
int split = target.lastIndexOf(':');
|
||||
if (split <= 0 || split >= target.length() - 1) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
int port = Integer.parseInt(target.substring(split + 1));
|
||||
if (port <= 0 || port > 65535) {
|
||||
return null;
|
||||
}
|
||||
return new TCPWarmupTarget(target.substring(0, split), port);
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDefaultDnsProbeDomains(Set<String> domains) {
|
||||
if (domains == null) {
|
||||
return;
|
||||
@@ -2724,6 +2953,9 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
|
||||
private List<byte[]> receiveDownlinkBatch(String relayUrl, RapApiClient client, String clusterId, String vpnConnectionId, int timeoutMs) throws Exception {
|
||||
if (!PACKET_WEBSOCKET_DATAPLANE_ENABLED) {
|
||||
return client.receiveClientPacketBatch(clusterId, vpnConnectionId, timeoutMs);
|
||||
}
|
||||
VpnPacketWebSocketRelay relay = packetWebSocketRelay;
|
||||
if (relay != null && relayUrl != null && relayUrl.equals(relay.baseUrl())) {
|
||||
List<byte[]> packets = relay.receiveClientPacketBatch(clusterId, vpnConnectionId, timeoutMs);
|
||||
@@ -2890,6 +3122,16 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
private static class TCPWarmupTarget {
|
||||
final String host;
|
||||
final int port;
|
||||
|
||||
TCPWarmupTarget(String host, int port) {
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean writePacketToTun(FileDescriptor fd, byte[] packet, int packetLength) throws Exception {
|
||||
int offset = 0;
|
||||
int attempts = 0;
|
||||
@@ -2950,6 +3192,19 @@ public class RapVpnService extends VpnService {
|
||||
}
|
||||
}
|
||||
|
||||
private String compactException(Exception e) {
|
||||
if (e == null) {
|
||||
return "";
|
||||
}
|
||||
String message = e.getMessage();
|
||||
String value = e.getClass().getSimpleName() + (message == null || message.trim().isEmpty() ? "" : ": " + message.trim());
|
||||
value = value.replace('\n', ' ').replace('\r', ' ').trim();
|
||||
if (value.length() > 240) {
|
||||
return value.substring(0, 240);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void closeFdQuietly(FileDescriptor fd) {
|
||||
if (fd == null) {
|
||||
return;
|
||||
@@ -3064,6 +3319,8 @@ public class RapVpnService extends VpnService {
|
||||
.putLong("runtime_watchdog_recoveries", runtimeWatchdogRecoveries.get())
|
||||
.putLong("tcp_handshake_stalls", tcpHandshakeStalls.get())
|
||||
.putLong("runtime_watchdog_hard_restarts", runtimeWatchdogHardRestarts.get())
|
||||
.putLong("diagnostic_watchdog_ensure_requests", diagnosticEnsureRequests.get())
|
||||
.putLong("diagnostic_watchdog_restart_requests", diagnosticRestartRequests.get())
|
||||
.putLong("uplink_source_mismatch_packets", uplinkSourceMismatchPackets.get())
|
||||
.putLong("downlink_destination_mismatch_packets", downlinkDestinationMismatchPackets.get())
|
||||
.putFloat("uplink_read_mbps", uplinkReadMbps)
|
||||
|
||||
@@ -1250,6 +1250,162 @@ then reports the expected next event window without mailbox reads, drains, acks,
|
||||
or consumer cursor mutation. The live smoke is
|
||||
`scripts/fabric/c19z1-remote-workspace-mailbox-preflight-smoke.ps1`.
|
||||
|
||||
C19Z2 adds telemetry for mailbox preflight checks. Workload status and heartbeat
|
||||
reports now expose preflight totals, ack/checkpoint split counters, and the last
|
||||
preflight cursor/window fields so diagnostics can distinguish handoff checks
|
||||
from mailbox reads. The live smoke is
|
||||
`scripts/fabric/c19z2-remote-workspace-mailbox-preflight-telemetry-smoke.ps1`.
|
||||
|
||||
C19Z3 adds stale-cursor diagnostics to mailbox preflight. If a consumer cursor
|
||||
falls behind retained mailbox events after bounded-mailbox drops, preflight
|
||||
reports retained sequence bounds, `stale_cursor`, `diagnostic_state`, and
|
||||
`missing_dropped_count`; the latest stale state is also visible in telemetry and
|
||||
readiness diagnostics. The live smoke is
|
||||
`scripts/fabric/c19z3-remote-workspace-mailbox-stale-preflight-smoke.ps1`.
|
||||
|
||||
C19Z4 adds action hints to mailbox preflight diagnostics. Stale cursor gaps now
|
||||
return `recommended_action=reset_consumer_and_resync` plus hints to reset the
|
||||
consumer cursor, request full adapter resync, and resume from checkpoint after
|
||||
resync. The live smoke is
|
||||
`scripts/fabric/c19z4-remote-workspace-mailbox-preflight-action-hints-smoke.ps1`.
|
||||
|
||||
C19Z5 adds provenance for the selected preflight action. Responses, telemetry,
|
||||
and readiness diagnostics include `action_reason` and structured
|
||||
`action_context` with cursor, retained sequence bounds, dropped/missing counts,
|
||||
and expected window values. The live smoke is
|
||||
`scripts/fabric/c19z5-remote-workspace-mailbox-preflight-provenance-smoke.ps1`.
|
||||
|
||||
C19Z6 adds the operator-facing summary for mailbox preflight. Responses,
|
||||
telemetry, and readiness diagnostics include `operator_summary` plus compact
|
||||
`operator_summary_fields` for the diagnostic state, selected action, reason,
|
||||
cursor, retained bounds, and expected window counters. The live smoke is
|
||||
`scripts/fabric/c19z6-remote-workspace-mailbox-preflight-summary-smoke.ps1`.
|
||||
|
||||
C19Z7 adds machine-sortable operator severity for mailbox preflight. Responses,
|
||||
telemetry, readiness diagnostics, and summary fields expose `operator_status`
|
||||
and `operator_severity`, classifying ready windows, caught-up cursors, and
|
||||
stale cursor gaps without parsing summary text. The live smoke is
|
||||
`scripts/fabric/c19z7-remote-workspace-mailbox-preflight-severity-smoke.ps1`.
|
||||
|
||||
C19Z8 adds the grouped readiness rollup for mailbox preflight. The readiness
|
||||
diagnostic keeps the flat fields and adds `last_preflight` with observed time,
|
||||
cursor, counts, diagnostic state, action hints/provenance, operator summary,
|
||||
status, severity, and summary fields. The live smoke is
|
||||
`scripts/fabric/c19z8-remote-workspace-mailbox-preflight-rollup-smoke.ps1`.
|
||||
|
||||
C19Z9 adds retained-window detail to that preflight rollup. The grouped
|
||||
`last_preflight` readiness object includes first/last retained sequence and
|
||||
mailbox dropped total so stale cursor explanations are visible without opening
|
||||
the raw preflight response. The live smoke is
|
||||
`scripts/fabric/c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1`.
|
||||
|
||||
C19Z10 adds a structured remediation checklist to that rollup. The grouped
|
||||
`last_preflight.remediation_checklist` entries expose required/satisfied
|
||||
operator steps derived from action hints, including cursor reset, full adapter
|
||||
resync, and resume after resync for stale cursor gaps. The live smoke is
|
||||
`scripts/fabric/c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1`.
|
||||
|
||||
C19Z11 adds checklist status and counts to that rollup. The grouped
|
||||
`last_preflight` readiness object exposes `remediation_checklist_status` and
|
||||
total/required/satisfied/pending counts for admin UI summaries. The live smoke
|
||||
is
|
||||
`scripts/fabric/c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1`.
|
||||
|
||||
C19Z12 adds session-level preflight operator status/severity counters.
|
||||
Readiness exposes status and severity count maps, mirrored in `last_preflight`,
|
||||
so repeated resync-required/warn preflights are visible without retaining a
|
||||
history log. The live smoke is
|
||||
`scripts/fabric/c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1`.
|
||||
|
||||
C19Z13 adds compact preflight attention status on top of those counters.
|
||||
Readiness and `last_preflight` expose `preflight_attention_status` so admin UI
|
||||
can sort clean, attention-needed, and repeated-resync sessions without
|
||||
interpreting count maps. The live smoke is
|
||||
`scripts/fabric/c19z13-remote-workspace-mailbox-preflight-attention-smoke.ps1`.
|
||||
|
||||
C19Z14 proves the repeated-resync attention branch. Unit and live smoke coverage
|
||||
perform multiple stale preflights on one active adapter session and verify
|
||||
`preflight_attention_status=repeated_resync_required` with repeated
|
||||
resync-required/warn counters. The live smoke is
|
||||
`scripts/fabric/c19z14-remote-workspace-mailbox-preflight-repeated-attention-smoke.ps1`.
|
||||
|
||||
C19Z15 adds the preflight attention reason. Readiness and `last_preflight`
|
||||
expose `preflight_attention_reason` beside the attention status, explaining
|
||||
clean, attention-needed, and repeated-resync states without UI-side counter
|
||||
parsing. The live smoke is
|
||||
`scripts/fabric/c19z15-remote-workspace-mailbox-preflight-attention-reason-smoke.ps1`.
|
||||
|
||||
C19Z16 completes focused proof coverage for those attention reasons. Unit tests
|
||||
cover clean, single-resync, repeated-resync, and no-preflight mappings; live
|
||||
smoke proves the single stale-preflight reason. The live smoke is
|
||||
`scripts/fabric/c19z16-remote-workspace-mailbox-preflight-attention-reason-coverage-smoke.ps1`.
|
||||
|
||||
C19Z17 adds the preflight diagnostics contract marker. The readiness
|
||||
`last_preflight` rollup includes `diagnostics_schema_version` and
|
||||
`diagnostics_contract` entries for retained-window, remediation-checklist,
|
||||
attention, and operator-count fields, allowing UI rendering to be gated safely.
|
||||
The live smoke is
|
||||
`scripts/fabric/c19z17-remote-workspace-mailbox-preflight-contract-smoke.ps1`.
|
||||
|
||||
C19Z18 adds boolean diagnostics feature flags to the same preflight rollup.
|
||||
`last_preflight.diagnostics_features` now exposes retained-window,
|
||||
remediation-checklist, attention, and operator-count support directly, so admin
|
||||
UI and automation can gate each diagnostics group without scanning the contract
|
||||
list. The live smoke is
|
||||
`scripts/fabric/c19z18-remote-workspace-mailbox-preflight-feature-flags-smoke.ps1`.
|
||||
|
||||
C19Z19 proves compatibility between the two diagnostics contract forms. Unit
|
||||
coverage and live smoke verify that workload and telemetry reports expose both
|
||||
the string `diagnostics_contract` entries and matching boolean
|
||||
`diagnostics_features` flags for every preflight diagnostics group. The live
|
||||
smoke is
|
||||
`scripts/fabric/c19z19-remote-workspace-mailbox-preflight-contract-compatibility-smoke.ps1`.
|
||||
|
||||
C19Z20 proves the no-preflight readiness shape. Before any mailbox preflight is
|
||||
observed, active adapter sessions expose `preflight_attention_status=unknown`,
|
||||
`preflight_attention_reason=no_preflight_observed`, zero session preflight
|
||||
count, and no grouped `last_preflight` rollup. The live smoke is
|
||||
`scripts/fabric/c19z20-remote-workspace-mailbox-preflight-absence-smoke.ps1`.
|
||||
|
||||
C19Z21 proves the no-active-session readiness shape. After closing the active
|
||||
adapter session, readiness exposes idle/not-ready state, zero active sessions,
|
||||
no active `adapter_session_id`, no grouped `last_preflight`, and terminal
|
||||
`last_session_state=closed` from the terminal-session ledger. The live smoke is
|
||||
`scripts/fabric/c19z21-remote-workspace-no-active-session-readiness-smoke.ps1`.
|
||||
|
||||
C19Z22 proves terminal-state readiness for `expire` and `reset` controls. The
|
||||
same no-active-session readiness shape now reports
|
||||
`last_session_state=expired` or `last_session_state=reset` from the
|
||||
terminal-session ledger. The live smoke is
|
||||
`scripts/fabric/c19z22-remote-workspace-terminal-state-readiness-smoke.ps1`.
|
||||
|
||||
C19Z23 adds grouped terminal-session summary metadata to no-active-session
|
||||
readiness. `terminal_session_summary` carries adapter session id, terminal
|
||||
state, reason, and control timestamp so admin UI can render the terminal cause
|
||||
without stitching flat fields. The live smoke is
|
||||
`scripts/fabric/c19z23-remote-workspace-terminal-session-summary-smoke.ps1`.
|
||||
|
||||
C19Z24 adds the terminal-session summary contract marker. The grouped summary
|
||||
now carries schema version
|
||||
`rap.remote_workspace_adapter_terminal_session_summary.v1` and a
|
||||
summary-contract field list for explicit UI gating. The live smoke is
|
||||
`scripts/fabric/c19z24-remote-workspace-terminal-summary-contract-smoke.ps1`.
|
||||
|
||||
C19Z25 adds boolean `summary_features` to the same grouped terminal-session
|
||||
summary, covering adapter session id, state, reason, and control timestamp. The
|
||||
live smoke is
|
||||
`scripts/fabric/c19z25-remote-workspace-terminal-summary-features-smoke.ps1`.
|
||||
|
||||
C19Z26 proves compatibility between `summary_contract` and `summary_features`
|
||||
for the grouped terminal-session summary in workload and telemetry reports. The
|
||||
live smoke is
|
||||
`scripts/fabric/c19z26-remote-workspace-terminal-summary-compatibility-smoke.ps1`.
|
||||
|
||||
C19Z27 proves the absence shape for terminal-session summary. Before any adapter
|
||||
session or terminal history exists, readiness reports `waiting_for_session` and
|
||||
does not include `terminal_session_summary`. The live smoke is
|
||||
`scripts/fabric/c19z27-remote-workspace-terminal-summary-absence-smoke.ps1`.
|
||||
|
||||
Includes:
|
||||
|
||||
- container/native workload contract
|
||||
@@ -1671,9 +1827,234 @@ with synthetic traffic only. C18 defines the VPN/IP tunnel service target model
|
||||
authorize VPN/IP tunnel runtime. C18A adds the VPN/IP tunnel control-plane
|
||||
data model and platform-admin skeleton only. C18B hardens single-active
|
||||
lease/fencing semantics. C18C adds node-agent desired-state/status reporting
|
||||
for scoped VPN assignments only. C19 is now reserved for the Version
|
||||
Storage/Update Repository and node-agent update/rollback foundation; it is not
|
||||
implemented by this document. No RDP, data-plane, VPN runtime, production
|
||||
relay, production mesh service traffic, node-agent VPN execution, host
|
||||
networking, service workload runtime, or production updater behavior is implied
|
||||
by this document.
|
||||
for scoped VPN assignments only. C19 Remote Workspace adapter probe layers are
|
||||
still node-local and probe-only; through C19Z30, fresh no-session runtime
|
||||
readiness exposes a grouped `no_session_summary` contract plus
|
||||
`summary_features`, with compatibility proof across workload and telemetry,
|
||||
while terminal-history readiness exposes `terminal_session_summary` and omits
|
||||
`no_session_summary`; summary exclusivity is proven across fresh, active, and
|
||||
terminal readiness states, and a compact readiness state matrix artifact exists
|
||||
for admin/runtime handoff. C19Z34 records the explicit probe-to-runtime gates
|
||||
and confirms Remote Workspace still has no production payload traffic. C19Z35
|
||||
adds the disabled-by-default real-adapter supervision status scaffold without
|
||||
enabling real adapter execution. C19Z36 proves that scaffold's env/status/
|
||||
guardrail compatibility. C19Z37 adds sanitized config projection for the future
|
||||
real adapter while still refusing activation and payload traffic. C19Z38 proves
|
||||
that projection for both default/empty and requested config shapes. C19Z39 adds
|
||||
an explicit blocked activation decision contract with required/missing gates.
|
||||
C19Z40 adds a compact handoff report proving scaffold/projection/decision
|
||||
alignment for requested and default node config.
|
||||
C19Z41 adds explicit feature flags for those real-adapter supervision fields.
|
||||
C19Z42 folds those feature flags into the compact handoff report for
|
||||
admin/runtime handoff.
|
||||
C19Z43 proves contract-probe precedence when real-adapter supervision is also
|
||||
requested in desired workload config.
|
||||
C19Z44 proves the real-adapter-only desired workload path remains degraded and
|
||||
blocked.
|
||||
C19Z45 adds a compact desired-workload mode matrix for probe-only,
|
||||
real-adapter-only, and combined requested modes.
|
||||
C19Z46 adds compatibility proof for the mode matrix row contract.
|
||||
C19Z47 adds a disabled process-supervisor preconditions contract for future
|
||||
external RDP worker supervision.
|
||||
C19Z48 proves that contract across requested/default config shapes.
|
||||
C19Z49 folds process-supervisor preconditions into the compact handoff report.
|
||||
C19Z50 folds process-supervisor preconditions into the desired-workload mode
|
||||
matrix.
|
||||
C19Z51 proves the mode matrix v2 row contract.
|
||||
C19Z52 adds a disabled process-health-probe contract for future external RDP
|
||||
worker supervision.
|
||||
C19Z53 proves that process-health-probe contract across requested/default
|
||||
status forms.
|
||||
C19Z54 folds process-health-probe visibility into the compact real-adapter
|
||||
handoff report.
|
||||
C19Z55 folds process-health-probe visibility into the desired-workload mode
|
||||
matrix.
|
||||
C19Z56 proves the mode matrix v3 row contract.
|
||||
C19Z57 adds a compact disabled real-adapter readiness/handoff checklist.
|
||||
C19Z58 proves the readiness/handoff summary and checklist contract.
|
||||
C19Z59 adds a disabled real-adapter operator action map.
|
||||
C19Z60 proves the disabled real-adapter operator action map contract.
|
||||
C19Z61 adds a compact disabled real-adapter admin handoff bundle.
|
||||
C19Z62 proves the disabled real-adapter admin handoff bundle contract.
|
||||
C19Z63 adds compact disabled real-adapter admin handoff digest rows.
|
||||
C19Z64 proves the disabled real-adapter admin handoff digest row contract.
|
||||
C19Z65 adds a disabled real-adapter admin handoff digest rollup.
|
||||
C19Z66 proves the disabled real-adapter admin handoff digest rollup contract.
|
||||
C19Z67 adds a disabled real-adapter admin handoff full-chain summary.
|
||||
C19Z68 proves the disabled real-adapter admin handoff full-chain summary
|
||||
contract.
|
||||
C19Z69 adds a disabled real-adapter admin handoff release marker.
|
||||
C19Z70 proves the disabled real-adapter admin handoff release marker contract.
|
||||
C19Z71 adds a final contract-only package index for the disabled real-adapter
|
||||
admin handoff chain.
|
||||
C19Z72 proves the final package index contract for the disabled real-adapter
|
||||
admin handoff chain.
|
||||
C19Z73 adds a contract-only runtime gate phase boundary for the next disabled
|
||||
real-adapter preflight phase.
|
||||
C19Z74 proves the runtime gate phase boundary contract.
|
||||
C19Z75 adds a disabled real-adapter runtime gate preflight checklist with all
|
||||
items still blocking runtime.
|
||||
C19Z76 proves the disabled real-adapter runtime gate preflight checklist
|
||||
contract.
|
||||
C19Z77 adds a disabled real-adapter runtime gate preflight status summary.
|
||||
C19Z78 proves the disabled real-adapter runtime gate preflight status summary
|
||||
contract.
|
||||
C19Z79 adds disabled real-adapter runtime gate preflight action hints.
|
||||
C19Z80 proves the disabled real-adapter runtime gate preflight action hints
|
||||
contract.
|
||||
C19Z81 adds a disabled real-adapter runtime gate preflight operator handoff
|
||||
bundle.
|
||||
C19Z82 proves the disabled real-adapter runtime gate preflight operator handoff
|
||||
bundle contract.
|
||||
C19Z83 adds a disabled real-adapter runtime gate preflight release marker.
|
||||
C19Z84 proves the disabled real-adapter runtime gate preflight release marker
|
||||
contract.
|
||||
C19Z85 adds a disabled real-adapter runtime gate preflight package index.
|
||||
C19Z86 proves the disabled real-adapter runtime gate preflight package index
|
||||
contract.
|
||||
C19Z87 adds a disabled real-adapter runtime gate preflight closeout summary.
|
||||
C19Z88 proves the disabled real-adapter runtime gate preflight closeout summary
|
||||
contract.
|
||||
C19Z89 starts the explicit real-adapter runtime gate enablement phase with a
|
||||
contract-only request that remains blocked pending validation.
|
||||
C19Z90 proves the explicit real-adapter runtime gate enablement request
|
||||
contract.
|
||||
C19Z91 adds contract-only operator confirmation validation while keeping the
|
||||
runtime gate blocked pending remaining validations.
|
||||
C19Z92 proves the operator confirmation validation contract.
|
||||
C19Z93 adds contract-only binary validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z94 proves the binary validation contract.
|
||||
C19Z95 adds contract-only permission validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z96 proves the permission validation contract.
|
||||
C19Z97 adds contract-only supervisor validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z98 proves the supervisor validation contract.
|
||||
C19Z99 adds contract-only health probe validation while keeping the runtime gate
|
||||
blocked pending payload gate validation.
|
||||
C19Z100 proves the health probe validation contract.
|
||||
C19Z101 adds contract-only payload gate validation with no remaining required
|
||||
validations while keeping runtime not enabled.
|
||||
C19Z102 proves the payload gate validation contract.
|
||||
C19Z103 adds the runtime gate validation closeout while keeping explicit
|
||||
operator enablement required.
|
||||
C19Z104 proves the runtime gate validation closeout contract.
|
||||
C19Z105 adds an operator enablement readiness package while keeping runtime
|
||||
disabled by default.
|
||||
C19Z106 proves the operator enablement readiness package contract.
|
||||
C19Z107 adds an operator enablement readiness release marker while keeping
|
||||
runtime disabled by default.
|
||||
C19Z108 proves the operator enablement readiness release marker contract.
|
||||
C19Z109 adds an operator enablement readiness package index while keeping
|
||||
runtime disabled by default.
|
||||
C19Z110 proves the operator enablement readiness package index contract.
|
||||
C19Z111 adds an operator readiness closeout summary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z112 proves the operator readiness closeout summary contract.
|
||||
C19Z113 adds an operator review decision request while keeping runtime disabled
|
||||
by default.
|
||||
C19Z114 proves the operator review decision request contract.
|
||||
C19Z115 adds an operator decision status summary while keeping runtime disabled
|
||||
by default.
|
||||
C19Z116 proves the operator decision status summary contract.
|
||||
C19Z117 adds an operator approval/rejection outcome contract with the outcome
|
||||
not approved and runtime disabled by default.
|
||||
C19Z118 proves the operator approval/rejection outcome contract.
|
||||
C19Z119 adds an operator outcome closeout/reopen boundary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z120 proves the operator outcome closeout/reopen boundary contract.
|
||||
C19Z121 adds a not-approved outcome release marker while keeping runtime
|
||||
disabled by default.
|
||||
C19Z122 proves the not-approved outcome release marker contract.
|
||||
C19Z123 adds a not-approved outcome package index while keeping runtime disabled
|
||||
by default.
|
||||
C19Z124 proves the not-approved outcome package index contract.
|
||||
C19Z125 adds a not-approved outcome closeout summary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z126 proves the not-approved outcome closeout summary contract.
|
||||
C19Z127 adds a final not-approved outcome release marker while keeping runtime
|
||||
disabled by default.
|
||||
C19Z128 proves the final not-approved outcome release marker contract.
|
||||
C19Z129 adds a final not-approved outcome package index/archive marker while
|
||||
keeping runtime disabled by default.
|
||||
C19Z130 proves the final not-approved outcome package index/archive marker
|
||||
contract.
|
||||
C19Z131 adds a not-approved outcome archive closeout manifest while keeping
|
||||
runtime disabled by default.
|
||||
C19Z132 proves the not-approved outcome archive closeout manifest contract.
|
||||
C19Z133 adds a stopped-branch sentinel for the not-approved outcome while
|
||||
keeping runtime disabled by default.
|
||||
C19Z134 proves the not-approved outcome stopped-branch sentinel contract.
|
||||
C19Z135 adds a no-continuation guard for the stopped not-approved outcome while
|
||||
keeping runtime disabled by default.
|
||||
C19Z136 proves the not-approved outcome no-continuation guard contract.
|
||||
C19Z137 adds continuation block enforcement for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z138 proves the not-approved outcome continuation block enforcement
|
||||
contract.
|
||||
C19Z139 adds a continuation block audit record for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z140 proves the not-approved outcome continuation block audit record
|
||||
contract.
|
||||
C19Z141 adds a continuation block audit rollup for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z142 proves the not-approved outcome continuation block audit rollup
|
||||
contract.
|
||||
C19Z143 adds an operator stop summary for the stopped not-approved outcome
|
||||
while keeping runtime disabled by default.
|
||||
C19Z144 proves the not-approved outcome operator stop summary contract.
|
||||
C19Z145 adds an operator stop handoff for the stopped not-approved outcome
|
||||
while keeping runtime disabled by default.
|
||||
C19Z146 proves the not-approved outcome operator stop handoff contract.
|
||||
C19Z147 adds an operator stop handoff digest for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z148 proves the not-approved outcome operator stop handoff digest contract.
|
||||
C19Z149 adds an operator stop status snapshot for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z150 proves the not-approved outcome operator stop status snapshot contract.
|
||||
C19Z151 adds an operator stop status snapshot index for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z152 proves the not-approved outcome operator stop status snapshot index
|
||||
contract.
|
||||
C19Z153 adds an operator stop status catalog for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z154 proves the not-approved outcome operator stop status catalog contract.
|
||||
C19Z155 adds an operator stop status catalog release marker for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z156 proves the not-approved outcome operator stop status catalog release
|
||||
marker contract.
|
||||
C19Z157 adds an operator stop status catalog package index for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z158 proves the not-approved outcome operator stop status catalog package
|
||||
index contract.
|
||||
C19Z159 adds an operator stop status catalog closeout summary for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z160 proves the not-approved outcome operator stop status catalog closeout
|
||||
summary contract.
|
||||
C19Z161 adds an operator stop status final archive marker for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z162 proves the not-approved outcome operator stop status final archive
|
||||
marker contract.
|
||||
C19Z163 adds an operator stop status final archive manifest for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z164 proves the not-approved outcome operator stop status final archive
|
||||
manifest contract.
|
||||
C19Z165 adds a terminal-complete marker for the stopped not-approved outcome
|
||||
factory while keeping runtime disabled by default.
|
||||
C19Z166 proves the not-approved outcome factory terminal-complete contract.
|
||||
C20Z1 opens a new explicit real-adapter enablement request while keeping
|
||||
runtime disabled by default.
|
||||
C20Z2 proves the new explicit real-adapter enablement request contract.
|
||||
C20Z3 adds the operator validation intake for the new explicit request while
|
||||
keeping runtime disabled by default.
|
||||
C20Z4 completes the operator validation checklist contract while keeping
|
||||
runtime disabled by default.
|
||||
C20Z5 closes the operator validation chain contract while keeping runtime
|
||||
disabled by default.
|
||||
C20Z6 proves the C20 stage terminal-complete contract.
|
||||
Version Storage/Update
|
||||
Repository and node-agent update/rollback foundation are not implemented by
|
||||
this document. No RDP, data-plane, VPN runtime, production relay, production
|
||||
mesh service traffic, node-agent VPN execution, host networking, service
|
||||
workload runtime, or production updater behavior is implied by this document.
|
||||
|
||||
@@ -1324,6 +1324,394 @@ C19Z1 adds a read-only mailbox handoff preflight endpoint. Adapter runtimes can
|
||||
call `/mailbox/preflight` with `consumer_id` and `resume_from=ack|checkpoint`
|
||||
to validate the stored cursor and inspect the next expected event window without
|
||||
reading, draining, acking, or mutating consumer state.
|
||||
C19Z2 adds separate telemetry for those handoff checks. Workload status and
|
||||
heartbeat reports expose preflight totals split by ack/checkpoint cursor and the
|
||||
last preflight session, consumer, cursor, after-sequence, available/returned/
|
||||
skipped counts, and expected sequence range; readiness diagnostics mirror the
|
||||
latest preflight summary.
|
||||
C19Z3 adds stale-cursor diagnostics to preflight. When a consumer cursor points
|
||||
behind dropped bounded-mailbox events, the preflight response reports retained
|
||||
sequence bounds, `diagnostic_state=stale_cursor_gap`, `stale_cursor=true`, and
|
||||
`missing_dropped_count`; workload/heartbeat telemetry and readiness diagnostics
|
||||
mirror that latest stale state.
|
||||
C19Z4 adds explicit action hints to those diagnostics. Preflight responses now
|
||||
include `recommended_action` and `action_hints`; stale cursor gaps recommend
|
||||
resetting the consumer cursor, requesting a full adapter resync, and resuming
|
||||
from checkpoint after resync. Telemetry and readiness diagnostics mirror the
|
||||
latest recommended action and hints.
|
||||
C19Z5 adds remediation provenance for those hints. Preflight responses,
|
||||
workload/heartbeat telemetry, and readiness diagnostics include
|
||||
`action_reason` plus structured `action_context` with the resume cursor,
|
||||
retained sequence bounds, dropped/missing counts, consumer checkpoint/ack, and
|
||||
expected window counters that explain why the recommended action was chosen.
|
||||
C19Z6 adds a compact operator-facing preflight summary derived from the same
|
||||
read-only state. Preflight responses, telemetry, and readiness diagnostics now
|
||||
include `operator_summary` and `operator_summary_fields` so dashboards can show
|
||||
the diagnostic state, action, reason, resume cursor, retained bounds, and key
|
||||
window counters without recomputing or mutating mailbox state.
|
||||
C19Z7 adds machine-sortable operator status and severity to that summary.
|
||||
Preflight responses, telemetry, readiness diagnostics, and
|
||||
`operator_summary_fields` now expose `operator_status` and `operator_severity`
|
||||
so dashboards can sort ready, caught-up, and resync-required handoffs without
|
||||
parsing human text.
|
||||
C19Z8 groups the latest preflight view for admin UI consumption. The readiness
|
||||
diagnostic keeps all existing flat latest-preflight fields and adds
|
||||
`last_preflight` with observed time, cursor, counts, diagnostic state, selected
|
||||
action, action provenance, operator summary, status, severity, and summary
|
||||
fields.
|
||||
C19Z9 adds retained-window detail to that grouped readiness view. The
|
||||
`last_preflight` object now includes first/last retained sequence and mailbox
|
||||
dropped total so stale-cursor summaries can explain the bounded mailbox window
|
||||
without requiring a separate raw preflight lookup.
|
||||
C19Z10 adds a structured remediation checklist to the grouped readiness view.
|
||||
The `last_preflight.remediation_checklist` entries are derived from diagnostic
|
||||
state and action hints, marking required/satisfied operator steps for cursor
|
||||
reset, adapter resync, and post-resync resume without executing those actions.
|
||||
C19Z11 adds summary status and counts for that checklist. The grouped readiness
|
||||
view now exposes `remediation_checklist_status` plus total, required,
|
||||
satisfied, and pending counts so admin UI can render checklist state without
|
||||
scanning the step array.
|
||||
C19Z12 adds per-session preflight operator status/severity counters. Readiness
|
||||
now exposes counts for statuses such as `ready_to_resume`, `caught_up`, and
|
||||
`resync_required`, plus severity counts such as `ok`, `info`, and `warn`, and
|
||||
the grouped latest-preflight rollup mirrors those counters for dashboard
|
||||
context.
|
||||
C19Z13 derives a compact preflight attention status from those counters.
|
||||
Readiness and `last_preflight` expose `preflight_attention_status` values such
|
||||
as `clean`, `needs_attention`, and `repeated_resync_required`, letting admin UI
|
||||
sort sessions without interpreting count maps directly.
|
||||
C19Z14 proves the repeated-resync branch. Unit and live smoke coverage now run
|
||||
multiple stale preflights on the same active adapter session and verify
|
||||
`preflight_attention_status=repeated_resync_required` with repeated
|
||||
`resync_required` / `warn` counters, while the preflight path remains read-only.
|
||||
C19Z15 adds `preflight_attention_reason` beside the attention status. The reason
|
||||
is derived from the latest preflight counters/status and explains clean,
|
||||
attention-needed, and repeated-resync states without requiring UI code to parse
|
||||
the counter maps.
|
||||
C19Z16 completes focused proof coverage for those reasons. Unit coverage proves
|
||||
clean, single-resync, repeated-resync, and no-preflight mappings, and live smoke
|
||||
proves the single stale-preflight `resync_required_preflight_observed` reason.
|
||||
C19Z17 adds a diagnostics contract marker to the grouped preflight readiness
|
||||
rollup. `last_preflight` now includes `diagnostics_schema_version` and a
|
||||
`diagnostics_contract` list for retained-window, remediation-checklist,
|
||||
attention, and operator-count fields so admin UI can gate rendering safely.
|
||||
C19Z18 adds machine-readable feature flags for that contract. `last_preflight`
|
||||
now includes boolean `diagnostics_features` entries for retained-window,
|
||||
remediation-checklist, attention, and operator-count diagnostics, allowing UI
|
||||
and automation clients to check support without scanning the contract list.
|
||||
C19Z19 adds a compatibility proof for the two contract forms. Unit and live
|
||||
smoke coverage now verify that workload and telemetry reports expose matching
|
||||
`diagnostics_contract` entries and `diagnostics_features` booleans for each
|
||||
preflight diagnostics group.
|
||||
C19Z20 adds the no-preflight absence proof. Active adapter sessions that have
|
||||
not observed a mailbox preflight report `preflight_attention_status=unknown`,
|
||||
`preflight_attention_reason=no_preflight_observed`, zero session preflight
|
||||
count, and no grouped `last_preflight` rollup, so UI can distinguish "not
|
||||
observed yet" from an observed clean state.
|
||||
C19Z21 adds the no-active-session readiness proof. After the last adapter
|
||||
session is closed, readiness reports idle/not-ready with zero active sessions,
|
||||
no active `adapter_session_id`, no `last_preflight` rollup, and terminal
|
||||
`last_session_state=closed` from the terminal-session ledger.
|
||||
C19Z22 extends terminal-state coverage to `expire` and `reset` controls. The
|
||||
same no-active-session readiness shape now proves `last_session_state=expired`
|
||||
and `last_session_state=reset` from the terminal-session ledger.
|
||||
C19Z23 adds grouped terminal-session summary metadata for the no-active-session
|
||||
case. Readiness now includes `terminal_session_summary` with adapter session id,
|
||||
terminal state, reason, and control timestamp while retaining flat compatibility
|
||||
fields.
|
||||
C19Z24 adds a contract marker to that summary. The grouped
|
||||
`terminal_session_summary` now carries a schema version and summary-contract
|
||||
field list so UI can gate rendering explicitly.
|
||||
C19Z25 adds boolean feature flags for the same grouped terminal summary fields,
|
||||
mirroring the preflight diagnostics contract/feature pattern.
|
||||
C19Z26 adds compatibility proof coverage for those two terminal summary contract
|
||||
forms, verifying that `summary_contract` entries and `summary_features` booleans
|
||||
stay aligned in workload and telemetry reports.
|
||||
C19Z27 adds absence proof coverage for a fresh no-session runtime: before any
|
||||
terminal history exists, readiness stays in `waiting_for_session` and does not
|
||||
include `terminal_session_summary`.
|
||||
C19Z28 adds the grouped no-session readiness summary for that empty-runtime
|
||||
state. Fresh adapter readiness now includes `no_session_summary` with schema
|
||||
version `rap.remote_workspace_adapter_no_session_summary.v1`, a summary
|
||||
contract for `status`, `diagnostic_state`, `active_session_count`, and
|
||||
`terminal_session_count`, and matching idle/waiting-for-session counts, while
|
||||
the terminal-session summary remains absent until terminal history exists.
|
||||
C19Z29 adds boolean `summary_features` to the same grouped no-session summary
|
||||
for `status`, `diagnostic_state`, `active_session_count`, and
|
||||
`terminal_session_count`, matching the terminal summary and preflight
|
||||
diagnostics feature-flag convention.
|
||||
C19Z30 adds compatibility proof coverage for the grouped no-session summary,
|
||||
verifying that `summary_contract` entries and `summary_features` booleans stay
|
||||
aligned in workload and telemetry reports.
|
||||
C19Z31 adds the inverse terminal-history absence proof: after adapter sessions
|
||||
reach terminal states, readiness exposes `terminal_session_summary` and omits
|
||||
`no_session_summary` in workload and telemetry reports.
|
||||
C19Z32 proves readiness summary exclusivity across the three runtime shapes:
|
||||
fresh exposes only `no_session_summary`, active exposes neither grouped summary,
|
||||
and terminal exposes only `terminal_session_summary`.
|
||||
C19Z33 adds a compact readiness state matrix artifact for admin/runtime handoff:
|
||||
fresh, active, and terminal rows are emitted for workload and telemetry with
|
||||
only the relevant readiness fields and summary-presence booleans.
|
||||
C19Z34 adds an explicit probe-to-runtime gate artifact. It confirms the current
|
||||
Remote Workspace runtime is still `contract_probe`, `probe_only=true`, and
|
||||
`payload_traffic=none`, lists the ready contracts, and records the remaining
|
||||
runtime gates before real RDP frame transport can be enabled.
|
||||
C19Z35 adds the disabled-by-default real-adapter supervision scaffold. The
|
||||
`rdp-worker` contract-probe status now advertises
|
||||
`rap.remote_workspace_real_adapter_supervision.v1` with future config env names,
|
||||
status contract fields, and guardrails, while `contract_probe` remains the only
|
||||
active execution mode and payload traffic remains `none`.
|
||||
C19Z36 adds compatibility proof for that scaffold, verifying the disabled state,
|
||||
status contract, env names, process model, and guardrails remain aligned in unit
|
||||
and live workload status coverage.
|
||||
C19Z37 adds disabled real-adapter config projection. Node-agent parses the
|
||||
future `RAP_REMOTE_WORKSPACE_REAL_ADAPTER_*` env values and reports only
|
||||
sanitized status metadata under
|
||||
`real_adapter_supervision.config_projection`: whether enable was requested,
|
||||
whether command/args/workdir are present, args JSON shape, and that raw values
|
||||
are redacted. This does not activate the real adapter; `enabled=false`,
|
||||
`activation_allowed=false`, and `payload_traffic=none` remain required.
|
||||
C19Z38 proves projection compatibility across default/empty and requested
|
||||
config shapes. Unit and live smoke coverage verify absent env and requested
|
||||
env both keep activation blocked, raw values redacted, and payload traffic
|
||||
disabled.
|
||||
C19Z39 adds an explicit disabled activation decision contract. The real adapter
|
||||
status now reports `decision=blocked`,
|
||||
`reason=real_runtime_stage_not_enabled`, `activation_allowed=false`, and the
|
||||
missing gates before a future stage may start an external RDP worker process.
|
||||
C19Z40 adds a compact handoff report proving that the supervision scaffold,
|
||||
config projection, and blocked activation decision remain aligned for both
|
||||
requested and default config shapes.
|
||||
C19Z41 adds real-adapter supervision feature flags for config projection,
|
||||
activation decision, missing gates, and raw-value redaction so UI and
|
||||
automation clients can gate rendering explicitly.
|
||||
C19Z42 folds those feature flags into the compact handoff report, proving
|
||||
scaffold/projection/decision/features alignment for requested and default node
|
||||
config in one admin/runtime artifact.
|
||||
C19Z43 proves contract-probe precedence when desired workload config includes
|
||||
both `adapter_contract_probe` and `real_adapter_supervision`; the runtime stays
|
||||
running in probe mode and real-adapter activation remains blocked.
|
||||
C19Z44 proves the real-adapter-only desired workload path remains degraded and
|
||||
blocked, with the same disabled activation contract and no payload traffic.
|
||||
C19Z45 adds a compact desired-workload mode matrix for probe-only,
|
||||
real-adapter-only, and combined requested modes, confirming all paths retain
|
||||
disabled real-adapter activation and no payload traffic.
|
||||
C19Z46 adds compatibility proof for that mode matrix row contract, including
|
||||
explicit feature-flag and missing-gate visibility markers.
|
||||
C19Z47 adds a disabled process-supervisor preconditions contract for the future
|
||||
external RDP worker process while keeping `process_start_allowed=false` and all
|
||||
payload traffic disabled.
|
||||
C19Z48 proves that process-supervisor preconditions contract across requested
|
||||
and default config shapes, including required/missing checks and disabled start.
|
||||
C19Z49 folds process-supervisor preconditions into the compact handoff report,
|
||||
proving alignment with projection, activation decision, and feature flags.
|
||||
C19Z50 folds those preconditions into the desired-workload mode matrix, proving
|
||||
process start remains disabled across probe-only, real-adapter-only, and
|
||||
combined requested modes.
|
||||
C19Z51 adds compatibility proof for that mode matrix v2 row contract.
|
||||
C19Z52 adds a disabled process-health-probe contract for the future external
|
||||
RDP worker process while keeping health probes disabled and payload traffic at
|
||||
`none`.
|
||||
C19Z53 proves that process-health-probe contract across requested/default
|
||||
status forms.
|
||||
C19Z54 folds process-health-probe visibility into the compact handoff report,
|
||||
proving disabled health probes and payload-free alignment across all
|
||||
real-adapter handoff contracts.
|
||||
C19Z55 folds process-health-probe visibility into the desired-workload mode
|
||||
matrix, proving disabled health probes and no payload traffic across probe-only,
|
||||
real-adapter-only, and combined requested modes.
|
||||
C19Z56 adds compatibility proof for that mode matrix v3 row contract.
|
||||
C19Z57 ties handoff v4 and mode matrix v3 compatibility into a compact disabled
|
||||
real-adapter readiness/handoff checklist.
|
||||
C19Z58 adds compatibility proof for that readiness/handoff summary and
|
||||
checklist contract.
|
||||
C19Z59 derives a disabled real-adapter operator action map from that checklist
|
||||
while keeping activation, process start, and payload forwarding blocked.
|
||||
C19Z60 adds compatibility proof for that operator action map contract.
|
||||
C19Z61 groups the disabled real-adapter readiness summary, checklist, and
|
||||
action map into one compact admin handoff bundle.
|
||||
C19Z62 adds compatibility proof for that admin handoff bundle contract.
|
||||
C19Z63 derives compact admin handoff digest display rows from the bundle while
|
||||
preserving disabled runtime guardrails.
|
||||
C19Z64 adds compatibility proof for that admin handoff digest row contract.
|
||||
C19Z65 adds a digest rollup with severity/state counts, primary action, and
|
||||
guardrail summary.
|
||||
C19Z66 adds compatibility proof for that digest rollup contract.
|
||||
C19Z67 summarizes the proven disabled real-adapter admin handoff chain from
|
||||
handoff v4 through digest rollup compatibility.
|
||||
C19Z68 adds compatibility proof for that full-chain summary contract.
|
||||
C19Z69 marks the disabled real-adapter admin handoff package as
|
||||
contract-only-ready while keeping the real runtime stage blocked.
|
||||
C19Z70 proves the release marker contract remains compatible while keeping the
|
||||
real runtime stage blocked.
|
||||
C19Z71 adds a final contract-only package index for the disabled real-adapter
|
||||
admin handoff chain.
|
||||
C19Z72 proves the final package index contract for the disabled real-adapter
|
||||
admin handoff chain.
|
||||
C19Z73 adds a contract-only runtime gate phase boundary for the next disabled
|
||||
real-adapter preflight phase.
|
||||
C19Z74 proves the runtime gate phase boundary contract.
|
||||
C19Z75 adds a disabled real-adapter runtime gate preflight checklist with all
|
||||
items still blocking runtime.
|
||||
C19Z76 proves the disabled real-adapter runtime gate preflight checklist
|
||||
contract.
|
||||
C19Z77 adds a disabled real-adapter runtime gate preflight status summary.
|
||||
C19Z78 proves the disabled real-adapter runtime gate preflight status summary
|
||||
contract.
|
||||
C19Z79 adds disabled real-adapter runtime gate preflight action hints.
|
||||
C19Z80 proves the disabled real-adapter runtime gate preflight action hints
|
||||
contract.
|
||||
C19Z81 adds a disabled real-adapter runtime gate preflight operator handoff
|
||||
bundle.
|
||||
C19Z82 proves the disabled real-adapter runtime gate preflight operator handoff
|
||||
bundle contract.
|
||||
C19Z83 adds a disabled real-adapter runtime gate preflight release marker.
|
||||
C19Z84 proves the disabled real-adapter runtime gate preflight release marker
|
||||
contract.
|
||||
C19Z85 adds a disabled real-adapter runtime gate preflight package index.
|
||||
C19Z86 proves the disabled real-adapter runtime gate preflight package index
|
||||
contract.
|
||||
C19Z87 adds a disabled real-adapter runtime gate preflight closeout summary.
|
||||
C19Z88 proves the disabled real-adapter runtime gate preflight closeout summary
|
||||
contract.
|
||||
C19Z89 starts the explicit real-adapter runtime gate enablement phase with a
|
||||
contract-only request that remains blocked pending validation.
|
||||
C19Z90 proves the explicit real-adapter runtime gate enablement request
|
||||
contract.
|
||||
C19Z91 adds contract-only operator confirmation validation while keeping the
|
||||
runtime gate blocked pending remaining validations.
|
||||
C19Z92 proves the operator confirmation validation contract.
|
||||
C19Z93 adds contract-only binary validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z94 proves the binary validation contract.
|
||||
C19Z95 adds contract-only permission validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z96 proves the permission validation contract.
|
||||
C19Z97 adds contract-only supervisor validation while keeping the runtime gate
|
||||
blocked pending remaining validations.
|
||||
C19Z98 proves the supervisor validation contract.
|
||||
C19Z99 adds contract-only health probe validation while keeping the runtime gate
|
||||
blocked pending payload gate validation.
|
||||
C19Z100 proves the health probe validation contract.
|
||||
C19Z101 adds contract-only payload gate validation with no remaining required
|
||||
validations while keeping runtime not enabled.
|
||||
C19Z102 proves the payload gate validation contract.
|
||||
C19Z103 adds the runtime gate validation closeout while keeping explicit
|
||||
operator enablement required.
|
||||
C19Z104 proves the runtime gate validation closeout contract.
|
||||
C19Z105 adds an operator enablement readiness package while keeping runtime
|
||||
disabled by default.
|
||||
C19Z106 proves the operator enablement readiness package contract.
|
||||
C19Z107 adds an operator enablement readiness release marker while keeping
|
||||
runtime disabled by default.
|
||||
C19Z108 proves the operator enablement readiness release marker contract.
|
||||
C19Z109 adds an operator enablement readiness package index while keeping
|
||||
runtime disabled by default.
|
||||
C19Z110 proves the operator enablement readiness package index contract.
|
||||
C19Z111 adds an operator readiness closeout summary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z112 proves the operator readiness closeout summary contract.
|
||||
C19Z113 adds an operator review decision request while keeping runtime disabled
|
||||
by default.
|
||||
C19Z114 proves the operator review decision request contract.
|
||||
C19Z115 adds an operator decision status summary while keeping runtime disabled
|
||||
by default.
|
||||
C19Z116 proves the operator decision status summary contract.
|
||||
C19Z117 adds an operator approval/rejection outcome contract with the outcome
|
||||
not approved and runtime disabled by default.
|
||||
C19Z118 proves the operator approval/rejection outcome contract.
|
||||
C19Z119 adds an operator outcome closeout/reopen boundary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z120 proves the operator outcome closeout/reopen boundary contract.
|
||||
C19Z121 adds a not-approved outcome release marker while keeping runtime
|
||||
disabled by default.
|
||||
C19Z122 proves the not-approved outcome release marker contract.
|
||||
C19Z123 adds a not-approved outcome package index while keeping runtime disabled
|
||||
by default.
|
||||
C19Z124 proves the not-approved outcome package index contract.
|
||||
C19Z125 adds a not-approved outcome closeout summary while keeping runtime
|
||||
disabled by default.
|
||||
C19Z126 proves the not-approved outcome closeout summary contract.
|
||||
C19Z127 adds a final not-approved outcome release marker while keeping runtime
|
||||
disabled by default.
|
||||
C19Z128 proves the final not-approved outcome release marker contract.
|
||||
C19Z129 adds a final not-approved outcome package index/archive marker while
|
||||
keeping runtime disabled by default.
|
||||
C19Z130 proves the final not-approved outcome package index/archive marker
|
||||
contract.
|
||||
C19Z131 adds a not-approved outcome archive closeout manifest while keeping
|
||||
runtime disabled by default.
|
||||
C19Z132 proves the not-approved outcome archive closeout manifest contract.
|
||||
C19Z133 adds a stopped-branch sentinel for the not-approved outcome while
|
||||
keeping runtime disabled by default.
|
||||
C19Z134 proves the not-approved outcome stopped-branch sentinel contract.
|
||||
C19Z135 adds a no-continuation guard for the stopped not-approved outcome while
|
||||
keeping runtime disabled by default.
|
||||
C19Z136 proves the not-approved outcome no-continuation guard contract.
|
||||
C19Z137 adds continuation block enforcement for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z138 proves the not-approved outcome continuation block enforcement
|
||||
contract.
|
||||
C19Z139 adds a continuation block audit record for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z140 proves the not-approved outcome continuation block audit record
|
||||
contract.
|
||||
C19Z141 adds a continuation block audit rollup for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z142 proves the not-approved outcome continuation block audit rollup
|
||||
contract.
|
||||
C19Z143 adds an operator stop summary for the stopped not-approved outcome
|
||||
while keeping runtime disabled by default.
|
||||
C19Z144 proves the not-approved outcome operator stop summary contract.
|
||||
C19Z145 adds an operator stop handoff for the stopped not-approved outcome
|
||||
while keeping runtime disabled by default.
|
||||
C19Z146 proves the not-approved outcome operator stop handoff contract.
|
||||
C19Z147 adds an operator stop handoff digest for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z148 proves the not-approved outcome operator stop handoff digest contract.
|
||||
C19Z149 adds an operator stop status snapshot for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z150 proves the not-approved outcome operator stop status snapshot contract.
|
||||
C19Z151 adds an operator stop status snapshot index for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z152 proves the not-approved outcome operator stop status snapshot index
|
||||
contract.
|
||||
C19Z153 adds an operator stop status catalog for the stopped not-approved
|
||||
outcome while keeping runtime disabled by default.
|
||||
C19Z154 proves the not-approved outcome operator stop status catalog contract.
|
||||
C19Z155 adds an operator stop status catalog release marker for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z156 proves the not-approved outcome operator stop status catalog release
|
||||
marker contract.
|
||||
C19Z157 adds an operator stop status catalog package index for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z158 proves the not-approved outcome operator stop status catalog package
|
||||
index contract.
|
||||
C19Z159 adds an operator stop status catalog closeout summary for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z160 proves the not-approved outcome operator stop status catalog closeout
|
||||
summary contract.
|
||||
C19Z161 adds an operator stop status final archive marker for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z162 proves the not-approved outcome operator stop status final archive
|
||||
marker contract.
|
||||
C19Z163 adds an operator stop status final archive manifest for the stopped
|
||||
not-approved outcome while keeping runtime disabled by default.
|
||||
C19Z164 proves the not-approved outcome operator stop status final archive
|
||||
manifest contract.
|
||||
C19Z165 adds a terminal-complete marker for the stopped not-approved outcome
|
||||
factory while keeping runtime disabled by default.
|
||||
C19Z166 proves the not-approved outcome factory terminal-complete contract.
|
||||
C20Z1 opens a new explicit real-adapter enablement request while keeping
|
||||
runtime disabled by default.
|
||||
C20Z2 proves the new explicit real-adapter enablement request contract.
|
||||
C20Z3 adds the operator validation intake for the new explicit request while
|
||||
keeping runtime disabled by default.
|
||||
C20Z4 completes the operator validation checklist contract while keeping
|
||||
runtime disabled by default.
|
||||
C20Z5 closes the operator validation chain contract while keeping runtime
|
||||
disabled by default.
|
||||
C20Z6 proves the C20 stage terminal-complete contract.
|
||||
5. Move VPN packet flow to the service channel and keep backend relay only as
|
||||
explicit degraded fallback.
|
||||
6. Run load tests against the fabric channel: many streams, route failure,
|
||||
|
||||
@@ -586,25 +586,31 @@ artifacts:
|
||||
`artifacts/c18z108-dedicated-breadcrumbs-smoke-result.json`, and
|
||||
`artifacts/c18z109-breadcrumb-freshness-window-smoke-result.json`.
|
||||
|
||||
Current active continuation after C19Z1:
|
||||
Current active continuation after C20Z6:
|
||||
|
||||
C19Z1 is implemented and runtime-smoke-proven. Remote Workspace adapter sessions
|
||||
now expose read-only mailbox handoff preflight:
|
||||
`GET /mesh/v1/remote-workspace/adapter-sessions/{adapter_session_id}/mailbox/preflight?consumer_id=...&resume_from=ack|checkpoint`.
|
||||
The response validates the consumer cursor and reports the expected next event
|
||||
window (`after_sequence`, available/returned/skipped counts, first/last expected
|
||||
sequence) without reading, draining, acking, or mutating consumer state.
|
||||
Node-agent image `rap-node-agent:codex-service-supervisor-20260512z2` is
|
||||
deployed on `test-1/2/3`. Verification artifacts:
|
||||
`artifacts/c19z1-remote-workspace-mailbox-preflight-smoke-result.json`, C19X
|
||||
source
|
||||
`artifacts/c19z1-remote-workspace-mailbox-preflight-source-result.json`, and
|
||||
C19Z regression
|
||||
`artifacts/c19z-remote-workspace-adapter-readiness-smoke-result.json`.
|
||||
C20Z1 through C20Z6 are implemented and runtime-smoke-proven. The C20 stage is
|
||||
terminal-complete by contract. It opened and validated a new explicit
|
||||
real-adapter enablement request as a contract-only transition:
|
||||
`rap.remote_workspace_real_adapter_c20_stage_terminal_complete.v1`, with
|
||||
`terminal_status=stage_terminal_complete_contract_only`,
|
||||
`stage_status=complete_no_more_c20_layers_required`,
|
||||
`stage_name=c20_real_adapter_new_explicit_enablement_request`,
|
||||
`validation_chain_status=complete_contract_only`,
|
||||
`enablement_boundary=runtime_enablement_requires_next_explicit_runtime_stage`,
|
||||
`enablement_decision=validated_contract_only_not_enabled`,
|
||||
`enablement_status=validated_not_enabled`,
|
||||
`runtime_gate_state=validated_contract_only_not_enabled`,
|
||||
`runtime_effect=contract_only_no_runtime_enablement`,
|
||||
`operator_default_action=keep_real_adapter_disabled_until_next_explicit_runtime_stage`,
|
||||
`next_allowed_entrypoint=next_explicit_runtime_enablement_stage_only`,
|
||||
`allows_process_start=false`, and `allows_payload_traffic=false`. Docker-test
|
||||
`test-1/2/3` remain on
|
||||
`rap-node-agent:codex-service-supervisor-20260513z52`. Verification artifact:
|
||||
`artifacts/c20z6-remote-workspace-real-adapter-stage-terminal-complete-compatibility-smoke-result.json`.
|
||||
|
||||
Next narrow Remote Workspace layer should stay probe-only and node-local. A good
|
||||
C19Z2 candidate is handoff preflight telemetry: add counters/last-preflight
|
||||
fields for the read-only preflight endpoint in workload status/heartbeat reports,
|
||||
so operators can distinguish handoff checks from mailbox reads. Do not add
|
||||
desktop frame transport, Android work, backend relay semantics, or production
|
||||
adapter payload forwarding in this slice.
|
||||
The not-approved factory remains terminal-complete by contract, and C20 is now
|
||||
also terminal-complete by contract. Do not add more C20 continuation layers.
|
||||
The only allowed next entrypoint is a new explicit runtime enablement stage.
|
||||
Keep the real adapter disabled until that new stage explicitly changes runtime
|
||||
state: no process start, no real RDP frame transport, no Android work, no
|
||||
backend relay semantics, and no production adapter payload forwarding.
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# RAP host-agent monitor
|
||||
|
||||
`rap-host-agent monitor-loop` is the local watchdog that runs near a node host.
|
||||
It complements the update loop:
|
||||
|
||||
- starts watched Docker containers when they are stopped;
|
||||
- restarts watched containers when Docker health is `unhealthy`;
|
||||
- restarts containers stuck in `restarting` longer than the stale threshold;
|
||||
- rate-limits repeated remediation with a restart cooldown;
|
||||
- watches disk pressure and runs safe cleanup when the cleanup threshold is reached;
|
||||
- removes old `/tmp/rap-*` and `/tmp/go-build*` build directories;
|
||||
- writes an optional JSON status file;
|
||||
- reports monitor status to the control plane through the node update-status channel.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
rap-host-agent monitor-loop \
|
||||
--backend-url http://127.0.0.1:18121/api/v1 \
|
||||
--cluster-id cfc0743d-d960-49fb-9de8-96e063d5e4aa \
|
||||
--node-id 108a0d66-d65e-4dea-b9a8-135366bf7dba \
|
||||
--current-version 0.2.261-vpnfarm \
|
||||
--interval-seconds 60 \
|
||||
--disk-warn-percent 80 \
|
||||
--disk-cleanup-percent 85 \
|
||||
--disk-critical-percent 95 \
|
||||
--status-file /tmp/rap-web-admin/html/downloads/ops/host-monitor-status.json \
|
||||
--watch-container rap_test_postgres \
|
||||
--watch-container rap_test_redis \
|
||||
--watch-container rap_test_backend
|
||||
```
|
||||
|
||||
On the shared test Docker host the current public status file is:
|
||||
|
||||
`http://docker-test.cin.su:18080/downloads/ops/host-monitor-status.json`
|
||||
@@ -0,0 +1,64 @@
|
||||
# Test Docker Disk Guard
|
||||
|
||||
`test-docker` is a shared build and runtime host. If `/` fills up, Postgres can
|
||||
restart-loop with `No space left on device`, which breaks VPN diagnostics and
|
||||
cluster tests. The disk guard is the first operational guardrail for that host.
|
||||
|
||||
## What It Does
|
||||
|
||||
- Checks `/` usage every run.
|
||||
- At `>= 85%`, removes safe reclaimable data:
|
||||
- Docker build cache.
|
||||
- Dangling Docker images.
|
||||
- Old RAP temporary build directories under `/tmp`.
|
||||
- At `>= 85%`, publishes a warning status after cleanup if the host is still above the warning line.
|
||||
- At `>= 95%` after cleanup, publishes critical status and exits with code `2`.
|
||||
- Writes machine-readable status to:
|
||||
- `http://docker-test.cin.su:18080/downloads/ops/test-docker-disk-guard-status.json`
|
||||
- Writes host log to:
|
||||
- `/tmp/rap-ops/test-docker-disk-guard.log`
|
||||
|
||||
## Install Or Refresh Schedule
|
||||
|
||||
Run from the repo root on the Windows workstation:
|
||||
|
||||
```powershell
|
||||
pwsh -ExecutionPolicy Bypass -File scripts/ops/test-docker-disk-guard.ps1 -InstallCron -RunOnce
|
||||
```
|
||||
|
||||
The wrapper uploads `scripts/ops/test-docker-disk-guard.sh` to
|
||||
`/home/test/bin/rap-test-docker-disk-guard` on `test-docker`. It installs cron
|
||||
when `crontab` exists; otherwise it installs a user systemd timer named
|
||||
`rap-test-docker-disk-guard.timer`.
|
||||
|
||||
## Manual Check
|
||||
|
||||
```powershell
|
||||
pwsh -ExecutionPolicy Bypass -File scripts/ops/test-docker-disk-guard.ps1 -RunOnce
|
||||
Invoke-RestMethod http://docker-test.cin.su:18080/downloads/ops/test-docker-disk-guard-status.json
|
||||
```
|
||||
|
||||
## Expansion Approach
|
||||
|
||||
Cleanup is only a pressure valve. If the status remains `warning` or `critical`
|
||||
after cleanup, expand the host disk.
|
||||
|
||||
Current host root is expected to be LVM. If the VM already has free VG space,
|
||||
the guard status will recommend:
|
||||
|
||||
```bash
|
||||
sudo lvextend -r -l +100%FREE /dev/mapper/ubuntu--vg-ubuntu--lv
|
||||
```
|
||||
|
||||
If there is no VG free space, first expand the VM disk in the hypervisor, then
|
||||
run `pvresize` for the physical volume and finally `lvextend -r` for the root
|
||||
logical volume.
|
||||
|
||||
## Optional Webhook
|
||||
|
||||
The shell guard supports `WEBHOOK_URL`. If set in cron/environment, warning and
|
||||
critical states are posted as JSON:
|
||||
|
||||
```json
|
||||
{"level":"warning","message":"...","host":"...","observed_at":"..."}
|
||||
```
|
||||
@@ -0,0 +1,101 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$EntryNodeName = "test-1",
|
||||
[string]$ExitNodeName = "test-2",
|
||||
[string]$EntryBaseUrl = "http://192.168.200.61:19131",
|
||||
[string]$ResultPath = "artifacts\c19z10-remote-workspace-mailbox-preflight-checklist-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z10-remote-workspace-mailbox-preflight-checklist-source-result.json"
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ChecklistStep {
|
||||
param([object]$Checklist, [string]$Step)
|
||||
foreach ($item in @($Checklist)) {
|
||||
if (
|
||||
[string](Get-PropertyValue -Item $item -Name "step" -Default "") -eq $Step -and
|
||||
[bool](Get-PropertyValue -Item $item -Name "required" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $item -Name "satisfied" -Default $true) -and
|
||||
[bool](Get-PropertyValue -Item $item -Name "source_hint" -Default $false)
|
||||
) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-PreflightChecklist {
|
||||
param([object]$Sink, [string]$AdapterSessionID)
|
||||
if ($null -eq $Sink) { return $false }
|
||||
$readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null
|
||||
$rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null
|
||||
if ($null -eq $rollup) { return $false }
|
||||
$checklist = Get-PropertyValue -Item $rollup -Name "remediation_checklist" -Default @()
|
||||
return (
|
||||
[string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and
|
||||
@($checklist).Count -eq 3 -and
|
||||
(Test-ChecklistStep -Checklist $checklist -Step "reset_consumer_cursor") -and
|
||||
(Test-ChecklistStep -Checklist $checklist -Step "request_full_adapter_resync") -and
|
||||
(Test-ChecklistStep -Checklist $checklist -Step "resume_from_checkpoint_after_resync")
|
||||
)
|
||||
}
|
||||
|
||||
$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z9-remote-workspace-mailbox-preflight-retained-window-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-EntryNodeName $EntryNodeName `
|
||||
-ExitNodeName $ExitNodeName `
|
||||
-EntryBaseUrl $EntryBaseUrl `
|
||||
-ResultPath $sourceResultPath
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "")
|
||||
$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$")
|
||||
workload_checklist_visible = (Test-PreflightChecklist -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID)
|
||||
telemetry_checklist_visible = (Test-PreflightChecklist -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID)
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z10.remote_workspace_mailbox_preflight_checklist_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
adapter_session_id = $adapterSessionID
|
||||
observed = $observed
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z10 remote workspace mailbox preflight checklist smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z10 remote workspace mailbox preflight checklist smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-source-result.json"
|
||||
$requiredValidationFields = @(
|
||||
"schema_version",
|
||||
"source_supervisor_validation_schema",
|
||||
"validation_key",
|
||||
"validation_status",
|
||||
"health_probe_validation_required",
|
||||
"health_probe_contract_verified",
|
||||
"failure_detection_verified",
|
||||
"remaining_required_validations",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$remainingRequiredValidations = @(
|
||||
"payload_gate_validation"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z99-remote-workspace-real-adapter-runtime-gate-health-probe-validation-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_health_probe_validation" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null
|
||||
|
||||
$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields
|
||||
$remainingValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Required $remainingRequiredValidations
|
||||
$validationValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "source_supervisor_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_supervisor_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "health_probe_validation" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "health_probe_validation_required" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "health_probe_contract_verified" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "failure_detection_verified" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "blocked_pending_remaining_validations" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z99.remote_workspace_real_adapter_runtime_gate_health_probe_validation_smoke.v1")
|
||||
health_probe_validation_present = ($null -ne $validation)
|
||||
validation_fields_compatible = $validationFieldsCompatible
|
||||
validation_values_compatible = $validationValuesCompatible
|
||||
remaining_validations_compatible = $remainingValidationsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z100.remote_workspace_real_adapter_runtime_gate_health_probe_validation_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_validation_fields = $requiredValidationFields
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
runtime_gate_health_probe_validation = $validation
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z100 remote workspace real-adapter runtime gate health probe validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z100 remote workspace real-adapter runtime gate health probe validation compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-source-result.json"
|
||||
$requiredValidationFields = @(
|
||||
"schema_version",
|
||||
"source_health_probe_validation_schema",
|
||||
"validation_key",
|
||||
"validation_status",
|
||||
"payload_gate_validation_required",
|
||||
"payload_policy_verified",
|
||||
"payload_isolation_verified",
|
||||
"remaining_required_validations",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$remainingRequiredValidations = @()
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayEquals {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
$actualValues = @($Actual | ForEach-Object { [string]$_ })
|
||||
if ($actualValues.Count -ne $Expected.Count) { return $false }
|
||||
foreach ($item in $Expected) {
|
||||
if ($actualValues -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z100-remote-workspace-real-adapter-runtime-gate-health-probe-validation-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$healthProbeValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_health_probe_validation" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $healthProbeValidation -Name "guardrail_summary" -Default $null
|
||||
|
||||
$validation = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1"
|
||||
source_health_probe_validation_schema = Get-PropertyValue -Item $healthProbeValidation -Name "schema_version" -Default $null
|
||||
validation_key = "payload_gate_validation"
|
||||
validation_status = "satisfied_contract_only"
|
||||
payload_gate_validation_required = $true
|
||||
payload_policy_verified = $true
|
||||
payload_isolation_verified = $true
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
runtime_gate_state = "validated_contract_only_not_enabled"
|
||||
runtime_effect = "contract_only_no_runtime_enablement"
|
||||
operator_default_action = Get-PropertyValue -Item $healthProbeValidation -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields
|
||||
$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations
|
||||
$validationValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "source_health_probe_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "payload_gate_validation" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_gate_validation_required" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_policy_verified" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_isolation_verified" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z100.remote_workspace_real_adapter_runtime_gate_health_probe_validation_compatibility_smoke.v1")
|
||||
health_probe_validation_present = ($null -ne $healthProbeValidation)
|
||||
validation_fields_compatible = $validationFieldsCompatible
|
||||
validation_values_compatible = $validationValuesCompatible
|
||||
remaining_validations_compatible = $remainingValidationsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z101.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_validation_fields = $requiredValidationFields
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
runtime_gate_payload_gate_validation = $validation
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z101 remote workspace real-adapter runtime gate payload gate validation smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z101 remote workspace real-adapter runtime gate payload gate validation smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-source-result.json"
|
||||
$requiredValidationFields = @(
|
||||
"schema_version",
|
||||
"source_health_probe_validation_schema",
|
||||
"validation_key",
|
||||
"validation_status",
|
||||
"payload_gate_validation_required",
|
||||
"payload_policy_verified",
|
||||
"payload_isolation_verified",
|
||||
"remaining_required_validations",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$remainingRequiredValidations = @()
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayEquals {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
$actualValues = @($Actual | ForEach-Object { [string]$_ })
|
||||
if ($actualValues.Count -ne $Expected.Count) { return $false }
|
||||
foreach ($item in $Expected) {
|
||||
if ($actualValues -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z101-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$validation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_payload_gate_validation" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $validation -Name "guardrail_summary" -Default $null
|
||||
|
||||
$validationFieldsCompatible = Test-ObjectHasFields -Item $validation -Fields $requiredValidationFields
|
||||
$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $validation -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations
|
||||
$validationValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $validation -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "source_health_probe_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_health_probe_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_key" -Default "") -eq "payload_gate_validation" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "validation_status" -Default "") -eq "satisfied_contract_only" -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_gate_validation_required" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_policy_verified" -Default $false) -and
|
||||
[bool](Get-PropertyValue -Item $validation -Name "payload_isolation_verified" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $validation -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $validation -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z101.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_smoke.v1")
|
||||
payload_gate_validation_present = ($null -ne $validation)
|
||||
validation_fields_compatible = $validationFieldsCompatible
|
||||
validation_values_compatible = $validationValuesCompatible
|
||||
remaining_validations_compatible = $remainingValidationsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z102.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_validation_fields = $requiredValidationFields
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
runtime_gate_payload_gate_validation = $validation
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z102 remote workspace real-adapter runtime gate payload gate validation compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z102 remote workspace real-adapter runtime gate payload gate validation compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_payload_gate_validation_schema",
|
||||
"phase_name",
|
||||
"validation_chain_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"required_validations",
|
||||
"remaining_required_validations",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredValidations = @(
|
||||
"operator_confirmation",
|
||||
"binary_validation",
|
||||
"permission_validation",
|
||||
"supervisor_validation",
|
||||
"health_probe_validation",
|
||||
"payload_gate_validation"
|
||||
)
|
||||
$remainingRequiredValidations = @()
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayEquals {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
$actualValues = @($Actual | ForEach-Object { [string]$_ })
|
||||
if ($actualValues.Count -ne $Expected.Count) { return $false }
|
||||
foreach ($item in $Expected) {
|
||||
if ($actualValues -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z102-remote-workspace-real-adapter-runtime-gate-payload-gate-validation-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$payloadGateValidation = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_payload_gate_validation" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $payloadGateValidation -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeout = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1"
|
||||
source_payload_gate_validation_schema = Get-PropertyValue -Item $payloadGateValidation -Name "schema_version" -Default $null
|
||||
phase_name = "explicit_real_runtime_gate_enablement_validation"
|
||||
validation_chain_status = "complete_contract_only"
|
||||
enablement_boundary = "explicit_operator_enablement_required"
|
||||
enablement_status = "not_enabled"
|
||||
required_validations = $requiredValidations
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
runtime_gate_state = "validated_contract_only_not_enabled"
|
||||
runtime_effect = "contract_only_no_runtime_enablement"
|
||||
operator_default_action = Get-PropertyValue -Item $payloadGateValidation -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$requiredValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $closeout -Name "required_validations" -Default @()) -Required $requiredValidations
|
||||
$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $closeout -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_payload_gate_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "phase_name" -Default "") -eq "explicit_real_runtime_gate_enablement_validation" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "validation_chain_status" -Default "") -eq "complete_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z102.remote_workspace_real_adapter_runtime_gate_payload_gate_validation_compatibility_smoke.v1")
|
||||
payload_gate_validation_present = ($null -ne $payloadGateValidation)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
required_validations_compatible = $requiredValidationsCompatible
|
||||
remaining_validations_compatible = $remainingValidationsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z103.remote_workspace_real_adapter_runtime_gate_validation_closeout_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_validations = $requiredValidations
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
runtime_gate_validation_closeout = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z103 remote workspace real-adapter runtime gate validation closeout smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z103 remote workspace real-adapter runtime gate validation closeout smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_payload_gate_validation_schema",
|
||||
"phase_name",
|
||||
"validation_chain_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"required_validations",
|
||||
"remaining_required_validations",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredValidations = @(
|
||||
"operator_confirmation",
|
||||
"binary_validation",
|
||||
"permission_validation",
|
||||
"supervisor_validation",
|
||||
"health_probe_validation",
|
||||
"payload_gate_validation"
|
||||
)
|
||||
$remainingRequiredValidations = @()
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayEquals {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
$actualValues = @($Actual | ForEach-Object { [string]$_ })
|
||||
if ($actualValues.Count -ne $Expected.Count) { return $false }
|
||||
foreach ($item in $Expected) {
|
||||
if ($actualValues -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z103-remote-workspace-real-adapter-runtime-gate-validation-closeout-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_validation_closeout" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$requiredValidationsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $closeout -Name "required_validations" -Default @()) -Required $requiredValidations
|
||||
$remainingValidationsCompatible = Test-ArrayEquals -Actual @(Get-PropertyValue -Item $closeout -Name "remaining_required_validations" -Default @()) -Expected $remainingRequiredValidations
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_payload_gate_validation_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_payload_gate_validation.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "phase_name" -Default "") -eq "explicit_real_runtime_gate_enablement_validation" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "validation_chain_status" -Default "") -eq "complete_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z103.remote_workspace_real_adapter_runtime_gate_validation_closeout_smoke.v1")
|
||||
closeout_present = ($null -ne $closeout)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
required_validations_compatible = $requiredValidationsCompatible
|
||||
remaining_validations_compatible = $remainingValidationsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z104.remote_workspace_real_adapter_runtime_gate_validation_closeout_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_validations = $requiredValidations
|
||||
remaining_required_validations = $remainingRequiredValidations
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
runtime_gate_validation_closeout = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z104 remote workspace real-adapter runtime gate validation closeout compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z104 remote workspace real-adapter runtime gate validation closeout compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+177
@@ -0,0 +1,177 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"package_status",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"included_contracts",
|
||||
"required_operator_actions",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$includedContracts = @(
|
||||
"operator_confirmation_validation",
|
||||
"binary_validation",
|
||||
"permission_validation",
|
||||
"supervisor_validation",
|
||||
"health_probe_validation",
|
||||
"payload_gate_validation",
|
||||
"validation_closeout"
|
||||
)
|
||||
$requiredOperatorActions = @(
|
||||
"review_validation_closeout",
|
||||
"confirm_real_runtime_enablement_intent",
|
||||
"select_runtime_targets",
|
||||
"approve_process_start",
|
||||
"approve_payload_traffic"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z104-remote-workspace-real-adapter-runtime-gate-validation-closeout-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "runtime_gate_validation_closeout" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$package = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1"
|
||||
source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null
|
||||
package_status = "ready_for_operator_review"
|
||||
operator_review_status = "not_reviewed"
|
||||
enablement_boundary = "explicit_operator_enablement_required"
|
||||
enablement_status = "not_enabled"
|
||||
included_contracts = $includedContracts
|
||||
required_operator_actions = $requiredOperatorActions
|
||||
runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $package -Fields $requiredPackageFields
|
||||
$includedContractsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "included_contracts" -Default @()) -Required $includedContracts
|
||||
$operatorActionsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "required_operator_actions" -Default @()) -Required $requiredOperatorActions
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $package -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "package_status" -Default "") -eq "ready_for_operator_review" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $package -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $package -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z104.remote_workspace_real_adapter_runtime_gate_validation_closeout_compatibility_smoke.v1")
|
||||
closeout_present = ($null -ne $closeout)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
included_contracts_compatible = $includedContractsCompatible
|
||||
operator_actions_compatible = $operatorActionsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z105.remote_workspace_real_adapter_operator_enablement_readiness_package_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
included_contracts = $includedContracts
|
||||
required_operator_actions = $requiredOperatorActions
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_enablement_readiness_package = $package
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z105 remote workspace real-adapter operator enablement readiness package smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z105 remote workspace real-adapter operator enablement readiness package smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"package_status",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"included_contracts",
|
||||
"required_operator_actions",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$includedContracts = @(
|
||||
"operator_confirmation_validation",
|
||||
"binary_validation",
|
||||
"permission_validation",
|
||||
"supervisor_validation",
|
||||
"health_probe_validation",
|
||||
"payload_gate_validation",
|
||||
"validation_closeout"
|
||||
)
|
||||
$requiredOperatorActions = @(
|
||||
"review_validation_closeout",
|
||||
"confirm_real_runtime_enablement_intent",
|
||||
"select_runtime_targets",
|
||||
"approve_process_start",
|
||||
"approve_payload_traffic"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z105-remote-workspace-real-adapter-operator-enablement-readiness-package-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$package = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $package -Name "guardrail_summary" -Default $null
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $package -Fields $requiredPackageFields
|
||||
$includedContractsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "included_contracts" -Default @()) -Required $includedContracts
|
||||
$operatorActionsCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $package -Name "required_operator_actions" -Default @()) -Required $requiredOperatorActions
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $package -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_runtime_gate_validation_closeout.v1" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "package_status" -Default "") -eq "ready_for_operator_review" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $package -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $package -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $package -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z105.remote_workspace_real_adapter_operator_enablement_readiness_package_smoke.v1")
|
||||
package_present = ($null -ne $package)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
included_contracts_compatible = $includedContractsCompatible
|
||||
operator_actions_compatible = $operatorActionsCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z106.remote_workspace_real_adapter_operator_enablement_readiness_package_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
included_contracts = $includedContracts
|
||||
required_operator_actions = $requiredOperatorActions
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_enablement_readiness_package = $package
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z106 remote workspace real-adapter operator enablement readiness package compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z106 remote workspace real-adapter operator enablement readiness package compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-source-result.json"
|
||||
$requiredReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_package_schema",
|
||||
"release_status",
|
||||
"release_marker",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z106-remote-workspace-real-adapter-operator-enablement-readiness-package-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$package = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $package -Name "guardrail_summary" -Default $null
|
||||
|
||||
$releaseMarker = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1"
|
||||
source_package_schema = Get-PropertyValue -Item $package -Name "schema_version" -Default $null
|
||||
release_status = "operator_readiness_package_contract_only"
|
||||
release_marker = "c19z107_real_adapter_operator_enablement_readiness_contract_only"
|
||||
operator_review_status = Get-PropertyValue -Item $package -Name "operator_review_status" -Default $null
|
||||
enablement_boundary = Get-PropertyValue -Item $package -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $package -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $package -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $package -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $package -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$releaseFieldsCompatible = Test-ObjectHasFields -Item $releaseMarker -Fields $requiredReleaseFields
|
||||
$releaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "release_status" -Default "") -eq "operator_readiness_package_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "release_marker" -Default "") -eq "c19z107_real_adapter_operator_enablement_readiness_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $releaseMarker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $releaseMarker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z106.remote_workspace_real_adapter_operator_enablement_readiness_package_compatibility_smoke.v1")
|
||||
package_present = ($null -ne $package)
|
||||
release_fields_compatible = $releaseFieldsCompatible
|
||||
release_values_compatible = $releaseValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z107.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_release_fields = $requiredReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_enablement_readiness_release_marker = $releaseMarker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z107 remote workspace real-adapter operator enablement readiness release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z107 remote workspace real-adapter operator enablement readiness release marker smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-source-result.json"
|
||||
$requiredReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_package_schema",
|
||||
"release_status",
|
||||
"release_marker",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z107-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$marker = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null
|
||||
|
||||
$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields
|
||||
$releaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "source_package_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "operator_readiness_package_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z107_real_adapter_operator_enablement_readiness_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z107.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_smoke.v1")
|
||||
release_marker_present = ($null -ne $marker)
|
||||
release_fields_compatible = $releaseFieldsCompatible
|
||||
release_values_compatible = $releaseValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z108.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_release_fields = $requiredReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_enablement_readiness_release_marker = $marker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z108 remote workspace real-adapter operator enablement readiness release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z108 remote workspace real-adapter operator enablement readiness release marker compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_release_marker_schema",
|
||||
"package_status",
|
||||
"package_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"closeout_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredCloseoutNotes = @(
|
||||
"operator_enablement_readiness_package_indexed",
|
||||
"operator_review_not_started",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z108-remote-workspace-real-adapter-operator-enablement-readiness-release-marker-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$marker = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null
|
||||
|
||||
$packageIndex = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1"
|
||||
source_release_marker_schema = Get-PropertyValue -Item $marker -Name "schema_version" -Default $null
|
||||
package_status = "indexed_contract_only"
|
||||
package_marker = "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only"
|
||||
covered_stage_range = "C19Z89-C19Z108"
|
||||
covered_stage_count = 20
|
||||
latest_compatibility_stage = "C19Z108"
|
||||
operator_review_status = Get-PropertyValue -Item $marker -Name "operator_review_status" -Default $null
|
||||
enablement_boundary = Get-PropertyValue -Item $marker -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $marker -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $marker -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $marker -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
closeout_notes = $requiredCloseoutNotes
|
||||
}
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields
|
||||
$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) -Required $requiredCloseoutNotes
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "indexed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and
|
||||
[int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 20 -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z108.remote_workspace_real_adapter_operator_enablement_readiness_release_marker_compatibility_smoke.v1")
|
||||
release_marker_present = ($null -ne $marker)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
closeout_notes_compatible = $closeoutNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z109.remote_workspace_real_adapter_operator_enablement_readiness_package_index_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_closeout_notes = $requiredCloseoutNotes
|
||||
operator_enablement_readiness_package_index = $packageIndex
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z109 remote workspace real-adapter operator enablement readiness package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z109 remote workspace real-adapter operator enablement readiness package index smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
@@ -0,0 +1,87 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$EntryNodeName = "test-1",
|
||||
[string]$ExitNodeName = "test-2",
|
||||
[string]$EntryBaseUrl = "http://192.168.200.61:19131",
|
||||
[string]$ResultPath = "artifacts\c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z11-remote-workspace-mailbox-preflight-checklist-status-source-result.json"
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ChecklistStatus {
|
||||
param([object]$Sink, [string]$AdapterSessionID)
|
||||
if ($null -eq $Sink) { return $false }
|
||||
$readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null
|
||||
$rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null
|
||||
if ($null -eq $rollup) { return $false }
|
||||
$counts = Get-PropertyValue -Item $rollup -Name "remediation_checklist_counts" -Default $null
|
||||
return (
|
||||
[string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "remediation_checklist_status" -Default "") -eq "action_required" -and
|
||||
[string](Get-PropertyValue -Item $counts -Name "status" -Default "") -eq "action_required" -and
|
||||
[int](Get-PropertyValue -Item $counts -Name "total_count" -Default -1) -eq 3 -and
|
||||
[int](Get-PropertyValue -Item $counts -Name "required_count" -Default -1) -eq 3 -and
|
||||
[int](Get-PropertyValue -Item $counts -Name "satisfied_count" -Default -1) -eq 0 -and
|
||||
[int](Get-PropertyValue -Item $counts -Name "pending_count" -Default -1) -eq 3
|
||||
)
|
||||
}
|
||||
|
||||
$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z10-remote-workspace-mailbox-preflight-checklist-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-EntryNodeName $EntryNodeName `
|
||||
-ExitNodeName $ExitNodeName `
|
||||
-EntryBaseUrl $EntryBaseUrl `
|
||||
-ResultPath $sourceResultPath
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "")
|
||||
$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$")
|
||||
workload_checklist_status_visible = (Test-ChecklistStatus -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID)
|
||||
telemetry_checklist_status_visible = (Test-ChecklistStatus -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID)
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z11.remote_workspace_mailbox_preflight_checklist_status_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
adapter_session_id = $adapterSessionID
|
||||
observed = $observed
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z11 remote workspace mailbox preflight checklist status smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z11 remote workspace mailbox preflight checklist status smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_release_marker_schema",
|
||||
"package_status",
|
||||
"package_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"closeout_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredCloseoutNotes = @(
|
||||
"operator_enablement_readiness_package_indexed",
|
||||
"operator_review_not_started",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z109-remote-workspace-real-adapter-operator-enablement-readiness-package-index-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$packageIndex = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package_index" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null
|
||||
$closeoutNotes = @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @())
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields
|
||||
$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Required $requiredCloseoutNotes
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "indexed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z109_real_adapter_operator_enablement_readiness_package_index_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and
|
||||
[int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 20 -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z109.remote_workspace_real_adapter_operator_enablement_readiness_package_index_smoke.v1")
|
||||
package_index_present = ($null -ne $packageIndex)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
closeout_notes_compatible = $closeoutNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z110.remote_workspace_real_adapter_operator_enablement_readiness_package_index_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_closeout_notes = $requiredCloseoutNotes
|
||||
operator_enablement_readiness_package_index = $packageIndex
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z110 remote workspace real-adapter operator enablement readiness package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z110 remote workspace real-adapter operator enablement readiness package index compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_package_index_schema",
|
||||
"closeout_status",
|
||||
"closeout_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z110-remote-workspace-real-adapter-operator-enablement-readiness-package-index-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$packageIndex = Get-PropertyValue -Item $sourceResult -Name "operator_enablement_readiness_package_index" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeout = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1"
|
||||
source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null
|
||||
closeout_status = "closed_contract_only_ready_for_operator_review"
|
||||
closeout_marker = "c19z111_real_adapter_operator_readiness_closed_contract_only"
|
||||
covered_stage_range = Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default $null
|
||||
enablement_boundary = Get-PropertyValue -Item $packageIndex -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default $null
|
||||
next_required_phase = "explicit_operator_review_and_enablement_decision"
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_ready_for_operator_review" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z111_real_adapter_operator_readiness_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and
|
||||
[int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 20 -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_operator_review_and_enablement_decision" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z110.remote_workspace_real_adapter_operator_enablement_readiness_package_index_compatibility_smoke.v1")
|
||||
package_index_present = ($null -ne $packageIndex)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z111.remote_workspace_real_adapter_operator_readiness_closeout_summary_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_readiness_closeout_summary = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z111 remote workspace real-adapter operator readiness closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z111 remote workspace real-adapter operator readiness closeout summary smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_package_index_schema",
|
||||
"closeout_status",
|
||||
"closeout_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z111-remote-workspace-real-adapter-operator-readiness-closeout-summary-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "operator_readiness_closeout_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_enablement_readiness_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_contract_only_ready_for_operator_review" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z111_real_adapter_operator_readiness_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z89-C19Z108" -and
|
||||
[int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 20 -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z108" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "not_reviewed" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_operator_review_and_enablement_decision" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z111.remote_workspace_real_adapter_operator_readiness_closeout_summary_smoke.v1")
|
||||
closeout_summary_present = ($null -ne $closeout)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z112.remote_workspace_real_adapter_operator_readiness_closeout_summary_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_readiness_closeout_summary = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z112 remote workspace real-adapter operator readiness closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z112 remote workspace real-adapter operator readiness closeout summary compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+172
@@ -0,0 +1,172 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z113-remote-workspace-real-adapter-operator-review-decision-request-source-result.json"
|
||||
$requiredRequestFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"review_request_status",
|
||||
"review_request_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"decision_prerequisites",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$decisionPrerequisites = @(
|
||||
"operator_reviews_closeout_summary",
|
||||
"operator_confirms_real_runtime_enablement_intent",
|
||||
"operator_selects_runtime_targets",
|
||||
"operator_approves_process_start",
|
||||
"operator_approves_payload_traffic"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z112-remote-workspace-real-adapter-operator-readiness-closeout-summary-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "operator_readiness_closeout_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$request = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_review_decision_request.v1"
|
||||
source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null
|
||||
review_request_status = "pending_operator_decision"
|
||||
review_request_marker = "c19z113_real_adapter_operator_review_decision_request_contract_only"
|
||||
requested_decision = "review_real_runtime_enablement"
|
||||
enablement_decision = "not_approved"
|
||||
operator_review_status = "pending"
|
||||
enablement_boundary = Get-PropertyValue -Item $closeout -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null
|
||||
decision_prerequisites = $decisionPrerequisites
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields
|
||||
$prerequisitesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $request -Name "decision_prerequisites" -Default @()) -Required $decisionPrerequisites
|
||||
$requestValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "review_request_status" -Default "") -eq "pending_operator_decision" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "review_request_marker" -Default "") -eq "c19z113_real_adapter_operator_review_decision_request_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "pending" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z112.remote_workspace_real_adapter_operator_readiness_closeout_summary_compatibility_smoke.v1")
|
||||
closeout_summary_present = ($null -ne $closeout)
|
||||
request_fields_compatible = $requestFieldsCompatible
|
||||
request_values_compatible = $requestValuesCompatible
|
||||
prerequisites_compatible = $prerequisitesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z113.remote_workspace_real_adapter_operator_review_decision_request_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_request_fields = $requiredRequestFields
|
||||
decision_prerequisites = $decisionPrerequisites
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_review_decision_request = $request
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z113 remote workspace real-adapter operator review decision request smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z113 remote workspace real-adapter operator review decision request smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z114-remote-workspace-real-adapter-operator-review-decision-request-source-result.json"
|
||||
$requiredRequestFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"review_request_status",
|
||||
"review_request_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"decision_prerequisites",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$decisionPrerequisites = @(
|
||||
"operator_reviews_closeout_summary",
|
||||
"operator_confirms_real_runtime_enablement_intent",
|
||||
"operator_selects_runtime_targets",
|
||||
"operator_approves_process_start",
|
||||
"operator_approves_payload_traffic"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z113-remote-workspace-real-adapter-operator-review-decision-request-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$request = Get-PropertyValue -Item $sourceResult -Name "operator_review_decision_request" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null
|
||||
|
||||
$requestFieldsCompatible = Test-ObjectHasFields -Item $request -Fields $requiredRequestFields
|
||||
$prerequisitesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $request -Name "decision_prerequisites" -Default @()) -Required $decisionPrerequisites
|
||||
$requestValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $request -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_readiness_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "review_request_status" -Default "") -eq "pending_operator_decision" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "review_request_marker" -Default "") -eq "c19z113_real_adapter_operator_review_decision_request_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "operator_review_status" -Default "") -eq "pending" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $request -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $request -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $request -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z113.remote_workspace_real_adapter_operator_review_decision_request_smoke.v1")
|
||||
decision_request_present = ($null -ne $request)
|
||||
request_fields_compatible = $requestFieldsCompatible
|
||||
request_values_compatible = $requestValuesCompatible
|
||||
prerequisites_compatible = $prerequisitesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z114.remote_workspace_real_adapter_operator_review_decision_request_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_request_fields = $requiredRequestFields
|
||||
decision_prerequisites = $decisionPrerequisites
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_review_decision_request = $request
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z114 remote workspace real-adapter operator review decision request compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z114 remote workspace real-adapter operator review decision request compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z115-remote-workspace-real-adapter-operator-decision-status-summary-source-result.json"
|
||||
$requiredSummaryFields = @(
|
||||
"schema_version",
|
||||
"source_decision_request_schema",
|
||||
"decision_status",
|
||||
"decision_summary_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z114-remote-workspace-real-adapter-operator-review-decision-request-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$request = Get-PropertyValue -Item $sourceResult -Name "operator_review_decision_request" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $request -Name "guardrail_summary" -Default $null
|
||||
|
||||
$summary = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1"
|
||||
source_decision_request_schema = Get-PropertyValue -Item $request -Name "schema_version" -Default $null
|
||||
decision_status = "pending_not_approved"
|
||||
decision_summary_marker = "c19z115_real_adapter_operator_decision_status_pending_contract_only"
|
||||
requested_decision = Get-PropertyValue -Item $request -Name "requested_decision" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $request -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $request -Name "operator_review_status" -Default $null
|
||||
enablement_boundary = Get-PropertyValue -Item $request -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $request -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $request -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $request -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $request -Name "operator_default_action" -Default $null
|
||||
next_required_phase = "explicit_operator_approval_or_rejection"
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields
|
||||
$summaryValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "source_decision_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "decision_status" -Default "") -eq "pending_not_approved" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "decision_summary_marker" -Default "") -eq "c19z115_real_adapter_operator_decision_status_pending_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "operator_review_status" -Default "") -eq "pending" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "next_required_phase" -Default "") -eq "explicit_operator_approval_or_rejection" -and
|
||||
-not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z114.remote_workspace_real_adapter_operator_review_decision_request_compatibility_smoke.v1")
|
||||
decision_request_present = ($null -ne $request)
|
||||
summary_fields_compatible = $summaryFieldsCompatible
|
||||
summary_values_compatible = $summaryValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z115.remote_workspace_real_adapter_operator_decision_status_summary_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_summary_fields = $requiredSummaryFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_decision_status_summary = $summary
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z115 remote workspace real-adapter operator decision status summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z115 remote workspace real-adapter operator decision status summary smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z116-remote-workspace-real-adapter-operator-decision-status-summary-source-result.json"
|
||||
$requiredSummaryFields = @(
|
||||
"schema_version",
|
||||
"source_decision_request_schema",
|
||||
"decision_status",
|
||||
"decision_summary_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z115-remote-workspace-real-adapter-operator-decision-status-summary-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$summary = Get-PropertyValue -Item $sourceResult -Name "operator_decision_status_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null
|
||||
|
||||
$summaryFieldsCompatible = Test-ObjectHasFields -Item $summary -Fields $requiredSummaryFields
|
||||
$summaryValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $summary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "source_decision_request_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_review_decision_request.v1" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "decision_status" -Default "") -eq "pending_not_approved" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "decision_summary_marker" -Default "") -eq "c19z115_real_adapter_operator_decision_status_pending_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "operator_review_status" -Default "") -eq "pending" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $summary -Name "next_required_phase" -Default "") -eq "explicit_operator_approval_or_rejection" -and
|
||||
-not [bool](Get-PropertyValue -Item $summary -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $summary -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z115.remote_workspace_real_adapter_operator_decision_status_summary_smoke.v1")
|
||||
decision_status_summary_present = ($null -ne $summary)
|
||||
summary_fields_compatible = $summaryFieldsCompatible
|
||||
summary_values_compatible = $summaryValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z116.remote_workspace_real_adapter_operator_decision_status_summary_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_summary_fields = $requiredSummaryFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_decision_status_summary = $summary
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z116 remote workspace real-adapter operator decision status summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z116 remote workspace real-adapter operator decision status summary compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-source-result.json"
|
||||
$requiredOutcomeFields = @(
|
||||
"schema_version",
|
||||
"source_decision_status_schema",
|
||||
"outcome_status",
|
||||
"outcome_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z116-remote-workspace-real-adapter-operator-decision-status-summary-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$summary = Get-PropertyValue -Item $sourceResult -Name "operator_decision_status_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $summary -Name "guardrail_summary" -Default $null
|
||||
|
||||
$outcome = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1"
|
||||
source_decision_status_schema = Get-PropertyValue -Item $summary -Name "schema_version" -Default $null
|
||||
outcome_status = "rejected_or_not_approved_contract_only"
|
||||
outcome_marker = "c19z117_real_adapter_operator_outcome_not_approved_contract_only"
|
||||
requested_decision = Get-PropertyValue -Item $summary -Name "requested_decision" -Default $null
|
||||
enablement_decision = "not_approved"
|
||||
operator_review_status = "closed_without_approval"
|
||||
enablement_boundary = Get-PropertyValue -Item $summary -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $summary -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $summary -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $summary -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $summary -Name "operator_default_action" -Default $null
|
||||
next_required_phase = "explicit_operator_reopen_or_new_enablement_request"
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$outcomeFieldsCompatible = Test-ObjectHasFields -Item $outcome -Fields $requiredOutcomeFields
|
||||
$outcomeValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $outcome -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "source_decision_status_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "outcome_marker" -Default "") -eq "c19z117_real_adapter_operator_outcome_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and
|
||||
-not [bool](Get-PropertyValue -Item $outcome -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $outcome -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z116.remote_workspace_real_adapter_operator_decision_status_summary_compatibility_smoke.v1")
|
||||
decision_status_summary_present = ($null -ne $summary)
|
||||
outcome_fields_compatible = $outcomeFieldsCompatible
|
||||
outcome_values_compatible = $outcomeValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z117.remote_workspace_real_adapter_operator_approval_rejection_outcome_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_outcome_fields = $requiredOutcomeFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_approval_rejection_outcome = $outcome
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z117 remote workspace real-adapter operator approval rejection outcome smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z117 remote workspace real-adapter operator approval rejection outcome smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-source-result.json"
|
||||
$requiredOutcomeFields = @(
|
||||
"schema_version",
|
||||
"source_decision_status_schema",
|
||||
"outcome_status",
|
||||
"outcome_marker",
|
||||
"requested_decision",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z117-remote-workspace-real-adapter-operator-approval-rejection-outcome-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$outcome = Get-PropertyValue -Item $sourceResult -Name "operator_approval_rejection_outcome" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $outcome -Name "guardrail_summary" -Default $null
|
||||
|
||||
$outcomeFieldsCompatible = Test-ObjectHasFields -Item $outcome -Fields $requiredOutcomeFields
|
||||
$outcomeValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $outcome -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "source_decision_status_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_decision_status_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "outcome_marker" -Default "") -eq "c19z117_real_adapter_operator_outcome_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "requested_decision" -Default "") -eq "review_real_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $outcome -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and
|
||||
-not [bool](Get-PropertyValue -Item $outcome -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $outcome -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z117.remote_workspace_real_adapter_operator_approval_rejection_outcome_smoke.v1")
|
||||
outcome_present = ($null -ne $outcome)
|
||||
outcome_fields_compatible = $outcomeFieldsCompatible
|
||||
outcome_values_compatible = $outcomeValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z118.remote_workspace_real_adapter_operator_approval_rejection_outcome_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_outcome_fields = $requiredOutcomeFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_approval_rejection_outcome = $outcome
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z118 remote workspace real-adapter operator approval rejection outcome compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z118 remote workspace real-adapter operator approval rejection outcome compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-source-result.json"
|
||||
$requiredBoundaryFields = @(
|
||||
"schema_version",
|
||||
"source_outcome_schema",
|
||||
"boundary_status",
|
||||
"boundary_marker",
|
||||
"closed_outcome_status",
|
||||
"reopen_policy",
|
||||
"next_required_phase",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z118-remote-workspace-real-adapter-operator-approval-rejection-outcome-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$outcome = Get-PropertyValue -Item $sourceResult -Name "operator_approval_rejection_outcome" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $outcome -Name "guardrail_summary" -Default $null
|
||||
|
||||
$boundary = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1"
|
||||
source_outcome_schema = Get-PropertyValue -Item $outcome -Name "schema_version" -Default $null
|
||||
boundary_status = "closed_not_approved_reopen_required"
|
||||
boundary_marker = "c19z119_real_adapter_operator_outcome_closeout_reopen_required"
|
||||
closed_outcome_status = Get-PropertyValue -Item $outcome -Name "outcome_status" -Default $null
|
||||
reopen_policy = "new_explicit_enablement_request_required"
|
||||
next_required_phase = "explicit_operator_reopen_or_new_enablement_request"
|
||||
enablement_decision = Get-PropertyValue -Item $outcome -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $outcome -Name "operator_review_status" -Default $null
|
||||
enablement_boundary = Get-PropertyValue -Item $outcome -Name "enablement_boundary" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $outcome -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $outcome -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $outcome -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $outcome -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$boundaryFieldsCompatible = Test-ObjectHasFields -Item $boundary -Fields $requiredBoundaryFields
|
||||
$boundaryValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $boundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "source_outcome_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "boundary_marker" -Default "") -eq "c19z119_real_adapter_operator_outcome_closeout_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $boundary -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $boundary -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z118.remote_workspace_real_adapter_operator_approval_rejection_outcome_compatibility_smoke.v1")
|
||||
outcome_present = ($null -ne $outcome)
|
||||
boundary_fields_compatible = $boundaryFieldsCompatible
|
||||
boundary_values_compatible = $boundaryValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z119.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_boundary_fields = $requiredBoundaryFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_outcome_closeout_reopen_boundary = $boundary
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z119 remote workspace real-adapter operator outcome closeout reopen boundary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z119 remote workspace real-adapter operator outcome closeout reopen boundary smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
@@ -0,0 +1,89 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$EntryNodeName = "test-1",
|
||||
[string]$ExitNodeName = "test-2",
|
||||
[string]$EntryBaseUrl = "http://192.168.200.61:19131",
|
||||
[string]$ResultPath = "artifacts\c19z12-remote-workspace-mailbox-preflight-status-counts-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z12-remote-workspace-mailbox-preflight-status-counts-source-result.json"
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-StatusCounts {
|
||||
param([object]$Sink, [string]$AdapterSessionID)
|
||||
if ($null -eq $Sink) { return $false }
|
||||
$readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null
|
||||
$rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null
|
||||
if ($null -eq $rollup) { return $false }
|
||||
$readinessStatusCounts = Get-PropertyValue -Item $readiness -Name "mailbox_preflight_operator_status_counts" -Default $null
|
||||
$readinessSeverityCounts = Get-PropertyValue -Item $readiness -Name "mailbox_preflight_operator_severity_counts" -Default $null
|
||||
$rollupStatusCounts = Get-PropertyValue -Item $rollup -Name "operator_status_counts" -Default $null
|
||||
$rollupSeverityCounts = Get-PropertyValue -Item $rollup -Name "operator_severity_counts" -Default $null
|
||||
return (
|
||||
[string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn" -and
|
||||
[int64](Get-PropertyValue -Item $readinessStatusCounts -Name "resync_required" -Default 0) -ge 1 -and
|
||||
[int64](Get-PropertyValue -Item $readinessSeverityCounts -Name "warn" -Default 0) -ge 1 -and
|
||||
[int64](Get-PropertyValue -Item $rollupStatusCounts -Name "resync_required" -Default 0) -ge 1 -and
|
||||
[int64](Get-PropertyValue -Item $rollupSeverityCounts -Name "warn" -Default 0) -ge 1
|
||||
)
|
||||
}
|
||||
|
||||
$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z11-remote-workspace-mailbox-preflight-checklist-status-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-EntryNodeName $EntryNodeName `
|
||||
-ExitNodeName $ExitNodeName `
|
||||
-EntryBaseUrl $EntryBaseUrl `
|
||||
-ResultPath $sourceResultPath
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "")
|
||||
$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$")
|
||||
workload_status_counts_visible = (Test-StatusCounts -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID)
|
||||
telemetry_status_counts_visible = (Test-StatusCounts -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID)
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z12.remote_workspace_mailbox_preflight_status_counts_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
adapter_session_id = $adapterSessionID
|
||||
observed = $observed
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z12 remote workspace mailbox preflight status counts smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z12 remote workspace mailbox preflight status counts smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-source-result.json"
|
||||
$requiredBoundaryFields = @(
|
||||
"schema_version",
|
||||
"source_outcome_schema",
|
||||
"boundary_status",
|
||||
"boundary_marker",
|
||||
"closed_outcome_status",
|
||||
"reopen_policy",
|
||||
"next_required_phase",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_boundary",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z119-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$boundary = Get-PropertyValue -Item $sourceResult -Name "operator_outcome_closeout_reopen_boundary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null
|
||||
|
||||
$boundaryFieldsCompatible = Test-ObjectHasFields -Item $boundary -Fields $requiredBoundaryFields
|
||||
$boundaryValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $boundary -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "source_outcome_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_approval_rejection_outcome.v1" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "boundary_marker" -Default "") -eq "c19z119_real_adapter_operator_outcome_closeout_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "next_required_phase" -Default "") -eq "explicit_operator_reopen_or_new_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_boundary" -Default "") -eq "explicit_operator_enablement_required" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $boundary -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $boundary -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z119.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_smoke.v1")
|
||||
boundary_present = ($null -ne $boundary)
|
||||
boundary_fields_compatible = $boundaryFieldsCompatible
|
||||
boundary_values_compatible = $boundaryValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z120.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_boundary_fields = $requiredBoundaryFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
operator_outcome_closeout_reopen_boundary = $boundary
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z120 remote workspace real-adapter operator outcome closeout reopen boundary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z120 remote workspace real-adapter operator outcome closeout reopen boundary compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-source-result.json"
|
||||
$requiredReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_boundary_schema",
|
||||
"release_status",
|
||||
"release_marker",
|
||||
"boundary_status",
|
||||
"closed_outcome_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z120-remote-workspace-real-adapter-operator-outcome-closeout-reopen-boundary-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$boundary = Get-PropertyValue -Item $sourceResult -Name "operator_outcome_closeout_reopen_boundary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $boundary -Name "guardrail_summary" -Default $null
|
||||
|
||||
$marker = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1"
|
||||
source_boundary_schema = Get-PropertyValue -Item $boundary -Name "schema_version" -Default $null
|
||||
release_status = "not_approved_outcome_closed_contract_only"
|
||||
release_marker = "c19z121_real_adapter_not_approved_outcome_release_marker"
|
||||
boundary_status = Get-PropertyValue -Item $boundary -Name "boundary_status" -Default $null
|
||||
closed_outcome_status = Get-PropertyValue -Item $boundary -Name "closed_outcome_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $boundary -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $boundary -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $boundary -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $boundary -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $boundary -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $boundary -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $boundary -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields
|
||||
$releaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "source_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z121_real_adapter_not_approved_outcome_release_marker" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z120.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary_compatibility_smoke.v1")
|
||||
boundary_present = ($null -ne $boundary)
|
||||
release_fields_compatible = $releaseFieldsCompatible
|
||||
release_values_compatible = $releaseValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z121.remote_workspace_real_adapter_not_approved_outcome_release_marker_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_release_fields = $requiredReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
not_approved_outcome_release_marker = $marker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z121 remote workspace real-adapter not-approved outcome release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z121 remote workspace real-adapter not-approved outcome release marker smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-source-result.json"
|
||||
$requiredReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_boundary_schema",
|
||||
"release_status",
|
||||
"release_marker",
|
||||
"boundary_status",
|
||||
"closed_outcome_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z121-remote-workspace-real-adapter-not-approved-outcome-release-marker-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null
|
||||
|
||||
$releaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredReleaseFields
|
||||
$releaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "source_boundary_schema" -Default "") -eq "rap.remote_workspace_real_adapter_operator_outcome_closeout_reopen_boundary.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_marker" -Default "") -eq "c19z121_real_adapter_not_approved_outcome_release_marker" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "closed_outcome_status" -Default "") -eq "rejected_or_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z121.remote_workspace_real_adapter_not_approved_outcome_release_marker_smoke.v1")
|
||||
release_marker_present = ($null -ne $marker)
|
||||
release_fields_compatible = $releaseFieldsCompatible
|
||||
release_values_compatible = $releaseValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z122.remote_workspace_real_adapter_not_approved_outcome_release_marker_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_release_fields = $requiredReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
not_approved_outcome_release_marker = $marker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z122 remote workspace real-adapter not-approved outcome release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z122 remote workspace real-adapter not-approved outcome release marker compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+185
@@ -0,0 +1,185 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_release_marker_schema",
|
||||
"package_status",
|
||||
"package_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"closeout_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredCloseoutNotes = @(
|
||||
"not_approved_outcome_package_indexed",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z122-remote-workspace-real-adapter-not-approved-outcome-release-marker-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null
|
||||
|
||||
$packageIndex = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1"
|
||||
source_release_marker_schema = Get-PropertyValue -Item $marker -Name "schema_version" -Default $null
|
||||
package_status = "closed_not_approved_contract_only"
|
||||
package_marker = "c19z123_real_adapter_not_approved_outcome_package_index"
|
||||
covered_stage_range = "C19Z117-C19Z122"
|
||||
covered_stage_count = 6
|
||||
latest_compatibility_stage = "C19Z122"
|
||||
release_status = Get-PropertyValue -Item $marker -Name "release_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $marker -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $marker -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $marker -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $marker -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $marker -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $marker -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $marker -Name "operator_default_action" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
closeout_notes = $requiredCloseoutNotes
|
||||
}
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields
|
||||
$closeoutNotesCompatible = Test-ArrayContainsAll -Actual @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @()) -Required $requiredCloseoutNotes
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z123_real_adapter_not_approved_outcome_package_index" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z122.remote_workspace_real_adapter_not_approved_outcome_release_marker_compatibility_smoke.v1")
|
||||
release_marker_present = ($null -ne $marker)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
closeout_notes_compatible = $closeoutNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z123.remote_workspace_real_adapter_not_approved_outcome_package_index_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_closeout_notes = $requiredCloseoutNotes
|
||||
not_approved_outcome_package_index = $packageIndex
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z123 remote workspace real-adapter not-approved outcome package index smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z123 remote workspace real-adapter not-approved outcome package index smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-source-result.json"
|
||||
$requiredPackageFields = @(
|
||||
"schema_version",
|
||||
"source_release_marker_schema",
|
||||
"package_status",
|
||||
"package_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"closeout_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredCloseoutNotes = @(
|
||||
"not_approved_outcome_package_indexed",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Required)
|
||||
$values = @($Actual | ForEach-Object { [string]$_ })
|
||||
foreach ($item in $Required) {
|
||||
if ($values -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z123-remote-workspace-real-adapter-not-approved-outcome-package-index-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_package_index" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null
|
||||
$closeoutNotes = @(Get-PropertyValue -Item $packageIndex -Name "closeout_notes" -Default @())
|
||||
|
||||
$packageFieldsCompatible = Test-ObjectHasFields -Item $packageIndex -Fields $requiredPackageFields
|
||||
$closeoutNotesCompatible = Test-ArrayContainsAll -Actual $closeoutNotes -Required $requiredCloseoutNotes
|
||||
$packageValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "source_release_marker_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_status" -Default "") -eq "closed_not_approved_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "package_marker" -Default "") -eq "c19z123_real_adapter_not_approved_outcome_package_index" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $packageIndex -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z123.remote_workspace_real_adapter_not_approved_outcome_package_index_smoke.v1")
|
||||
package_index_present = ($null -ne $packageIndex)
|
||||
package_fields_compatible = $packageFieldsCompatible
|
||||
package_values_compatible = $packageValuesCompatible
|
||||
closeout_notes_compatible = $closeoutNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z124.remote_workspace_real_adapter_not_approved_outcome_package_index_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_package_fields = $requiredPackageFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_closeout_notes = $requiredCloseoutNotes
|
||||
not_approved_outcome_package_index = $packageIndex
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z124 remote workspace real-adapter not-approved outcome package index compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z124 remote workspace real-adapter not-approved outcome package index compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_package_index_schema",
|
||||
"closeout_status",
|
||||
"closeout_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z124-remote-workspace-real-adapter-not-approved-outcome-package-index-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$packageIndex = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_package_index" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $packageIndex -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeout = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1"
|
||||
source_package_index_schema = Get-PropertyValue -Item $packageIndex -Name "schema_version" -Default $null
|
||||
closeout_status = "closed_not_approved_package_complete"
|
||||
closeout_marker = "c19z125_real_adapter_not_approved_outcome_closed_contract_only"
|
||||
covered_stage_range = Get-PropertyValue -Item $packageIndex -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $packageIndex -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $packageIndex -Name "latest_compatibility_stage" -Default $null
|
||||
release_status = Get-PropertyValue -Item $packageIndex -Name "release_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $packageIndex -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $packageIndex -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $packageIndex -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $packageIndex -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $packageIndex -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $packageIndex -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $packageIndex -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $packageIndex -Name "operator_default_action" -Default $null
|
||||
next_required_phase = "explicit_new_enablement_request_only"
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
}
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z125_real_adapter_not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z124.remote_workspace_real_adapter_not_approved_outcome_package_index_compatibility_smoke.v1")
|
||||
package_index_present = ($null -ne $packageIndex)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z125.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
not_approved_outcome_closeout_summary = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z125 remote workspace real-adapter not-approved outcome closeout summary smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z125 remote workspace real-adapter not-approved outcome closeout summary smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-source-result.json"
|
||||
$requiredCloseoutFields = @(
|
||||
"schema_version",
|
||||
"source_package_index_schema",
|
||||
"closeout_status",
|
||||
"closeout_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z125-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_closeout_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$closeoutFieldsCompatible = Test-ObjectHasFields -Item $closeout -Fields $requiredCloseoutFields
|
||||
$closeoutValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $closeout -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "source_package_index_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_package_index.v1" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "closeout_marker" -Default "") -eq "c19z125_real_adapter_not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $closeout -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z125.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_smoke.v1")
|
||||
closeout_summary_present = ($null -ne $closeout)
|
||||
closeout_fields_compatible = $closeoutFieldsCompatible
|
||||
closeout_values_compatible = $closeoutValuesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z126.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_closeout_fields = $requiredCloseoutFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
not_approved_outcome_closeout_summary = $closeout
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z126 remote workspace real-adapter not-approved outcome closeout summary compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z126 remote workspace real-adapter not-approved outcome closeout summary compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-source-result.json"
|
||||
$requiredFinalReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"final_release_status",
|
||||
"final_release_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"final_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredFinalNotes = @(
|
||||
"not_approved_outcome_final_release_marked",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z126-remote-workspace-real-adapter-not-approved-outcome-closeout-summary-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$closeout = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_closeout_summary" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $closeout -Name "guardrail_summary" -Default $null
|
||||
|
||||
$marker = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1"
|
||||
source_closeout_schema = Get-PropertyValue -Item $closeout -Name "schema_version" -Default $null
|
||||
final_release_status = "closed_not_approved_final_contract_only"
|
||||
final_release_marker = "c19z127_real_adapter_not_approved_outcome_final_release_marker"
|
||||
covered_stage_range = Get-PropertyValue -Item $closeout -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $closeout -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $closeout -Name "latest_compatibility_stage" -Default $null
|
||||
release_status = Get-PropertyValue -Item $closeout -Name "release_status" -Default $null
|
||||
closeout_status = Get-PropertyValue -Item $closeout -Name "closeout_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $closeout -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $closeout -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $closeout -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $closeout -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $closeout -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $closeout -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $closeout -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $closeout -Name "operator_default_action" -Default $null
|
||||
next_required_phase = Get-PropertyValue -Item $closeout -Name "next_required_phase" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
final_notes = $requiredFinalNotes
|
||||
}
|
||||
|
||||
$finalReleaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredFinalReleaseFields
|
||||
$finalReleaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "final_release_marker" -Default "") -eq "c19z127_real_adapter_not_approved_outcome_final_release_marker" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $marker -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$finalNotesCompatible = Test-ArrayContainsAll -Actual @($marker.final_notes) -Expected $requiredFinalNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z126.remote_workspace_real_adapter_not_approved_outcome_closeout_summary_compatibility_smoke.v1")
|
||||
closeout_summary_present = ($null -ne $closeout)
|
||||
final_release_fields_compatible = $finalReleaseFieldsCompatible
|
||||
final_release_values_compatible = $finalReleaseValuesCompatible
|
||||
final_notes_compatible = $finalNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z127.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_final_release_fields = $requiredFinalReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_final_notes = $requiredFinalNotes
|
||||
not_approved_outcome_final_release_marker = $marker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z127 remote workspace real-adapter not-approved outcome final release marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z127 remote workspace real-adapter not-approved outcome final release marker smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-source-result.json"
|
||||
$requiredFinalReleaseFields = @(
|
||||
"schema_version",
|
||||
"source_closeout_schema",
|
||||
"final_release_status",
|
||||
"final_release_marker",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"final_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredFinalNotes = @(
|
||||
"not_approved_outcome_final_release_marked",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z127-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$marker = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $marker -Name "guardrail_summary" -Default $null
|
||||
$finalNotes = @(Get-PropertyValue -Item $marker -Name "final_notes" -Default @())
|
||||
|
||||
$finalReleaseFieldsCompatible = Test-ObjectHasFields -Item $marker -Fields $requiredFinalReleaseFields
|
||||
$finalReleaseValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $marker -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "source_closeout_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_closeout_summary.v1" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "final_release_marker" -Default "") -eq "c19z127_real_adapter_not_approved_outcome_final_release_marker" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $marker -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $marker -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $marker -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$finalNotesCompatible = Test-ArrayContainsAll -Actual $finalNotes -Expected $requiredFinalNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z127.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_smoke.v1")
|
||||
final_release_marker_present = ($null -ne $marker)
|
||||
final_release_fields_compatible = $finalReleaseFieldsCompatible
|
||||
final_release_values_compatible = $finalReleaseValuesCompatible
|
||||
final_notes_compatible = $finalNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z128.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_final_release_fields = $requiredFinalReleaseFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_final_notes = $requiredFinalNotes
|
||||
not_approved_outcome_final_release_marker = $marker
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z128 remote workspace real-adapter not-approved outcome final release marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z128 remote workspace real-adapter not-approved outcome final release marker compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+197
@@ -0,0 +1,197 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-source-result.json"
|
||||
$requiredArchiveFields = @(
|
||||
"schema_version",
|
||||
"source_final_release_schema",
|
||||
"archive_status",
|
||||
"archive_marker",
|
||||
"package_status",
|
||||
"final_release_status",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"archive_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredArchiveNotes = @(
|
||||
"not_approved_outcome_final_package_indexed",
|
||||
"not_approved_outcome_archived_contract_only",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z128-remote-workspace-real-adapter-not-approved-outcome-final-release-marker-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$finalRelease = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_release_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $finalRelease -Name "guardrail_summary" -Default $null
|
||||
|
||||
$archive = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1"
|
||||
source_final_release_schema = Get-PropertyValue -Item $finalRelease -Name "schema_version" -Default $null
|
||||
archive_status = "closed_not_approved_archived_contract_only"
|
||||
archive_marker = "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker"
|
||||
package_status = "final_package_indexed_and_archived_contract_only"
|
||||
final_release_status = Get-PropertyValue -Item $finalRelease -Name "final_release_status" -Default $null
|
||||
covered_stage_range = Get-PropertyValue -Item $finalRelease -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $finalRelease -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $finalRelease -Name "latest_compatibility_stage" -Default $null
|
||||
release_status = Get-PropertyValue -Item $finalRelease -Name "release_status" -Default $null
|
||||
closeout_status = Get-PropertyValue -Item $finalRelease -Name "closeout_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $finalRelease -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $finalRelease -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $finalRelease -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $finalRelease -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $finalRelease -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $finalRelease -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $finalRelease -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $finalRelease -Name "operator_default_action" -Default $null
|
||||
next_required_phase = Get-PropertyValue -Item $finalRelease -Name "next_required_phase" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
archive_notes = $requiredArchiveNotes
|
||||
}
|
||||
|
||||
$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields
|
||||
$archiveValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "source_final_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "archive_marker" -Default "") -eq "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$archiveNotesCompatible = Test-ArrayContainsAll -Actual @($archive.archive_notes) -Expected $requiredArchiveNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z128.remote_workspace_real_adapter_not_approved_outcome_final_release_marker_compatibility_smoke.v1")
|
||||
final_release_marker_present = ($null -ne $finalRelease)
|
||||
archive_fields_compatible = $archiveFieldsCompatible
|
||||
archive_values_compatible = $archiveValuesCompatible
|
||||
archive_notes_compatible = $archiveNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z129.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_archive_fields = $requiredArchiveFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_archive_notes = $requiredArchiveNotes
|
||||
not_approved_outcome_final_package_index_archive_marker = $archive
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z129 remote workspace real-adapter not-approved outcome final package index archive marker smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z129 remote workspace real-adapter not-approved outcome final package index archive marker smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
@@ -0,0 +1,83 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$EntryNodeName = "test-1",
|
||||
[string]$ExitNodeName = "test-2",
|
||||
[string]$EntryBaseUrl = "http://192.168.200.61:19131",
|
||||
[string]$ResultPath = "artifacts\c19z13-remote-workspace-mailbox-preflight-attention-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z13-remote-workspace-mailbox-preflight-attention-source-result.json"
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-AttentionStatus {
|
||||
param([object]$Sink, [string]$AdapterSessionID)
|
||||
if ($null -eq $Sink) { return $false }
|
||||
$readiness = Get-PropertyValue -Item $Sink -Name "adapter_runtime_readiness" -Default $null
|
||||
$rollup = Get-PropertyValue -Item $readiness -Name "last_preflight" -Default $null
|
||||
if ($null -eq $rollup) { return $false }
|
||||
return (
|
||||
[string](Get-PropertyValue -Item $readiness -Name "adapter_session_id" -Default "") -eq $AdapterSessionID -and
|
||||
[string](Get-PropertyValue -Item $readiness -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "preflight_attention_status" -Default "") -eq "needs_attention" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_status" -Default "") -eq "resync_required" -and
|
||||
[string](Get-PropertyValue -Item $rollup -Name "operator_severity" -Default "") -eq "warn"
|
||||
)
|
||||
}
|
||||
|
||||
$source = & powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z12-remote-workspace-mailbox-preflight-status-counts-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-EntryNodeName $EntryNodeName `
|
||||
-ExitNodeName $ExitNodeName `
|
||||
-EntryBaseUrl $EntryBaseUrl `
|
||||
-ResultPath $sourceResultPath
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$adapterSessionID = [string](Get-PropertyValue -Item $sourceResult -Name "adapter_session_id" -Default "")
|
||||
$observed = Get-PropertyValue -Item $sourceResult -Name "observed" -Default $null
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
adapter_session_id_present = ($adapterSessionID -match "^rap-rw-adapter-session-[0-9a-f]{24}$")
|
||||
workload_attention_visible = (Test-AttentionStatus -Sink $observed.workload_sink -AdapterSessionID $adapterSessionID)
|
||||
telemetry_attention_visible = (Test-AttentionStatus -Sink $observed.telemetry_sink -AdapterSessionID $adapterSessionID)
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z13.remote_workspace_mailbox_preflight_attention_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
adapter_session_id = $adapterSessionID
|
||||
observed = $observed
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z13 remote workspace mailbox preflight attention smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z13 remote workspace mailbox preflight attention smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-source-result.json"
|
||||
$requiredArchiveFields = @(
|
||||
"schema_version",
|
||||
"source_final_release_schema",
|
||||
"archive_status",
|
||||
"archive_marker",
|
||||
"package_status",
|
||||
"final_release_status",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"archive_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredArchiveNotes = @(
|
||||
"not_approved_outcome_final_package_indexed",
|
||||
"not_approved_outcome_archived_contract_only",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z129-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_package_index_archive_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null
|
||||
$archiveNotes = @(Get-PropertyValue -Item $archive -Name "archive_notes" -Default @())
|
||||
|
||||
$archiveFieldsCompatible = Test-ObjectHasFields -Item $archive -Fields $requiredArchiveFields
|
||||
$archiveValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $archive -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "source_final_release_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_release_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "archive_marker" -Default "") -eq "c19z129_real_adapter_not_approved_outcome_final_package_index_archive_marker" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $archive -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $archive -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $archive -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$archiveNotesCompatible = Test-ArrayContainsAll -Actual $archiveNotes -Expected $requiredArchiveNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z129.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_smoke.v1")
|
||||
archive_marker_present = ($null -ne $archive)
|
||||
archive_fields_compatible = $archiveFieldsCompatible
|
||||
archive_values_compatible = $archiveValuesCompatible
|
||||
archive_notes_compatible = $archiveNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z130.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_archive_fields = $requiredArchiveFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_archive_notes = $requiredArchiveNotes
|
||||
not_approved_outcome_final_package_index_archive_marker = $archive
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z130 remote workspace real-adapter not-approved outcome final package index archive marker compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z130 remote workspace real-adapter not-approved outcome final package index archive marker compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-source-result.json"
|
||||
$requiredManifestFields = @(
|
||||
"schema_version",
|
||||
"source_archive_schema",
|
||||
"manifest_status",
|
||||
"manifest_marker",
|
||||
"archive_status",
|
||||
"package_status",
|
||||
"final_release_status",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"manifest_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredManifestNotes = @(
|
||||
"not_approved_archive_closeout_manifested",
|
||||
"not_approved_branch_closed_until_new_request",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z130-remote-workspace-real-adapter-not-approved-outcome-final-package-index-archive-marker-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$archive = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_final_package_index_archive_marker" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $archive -Name "guardrail_summary" -Default $null
|
||||
|
||||
$manifest = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1"
|
||||
source_archive_schema = Get-PropertyValue -Item $archive -Name "schema_version" -Default $null
|
||||
manifest_status = "closed_not_approved_archive_manifest_complete"
|
||||
manifest_marker = "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest"
|
||||
archive_status = Get-PropertyValue -Item $archive -Name "archive_status" -Default $null
|
||||
package_status = Get-PropertyValue -Item $archive -Name "package_status" -Default $null
|
||||
final_release_status = Get-PropertyValue -Item $archive -Name "final_release_status" -Default $null
|
||||
covered_stage_range = Get-PropertyValue -Item $archive -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $archive -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $archive -Name "latest_compatibility_stage" -Default $null
|
||||
release_status = Get-PropertyValue -Item $archive -Name "release_status" -Default $null
|
||||
closeout_status = Get-PropertyValue -Item $archive -Name "closeout_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $archive -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $archive -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $archive -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $archive -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $archive -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $archive -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $archive -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $archive -Name "operator_default_action" -Default $null
|
||||
next_required_phase = Get-PropertyValue -Item $archive -Name "next_required_phase" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
manifest_notes = $requiredManifestNotes
|
||||
}
|
||||
|
||||
$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields
|
||||
$manifestValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "manifest_marker" -Default "") -eq "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$manifestNotesCompatible = Test-ArrayContainsAll -Actual @($manifest.manifest_notes) -Expected $requiredManifestNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z130.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker_compatibility_smoke.v1")
|
||||
archive_marker_present = ($null -ne $archive)
|
||||
manifest_fields_compatible = $manifestFieldsCompatible
|
||||
manifest_values_compatible = $manifestValuesCompatible
|
||||
manifest_notes_compatible = $manifestNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z131.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_manifest_fields = $requiredManifestFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_manifest_notes = $requiredManifestNotes
|
||||
not_approved_outcome_archive_closeout_manifest = $manifest
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z131 remote workspace real-adapter not-approved outcome archive closeout manifest smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z131 remote workspace real-adapter not-approved outcome archive closeout manifest smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-source-result.json"
|
||||
$requiredManifestFields = @(
|
||||
"schema_version",
|
||||
"source_archive_schema",
|
||||
"manifest_status",
|
||||
"manifest_marker",
|
||||
"archive_status",
|
||||
"package_status",
|
||||
"final_release_status",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"manifest_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredManifestNotes = @(
|
||||
"not_approved_archive_closeout_manifested",
|
||||
"not_approved_branch_closed_until_new_request",
|
||||
"operator_outcome_closed_without_approval",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z131-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_archive_closeout_manifest" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null
|
||||
$manifestNotes = @(Get-PropertyValue -Item $manifest -Name "manifest_notes" -Default @())
|
||||
|
||||
$manifestFieldsCompatible = Test-ObjectHasFields -Item $manifest -Fields $requiredManifestFields
|
||||
$manifestValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $manifest -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "source_archive_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_final_package_index_archive_marker.v1" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "manifest_marker" -Default "") -eq "c19z131_real_adapter_not_approved_outcome_archive_closeout_manifest" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $manifest -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $manifest -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$manifestNotesCompatible = Test-ArrayContainsAll -Actual $manifestNotes -Expected $requiredManifestNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z131.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_smoke.v1")
|
||||
manifest_present = ($null -ne $manifest)
|
||||
manifest_fields_compatible = $manifestFieldsCompatible
|
||||
manifest_values_compatible = $manifestValuesCompatible
|
||||
manifest_notes_compatible = $manifestNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z132.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_manifest_fields = $requiredManifestFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_manifest_notes = $requiredManifestNotes
|
||||
not_approved_outcome_archive_closeout_manifest = $manifest
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z132 remote workspace real-adapter not-approved outcome archive closeout manifest compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z132 remote workspace real-adapter not-approved outcome archive closeout manifest compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-source-result.json"
|
||||
$requiredSentinelFields = @(
|
||||
"schema_version",
|
||||
"source_manifest_schema",
|
||||
"sentinel_status",
|
||||
"sentinel_marker",
|
||||
"branch_state",
|
||||
"continuation_policy",
|
||||
"manifest_status",
|
||||
"archive_status",
|
||||
"package_status",
|
||||
"final_release_status",
|
||||
"covered_stage_range",
|
||||
"covered_stage_count",
|
||||
"latest_compatibility_stage",
|
||||
"release_status",
|
||||
"closeout_status",
|
||||
"boundary_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"sentinel_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredSentinelNotes = @(
|
||||
"not_approved_branch_stopped",
|
||||
"no_further_not_approved_layers_required",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z132-remote-workspace-real-adapter-not-approved-outcome-archive-closeout-manifest-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$manifest = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_archive_closeout_manifest" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $manifest -Name "guardrail_summary" -Default $null
|
||||
|
||||
$sentinel = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1"
|
||||
source_manifest_schema = Get-PropertyValue -Item $manifest -Name "schema_version" -Default $null
|
||||
sentinel_status = "stopped_until_new_explicit_enablement_request"
|
||||
sentinel_marker = "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel"
|
||||
branch_state = "not_approved_branch_closed"
|
||||
continuation_policy = "do_not_continue_without_new_explicit_enablement_request"
|
||||
manifest_status = Get-PropertyValue -Item $manifest -Name "manifest_status" -Default $null
|
||||
archive_status = Get-PropertyValue -Item $manifest -Name "archive_status" -Default $null
|
||||
package_status = Get-PropertyValue -Item $manifest -Name "package_status" -Default $null
|
||||
final_release_status = Get-PropertyValue -Item $manifest -Name "final_release_status" -Default $null
|
||||
covered_stage_range = Get-PropertyValue -Item $manifest -Name "covered_stage_range" -Default $null
|
||||
covered_stage_count = Get-PropertyValue -Item $manifest -Name "covered_stage_count" -Default $null
|
||||
latest_compatibility_stage = Get-PropertyValue -Item $manifest -Name "latest_compatibility_stage" -Default $null
|
||||
release_status = Get-PropertyValue -Item $manifest -Name "release_status" -Default $null
|
||||
closeout_status = Get-PropertyValue -Item $manifest -Name "closeout_status" -Default $null
|
||||
boundary_status = Get-PropertyValue -Item $manifest -Name "boundary_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $manifest -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $manifest -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $manifest -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $manifest -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $manifest -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $manifest -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $manifest -Name "operator_default_action" -Default $null
|
||||
next_required_phase = Get-PropertyValue -Item $manifest -Name "next_required_phase" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
sentinel_notes = $requiredSentinelNotes
|
||||
}
|
||||
|
||||
$sentinelFieldsCompatible = Test-ObjectHasFields -Item $sentinel -Fields $requiredSentinelFields
|
||||
$sentinelValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "sentinel_marker" -Default "") -eq "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $sentinel -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $sentinel -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $sentinel -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$sentinelNotesCompatible = Test-ArrayContainsAll -Actual @($sentinel.sentinel_notes) -Expected $requiredSentinelNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z132.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest_compatibility_smoke.v1")
|
||||
manifest_present = ($null -ne $manifest)
|
||||
sentinel_fields_compatible = $sentinelFieldsCompatible
|
||||
sentinel_values_compatible = $sentinelValuesCompatible
|
||||
sentinel_notes_compatible = $sentinelNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z133.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_sentinel_fields = $requiredSentinelFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_sentinel_notes = $requiredSentinelNotes
|
||||
not_approved_outcome_stopped_branch_sentinel = $sentinel
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z133 remote workspace real-adapter not-approved outcome stopped branch sentinel smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z133 remote workspace real-adapter not-approved outcome stopped branch sentinel smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-source-result.json"
|
||||
$requiredSentinelFields = @("schema_version", "source_manifest_schema", "sentinel_status", "sentinel_marker", "branch_state", "continuation_policy", "manifest_status", "archive_status", "package_status", "final_release_status", "covered_stage_range", "covered_stage_count", "latest_compatibility_stage", "release_status", "closeout_status", "boundary_status", "reopen_policy", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "sentinel_notes")
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredSentinelNotes = @("not_approved_branch_stopped", "no_further_not_approved_layers_required", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z133-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$sentinel = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_stopped_branch_sentinel" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $sentinel -Name "guardrail_summary" -Default $null
|
||||
$sentinelNotes = @(Get-PropertyValue -Item $sentinel -Name "sentinel_notes" -Default @())
|
||||
|
||||
$sentinelFieldsCompatible = Test-ObjectHasFields -Item $sentinel -Fields $requiredSentinelFields
|
||||
$sentinelValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "source_manifest_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_archive_closeout_manifest.v1" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "sentinel_marker" -Default "") -eq "c19z133_real_adapter_not_approved_outcome_stopped_branch_sentinel" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "manifest_status" -Default "") -eq "closed_not_approved_archive_manifest_complete" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "archive_status" -Default "") -eq "closed_not_approved_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "package_status" -Default "") -eq "final_package_indexed_and_archived_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "final_release_status" -Default "") -eq "closed_not_approved_final_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "covered_stage_range" -Default "") -eq "C19Z117-C19Z122" -and
|
||||
[int](Get-PropertyValue -Item $sentinel -Name "covered_stage_count" -Default -1) -eq 6 -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "latest_compatibility_stage" -Default "") -eq "C19Z122" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "release_status" -Default "") -eq "not_approved_outcome_closed_contract_only" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "closeout_status" -Default "") -eq "closed_not_approved_package_complete" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "boundary_status" -Default "") -eq "closed_not_approved_reopen_required" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $sentinel -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $sentinel -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$sentinelNotesCompatible = Test-ArrayContainsAll -Actual $sentinelNotes -Expected $requiredSentinelNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z133.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_smoke.v1")
|
||||
sentinel_present = ($null -ne $sentinel)
|
||||
sentinel_fields_compatible = $sentinelFieldsCompatible
|
||||
sentinel_values_compatible = $sentinelValuesCompatible
|
||||
sentinel_notes_compatible = $sentinelNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z134.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_sentinel_fields = $requiredSentinelFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_sentinel_notes = $requiredSentinelNotes
|
||||
not_approved_outcome_stopped_branch_sentinel = $sentinel
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z134 remote workspace real-adapter not-approved outcome stopped branch sentinel compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z134 remote workspace real-adapter not-approved outcome stopped branch sentinel compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-source-result.json"
|
||||
$requiredGuardFields = @(
|
||||
"schema_version",
|
||||
"source_sentinel_schema",
|
||||
"guard_status",
|
||||
"guard_marker",
|
||||
"branch_state",
|
||||
"continuation_policy",
|
||||
"next_allowed_entrypoint",
|
||||
"blocks_not_approved_extension",
|
||||
"sentinel_status",
|
||||
"reopen_policy",
|
||||
"enablement_decision",
|
||||
"operator_review_status",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"operator_default_action",
|
||||
"next_required_phase",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"guard_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredGuardNotes = @(
|
||||
"not_approved_branch_no_continuation_guard_active",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z134-remote-workspace-real-adapter-not-approved-outcome-stopped-branch-sentinel-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$sentinel = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_stopped_branch_sentinel" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $sentinel -Name "guardrail_summary" -Default $null
|
||||
|
||||
$guard = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1"
|
||||
source_sentinel_schema = Get-PropertyValue -Item $sentinel -Name "schema_version" -Default $null
|
||||
guard_status = "no_continuation_without_new_explicit_enablement_request"
|
||||
guard_marker = "c19z135_real_adapter_not_approved_outcome_no_continuation_guard"
|
||||
branch_state = Get-PropertyValue -Item $sentinel -Name "branch_state" -Default $null
|
||||
continuation_policy = Get-PropertyValue -Item $sentinel -Name "continuation_policy" -Default $null
|
||||
next_allowed_entrypoint = "new_explicit_enablement_request_only"
|
||||
blocks_not_approved_extension = $true
|
||||
sentinel_status = Get-PropertyValue -Item $sentinel -Name "sentinel_status" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $sentinel -Name "reopen_policy" -Default $null
|
||||
enablement_decision = Get-PropertyValue -Item $sentinel -Name "enablement_decision" -Default $null
|
||||
operator_review_status = Get-PropertyValue -Item $sentinel -Name "operator_review_status" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $sentinel -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $sentinel -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $sentinel -Name "runtime_effect" -Default $null
|
||||
operator_default_action = Get-PropertyValue -Item $sentinel -Name "operator_default_action" -Default $null
|
||||
next_required_phase = Get-PropertyValue -Item $sentinel -Name "next_required_phase" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
guard_notes = $requiredGuardNotes
|
||||
}
|
||||
|
||||
$guardFieldsCompatible = Test-ObjectHasFields -Item $guard -Fields $requiredGuardFields
|
||||
$guardValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $guard -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "source_sentinel_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "guard_marker" -Default "") -eq "c19z135_real_adapter_not_approved_outcome_no_continuation_guard" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and
|
||||
[bool](Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $guard -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guard -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardNotesCompatible = Test-ArrayContainsAll -Actual @($guard.guard_notes) -Expected $requiredGuardNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z134.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel_compatibility_smoke.v1")
|
||||
sentinel_present = ($null -ne $sentinel)
|
||||
guard_fields_compatible = $guardFieldsCompatible
|
||||
guard_values_compatible = $guardValuesCompatible
|
||||
guard_notes_compatible = $guardNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z135.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_guard_fields = $requiredGuardFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_guard_notes = $requiredGuardNotes
|
||||
not_approved_outcome_no_continuation_guard = $guard
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z135 remote workspace real-adapter not-approved outcome no-continuation guard smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z135 remote workspace real-adapter not-approved outcome no-continuation guard smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-source-result.json"
|
||||
$requiredGuardFields = @("schema_version", "source_sentinel_schema", "guard_status", "guard_marker", "branch_state", "continuation_policy", "next_allowed_entrypoint", "blocks_not_approved_extension", "sentinel_status", "reopen_policy", "enablement_decision", "operator_review_status", "enablement_status", "runtime_gate_state", "runtime_effect", "operator_default_action", "next_required_phase", "allows_process_start", "allows_payload_traffic", "guardrail_summary", "guard_notes")
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredGuardNotes = @("not_approved_branch_no_continuation_guard_active", "new_explicit_enablement_request_required", "real_runtime_gate_not_enabled", "process_start_disabled", "payload_forwarding_disabled")
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z135-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$guard = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_no_continuation_guard" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $guard -Name "guardrail_summary" -Default $null
|
||||
$guardNotes = @(Get-PropertyValue -Item $guard -Name "guard_notes" -Default @())
|
||||
|
||||
$guardFieldsCompatible = Test-ObjectHasFields -Item $guard -Fields $requiredGuardFields
|
||||
$guardValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $guard -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "source_sentinel_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_stopped_branch_sentinel.v1" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "guard_marker" -Default "") -eq "c19z135_real_adapter_not_approved_outcome_no_continuation_guard" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and
|
||||
[bool](Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "sentinel_status" -Default "") -eq "stopped_until_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "enablement_decision" -Default "") -eq "not_approved" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "operator_review_status" -Default "") -eq "closed_without_approval" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "operator_default_action" -Default "") -eq "keep_real_adapter_disabled" -and
|
||||
[string](Get-PropertyValue -Item $guard -Name "next_required_phase" -Default "") -eq "explicit_new_enablement_request_only" -and
|
||||
-not [bool](Get-PropertyValue -Item $guard -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guard -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$guardNotesCompatible = Test-ArrayContainsAll -Actual $guardNotes -Expected $requiredGuardNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z135.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_smoke.v1")
|
||||
guard_present = ($null -ne $guard)
|
||||
guard_fields_compatible = $guardFieldsCompatible
|
||||
guard_values_compatible = $guardValuesCompatible
|
||||
guard_notes_compatible = $guardNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z136.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_compatibility_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_guard_fields = $requiredGuardFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_guard_notes = $requiredGuardNotes
|
||||
not_approved_outcome_no_continuation_guard = $guard
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z136 remote workspace real-adapter not-approved outcome no-continuation guard compatibility smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z136 remote workspace real-adapter not-approved outcome no-continuation guard compatibility smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = "http://192.168.200.61:18121/api/v1",
|
||||
[string]$ClusterID = "cfc0743d-d960-49fb-9de8-96e063d5e4aa",
|
||||
[string]$ActorUserID = "f67d943f-5397-4b3a-a229-695fe67ad700",
|
||||
[string]$RequestedNodeName = "test-1",
|
||||
[string]$DefaultNodeName = "test-2",
|
||||
[string]$MatrixNodeName = "test-1",
|
||||
[string]$ResultPath = "artifacts\c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-smoke-result.json"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).ProviderPath
|
||||
$sourceResultPath = "artifacts\c19z137-remote-workspace-real-adapter-not-approved-outcome-continuation-block-enforcement-source-result.json"
|
||||
$requiredEnforcementFields = @(
|
||||
"schema_version",
|
||||
"source_guard_schema",
|
||||
"enforcement_status",
|
||||
"enforcement_marker",
|
||||
"attempted_action",
|
||||
"attempt_allowed",
|
||||
"block_reason",
|
||||
"next_allowed_entrypoint",
|
||||
"blocks_not_approved_extension",
|
||||
"guard_status",
|
||||
"branch_state",
|
||||
"continuation_policy",
|
||||
"reopen_policy",
|
||||
"enablement_status",
|
||||
"runtime_gate_state",
|
||||
"runtime_effect",
|
||||
"allows_process_start",
|
||||
"allows_payload_traffic",
|
||||
"guardrail_summary",
|
||||
"enforcement_notes"
|
||||
)
|
||||
$requiredGuardrailFields = @("activation_blocked", "process_start_allowed", "health_probe_enabled", "payload_traffic", "allows_process_start", "allows_payload_traffic")
|
||||
$requiredEnforcementNotes = @(
|
||||
"continuation_attempt_blocked",
|
||||
"not_approved_branch_remains_closed",
|
||||
"new_explicit_enablement_request_required",
|
||||
"real_runtime_gate_not_enabled",
|
||||
"process_start_disabled",
|
||||
"payload_forwarding_disabled"
|
||||
)
|
||||
|
||||
function Get-PropertyValue {
|
||||
param([object]$Item, [string]$Name, [object]$Default = $null)
|
||||
if ($null -eq $Item) { return $Default }
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if ($Item.Contains($Name)) { return $Item[$Name] }
|
||||
return $Default
|
||||
}
|
||||
$property = $Item.PSObject.Properties[$Name]
|
||||
if ($null -eq $property) { return $Default }
|
||||
return $property.Value
|
||||
}
|
||||
|
||||
function Test-ObjectHasFields {
|
||||
param([object]$Item, [string[]]$Fields)
|
||||
if ($null -eq $Item) { return $false }
|
||||
foreach ($field in $Fields) {
|
||||
if ($Item -is [System.Collections.IDictionary]) {
|
||||
if (-not $Item.Contains($field)) { return $false }
|
||||
continue
|
||||
}
|
||||
if ($null -eq $Item.PSObject.Properties[$field]) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
function Test-ArrayContainsAll {
|
||||
param([object[]]$Actual, [string[]]$Expected)
|
||||
foreach ($item in $Expected) {
|
||||
if ($Actual -notcontains $item) { return $false }
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "c19z136-remote-workspace-real-adapter-not-approved-outcome-no-continuation-guard-compatibility-smoke.ps1") `
|
||||
-ApiBaseUrl $ApiBaseUrl `
|
||||
-ClusterID $ClusterID `
|
||||
-ActorUserID $ActorUserID `
|
||||
-RequestedNodeName $RequestedNodeName `
|
||||
-DefaultNodeName $DefaultNodeName `
|
||||
-MatrixNodeName $MatrixNodeName `
|
||||
-ResultPath $sourceResultPath | Out-Null
|
||||
|
||||
$sourceFile = Join-Path $repoRoot $sourceResultPath
|
||||
$sourceResult = Get-Content -Raw -Path $sourceFile | ConvertFrom-Json
|
||||
$guard = Get-PropertyValue -Item $sourceResult -Name "not_approved_outcome_no_continuation_guard" -Default $null
|
||||
$guardrails = Get-PropertyValue -Item $guard -Name "guardrail_summary" -Default $null
|
||||
|
||||
$enforcement = [ordered]@{
|
||||
schema_version = "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1"
|
||||
source_guard_schema = Get-PropertyValue -Item $guard -Name "schema_version" -Default $null
|
||||
enforcement_status = "blocked_continuation_enforced"
|
||||
enforcement_marker = "c19z137_real_adapter_not_approved_outcome_continuation_block_enforcement"
|
||||
attempted_action = "continue_not_approved_branch_without_new_explicit_enablement_request"
|
||||
attempt_allowed = $false
|
||||
block_reason = "new_explicit_enablement_request_required"
|
||||
next_allowed_entrypoint = Get-PropertyValue -Item $guard -Name "next_allowed_entrypoint" -Default $null
|
||||
blocks_not_approved_extension = Get-PropertyValue -Item $guard -Name "blocks_not_approved_extension" -Default $null
|
||||
guard_status = Get-PropertyValue -Item $guard -Name "guard_status" -Default $null
|
||||
branch_state = Get-PropertyValue -Item $guard -Name "branch_state" -Default $null
|
||||
continuation_policy = Get-PropertyValue -Item $guard -Name "continuation_policy" -Default $null
|
||||
reopen_policy = Get-PropertyValue -Item $guard -Name "reopen_policy" -Default $null
|
||||
enablement_status = Get-PropertyValue -Item $guard -Name "enablement_status" -Default $null
|
||||
runtime_gate_state = Get-PropertyValue -Item $guard -Name "runtime_gate_state" -Default $null
|
||||
runtime_effect = Get-PropertyValue -Item $guard -Name "runtime_effect" -Default $null
|
||||
allows_process_start = $false
|
||||
allows_payload_traffic = $false
|
||||
guardrail_summary = $guardrails
|
||||
enforcement_notes = $requiredEnforcementNotes
|
||||
}
|
||||
|
||||
$enforcementFieldsCompatible = Test-ObjectHasFields -Item $enforcement -Fields $requiredEnforcementFields
|
||||
$enforcementValuesCompatible = (
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "schema_version" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement.v1" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "source_guard_schema" -Default "") -eq "rap.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard.v1" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "enforcement_status" -Default "") -eq "blocked_continuation_enforced" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "attempted_action" -Default "") -eq "continue_not_approved_branch_without_new_explicit_enablement_request" -and
|
||||
-not [bool](Get-PropertyValue -Item $enforcement -Name "attempt_allowed" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "block_reason" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "next_allowed_entrypoint" -Default "") -eq "new_explicit_enablement_request_only" -and
|
||||
[bool](Get-PropertyValue -Item $enforcement -Name "blocks_not_approved_extension" -Default $false) -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "guard_status" -Default "") -eq "no_continuation_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "branch_state" -Default "") -eq "not_approved_branch_closed" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "continuation_policy" -Default "") -eq "do_not_continue_without_new_explicit_enablement_request" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "reopen_policy" -Default "") -eq "new_explicit_enablement_request_required" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "enablement_status" -Default "") -eq "not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "runtime_gate_state" -Default "") -eq "validated_contract_only_not_enabled" -and
|
||||
[string](Get-PropertyValue -Item $enforcement -Name "runtime_effect" -Default "") -eq "contract_only_no_runtime_enablement" -and
|
||||
-not [bool](Get-PropertyValue -Item $enforcement -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $enforcement -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
$enforcementNotesCompatible = Test-ArrayContainsAll -Actual @($enforcement.enforcement_notes) -Expected $requiredEnforcementNotes
|
||||
$guardrailsCompatible = (
|
||||
(Test-ObjectHasFields -Item $guardrails -Fields $requiredGuardrailFields) -and
|
||||
[bool](Get-PropertyValue -Item $guardrails -Name "activation_blocked" -Default $false) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "process_start_allowed" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "health_probe_enabled" -Default $true) -and
|
||||
[string](Get-PropertyValue -Item $guardrails -Name "payload_traffic" -Default "") -eq "none" -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_process_start" -Default $true) -and
|
||||
-not [bool](Get-PropertyValue -Item $guardrails -Name "allows_payload_traffic" -Default $true)
|
||||
)
|
||||
|
||||
$checks = [ordered]@{
|
||||
source_smoke_passed = ([bool]$sourceResult.passed)
|
||||
source_schema_expected = ([string]$sourceResult.schema_version -eq "c19z136.remote_workspace_real_adapter_not_approved_outcome_no_continuation_guard_compatibility_smoke.v1")
|
||||
guard_present = ($null -ne $guard)
|
||||
enforcement_fields_compatible = $enforcementFieldsCompatible
|
||||
enforcement_values_compatible = $enforcementValuesCompatible
|
||||
enforcement_notes_compatible = $enforcementNotesCompatible
|
||||
guardrails_compatible = $guardrailsCompatible
|
||||
}
|
||||
$failed = @($checks.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { $_.Key })
|
||||
|
||||
$result = [ordered]@{
|
||||
schema_version = "c19z137.remote_workspace_real_adapter_not_approved_outcome_continuation_block_enforcement_smoke.v1"
|
||||
source_result_path = $sourceFile
|
||||
cluster_id = $ClusterID
|
||||
required_enforcement_fields = $requiredEnforcementFields
|
||||
required_guardrail_fields = $requiredGuardrailFields
|
||||
required_enforcement_notes = $requiredEnforcementNotes
|
||||
not_approved_outcome_continuation_block_enforcement = $enforcement
|
||||
checks = $checks
|
||||
failed_checks = $failed
|
||||
passed = ($failed.Count -eq 0)
|
||||
}
|
||||
|
||||
$fullResultPath = Join-Path $repoRoot $ResultPath
|
||||
$resultDir = Split-Path -Parent $fullResultPath
|
||||
if ($resultDir) { New-Item -ItemType Directory -Force -Path $resultDir | Out-Null }
|
||||
$result | ConvertTo-Json -Depth 100 | Set-Content -Encoding UTF8 -Path $fullResultPath
|
||||
|
||||
if (-not $result.passed) {
|
||||
throw "C19Z137 remote workspace real-adapter not-approved outcome continuation block enforcement smoke failed. Result: $fullResultPath Failed: $($failed -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "C19Z137 remote workspace real-adapter not-approved outcome continuation block enforcement smoke passed. Result: $fullResultPath"
|
||||
$result
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user