From 5ad60d066991ca83bc292644b2bf04f58551c280 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:35:16 +0000 Subject: [PATCH 1/9] New configuration schema for Launch Manager --- .../examples/example_conf.json | 175 +++++++ .../s-core_launch_manager.schema.json | 492 ++++++++++++++++++ .../schema/s-core_launch_manager.schema.json | 123 +++++ .../types/alive_supervision.schema.json | 15 + .../types/component_properties.schema.json | 99 ++++ .../types/deployment_config.schema.json | 126 +++++ .../schema/types/recovery_action.schema.json | 43 ++ .../schema/types/run_target.schema.json | 45 ++ .../schema/types/watchdog.schema.json | 27 + .../new_config_schema/scripts/bundle.py | 193 +++++++ .../new_config_schema/scripts/validate.py | 157 ++++++ 11 files changed, 1495 insertions(+) create mode 100755 src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json create mode 100644 src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json create mode 100755 src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py create mode 100755 src/launch_manager_daemon/config/new_config_schema/scripts/validate.py diff --git a/src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json b/src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json new file mode 100755 index 00000000..fc69c5c3 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json @@ -0,0 +1,175 @@ +{ + "schema_version": 1, + "defaults": { + "deployment_config": { + "ready_timeout": 0.5, + "shutdown_timeout": 0.5, + "environmental_variables": { + "LD_LIBRARY_PATH": "/opt/lib" + }, + "bin_dir": "/opt", + "working_dir": "/tmp", + "ready_recovery_action": { + "restart": { + "number_of_attempts": 0, + "delay_before_restart": 0 + } + }, + "recovery_action": { + "switch_run_target": { + "run_target": "Off" + } + }, + "sandbox": { + "uid": 1000, + "gid": 1000, + "supplementary_group_ids": [], + "scheduling_policy": "SCHED_OTHER", + "scheduling_priority": 0 + } + }, + "component_properties": { + "application_profile": { + "application_type": "Reporting_And_Supervised", + "is_self_terminating": false, + "alive_supervision": { + "reporting_cycle": 0.5, + "failed_cycles_tolerance": 2, + "min_indications": 1, + "max_indications": 3 + } + }, + "depends_on": [], + "process_arguments": [], + "ready_condition": { + "process_state": "Running" + } + }, + "run_target": { + "depends_on": [], + "transition_timeout": 5, + "recovery_action": { + "switch_run_target": { + "run_target": "Off" + } + } + }, + "alive_supervision" : { + "evaluation_cycle": 0.5 + }, + "watchdog": { + "device_file_path": "/dev/watchdog", + "max_timeout": 2.0, + "deactivate_on_shutdown": true, + "require_magic_close": false + } + }, + "components": { + "setup_filesystem_sh": { + "description": "Script to mount partitions at the right directories", + "component_properties": { + "binary_name": "bin/setup_filesystem.sh", + "application_profile": { + "application_type": "Native", + "is_self_terminating": true + }, + "process_arguments": ["-a", "-b"], + "ready_condition": { + "process_state": "Terminated" + } + }, + "deployment_config": { + "bin_dir": "/opt/scripts" + } + }, + "dlt-daemon": { + "description": "Logging application", + "component_properties": { + "binary_name": "dltd", + "application_profile": { + "application_type": "Native" + }, + "depends_on": ["setup_filesystem_sh"] + }, + "deployment_config": { + "bin_dir" : "/opt/apps/dlt-daemon" + } + }, + "someip-daemon": { + "description": "SOME/IP application", + "component_properties": { + "binary_name": "someipd" + }, + "deployment_config": { + "bin_dir" : "/opt/apps/someip" + } + }, + "test_app1": { + "description": "Simple test application", + "component_properties": { + "binary_name": "test_app1", + "depends_on": ["dlt-daemon", "someip-daemon"] + }, + "deployment_config": { + "bin_dir" : "/opt/apps/test_app1" + } + }, + "state_manager": { + "description": "Application that manages life cycle of the ECU", + "component_properties": { + "binary_name": "sm", + "application_profile": { + "application_type": "State_Manager" + }, + "depends_on": ["setup_filesystem_sh"] + }, + "deployment_config": { + "bin_dir" : "/opt/apps/state_manager" + } + } + }, + "run_targets": { + "Minimal": { + "description": "Minimal functionality of the system", + "depends_on": ["state_manager"], + "recovery_action": { + "switch_run_target": { + "run_target": "Off" + } + } + }, + "Full": { + "description": "Everything running", + "depends_on": ["test_app1", "Minimal"], + "transition_timeout": 5, + "recovery_action": { + "switch_run_target": { + "run_target": "Minimal" + } + } + }, + "Off": { + "description": "Nothing is running", + "recovery_action": { + "switch_run_target": { + "run_target": "Off" + } + } + } + }, + "alive_supervision" : { + "evaluation_cycle": 0.5 + }, + "fallback_run_target": { + "description": "Switching off everything", + "depends_on": [], + "transition_timeout": 1.5 + }, + "initial_run_target": "Minimal", + "watchdog": { + "device_file_path": "/dev/watchdog", + "max_timeout": 2, + "deactivate_on_shutdown": true, + "require_magic_close": false + } +} diff --git a/src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json new file mode 100644 index 00000000..1cf10a09 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json @@ -0,0 +1,492 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Configuration schema for the S-CORE Launch Manager", + "description": "Defines the structure and valid values for the Launch Manager configuration file, which specifies managed components, run targets, and recovery behaviors.", + "$defs": { + "component_properties": { + "type": "object", + "description": "Defines a reusable type that captures essential development-time characteristics of a software component.", + "properties": { + "binary_name": { + "type": "string", + "description": "Specifies the relative path of the executable file inside the directory defined by 'deployment_config.bin_dir'. The final executable path will be resolved as '{bin_dir}/{binary_name}'. Example values include simple filenames (e.g., 'test_app1') or subdirectory paths (e.g., 'bin/test_app1')." + }, + "application_profile": { + "type": "object", + "description": "Defines the application profile that specifies the runtime behavior and capabilities of this component.", + "properties": { + "application_type": { + "type": "string", + "enum": [ + "Native", + "Reporting", + "Reporting_And_Supervised", + "State_Manager" + ], + "description": "Specifies the level of integration between the component and the Launch Manager. 'Native': no integration with Launch Manager. 'Reporting': uses Launch Manager lifecycle APIs. 'Reporting_And_Supervised': uses lifecycle APIs and sends alive notifications. 'State_Manager': uses lifecycle APIs, sends alive notifications, and has permission to change the active Run Target." + }, + "is_self_terminating": { + "type": "boolean", + "description": "Indicates whether the component is designed to terminate automatically once its planned tasks are completed (true), or remain running until explicitly requested to terminate by the Launch Manager (false)." + }, + "alive_supervision": { + "type": "object", + "description": "Defines the configuration parameters used for alive monitoring of the component.", + "properties": { + "reporting_cycle": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the duration of the time interval used to verify that the component sends alive notifications within the expected time frame." + }, + "failed_cycles_tolerance": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of consecutive reporting cycle failures (see 'reporting_cycle'). Once the number of failed cycles exceeds this maximum, the Launch Manager will trigger the configured recovery action." + }, + "min_indications": { + "type": "integer", + "minimum": 0, + "description": "Specifies the minimum number of checkpoints that must be reported within each configured 'reporting_cycle'." + }, + "max_indications": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of checkpoints that may be reported within each configured 'reporting_cycle'." + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + "depends_on": { + "type": "array", + "description": "Specifies the names of components that this component depends on. Each dependency must be initialized and reach its ready state before this component can start.", + "items": { + "type": "string", + "description": "Specifies the name of a component on which this component depends." + } + }, + "process_arguments": { + "type": "array", + "description": "Specifies an ordered list of command-line arguments passed to the component at startup.", + "items": { + "type": "string", + "description": "Specifies a single command-line argument token as a UTF-8 string; order is preserved." + } + }, + "ready_condition": { + "type": "object", + "description": "Defines the set of conditions that determine when the component completes its initializing state and enters the ready state.", + "properties": { + "process_state": { + "type": "string", + "enum": [ + "Running", + "Terminated" + ], + "description": "Specifies the required state of the component's POSIX process. 'Running': the process has started and reached its running state. 'Terminated': the process has started, reached its running state, and then terminated successfully." + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + "recovery_action": { + "type": "object", + "description": "Defines a reusable type that specifies recovery actions to execute when an error or failure occurs.", + "properties": { + "restart": { + "type": "object", + "description": "Defines a recovery action that restarts the POSIX process associated with this component.", + "properties": { + "number_of_attempts": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of restart attempts before the Launch Manager concludes that recovery cannot succeed." + }, + "delay_before_restart": { + "type": "number", + "minimum": 0, + "description": "Specifies the delay duration that the Launch Manager waits before initiating a restart attempt." + } + }, + "required": [], + "additionalProperties": false + }, + "switch_run_target": { + "type": "object", + "description": "Defines a recovery action that switches to a Run Target. This can be a different Run Target or the same one to retry activation of the current Run Target.", + "properties": { + "run_target": { + "type": "string", + "description": "Specifies the name of the Run Target that the Launch Manager should switch to." + } + }, + "required": [], + "additionalProperties": false + } + }, + "oneOf": [ + { + "required": [ + "restart" + ] + }, + { + "required": [ + "switch_run_target" + ] + } + ], + "additionalProperties": false + }, + "deployment_config": { + "type": "object", + "description": "Defines a reusable type that contains configuration parameters that are specific to a particular deployment environment or system setup.", + "properties": { + "ready_timeout": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the maximum time allowed for the component to reach its ready state. The timeout is measured from when the component process is created until the ready conditions specified in 'component_properties.ready_condition' are met." + }, + "shutdown_timeout": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the maximum time allowed for the component to terminate after it receives a SIGTERM signal from the Launch Manager. The timeout is measured from when the Launch Manager sends the SIGTERM signal until the Operating System notifies the Launch Manager that the child process has terminated." + }, + "environmental_variables": { + "type": "object", + "description": "Defines the set of environment variables passed to the component at startup.", + "additionalProperties": { + "type": "string", + "description": "Specifies the environment variable's value as a string. An empty string is allowed and represents an intentionally empty environment variable." + } + }, + "bin_dir": { + "type": "string", + "description": "Specifies the absolute filesystem path to the directory where the component is installed." + }, + "working_dir": { + "type": "string", + "description": "Specifies the directory used as the working directory for the component during execution." + }, + "ready_recovery_action": { + "allOf": [ + { + "$ref": "#/$defs/recovery_action" + }, + { + "properties": { + "restart": true + }, + "required": [ + "restart" + ], + "not": { + "required": [ + "switch_run_target" + ] + } + } + ], + "description": "Specifies the recovery action to execute when the component fails to reach its ready state within the configured timeout." + }, + "recovery_action": { + "allOf": [ + { + "$ref": "#/$defs/recovery_action" + }, + { + "properties": { + "switch_run_target": true + }, + "required": [ + "switch_run_target" + ], + "not": { + "required": [ + "restart" + ] + } + } + ], + "description": "Specifies the recovery action to execute when the component malfunctions after reaching its ready state." + }, + "sandbox": { + "type": "object", + "description": "Defines the sandbox configuration parameters that isolate and constrain the component's runtime execution.", + "properties": { + "uid": { + "type": "integer", + "minimum": 0, + "description": "Specifies the POSIX user ID (UID) under which this component executes." + }, + "gid": { + "type": "integer", + "minimum": 0, + "description": "Specifies the primary POSIX group ID (GID) under which this component executes." + }, + "supplementary_group_ids": { + "type": "array", + "description": "Specifies the list of supplementary POSIX group IDs (GIDs) assigned to this component.", + "items": { + "type": "integer", + "minimum": 0, + "description": "Specifies a single supplementary POSIX group ID (GID)." + } + }, + "security_policy": { + "type": "string", + "description": "Specifies the security policy or confinement profile name (such as an SELinux or AppArmor profile) assigned to the component." + }, + "scheduling_policy": { + "type": "string", + "description": "Specifies the scheduling policy applied to the component's initial thread. Supported values correspond to OS-defined policies (e.g., FIFO, RR, OTHER).", + "anyOf": [ + { + "enum": [ + "SCHED_FIFO", + "SCHED_RR", + "SCHED_OTHER" + ] + }, + { + "type": "string" + } + ] + }, + "scheduling_priority": { + "type": "integer", + "description": "Specifies the scheduling priority applied to the component's initial thread." + }, + "max_memory_usage": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Specifies the maximum amount of memory, in bytes, that the component is permitted to use during runtime." + }, + "max_cpu_usage": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Specifies the maximum CPU usage limit for the component, expressed as a percentage of total CPU capacity." + } + }, + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + "run_target": { + "type": "object", + "description": "Defines a reusable type that specifies configuration parameters for a Run Target.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a user-defined description of the Run Target." + }, + "depends_on": { + "type": "array", + "description": "Specifies the names of components and Run Targets that must be activated when this Run Target is activated.", + "items": { + "type": "string", + "description": "Specifies the name of a component or Run Target that this Run Target depends on." + } + }, + "transition_timeout": { + "type": "number", + "description": "Specifies the time limit for the Run Target transition. If this limit is exceeded, the transition is considered failed.", + "exclusiveMinimum": 0 + }, + "recovery_action": { + "allOf": [ + { + "$ref": "#/$defs/recovery_action" + }, + { + "properties": { + "switch_run_target": true + }, + "required": [ + "switch_run_target" + ], + "not": { + "required": [ + "restart" + ] + } + } + ], + "description": "Specifies the recovery action to execute when a component assigned to this Run Target fails." + } + }, + "required": [ + "recovery_action" + ], + "additionalProperties": false + }, + "alive_supervision": { + "type": "object", + "description": "Defines a reusable type that contains configuration parameters for alive supervision.", + "properties": { + "evaluation_cycle": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the length of the time window used to assess incoming alive supervision reports." + } + }, + "required": [], + "additionalProperties": false + }, + "watchdog": { + "type": "object", + "description": "Defines a reusable type that contains configuration parameters for the external watchdog.", + "properties": { + "device_file_path": { + "type": "string", + "description": "Specifies the path to the external watchdog device file (e.g., /dev/watchdog)." + }, + "max_timeout": { + "type": "number", + "minimum": 0, + "description": "Specifies the maximum timeout value that the Launch Manager configures on the external watchdog during startup. The external watchdog uses this timeout as the deadline for receiving periodic alive reports from the Launch Manager." + }, + "deactivate_on_shutdown": { + "type": "boolean", + "description": "Specifies whether the Launch Manager disables the external watchdog during shutdown. When set to true, the watchdog is deactivated; when false, it remains active." + }, + "require_magic_close": { + "type": "boolean", + "description": "Specifies whether the Launch Manager performs a defined shutdown sequence to inform the external watchdog that the shutdown is intentional and to prevent a watchdog-initiated reset. When true, the magic close sequence is performed; when false, it is not." + } + }, + "required": [], + "additionalProperties": false + } + }, + "properties": { + "schema_version": { + "type": "integer", + "description": "Specifies the schema version number that the Launch Manager uses to determine how to parse and validate this configuration file.", + "enum": [ + 1 + ] + }, + "defaults": { + "type": "object", + "description": "Defines default configuration values that components and Run Targets inherit unless they provide their own overriding values.", + "properties": { + "component_properties": { + "description": "Defines default component property values applied to all components unless overridden in individual component definitions.", + "$ref": "#/$defs/component_properties" + }, + "deployment_config": { + "description": "Defines default deployment configuration values applied to all components unless overridden in individual component definitions.", + "$ref": "#/$defs/deployment_config" + }, + "run_target": { + "description": "Defines default Run Target configuration values applied to all Run Targets unless overridden in individual Run Target definitions.", + "$ref": "#/$defs/run_target" + }, + "alive_supervision": { + "description": "Defines default alive supervision configuration values used unless a global 'alive_supervision' configuration is specified at the root level.", + "$ref": "#/$defs/alive_supervision" + }, + "watchdog": { + "description": "Defines default watchdog configuration values applied to all watchdogs unless overridden in individual watchdog definitions.", + "$ref": "#/$defs/watchdog" + } + }, + "required": [], + "additionalProperties": false + }, + "components": { + "type": "object", + "description": "Defines software components managed by the Launch Manager, where each property name is a unique component identifier and its value contains the component's configuration.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "description": "Defines an individual component's configuration properties and deployment settings.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a human-readable description of the component's purpose." + }, + "component_properties": { + "description": "Defines component properties for this component; any properties not specified here are inherited from 'defaults.component_properties'.", + "$ref": "#/$defs/component_properties" + }, + "deployment_config": { + "description": "Defines deployment configuration for this component; any properties not specified here are inherited from 'defaults.deployment_config'.", + "$ref": "#/$defs/deployment_config" + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + "run_targets": { + "type": "object", + "description": "Defines Run Targets representing different operational modes of the system, where each property name is a unique Run Target identifier and its value contains the Run Target's configuration.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$ref": "#/$defs/run_target" + } + }, + "required": [], + "additionalProperties": false + }, + "initial_run_target": { + "type": "string", + "description": "Specifies the name of the initial Run Target that the Launch Manager activates during the startup sequence. This name must match a Run Target defined in 'run_targets'." + }, + "fallback_run_target": { + "type": "object", + "description": "Defines the fallback Run Target configuration that the Launch Manager activates when all recovery attempts have been exhausted. This Run Target does not include a recovery_action property.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a human-readable description of the fallback Run Target." + }, + "depends_on": { + "type": "array", + "description": "Specifies the names of components and Run Targets that must be activated when this Run Target is activated.", + "items": { + "type": "string", + "description": "Specifies the name of a component or Run Target that this Run Target depends on." + } + }, + "transition_timeout": { + "type": "number", + "description": "Specifies the time limit for the Run Target transition. If this limit is exceeded, the transition is considered failed.", + "exclusiveMinimum": 0 + } + }, + "required": [ + "depends_on" + ], + "additionalProperties": false + }, + "alive_supervision": { + "description": "Defines the alive supervision configuration parameters used to monitor component health. This configuration overrides 'defaults.alive_supervision' if specified.", + "$ref": "#/$defs/alive_supervision" + }, + "watchdog": { + "description": "Defines the external watchdog device configuration used by the Launch Manager. This configuration overrides 'defaults.watchdog' if specified.", + "$ref": "#/$defs/watchdog" + } + }, + "required": [ + "schema_version" + ], + "additionalProperties": false +} \ No newline at end of file diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json new file mode 100755 index 00000000..d7344161 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json @@ -0,0 +1,123 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "title": "Configuration schema for the S-CORE Launch Manager", + "description": "Defines the structure and valid values for the Launch Manager configuration file, which specifies managed components, run targets, and recovery behaviors.", + "properties": { + "schema_version": { + "type": "integer", + "description": "Specifies the schema version number that the Launch Manager uses to determine how to parse and validate this configuration file.", + "enum": [ 1 ] + }, + "defaults": { + "type": "object", + "description": "Defines default configuration values that components and Run Targets inherit unless they provide their own overriding values.", + "properties": { + "component_properties": { + "$ref": "./types/component_properties.schema.json", + "description": "Defines default component property values applied to all components unless overridden in individual component definitions." + }, + "deployment_config": { + "$ref": "./types/deployment_config.schema.json", + "description": "Defines default deployment configuration values applied to all components unless overridden in individual component definitions." + }, + "run_target": { + "$ref": "./types/run_target.schema.json", + "description": "Defines default Run Target configuration values applied to all Run Targets unless overridden in individual Run Target definitions." + }, + "alive_supervision": { + "$ref": "./types/alive_supervision.schema.json", + "description": "Defines default alive supervision configuration values used unless a global 'alive_supervision' configuration is specified at the root level." + }, + "watchdog": { + "$ref": "./types/watchdog.schema.json", + "description": "Defines default watchdog configuration values applied to all watchdogs unless overridden in individual watchdog definitions." + } + }, + "required": [], + "additionalProperties": false + }, + "components": { + "type": "object", + "description": "Defines software components managed by the Launch Manager, where each property name is a unique component identifier and its value contains the component's configuration.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "type": "object", + "description": "Defines an individual component's configuration properties and deployment settings.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a human-readable description of the component's purpose." + }, + "component_properties": { + "$ref": "./types/component_properties.schema.json", + "description": "Defines component properties for this component; any properties not specified here are inherited from 'defaults.component_properties'." + }, + "deployment_config": { + "$ref": "./types/deployment_config.schema.json", + "description": "Defines deployment configuration for this component; any properties not specified here are inherited from 'defaults.deployment_config'." + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + "run_targets": { + "type": "object", + "description": "Defines Run Targets representing different operational modes of the system, where each property name is a unique Run Target identifier and its value contains the Run Target's configuration.", + "patternProperties": { + "^[a-zA-Z0-9_-]+$": { + "$ref": "./types/run_target.schema.json" + } + }, + "required": [], + "additionalProperties": false + }, + "initial_run_target": { + "type": "string", + "description": "Specifies the name of the initial Run Target that the Launch Manager activates during the startup sequence. This name must match a Run Target defined in 'run_targets'." + }, + "fallback_run_target": { + "type": "object", + "description": "Defines the fallback Run Target configuration that the Launch Manager activates when all recovery attempts have been exhausted. This Run Target does not include a recovery_action property.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a human-readable description of the fallback Run Target." + }, + "depends_on": { + "type": "array", + "description": "Specifies the names of components and Run Targets that must be activated when this Run Target is activated.", + "items": { + "type": "string", + "description": "Specifies the name of a component or Run Target that this Run Target depends on." + } + }, + "transition_timeout": { + "type": "number", + "description": "Specifies the time limit for the Run Target transition. If this limit is exceeded, the transition is considered failed.", + "exclusiveMinimum": 0 + } + }, + "required": [ + "depends_on" + ], + "additionalProperties": false + }, + "alive_supervision": { + "$ref": "./types/alive_supervision.schema.json", + "description": "Defines the alive supervision configuration parameters used to monitor component health. This configuration overrides 'defaults.alive_supervision' if specified." + }, + "watchdog": { + "$ref": "./types/watchdog.schema.json", + "description": "Defines the external watchdog device configuration used by the Launch Manager. This configuration overrides 'defaults.watchdog' if specified." + } + }, + "required": [ + "schema_version" + ], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json new file mode 100755 index 00000000..919de721 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that contains configuration parameters for alive supervision.", + "properties": { + "evaluation_cycle": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the length of the time window used to assess incoming alive supervision reports." + } + }, + + "required": [], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json new file mode 100755 index 00000000..087b470f --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json @@ -0,0 +1,99 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that captures essential development-time characteristics of a software component.", + "properties": { + "binary_name": { + "type": "string", + "description": "Specifies the relative path of the executable file inside the directory defined by 'deployment_config.bin_dir'. The final executable path will be resolved as '{bin_dir}/{binary_name}'. Example values include simple filenames (e.g., 'test_app1') or subdirectory paths (e.g., 'bin/test_app1')." + }, + + "application_profile": { + "type": "object", + "description": "Defines the application profile that specifies the runtime behavior and capabilities of this component.", + "properties": { + "application_type": { + "type": "string", + "enum": [ + "Native", + "Reporting", + "Reporting_And_Supervised", + "State_Manager" + ], + "description": "Specifies the level of integration between the component and the Launch Manager. 'Native': no integration with Launch Manager. 'Reporting': uses Launch Manager lifecycle APIs. 'Reporting_And_Supervised': uses lifecycle APIs and sends alive notifications. 'State_Manager': uses lifecycle APIs, sends alive notifications, and has permission to change the active Run Target." + }, + "is_self_terminating": { + "type": "boolean", + "description": "Indicates whether the component is designed to terminate automatically once its planned tasks are completed (true), or remain running until explicitly requested to terminate by the Launch Manager (false)." + }, + "alive_supervision": { + "type": "object", + "description": "Defines the configuration parameters used for alive monitoring of the component.", + "properties": { + "reporting_cycle": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the duration of the time interval used to verify that the component sends alive notifications within the expected time frame." + }, + "failed_cycles_tolerance": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of consecutive reporting cycle failures (see 'reporting_cycle'). Once the number of failed cycles exceeds this maximum, the Launch Manager will trigger the configured recovery action." + }, + "min_indications": { + "type": "integer", + "minimum": 0, + "description": "Specifies the minimum number of checkpoints that must be reported within each configured 'reporting_cycle'." + }, + "max_indications": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of checkpoints that may be reported within each configured 'reporting_cycle'." + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false + }, + + "depends_on": { + "type": "array", + "description": "Specifies the names of components that this component depends on. Each dependency must be initialized and reach its ready state before this component can start.", + "items": { + "type": "string", + "description": "Specifies the name of a component on which this component depends." + } + }, + + "process_arguments": { + "type": "array", + "description": "Specifies an ordered list of command-line arguments passed to the component at startup.", + "items": { + "type": "string", + "description": "Specifies a single command-line argument token as a UTF-8 string; order is preserved." + } + }, + + "ready_condition": { + "type": "object", + "description": "Defines the set of conditions that determine when the component completes its initializing state and enters the ready state.", + "properties": { + "process_state": { + "type": "string", + "enum": [ + "Running", + "Terminated" + ], + "description": "Specifies the required state of the component's POSIX process. 'Running': the process has started and reached its running state. 'Terminated': the process has started, reached its running state, and then terminated successfully." + } + }, + "required": [], + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json new file mode 100755 index 00000000..7a22b221 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that contains configuration parameters that are specific to a particular deployment environment or system setup.", + + "properties": { + "ready_timeout": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the maximum time allowed for the component to reach its ready state. The timeout is measured from when the component process is created until the ready conditions specified in 'component_properties.ready_condition' are met." + }, + "shutdown_timeout": { + "type": "number", + "exclusiveMinimum": 0, + "description": "Specifies the maximum time allowed for the component to terminate after it receives a SIGTERM signal from the Launch Manager. The timeout is measured from when the Launch Manager sends the SIGTERM signal until the Operating System notifies the Launch Manager that the child process has terminated." + }, + "environmental_variables": { + "type": "object", + "description": "Defines the set of environment variables passed to the component at startup.", + "additionalProperties": { + "type": "string", + "description": "Specifies the environment variable's value as a string. An empty string is allowed and represents an intentionally empty environment variable." + } + }, + "bin_dir": { + "type": "string", + "description": "Specifies the absolute filesystem path to the directory where the component is installed." + }, + "working_dir": { + "type": "string", + "description": "Specifies the directory used as the working directory for the component during execution." + }, + "ready_recovery_action": { + "allOf": [ + { "$ref": "./recovery_action.schema.json" }, + { + "properties": { + "restart": true + }, + "required": ["restart"], + "not": { + "required": ["switch_run_target"] + } + } + ], + "description": "Specifies the recovery action to execute when the component fails to reach its ready state within the configured timeout." + }, + "recovery_action": { + "allOf": [ + { "$ref": "./recovery_action.schema.json" }, + { + "properties": { + "switch_run_target": true + }, + "required": ["switch_run_target"], + "not": { + "required": ["restart"] + } + } + ], + "description": "Specifies the recovery action to execute when the component malfunctions after reaching its ready state." + }, + "sandbox": { + "type": "object", + "description": "Defines the sandbox configuration parameters that isolate and constrain the component's runtime execution.", + "properties": { + "uid": { + "type": "integer", + "minimum": 0, + "description": "Specifies the POSIX user ID (UID) under which this component executes." + }, + "gid": { + "type": "integer", + "minimum": 0, + "description": "Specifies the primary POSIX group ID (GID) under which this component executes." + }, + "supplementary_group_ids": { + "type": "array", + "description": "Specifies the list of supplementary POSIX group IDs (GIDs) assigned to this component.", + "items": { + "type": "integer", + "minimum": 0, + "description": "Specifies a single supplementary POSIX group ID (GID)." + } + }, + "security_policy": { + "type": "string", + "description": "Specifies the security policy or confinement profile name (such as an SELinux or AppArmor profile) assigned to the component." + }, + "scheduling_policy": { + "type": "string", + "description": "Specifies the scheduling policy applied to the component's initial thread. Supported values correspond to OS-defined policies (e.g., FIFO, RR, OTHER).", + "anyOf": [ + { + "enum": [ + "SCHED_FIFO", + "SCHED_RR", + "SCHED_OTHER" + ] + }, + { + "type": "string" + } + ] + }, + "scheduling_priority": { + "type": "integer", + "description": "Specifies the scheduling priority applied to the component's initial thread." + }, + "max_memory_usage": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Specifies the maximum amount of memory, in bytes, that the component is permitted to use during runtime." + }, + "max_cpu_usage": { + "type": "integer", + "exclusiveMinimum": 0, + "description": "Specifies the maximum CPU usage limit for the component, expressed as a percentage of total CPU capacity." + } + }, + "additionalProperties": false + } + }, + "required": [], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json new file mode 100755 index 00000000..2f5dd119 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that specifies recovery actions to execute when an error or failure occurs.", + "properties": { + "restart": { + "type": "object", + "description": "Defines a recovery action that restarts the POSIX process associated with this component.", + "properties": { + "number_of_attempts": { + "type": "integer", + "minimum": 0, + "description": "Specifies the maximum number of restart attempts before the Launch Manager concludes that recovery cannot succeed." + }, + "delay_before_restart": { + "type": "number", + "minimum": 0, + "description": "Specifies the delay duration that the Launch Manager waits before initiating a restart attempt." + } + }, + "required": [], + "additionalProperties": false + }, + "switch_run_target": { + "type": "object", + "description": "Defines a recovery action that switches to a Run Target. This can be a different Run Target or the same one to retry activation of the current Run Target.", + "properties": { + "run_target": { + "type": "string", + "description": "Specifies the name of the Run Target that the Launch Manager should switch to." + } + }, + "required": [], + "additionalProperties": false + } + }, + + "oneOf": [ + { "required": ["restart"] }, + { "required": ["switch_run_target"] } + ], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json new file mode 100755 index 00000000..946748d9 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that specifies configuration parameters for a Run Target.", + "properties": { + "description": { + "type": "string", + "description": "Specifies a user-defined description of the Run Target." + }, + + "depends_on": { + "type": "array", + "description": "Specifies the names of components and Run Targets that must be activated when this Run Target is activated.", + "items": { + "type": "string", + "description": "Specifies the name of a component or Run Target that this Run Target depends on." + } + }, + + "transition_timeout": { + "type": "number", + "description": "Specifies the time limit for the Run Target transition. If this limit is exceeded, the transition is considered failed.", + "exclusiveMinimum": 0 + }, + + "recovery_action": { + "allOf": [ + { "$ref": "./recovery_action.schema.json" }, + { + "properties": { + "switch_run_target": true + }, + "required": ["switch_run_target"], + "not": { + "required": ["restart"] + } + } + ], + "description": "Specifies the recovery action to execute when a component assigned to this Run Target fails." + } + }, + + "required": ["recovery_action"], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json b/src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json new file mode 100755 index 00000000..a7716025 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "description": "Defines a reusable type that contains configuration parameters for the external watchdog.", + "properties": { + "device_file_path": { + "type": "string", + "description": "Specifies the path to the external watchdog device file (e.g., /dev/watchdog)." + }, + "max_timeout": { + "type": "number", + "minimum": 0, + "description": "Specifies the maximum timeout value that the Launch Manager configures on the external watchdog during startup. The external watchdog uses this timeout as the deadline for receiving periodic alive reports from the Launch Manager." + }, + "deactivate_on_shutdown": { + "type": "boolean", + "description": "Specifies whether the Launch Manager disables the external watchdog during shutdown. When set to true, the watchdog is deactivated; when false, it remains active." + }, + "require_magic_close": { + "type": "boolean", + "description": "Specifies whether the Launch Manager performs a defined shutdown sequence to inform the external watchdog that the shutdown is intentional and to prevent a watchdog-initiated reset. When true, the magic close sequence is performed; when false, it is not." + } + }, + + "required": [], + "additionalProperties": false +} diff --git a/src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py b/src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py new file mode 100755 index 00000000..20b6e452 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 + +""" +Bundle a multi-file JSON Schema into a single file using $defs. + +Usage: + python scripts/bundle_defs.py \ + --input config/schema/s-core-launch-manager.schema.json \ + --output config/schema/s-core-launch-manager.defs.bundle.json + + +Notes: +- Instead of fully inlining (flattening) each $ref, this script: + 1) Collects every local file $ref (e.g. "./types/*.schema.json"). + 2) Imports each referenced schema once into the top-level "$defs" (deduplicated). + 3) Rewrites $ref values to point to "#/$defs/" (JSON Pointer fragments like "#/foo/bar" are preserved by converting) + * For example: "file.json#/foo/bar" -> "#/$defs//foo/bar". +- If the imported schema itself has local file refs, those are also pulled into $defs recursively with the same logic. +- Name derivation for $defs keys: file basename without ".schema.json" (e.g., "watchdog"). Collisions are resolved by appending a numeric suffix (e.g., watchdog_2). +- To avoid base-URI confusion inside bundled defs, $id and nested $schema are stripped from the imported defs. +""" + +from __future__ import annotations +import argparse +import json +from pathlib import Path +from typing import Any, Dict, Tuple + +Json = Any + +class DefsBundler: + def __init__(self, input_file: Path) -> None: + self.input_file = input_file.resolve() + self.defs: Dict[str, Json] = {} + self.file_to_defname: Dict[Path, str] = {} + + @staticmethod + def _deepcopy(obj: Json) -> Json: + return json.loads(json.dumps(obj)) + + @staticmethod + def _is_local_file_ref(ref: str) -> bool: + if not isinstance(ref, str): + return False + if ref.startswith('#'): + return False + if '://' in ref: + return False + return True + + @staticmethod + def _split_ref(ref: str) -> Tuple[str, str]: + # Returns (file_part, fragment_pointer) where fragment_pointer is like "/a/b" (without leading '#') or '' + if '#' in ref: + file_part, frag = ref.split('#', 1) + if frag.startswith('/'): + return file_part, frag # JSON Pointer already + if frag.startswith('#/'): + return file_part, frag[1:] + # treat unknown as JSON Pointer missing leading '/' + return file_part, '/' + frag if frag else '' + return ref, '' + + @staticmethod + def _derive_name_from_file(path: Path) -> str: + name = path.stem # e.g., "watchdog.schema" + if name.endswith('.schema'): + name = name[:-7] # remove trailing ".schema" + return name + + def _unique_def_name(self, base: str) -> str: + if base not in self.defs: + return base + i = 2 + while True: + cand = f"{base}_{i}" + if cand not in self.defs: + return cand + i += 1 + + def _strip_ids(self, schema: Json) -> Json: + # Remove $id and nested $schema fields from imported defs to avoid base-URI conflicts + if isinstance(schema, dict): + schema = {k: self._strip_ids(v) for k, v in schema.items() if k not in ('$id', '$schema')} + elif isinstance(schema, list): + schema = [self._strip_ids(v) for v in schema] + return schema + + def _register_def_from_file(self, current_file: Path, ref_path: str) -> str: + target = (current_file.parent / ref_path).resolve() + if target in self.file_to_defname: + return self.file_to_defname[target] + with open(target, 'r', encoding='utf-8') as f: + schema = json.load(f) + name_base = self._derive_name_from_file(target) + name = self._unique_def_name(name_base) + cleaned = self._strip_ids(schema) + # Before storing, rewrite refs inside this imported schema + rewritten = self._rewrite_refs(cleaned, target) + self.defs[name] = rewritten + self.file_to_defname[target] = name + return name + + def _rewrite_refs(self, node: Json, current_file: Path) -> Json: + # Traverse node; for any local file $ref, add that file into $defs and rewrite the $ref to #/$defs//fragment + if isinstance(node, dict): + if '$ref' in node and isinstance(node['$ref'], str): + ref_str = node['$ref'] + if self._is_local_file_ref(ref_str): + file_part, frag = self._split_ref(ref_str) + defname = self._register_def_from_file(current_file, file_part) if file_part else None + if defname: + # Compose new JSON Pointer: #/$defs/ + pointer = f"#/$defs/{defname}{frag}" + return {**{k: v for k, v in node.items() if k != '$ref'}, '$ref': pointer} + # Otherwise, descend + return {k: self._rewrite_refs(v, current_file) for k, v in node.items()} + elif isinstance(node, list): + return [self._rewrite_refs(v, current_file) for v in node] + else: + return node + + def bundle(self, out_path: Path) -> None: + with open(self.input_file, 'r', encoding='utf-8') as f: + root = json.load(f) + + bundled_root = self._deepcopy(root) + + # Rewrite refs in the root + bundled_root = self._rewrite_refs(bundled_root, self.input_file) + + # Merge with existing $defs if any + existing_defs = bundled_root.get('$defs', {}) if isinstance(bundled_root, dict) else {} + if not isinstance(existing_defs, dict): + existing_defs = {} + + merged_defs: Dict[str, Json] = {} + + # Copy existing defs + for k, v in existing_defs.items(): + merged_defs[k] = v + + # Add generated defs (dedupe) + for k, v in self.defs.items(): + kk = k + ctr = 2 + while kk in merged_defs: + kk = f"{k}_{ctr}" + ctr += 1 + merged_defs[kk] = v + + # ---------- force a specific order in generated schema ---------- + if isinstance(bundled_root, dict): + new_root = {} + + # The "$schema", "type", "$id", "title", and "description" elements should be at the top of the file + for key in ["$schema", "type", "$id", "title", "description"]: + if key in bundled_root: + new_root[key] = bundled_root[key] + + # Then insert "$defs" (before "properties") + new_root["$defs"] = merged_defs + + # Insert "properties" immediately after "$defs" + if "properties" in bundled_root: + new_root["properties"] = bundled_root["properties"] + + # Insert remaining keys (required, additionalProperties, etc.) + for key, value in bundled_root.items(): + if key not in new_root: + new_root[key] = value + + bundled_root = new_root + # ---------------------------------------------------------------- + + out_path.parent.mkdir(parents=True, exist_ok=True) + with open(out_path, 'w', encoding='utf-8') as f: + json.dump(bundled_root, f, indent=2, ensure_ascii=False) + + +def main() -> None: + ap = argparse.ArgumentParser(description='Bundle multi-file JSON Schema into one file using $defs (deduplicated).') + ap.add_argument('--input', required=True, help='Path to the top-level schema JSON') + ap.add_argument('--output', required=True, help='Path to write the bundled schema JSON') + args = ap.parse_args() + + bundler = DefsBundler(Path(args.input)) + bundler.bundle(Path(args.output)) + print(f"Bundled schema written to: {args.output}") + +if __name__ == '__main__': + main() + diff --git a/src/launch_manager_daemon/config/new_config_schema/scripts/validate.py b/src/launch_manager_daemon/config/new_config_schema/scripts/validate.py new file mode 100755 index 00000000..4f9cd520 --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/scripts/validate.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 + +""" +Validate JSON instance(s) against a multi-file JSON Schema with relative $ref paths. + +Usage examples: + Validate a single file: + python validate_config.py --schema ./schema/s-core_launch_manager.schema.json --instance ./examples/config.json + + Validate all JSON files in a directory (recursively): + python validate_config.py --schema ./schema/s-core_launch_manager.schema.json --instances-dir ./examples + +Exit codes: + 0 -> all instances are valid + 1 -> at least one instance failed validation or there was an error +""" + +import argparse +import json +import sys +from pathlib import Path + +try: + from jsonschema import validators, RefResolver, FormatChecker + from jsonschema.exceptions import RefResolutionError, SchemaError, ValidationError +except ImportError: + print("This script requires the 'jsonschema' package. Install with:\n pip install jsonschema", file=sys.stderr) + sys.exit(1) + + +def load_json(path: Path): + try: + with path.open("r", encoding="utf-8") as f: + return json.load(f) + except json.JSONDecodeError as e: + raise ValueError(f"Failed to parse JSON file '{path}': {e}") from e + except OSError as e: + raise ValueError(f"Failed to read file '{path}': {e}") from e + + +def json_pointer_path(parts): + """ + Convert an error path into a friendly string like: + $.topLevel.items[2].name + """ + if not parts: + return "$" + s = "$" + for p in parts: + if isinstance(p, int): + s += f"[{p}]" + else: + s += f".{p}" + return s + + +def build_validator(schema_path: Path): + schema = load_json(schema_path) + + # Choose validator based on $schema automatically (Draft-07 / 2019-09 / 2020-12, etc.) + ValidatorClass = validators.validator_for(schema) + # Validate the schema itself (optional but helpful) + try: + ValidatorClass.check_schema(schema) + except SchemaError as e: + raise SchemaError(f"Your schema appears invalid: {e.message}\nAt: {'/'.join(map(str, e.path))}") from e + + # Base URI for resolving relative $refs like "./types/*.schema.json" + base_uri = schema_path.resolve().parent.as_uri() + "/" + + # RefResolver is deprecated upstream but still widely supported and reliable for local file resolution. + resolver = RefResolver(base_uri=base_uri, referrer=schema) + + # Enable common format checks (e.g., "uri", "email", "date-time") + format_checker = FormatChecker() + + return ValidatorClass(schema, resolver=resolver, format_checker=format_checker) + + +def validate_instance(validator, instance_path: Path): + instance = load_json(instance_path) + errors = sorted(validator.iter_errors(instance), key=lambda e: list(e.path)) + return errors + + +def find_json_files(root: Path): + # Recurse and pick *.json files only + return [p for p in root.rglob("*.json") if p.is_file()] + + +def main(): + parser = argparse.ArgumentParser(description="Validate JSON instance(s) against a multi-file JSON Schema.") + parser.add_argument("--schema", required=True, type=Path, help="Path to the top-level schema (e.g., ./schema/s-core_launch_manager.schema.json)") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--instance", type=Path, help="Path to a single JSON instance to validate") + group.add_argument("--instances-dir", type=Path, help="Path to a directory containing JSON instances (recursively)") + parser.add_argument("--stop-on-first", action="store_true", help="Stop after the first instance with errors") + args = parser.parse_args() + + try: + validator = build_validator(args.schema) + except (ValueError, SchemaError) as e: + print(f"[Schema Error] {e}", file=sys.stderr) + sys.exit(1) + + instance_paths = [] + if args.instance: + instance_paths = [args.instance] + else: + if not args.instances_dir.exists(): + print(f"[Error] Instances directory not found: {args.instances_dir}", file=sys.stderr) + sys.exit(1) + instance_paths = find_json_files(args.instances_dir) + if not instance_paths: + print(f"[Info] No JSON files found under: {args.instances_dir}") + sys.exit(0) + + any_failed = False + for path in instance_paths: + try: + errors = validate_instance(validator, path) + except ValueError as e: + print(f"Error --> {path}: {e}", file=sys.stderr) + any_failed = True + if args.stop_on_first: + break + continue + except RefResolutionError as e: + print(f"Error --> {path}: Failed to resolve a $ref - {e}", file=sys.stderr) + print(" Tips:") + print(" * Ensure $ref paths like './types/...' are correct relative to the top-level schema file.") + print(" * Make sure referenced files exist and are valid JSON schemas.") + any_failed = True + if args.stop_on_first: + break + continue + + if not errors: + print(f"Success --> {path}: valid") + else: + any_failed = True + print(f"Error --> {path}: {len(errors)} error(s)") + for i, err in enumerate(errors, 1): + instance_loc = json_pointer_path(err.path) + schema_loc = "/".join(map(str, err.schema_path)) if err.schema_path else "(root)" + print(f" [{i}] at {instance_loc}") + print(f" --> {err.message}") + print(f" schema path: {schema_loc}") + if args.stop_on_first: + break + + sys.exit(1 if any_failed else 0) + + +if __name__ == "__main__": + main() + From 415be1f6a5f65e71da77c7bc3d22b255c1aaa0f7 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Thu, 19 Feb 2026 15:43:20 +0000 Subject: [PATCH 2/9] Adding first version of documentation for json schema --- .../config/new_config_schema/README.rst | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 src/launch_manager_daemon/config/new_config_schema/README.rst diff --git a/src/launch_manager_daemon/config/new_config_schema/README.rst b/src/launch_manager_daemon/config/new_config_schema/README.rst new file mode 100755 index 00000000..e46184cf --- /dev/null +++ b/src/launch_manager_daemon/config/new_config_schema/README.rst @@ -0,0 +1,53 @@ +################################### +Launch Manager Configuration Schema +################################### + +This folder contains the development environment for the Launch Manager configuration JSON Schema. The schema is developed across multiple files to facilitate maintenance and modifications, then compiled into a single file for publication. This approach simplifies the development process while maintaining convenience for end users. + + +******** +Examples +******** + +Configuration examples are provided in the ``examples`` folder, each accompanied by a brief description. + + +****** +Schema +****** + +The ``schema`` folder contains the primary development work. The setup uses a multi-file structure where reusable ``types`` are stored in the types folder, and the top-level schema resides in the ``s-core_launch_manager.schema.json`` file. The key feature of this setup is the use of relative ``$ref`` paths throughout the folder structure. Note that all references are constructed relative to the current file's location. + +For instance, to reference a file from a subfolder use: + +.. code-block:: json + + "$ref": "./subfolder_name/file_name" + +And to reference a file from the same folder use: + +.. code-block:: json + + "$ref": "./file_name" + + +**************** +Published Schema +**************** + +The official, end-user consumable schema is placed in the ``published_schema`` folder. Upon completion of development, the multi-file schema from the ``schema`` folder is merged into a single file and published here. + +******* +Scripts +******* + +Utility scripts for schema development are located in ``scripts`` folder: + + * bundle.py + + * Merges the multi-file schema into a single file for end-user distribution. + + * validate.py + + * Validates Launch Manager configuration instances against the schema. This script supports both single-file and multi-file schema formats. + From 1989e86246fbbdd13e3c72925d253dd5b7845438 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:24:13 +0000 Subject: [PATCH 3/9] This schema is not yet in use, new folder name reflects this better --- .../config/{new_config_schema => future_config_schema}/README.rst | 0 .../examples/example_conf.json | 0 .../published_schema/s-core_launch_manager.schema.json | 0 .../schema/s-core_launch_manager.schema.json | 0 .../schema/types/alive_supervision.schema.json | 0 .../schema/types/component_properties.schema.json | 0 .../schema/types/deployment_config.schema.json | 0 .../schema/types/recovery_action.schema.json | 0 .../schema/types/run_target.schema.json | 0 .../schema/types/watchdog.schema.json | 0 .../{new_config_schema => future_config_schema}/scripts/bundle.py | 0 .../scripts/validate.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/README.rst (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/examples/example_conf.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/published_schema/s-core_launch_manager.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/s-core_launch_manager.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/alive_supervision.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/component_properties.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/deployment_config.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/recovery_action.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/run_target.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/schema/types/watchdog.schema.json (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/scripts/bundle.py (100%) rename src/launch_manager_daemon/config/{new_config_schema => future_config_schema}/scripts/validate.py (100%) diff --git a/src/launch_manager_daemon/config/new_config_schema/README.rst b/src/launch_manager_daemon/config/future_config_schema/README.rst similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/README.rst rename to src/launch_manager_daemon/config/future_config_schema/README.rst diff --git a/src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json b/src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/examples/example_conf.json rename to src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json diff --git a/src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/published_schema/s-core_launch_manager.schema.json rename to src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/s-core_launch_manager.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/s-core_launch_manager.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/s-core_launch_manager.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/alive_supervision.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/alive_supervision.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/alive_supervision.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/component_properties.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/component_properties.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/component_properties.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/deployment_config.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/deployment_config.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/deployment_config.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/recovery_action.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/recovery_action.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/recovery_action.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/run_target.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/run_target.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/run_target.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json b/src/launch_manager_daemon/config/future_config_schema/schema/types/watchdog.schema.json similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/schema/types/watchdog.schema.json rename to src/launch_manager_daemon/config/future_config_schema/schema/types/watchdog.schema.json diff --git a/src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py b/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/scripts/bundle.py rename to src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py diff --git a/src/launch_manager_daemon/config/new_config_schema/scripts/validate.py b/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py similarity index 100% rename from src/launch_manager_daemon/config/new_config_schema/scripts/validate.py rename to src/launch_manager_daemon/config/future_config_schema/scripts/validate.py From 63e45bda507400d217bfdf0722d7953c0ba89905 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 09:38:55 +0000 Subject: [PATCH 4/9] New name should better reflect what is inside this folder --- .../{schema => draft_schema}/s-core_launch_manager.schema.json | 0 .../{schema => draft_schema}/types/alive_supervision.schema.json | 0 .../types/component_properties.schema.json | 0 .../{schema => draft_schema}/types/deployment_config.schema.json | 0 .../{schema => draft_schema}/types/recovery_action.schema.json | 0 .../{schema => draft_schema}/types/run_target.schema.json | 0 .../{schema => draft_schema}/types/watchdog.schema.json | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/s-core_launch_manager.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/alive_supervision.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/component_properties.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/deployment_config.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/recovery_action.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/run_target.schema.json (100%) rename src/launch_manager_daemon/config/future_config_schema/{schema => draft_schema}/types/watchdog.schema.json (100%) diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/s-core_launch_manager.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/alive_supervision.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/alive_supervision.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/alive_supervision.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/alive_supervision.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/component_properties.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/component_properties.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/component_properties.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/component_properties.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/deployment_config.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/deployment_config.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/deployment_config.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/deployment_config.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/recovery_action.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/recovery_action.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/recovery_action.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/recovery_action.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/run_target.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/run_target.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json diff --git a/src/launch_manager_daemon/config/future_config_schema/schema/types/watchdog.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/watchdog.schema.json similarity index 100% rename from src/launch_manager_daemon/config/future_config_schema/schema/types/watchdog.schema.json rename to src/launch_manager_daemon/config/future_config_schema/draft_schema/types/watchdog.schema.json From 596a7327cafe4f4563ae9249a37e876aaf3862e2 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 10:40:52 +0000 Subject: [PATCH 5/9] Improve development setup documentation --- .../config/future_config_schema/README.rst | 164 +++++++++++++++--- 1 file changed, 142 insertions(+), 22 deletions(-) diff --git a/src/launch_manager_daemon/config/future_config_schema/README.rst b/src/launch_manager_daemon/config/future_config_schema/README.rst index e46184cf..528c4f88 100755 --- a/src/launch_manager_daemon/config/future_config_schema/README.rst +++ b/src/launch_manager_daemon/config/future_config_schema/README.rst @@ -1,53 +1,173 @@ -################################### +.. + # ******************************************************************************* + # Copyright (c) 2025 Contributors to the Eclipse Foundation + # + # See the NOTICE file(s) distributed with this work for additional + # information regarding copyright ownership. + # + # This program and the accompanying materials are made available under the + # terms of the Apache License Version 2.0 which is available at + # https://www.apache.org/licenses/LICENSE-2.0 + # + # SPDX-License-Identifier: Apache-2.0 + # ******************************************************************************* + + Launch Manager Configuration Schema ################################### -This folder contains the development environment for the Launch Manager configuration JSON Schema. The schema is developed across multiple files to facilitate maintenance and modifications, then compiled into a single file for publication. This approach simplifies the development process while maintaining convenience for end users. - +This folder contains the development environment for the Launch Manager configuration JSON Schema. The schema defines and validates the structure of Launch Manager configuration files. +Overview ******** + +This project uses a **two-folder approach** for schema management: + +- ``draft_schema/`` - Multi-file schema structure for active development +- ``published_schema/`` - Single-file schema for end-user consumption + +The multi-file structure in ``draft_schema/`` makes it easier to maintain and modify the schema by organizing reusable components into separate files. When development is complete, these files are compiled into a single file in ``published_schema/`` for convenience of end users. + +**Project Structure:** + +:: + + +-- draft_schema/ # Multi-file schema under development + +-- published_schema/ # Single-file schema ready for use + +-- examples/ # Sample configuration files + +-- scripts/ # Tools for bundling and validation + +Quick Start +*********** + +For End Users +============= + +If you just want to validate your Launch Manager configuration: + +1. Use the schema in ``published_schema/s-core_launch_manager.schema.json`` +2. Check the ``examples/`` folder for sample configurations +3. Validate your config: + + .. code-block:: bash + + validate.py --schema published_schema/s-core_launch_manager.schema.json --instance your_config.json + +For Schema Developers +====================== + +If you're modifying or extending the schema: + +1. Edit files in ``draft_schema/`` +2. Bundle your changes: + + .. code-block:: bash + + bundle.py --input draft_schema/s-core_launch_manager.schema.json --output published_schema/s-core_launch_manager.schema.json + +3. Test against examples to ensure nothing broke + + Examples ******** -Configuration examples are provided in the ``examples`` folder, each accompanied by a brief description. +Configuration examples are provided in the ``examples`` folder, each accompanied by a brief description. **Start here** if you're new to Launch Manager configurations - these show real-world usage patterns. + + +Schema Development (draft_schema) +********************************** +The ``draft_schema`` folder contains the primary development work. The setup uses a multi-file structure where: -****** -Schema -****** +- **Reusable types** are stored in the ``types/`` subfolder +- **Top-level schema** resides in ``s-core_launch_manager.schema.json`` file -The ``schema`` folder contains the primary development work. The setup uses a multi-file structure where reusable ``types`` are stored in the types folder, and the top-level schema resides in the ``s-core_launch_manager.schema.json`` file. The key feature of this setup is the use of relative ``$ref`` paths throughout the folder structure. Note that all references are constructed relative to the current file's location. +Working with $ref Paths +======================== -For instance, to reference a file from a subfolder use: +The multi-file schema uses JSON Schema's ``$ref`` keyword to reference definitions across files. Understanding how these references work is crucial when modifying the schema. + +**Key principle:** All ``$ref`` paths are relative to the location of the file containing the reference, not to any root folder. + +Reference Examples +------------------ + +**To reference a file in a subfolder** (e.g., from ``s-core_launch_manager.schema.json`` to ``types/deployment_config.schema.json``): .. code-block:: json - "$ref": "./subfolder_name/file_name" + "$ref": "./types/deployment_config.schema.json" -And to reference a file from the same folder use: +**To reference a file in the same folder:** (e.g., from ``types/deployment_config.schema.json`` to ``types/recovery_action.schema.json``): .. code-block:: json - "$ref": "./file_name" + "$ref": "./recovery_action.schema.json" +Common Pitfalls +--------------- -**************** -Published Schema -**************** +- **Always use relative paths** starting with ``./`` or ``../`` +- **Don't use absolute paths** or paths from the project root +- **Remember the current file's location** when constructing paths +- When moving files, **update all references** to and from that file + +The bundling script resolves all these relative references into a single file, so the published schema doesn't need external file references. + + +Published Schema (published_schema) +************************************ + +The official, end-user consumable schema is placed in the ``published_schema`` folder. Upon completion of development, the multi-file schema from the ``draft_schema`` folder is merged into a single file and published here. + +**This is the version end users should reference** in their validation tools and IDE configurations. -The official, end-user consumable schema is placed in the ``published_schema`` folder. Upon completion of development, the multi-file schema from the ``schema`` folder is merged into a single file and published here. -******* Scripts ******* -Utility scripts for schema development are located in ``scripts`` folder: +Utility scripts for schema development are located in the ``scripts`` folder: + +bundle.py +========= + +Merges the multi-file schema into a single file for end-user distribution. - * bundle.py +**Usage:** - * Merges the multi-file schema into a single file for end-user distribution. +.. code-block:: bash - * validate.py + bundle.py --input ../draft_schema/s-core_launch_manager.schema.json --output ../published_schema/s-core_launch_manager.schema.json + Bundled schema written to: ../published_schema/s-core_launch_manager.schema.json - * Validates Launch Manager configuration instances against the schema. This script supports both single-file and multi-file schema formats. +**When to use:** After making changes in ``draft_schema/``, run this to create the publishable version. + +validate.py +=========== + +Validates Launch Manager configuration instances against the schema. This script supports both single-file and multi-file schema formats. + +**Validate against published schema:** + +.. code-block:: bash + + validate.py --schema ../published_schema/s-core_launch_manager.schema.json --instance ../examples/example_conf.json + Success --> ../examples/example_conf.json: valid + +**Validate against draft schema (during development):** + +.. code-block:: bash + + validate.py --schema ../draft_schema/s-core_launch_manager.schema.json --instance ../examples/example_conf.json + Success --> ../examples/example_conf.json: valid + +**When to use:** Run this frequently during development to catch errors early. Always validate examples before publishing. + + +Typical Workflow +**************** +1. **Modify** schema files in ``draft_schema/`` +2. **Validate** your changes against examples using the draft schema +3. **Bundle** the multi-file schema into a single file +4. **Validate** examples again against the published schema From 7ecce4332c052b0a9e2b5605956c97bc2adc4060 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:10:33 +0000 Subject: [PATCH 6/9] Changing mandatory configuration requirements Make initial_run_target mandatory, to force users to explicitly specify system startup behavior. Remove recovery_action requirement from run_target type, as meaningful defaults are difficult to define and enforcement should occur outside the schema. --- .../draft_schema/s-core_launch_manager.schema.json | 3 ++- .../draft_schema/types/run_target.schema.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json index d7344161..f76e3591 100755 --- a/src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json +++ b/src/launch_manager_daemon/config/future_config_schema/draft_schema/s-core_launch_manager.schema.json @@ -117,7 +117,8 @@ } }, "required": [ - "schema_version" + "schema_version", + "initial_run_target" ], "additionalProperties": false } diff --git a/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json index 946748d9..72f72a0a 100755 --- a/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json +++ b/src/launch_manager_daemon/config/future_config_schema/draft_schema/types/run_target.schema.json @@ -40,6 +40,6 @@ } }, - "required": ["recovery_action"], + "required": [], "additionalProperties": false } From 346b7b5a86f97f75cf5da856332ec4ddd1c678b4 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:30:49 +0000 Subject: [PATCH 7/9] Adaptations to reflect changes in previous commit --- .../config/future_config_schema/examples/example_conf.json | 7 +------ .../published_schema/s-core_launch_manager.schema.json | 7 +++---- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json b/src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json index fc69c5c3..ddb3bc7a 100755 --- a/src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json +++ b/src/launch_manager_daemon/config/future_config_schema/examples/example_conf.json @@ -47,12 +47,7 @@ }, "run_target": { "depends_on": [], - "transition_timeout": 5, - "recovery_action": { - "switch_run_target": { - "run_target": "Off" - } - } + "transition_timeout": 5 }, "alive_supervision" : { "evaluation_cycle": 0.5 diff --git a/src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json b/src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json index 1cf10a09..7a2c547d 100644 --- a/src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json +++ b/src/launch_manager_daemon/config/future_config_schema/published_schema/s-core_launch_manager.schema.json @@ -326,9 +326,7 @@ "description": "Specifies the recovery action to execute when a component assigned to this Run Target fails." } }, - "required": [ - "recovery_action" - ], + "required": [], "additionalProperties": false }, "alive_supervision": { @@ -486,7 +484,8 @@ } }, "required": [ - "schema_version" + "schema_version", + "initial_run_target" ], "additionalProperties": false } \ No newline at end of file From 75d558c76676466aa526670f9387befcda38ff05 Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:48:31 +0000 Subject: [PATCH 8/9] Fixing copyright headers --- .../config/future_config_schema/README.rst | 2 +- .../config/future_config_schema/scripts/bundle.py | 12 ++++++++++++ .../config/future_config_schema/scripts/validate.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/launch_manager_daemon/config/future_config_schema/README.rst b/src/launch_manager_daemon/config/future_config_schema/README.rst index 528c4f88..281467ca 100755 --- a/src/launch_manager_daemon/config/future_config_schema/README.rst +++ b/src/launch_manager_daemon/config/future_config_schema/README.rst @@ -1,6 +1,6 @@ .. # ******************************************************************************* - # Copyright (c) 2025 Contributors to the Eclipse Foundation + # Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. diff --git a/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py b/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py index 20b6e452..b54ae847 100755 --- a/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py +++ b/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py @@ -1,4 +1,16 @@ #!/usr/bin/env python3 +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """ Bundle a multi-file JSON Schema into a single file using $defs. diff --git a/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py b/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py index 4f9cd520..5521528a 100755 --- a/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py +++ b/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py @@ -1,4 +1,16 @@ #!/usr/bin/env python3 +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* """ Validate JSON instance(s) against a multi-file JSON Schema with relative $ref paths. From 31bb735f31d951d009abe7147cc7e9d698eab4dc Mon Sep 17 00:00:00 2001 From: SimonKozik <244535158+SimonKozik@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:05:32 +0000 Subject: [PATCH 9/9] Changes introduced by 'bazel run //:format.fix' --- .../future_config_schema/scripts/bundle.py | 64 ++++++++++++------- .../future_config_schema/scripts/validate.py | 50 +++++++++++---- 2 files changed, 80 insertions(+), 34 deletions(-) diff --git a/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py b/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py index b54ae847..d5dc26f3 100755 --- a/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py +++ b/src/launch_manager_daemon/config/future_config_schema/scripts/bundle.py @@ -40,6 +40,7 @@ Json = Any + class DefsBundler: def __init__(self, input_file: Path) -> None: self.input_file = input_file.resolve() @@ -54,29 +55,29 @@ def _deepcopy(obj: Json) -> Json: def _is_local_file_ref(ref: str) -> bool: if not isinstance(ref, str): return False - if ref.startswith('#'): + if ref.startswith("#"): return False - if '://' in ref: + if "://" in ref: return False return True @staticmethod def _split_ref(ref: str) -> Tuple[str, str]: # Returns (file_part, fragment_pointer) where fragment_pointer is like "/a/b" (without leading '#') or '' - if '#' in ref: - file_part, frag = ref.split('#', 1) - if frag.startswith('/'): + if "#" in ref: + file_part, frag = ref.split("#", 1) + if frag.startswith("/"): return file_part, frag # JSON Pointer already - if frag.startswith('#/'): + if frag.startswith("#/"): return file_part, frag[1:] # treat unknown as JSON Pointer missing leading '/' - return file_part, '/' + frag if frag else '' - return ref, '' + return file_part, "/" + frag if frag else "" + return ref, "" @staticmethod def _derive_name_from_file(path: Path) -> str: name = path.stem # e.g., "watchdog.schema" - if name.endswith('.schema'): + if name.endswith(".schema"): name = name[:-7] # remove trailing ".schema" return name @@ -93,7 +94,11 @@ def _unique_def_name(self, base: str) -> str: def _strip_ids(self, schema: Json) -> Json: # Remove $id and nested $schema fields from imported defs to avoid base-URI conflicts if isinstance(schema, dict): - schema = {k: self._strip_ids(v) for k, v in schema.items() if k not in ('$id', '$schema')} + schema = { + k: self._strip_ids(v) + for k, v in schema.items() + if k not in ("$id", "$schema") + } elif isinstance(schema, list): schema = [self._strip_ids(v) for v in schema] return schema @@ -102,7 +107,7 @@ def _register_def_from_file(self, current_file: Path, ref_path: str) -> str: target = (current_file.parent / ref_path).resolve() if target in self.file_to_defname: return self.file_to_defname[target] - with open(target, 'r', encoding='utf-8') as f: + with open(target, "r", encoding="utf-8") as f: schema = json.load(f) name_base = self._derive_name_from_file(target) name = self._unique_def_name(name_base) @@ -116,15 +121,22 @@ def _register_def_from_file(self, current_file: Path, ref_path: str) -> str: def _rewrite_refs(self, node: Json, current_file: Path) -> Json: # Traverse node; for any local file $ref, add that file into $defs and rewrite the $ref to #/$defs//fragment if isinstance(node, dict): - if '$ref' in node and isinstance(node['$ref'], str): - ref_str = node['$ref'] + if "$ref" in node and isinstance(node["$ref"], str): + ref_str = node["$ref"] if self._is_local_file_ref(ref_str): file_part, frag = self._split_ref(ref_str) - defname = self._register_def_from_file(current_file, file_part) if file_part else None + defname = ( + self._register_def_from_file(current_file, file_part) + if file_part + else None + ) if defname: # Compose new JSON Pointer: #/$defs/ pointer = f"#/$defs/{defname}{frag}" - return {**{k: v for k, v in node.items() if k != '$ref'}, '$ref': pointer} + return { + **{k: v for k, v in node.items() if k != "$ref"}, + "$ref": pointer, + } # Otherwise, descend return {k: self._rewrite_refs(v, current_file) for k, v in node.items()} elif isinstance(node, list): @@ -133,7 +145,7 @@ def _rewrite_refs(self, node: Json, current_file: Path) -> Json: return node def bundle(self, out_path: Path) -> None: - with open(self.input_file, 'r', encoding='utf-8') as f: + with open(self.input_file, "r", encoding="utf-8") as f: root = json.load(f) bundled_root = self._deepcopy(root) @@ -142,7 +154,9 @@ def bundle(self, out_path: Path) -> None: bundled_root = self._rewrite_refs(bundled_root, self.input_file) # Merge with existing $defs if any - existing_defs = bundled_root.get('$defs', {}) if isinstance(bundled_root, dict) else {} + existing_defs = ( + bundled_root.get("$defs", {}) if isinstance(bundled_root, dict) else {} + ) if not isinstance(existing_defs, dict): existing_defs = {} @@ -186,20 +200,24 @@ def bundle(self, out_path: Path) -> None: # ---------------------------------------------------------------- out_path.parent.mkdir(parents=True, exist_ok=True) - with open(out_path, 'w', encoding='utf-8') as f: + with open(out_path, "w", encoding="utf-8") as f: json.dump(bundled_root, f, indent=2, ensure_ascii=False) def main() -> None: - ap = argparse.ArgumentParser(description='Bundle multi-file JSON Schema into one file using $defs (deduplicated).') - ap.add_argument('--input', required=True, help='Path to the top-level schema JSON') - ap.add_argument('--output', required=True, help='Path to write the bundled schema JSON') + ap = argparse.ArgumentParser( + description="Bundle multi-file JSON Schema into one file using $defs (deduplicated)." + ) + ap.add_argument("--input", required=True, help="Path to the top-level schema JSON") + ap.add_argument( + "--output", required=True, help="Path to write the bundled schema JSON" + ) args = ap.parse_args() bundler = DefsBundler(Path(args.input)) bundler.bundle(Path(args.output)) print(f"Bundled schema written to: {args.output}") -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() diff --git a/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py b/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py index 5521528a..f34418be 100755 --- a/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py +++ b/src/launch_manager_daemon/config/future_config_schema/scripts/validate.py @@ -36,7 +36,10 @@ from jsonschema import validators, RefResolver, FormatChecker from jsonschema.exceptions import RefResolutionError, SchemaError, ValidationError except ImportError: - print("This script requires the 'jsonschema' package. Install with:\n pip install jsonschema", file=sys.stderr) + print( + "This script requires the 'jsonschema' package. Install with:\n pip install jsonschema", + file=sys.stderr, + ) sys.exit(1) @@ -75,7 +78,9 @@ def build_validator(schema_path: Path): try: ValidatorClass.check_schema(schema) except SchemaError as e: - raise SchemaError(f"Your schema appears invalid: {e.message}\nAt: {'/'.join(map(str, e.path))}") from e + raise SchemaError( + f"Your schema appears invalid: {e.message}\nAt: {'/'.join(map(str, e.path))}" + ) from e # Base URI for resolving relative $refs like "./types/*.schema.json" base_uri = schema_path.resolve().parent.as_uri() + "/" @@ -101,12 +106,29 @@ def find_json_files(root: Path): def main(): - parser = argparse.ArgumentParser(description="Validate JSON instance(s) against a multi-file JSON Schema.") - parser.add_argument("--schema", required=True, type=Path, help="Path to the top-level schema (e.g., ./schema/s-core_launch_manager.schema.json)") + parser = argparse.ArgumentParser( + description="Validate JSON instance(s) against a multi-file JSON Schema." + ) + parser.add_argument( + "--schema", + required=True, + type=Path, + help="Path to the top-level schema (e.g., ./schema/s-core_launch_manager.schema.json)", + ) group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--instance", type=Path, help="Path to a single JSON instance to validate") - group.add_argument("--instances-dir", type=Path, help="Path to a directory containing JSON instances (recursively)") - parser.add_argument("--stop-on-first", action="store_true", help="Stop after the first instance with errors") + group.add_argument( + "--instance", type=Path, help="Path to a single JSON instance to validate" + ) + group.add_argument( + "--instances-dir", + type=Path, + help="Path to a directory containing JSON instances (recursively)", + ) + parser.add_argument( + "--stop-on-first", + action="store_true", + help="Stop after the first instance with errors", + ) args = parser.parse_args() try: @@ -120,7 +142,10 @@ def main(): instance_paths = [args.instance] else: if not args.instances_dir.exists(): - print(f"[Error] Instances directory not found: {args.instances_dir}", file=sys.stderr) + print( + f"[Error] Instances directory not found: {args.instances_dir}", + file=sys.stderr, + ) sys.exit(1) instance_paths = find_json_files(args.instances_dir) if not instance_paths: @@ -140,7 +165,9 @@ def main(): except RefResolutionError as e: print(f"Error --> {path}: Failed to resolve a $ref - {e}", file=sys.stderr) print(" Tips:") - print(" * Ensure $ref paths like './types/...' are correct relative to the top-level schema file.") + print( + " * Ensure $ref paths like './types/...' are correct relative to the top-level schema file." + ) print(" * Make sure referenced files exist and are valid JSON schemas.") any_failed = True if args.stop_on_first: @@ -154,7 +181,9 @@ def main(): print(f"Error --> {path}: {len(errors)} error(s)") for i, err in enumerate(errors, 1): instance_loc = json_pointer_path(err.path) - schema_loc = "/".join(map(str, err.schema_path)) if err.schema_path else "(root)" + schema_loc = ( + "/".join(map(str, err.schema_path)) if err.schema_path else "(root)" + ) print(f" [{i}] at {instance_loc}") print(f" --> {err.message}") print(f" schema path: {schema_loc}") @@ -166,4 +195,3 @@ def main(): if __name__ == "__main__": main() -