diff --git a/data-machine-code.php b/data-machine-code.php
index 406200c..51c094d 100644
--- a/data-machine-code.php
+++ b/data-machine-code.php
@@ -6,7 +6,6 @@
* Version: 0.47.56
* Requires at least: 6.9
* Requires PHP: 8.2
- * Requires Plugins: data-machine
* Author: Chris Huber, extrachill
* Author URI: https://chubes.net
* License: GPL v2 or later
@@ -25,10 +24,6 @@
// PSR-4 Autoloading.
require_once __DIR__ . '/vendor/autoload.php';
-// Bundle artifact types must be registered as soon as the plugin is loaded so
-// Data Machine can validate DMC-owned artifacts during early import paths.
-( new \DataMachineCode\Bundle\WorkspacePreloadArtifact() )->register();
-
/**
* Install DMC-owned database tables.
*/
@@ -63,12 +58,46 @@ function datamachine_code_maybe_upgrade_schema(): void {
}
add_action('plugins_loaded', 'datamachine_code_maybe_upgrade_schema', 5);
+/**
+ * Whether Data Machine-specific integration surfaces are available.
+ */
+function datamachine_code_has_datamachine_integration(): bool {
+ return class_exists('DataMachine\Abilities\PermissionHelper');
+}
+
+/**
+ * Register optional Data Machine integrations.
+ */
+function datamachine_code_register_datamachine_integrations(): void {
+ static $registered = false;
+
+ if ( $registered || ! datamachine_code_has_datamachine_integration() ) {
+ return;
+ }
+
+ $registered = true;
+
+ // Bundle artifact types are Data Machine-specific and are only registered
+ // when the bundle importer substrate is present.
+ ( new \DataMachineCode\Bundle\WorkspacePreloadArtifact() )->register();
+
+ // Project active workspace identity into Data Machine's engine_data snapshot.
+ \DataMachineCode\Runtime\ActiveWorkspaceProjector::register();
+
+ if ( class_exists('DataMachine\Core\Steps\HandlerRegistrationTrait') ) {
+ new \DataMachineCode\Handlers\GitHub\GitHub();
+ new \DataMachineCode\Handlers\GitHub\GitHubIssuePublish();
+ new \DataMachineCode\Handlers\GitHub\GitHubPullRequestPublish();
+ new \DataMachineCode\Handlers\GitHub\GitHubUpsert();
+ }
+}
+
/**
* Bootstrap the plugin after all plugins are loaded.
*
- * Data Machine core must be active — check at plugins_loaded time
- * (not at plugin load time, since load order is alphabetical and
- * data-machine-code loads before data-machine).
+ * Core workspace/GitHub/Agents API surfaces do not require Data Machine.
+ * Data Machine-specific handlers and memory/runtime hooks register through
+ * datamachine_code_register_datamachine_integrations() when available.
*/
function datamachine_code_bootstrap() {
static $bootstrapped = false;
@@ -77,22 +106,6 @@ function datamachine_code_bootstrap() {
return;
}
- if ( ! class_exists('DataMachine\Abilities\PermissionHelper') ) {
- add_action('init', 'datamachine_code_bootstrap', 1);
- add_action('wp_abilities_api_init', 'datamachine_code_bootstrap', 1);
-
- add_action(
- 'admin_notices', function () {
- ?>
-
- to_array() : get_object_vars($principal));
- if ( '' !== $agent_slug ) {
- return $agent_slug;
- }
+ $principal = PermissionHelper::get_execution_principal();
+ if ( is_object($principal) ) {
+ $agent_slug = self::agentSlugFromContext(method_exists($principal, 'to_array') ? $principal->to_array() : get_object_vars($principal));
+ if ( '' !== $agent_slug ) {
+ return $agent_slug;
}
}
- if ( method_exists(PermissionHelper::class, 'get_acting_agent_slug') ) {
- $agent_slug = PermissionHelper::get_acting_agent_slug();
- if ( is_string($agent_slug) && '' !== trim($agent_slug) ) {
- return sanitize_text_field($agent_slug);
- }
+ $agent_slug = PermissionHelper::get_acting_agent_slug();
+ if ( '' !== trim($agent_slug) ) {
+ return sanitize_text_field($agent_slug);
}
return '';
diff --git a/inc/Abilities/GitSyncAbilities.php b/inc/Abilities/GitSyncAbilities.php
index 5a27c1d..fb514ae 100644
--- a/inc/Abilities/GitSyncAbilities.php
+++ b/inc/Abilities/GitSyncAbilities.php
@@ -17,7 +17,7 @@
namespace DataMachineCode\Abilities;
-use DataMachine\Abilities\PermissionHelper;
+use DataMachineCode\Support\PermissionHelper;
use DataMachineCode\GitSync\GitSync;
defined('ABSPATH') || exit;
diff --git a/inc/Abilities/WordPressRuntimeAbilities.php b/inc/Abilities/WordPressRuntimeAbilities.php
index 5dec368..170f52f 100644
--- a/inc/Abilities/WordPressRuntimeAbilities.php
+++ b/inc/Abilities/WordPressRuntimeAbilities.php
@@ -7,7 +7,7 @@
namespace DataMachineCode\Abilities;
-use DataMachine\Abilities\PermissionHelper;
+use DataMachineCode\Support\PermissionHelper;
use DataMachineCode\Runtime\WordPressRuntimeInspector;
defined('ABSPATH') || exit;
diff --git a/inc/Abilities/WorkspaceAbilities.php b/inc/Abilities/WorkspaceAbilities.php
index 604c8c3..ccc05b9 100644
--- a/inc/Abilities/WorkspaceAbilities.php
+++ b/inc/Abilities/WorkspaceAbilities.php
@@ -14,7 +14,7 @@
namespace DataMachineCode\Abilities;
-use DataMachine\Abilities\PermissionHelper;
+use DataMachineCode\Support\PermissionHelper;
use DataMachineCode\Workspace\CleanupRunService;
use DataMachineCode\Workspace\RemoteWorkspaceBackend;
use DataMachineCode\Workspace\Workspace;
diff --git a/inc/Abilities/WorkspaceDiffAbilities.php b/inc/Abilities/WorkspaceDiffAbilities.php
index 8891195..c47e120 100644
--- a/inc/Abilities/WorkspaceDiffAbilities.php
+++ b/inc/Abilities/WorkspaceDiffAbilities.php
@@ -7,7 +7,7 @@
namespace DataMachineCode\Abilities;
-use DataMachine\Abilities\PermissionHelper;
+use DataMachineCode\Support\PermissionHelper;
use DataMachineCode\Workspace\RemoteWorkspaceBackend;
use DataMachineCode\Workspace\WorkspaceDiff;
diff --git a/inc/Support/GitHubCredentialResolver.php b/inc/Support/GitHubCredentialResolver.php
index fc07454..1f7376a 100644
--- a/inc/Support/GitHubCredentialResolver.php
+++ b/inc/Support/GitHubCredentialResolver.php
@@ -32,7 +32,7 @@
namespace DataMachineCode\Support;
-use DataMachine\Core\PluginSettings;
+use DataMachineCode\Support\PluginSettings;
defined('ABSPATH') || exit;
@@ -197,7 +197,13 @@ public static function mintJwt( string $app_id, string $private_key, int $now ):
'iss' => $app_id,
);
- $unsigned = self::base64UrlEncode(wp_json_encode($header)) . '.' . self::base64UrlEncode(wp_json_encode($payload));
+ $header_json = wp_json_encode($header);
+ $payload_json = wp_json_encode($payload);
+ if ( ! is_string($header_json) || ! is_string($payload_json) ) {
+ return new \WP_Error('github_app_jwt_encode_failed', 'GitHub App JWT payload could not be encoded.', array( 'status' => 500 ));
+ }
+
+ $unsigned = self::base64UrlEncode($header_json) . '.' . self::base64UrlEncode($payload_json);
$signature = '';
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- openssl_sign emits warnings for invalid keys; callers receive a WP_Error below.
$ok = @openssl_sign($unsigned, $signature, $private_key, OPENSSL_ALGO_SHA256);
diff --git a/inc/Support/GitHubCredentialSettingsMigration.php b/inc/Support/GitHubCredentialSettingsMigration.php
index d1412a2..c9355a4 100644
--- a/inc/Support/GitHubCredentialSettingsMigration.php
+++ b/inc/Support/GitHubCredentialSettingsMigration.php
@@ -7,7 +7,7 @@
namespace DataMachineCode\Support;
-use DataMachine\Core\PluginSettings;
+use DataMachineCode\Support\PluginSettings;
defined('ABSPATH') || exit;
diff --git a/inc/Support/PermissionHelper.php b/inc/Support/PermissionHelper.php
new file mode 100644
index 0000000..5359450
--- /dev/null
+++ b/inc/Support/PermissionHelper.php
@@ -0,0 +1,65 @@
+ */
+ public static function get_runtime_context(): array {
+ $class = self::data_machine_permission_helper_class();
+ $callback = array( $class, 'get_runtime_context' );
+ if ( is_callable($callback) ) {
+ $context = call_user_func($callback);
+ return is_array($context) ? $context : array();
+ }
+
+ return array();
+ }
+
+ public static function get_execution_principal(): mixed {
+ $class = self::data_machine_permission_helper_class();
+ $callback = array( $class, 'get_execution_principal' );
+ if ( is_callable($callback) ) {
+ return call_user_func($callback);
+ }
+
+ return null;
+ }
+
+ public static function get_acting_agent_slug(): string {
+ $class = self::data_machine_permission_helper_class();
+ $callback = array( $class, 'get_acting_agent_slug' );
+ if ( is_callable($callback) ) {
+ $agent_slug = call_user_func($callback);
+ return is_string($agent_slug) ? $agent_slug : '';
+ }
+
+ return '';
+ }
+
+ private static function data_machine_permission_helper_class(): string {
+ $class = apply_filters('datamachine_code_datamachine_permission_helper_class', '\\DataMachine\\Abilities\\PermissionHelper');
+ return is_string($class) ? $class : '';
+ }
+}
diff --git a/inc/Support/PluginSettings.php b/inc/Support/PluginSettings.php
new file mode 100644
index 0000000..7a207e4
--- /dev/null
+++ b/inc/Support/PluginSettings.php
@@ -0,0 +1,35 @@
+code; }
+ public function get_error_message(): string { return $this->message; }
+ public function get_error_data(): mixed { return $this->data; }
+}
+
+function dmc_standalone_add_hook( array &$store, string $hook, callable $callback, int $priority, int $accepted_args ): void {
+ $store[ $hook ][ $priority ][] = compact('callback', 'accepted_args');
+}
+
+function add_action( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): void {
+ dmc_standalone_add_hook($GLOBALS['dmc_standalone_actions'], $hook, $callback, $priority, $accepted_args);
+}
+
+function add_filter( string $hook, callable $callback, int $priority = 10, int $accepted_args = 1 ): void {
+ dmc_standalone_add_hook($GLOBALS['dmc_standalone_filters'], $hook, $callback, $priority, $accepted_args);
+}
+
+function do_action( string $hook, ...$args ): void {
+ $GLOBALS['dmc_standalone_did_action'][ $hook ] = ( $GLOBALS['dmc_standalone_did_action'][ $hook ] ?? 0 ) + 1;
+ $callbacks = $GLOBALS['dmc_standalone_actions'][ $hook ] ?? array();
+ ksort($callbacks);
+ foreach ( $callbacks as $priority_callbacks ) {
+ foreach ( $priority_callbacks as $entry ) {
+ call_user_func($entry['callback'], ...array_slice($args, 0, (int) $entry['accepted_args']));
+ }
+ }
+}
+
+function apply_filters( string $hook, $value, ...$args ) {
+ $callbacks = $GLOBALS['dmc_standalone_filters'][ $hook ] ?? array();
+ ksort($callbacks);
+ foreach ( $callbacks as $priority_callbacks ) {
+ foreach ( $priority_callbacks as $entry ) {
+ $value = call_user_func($entry['callback'], $value, ...array_slice($args, 0, (int) $entry['accepted_args'] - 1));
+ }
+ }
+ return $value;
+}
+
+function did_action( string $hook ): int { return (int) ( $GLOBALS['dmc_standalone_did_action'][ $hook ] ?? 0 ); }
+function doing_action( string $hook ): bool { return did_action($hook) > 0; }
+function register_activation_hook( string $file, callable $callback ): void { unset($file, $callback); }
+function plugin_dir_path( string $file ): string { return dirname($file) . '/'; }
+function plugin_dir_url( string $file ): string { return 'https://example.test/plugins/' . basename(dirname($file)) . '/'; }
+function wp_installing(): bool { return false; }
+function get_option( string $name, mixed $default = false ): mixed { return $GLOBALS['dmc_standalone_options'][ $name ] ?? $default; }
+function update_option( string $name, mixed $value, mixed $autoload = null ): bool { unset($autoload); $GLOBALS['dmc_standalone_options'][ $name ] = $value; return true; }
+function current_user_can( string $capability ): bool { unset($capability); return true; }
+function wp_register_ability_category( string $category, array $args ): void { $GLOBALS['dmc_standalone_categories'][ $category ] = $args; }
+function wp_register_ability( string $ability, array $args ): void { $GLOBALS['dmc_standalone_abilities'][ $ability ] = $args; }
+function wp_has_ability( string $ability ): bool { return isset($GLOBALS['dmc_standalone_abilities'][ $ability ]); }
+function wp_json_encode( mixed $value ): string|false { return json_encode($value); }
+function is_wp_error( mixed $value ): bool { return $value instanceof WP_Error; }
+function sanitize_text_field( mixed $value ): string { return trim((string) $value); }
+function sanitize_key( mixed $value ): string { return strtolower(preg_replace('/[^a-zA-Z0-9_\-]/', '', (string) $value) ?? ''); }
+function __( string $text, string $domain = 'default' ): string { unset($domain); return $text; }
+function esc_html_e( string $text, string $domain = 'default' ): void { unset($domain); echo htmlspecialchars($text, ENT_QUOTES); }
+function wp_parse_url( string $url, int $component = -1 ): mixed { return parse_url($url, $component); }
+
+require __DIR__ . '/../data-machine-code.php';
+
+do_action('plugins_loaded');
+do_action('wp_abilities_api_categories_init');
+do_action('wp_abilities_api_init');
+
+$failures = array();
+$assert = static function ( string $label, bool $condition ) use ( &$failures ): void {
+ echo ( $condition ? ' ok ' : ' fail ' ) . $label . "\n";
+ if ( ! $condition ) {
+ $failures[] = $label;
+ }
+};
+
+echo "Data Machine Code standalone bootstrap - smoke\n";
+
+$assert('Data Machine PermissionHelper is absent', ! class_exists('DataMachine\\Abilities\\PermissionHelper'));
+$assert('DMC version constant is defined', defined('DATAMACHINE_CODE_VERSION'));
+$assert('workspace category registers', isset($GLOBALS['dmc_standalone_categories']['datamachine-code-workspace']));
+$assert('GitHub category registers', isset($GLOBALS['dmc_standalone_categories']['datamachine-code-github']));
+$assert('workspace ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/workspace-read']));
+$assert('GitHub ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/list-github-issues']));
+$assert('GitSync ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/gitsync-status']));
+$assert('code task ability registers', isset($GLOBALS['dmc_standalone_abilities']['datamachine-code/create-code-task']));
+$assert('permission facade allows core callbacks', \DataMachineCode\Support\PermissionHelper::can_manage());
+$assert('settings facade falls back to options', 'fallback' === \DataMachineCode\Support\PluginSettings::get('missing_setting', 'fallback'));
+
+if ( ! empty($failures) ) {
+ echo "\nFAIL: " . count($failures) . " assertion(s) failed\n";
+ exit(1);
+}
+
+echo "\nOK\n";