Skip to content

Commit 47d45bc

Browse files
authored
refactor: route lock owner-context runtime IDs through signatures registry (#482)
WorkspaceLockStore::default_owner_context() hardcoded kimaki_session_id / opencode_session_id env-var literals in DMC generic core, violating the RULES.md layer-purity law. Re-point it at the existing datamachine_code_worktree_runtime_signatures filter (the same registry WorktreeContextInjector already reads), projecting runtime session IDs into a generic runtime-keyed `runtime_ids` envelope. DMCs own DATAMACHINE_* keys stay (DMC namespace, not a vendor leak). WorkspaceCommand::render_workspace_error() no longer special-cases kimaki_session_id; it renders runtime session IDs generically from the runtime_ids envelope (prefers session_id, falls back to any subkey, scanning runtimes in registration order). Adding a new runtime is now a filter/config change, never a DMC core edit. Closes #481
1 parent 46a57e7 commit 47d45bc

2 files changed

Lines changed: 100 additions & 4 deletions

File tree

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,14 +2789,50 @@ private function render_workspace_error( \WP_Error $error ): void {
27892789
if ( ! empty($owner_context['wp_cli_args']) ) {
27902790
WP_CLI::log(sprintf('Command: %s', (string) $owner_context['wp_cli_args']));
27912791
}
2792-
if ( ! empty($owner_context['kimaki_session_id']) || ! empty($owner_context['opencode_session_id']) ) {
2793-
WP_CLI::log(sprintf('Session: %s', (string) ( $owner_context['kimaki_session_id'] ?? $owner_context['opencode_session_id'] ?? '' )));
2792+
$session_id = $this->resolve_owner_context_session_id($owner_context);
2793+
if ( '' !== $session_id ) {
2794+
WP_CLI::log(sprintf('Session: %s', $session_id));
27942795
}
27952796
}
27962797

27972798
WP_CLI::error($error->get_error_message());
27982799
}
27992800

2801+
/**
2802+
* Resolve a displayable runtime session identifier from a lock owner context.
2803+
*
2804+
* Runtime-specific session IDs live under the generic `runtime_ids` envelope
2805+
* (runtime-id => { subkey => value }) populated by WorkspaceLockStore from the
2806+
* `datamachine_code_worktree_runtime_signatures` registry. DMC names no
2807+
* runtimes here: it prefers a `session_id` subkey, then falls back to the
2808+
* first available subkey value, scanning runtimes in registration order.
2809+
*
2810+
* @param array $owner_context Decoded lock owner context.
2811+
* @return string Session identifier, or '' when none is available.
2812+
*/
2813+
private function resolve_owner_context_session_id( array $owner_context ): string {
2814+
$runtime_ids = (array) ( $owner_context['runtime_ids'] ?? array() );
2815+
2816+
foreach ( $runtime_ids as $entry ) {
2817+
if ( is_array($entry) && '' !== trim( (string) ( $entry['session_id'] ?? '' )) ) {
2818+
return (string) $entry['session_id'];
2819+
}
2820+
}
2821+
2822+
foreach ( $runtime_ids as $entry ) {
2823+
if ( ! is_array($entry) ) {
2824+
continue;
2825+
}
2826+
foreach ( $entry as $value ) {
2827+
if ( '' !== trim( (string) $value) ) {
2828+
return (string) $value;
2829+
}
2830+
}
2831+
}
2832+
2833+
return '';
2834+
}
2835+
28002836
/**
28012837
* Render workspace hygiene report output.
28022838
*

inc/Workspace/WorkspaceLockStore.php

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,8 @@ public static function default_owner_context(): array {
343343
'host' => function_exists('gethostname') ? (string) gethostname() : 'unknown-host',
344344
'pid' => function_exists('getmypid') ? (string) getmypid() : 'unknown-pid',
345345
);
346+
// DMC's own namespace — these are not vendor leaks.
346347
$env_map = array(
347-
'kimaki_session_id' => 'KIMAKI_SESSION_ID',
348-
'opencode_session_id' => 'OPENCODE_SESSION_ID',
349348
'datamachine_task_url' => 'DATAMACHINE_TASK_URL',
350349
'datamachine_task_ref' => 'DATAMACHINE_TASK_REF',
351350
'datamachine_agent' => 'DATAMACHINE_AGENT',
@@ -358,13 +357,74 @@ public static function default_owner_context(): array {
358357
}
359358
}
360359

360+
// Runtime-specific session identifiers are NOT hardcoded here. Integration
361+
// layers (e.g. wp-coding-agents) declare which env vars to sniff and how to
362+
// project them into a runtime-keyed envelope via the
363+
// `datamachine_code_worktree_runtime_signatures` filter — the same registry
364+
// WorktreeContextInjector reads. DMC enumerates no runtime IDs and no
365+
// vendor-specific env-var names.
366+
$runtime_ids = self::resolve_runtime_ids();
367+
if ( ! empty($runtime_ids) ) {
368+
$context['runtime_ids'] = $runtime_ids;
369+
}
370+
361371
if ( isset($_SERVER['argv']) && is_array($_SERVER['argv']) ) {
362372
$context['wp_cli_args'] = self::bounded_string(self::redact_secret_values(implode(' ', array_map('strval', $_SERVER['argv']))), 1000);
363373
}
364374

365375
return $context;
366376
}
367377

378+
/**
379+
* Resolve runtime-specific session identifiers from registered signatures.
380+
*
381+
* Reads the `datamachine_code_worktree_runtime_signatures` filter (the same
382+
* public contract WorktreeContextInjector consumes) and projects any env
383+
* vars the surrounding runtime exposes into a runtime-keyed envelope:
384+
*
385+
* array(
386+
* '<runtime-id>' => array( '<subkey>' => '<value>', ... ),
387+
* )
388+
*
389+
* DMC enumerates no runtime IDs and no vendor-specific env-var names; the
390+
* integration layer declares both. Missing fields stay missing.
391+
*
392+
* @return array<string,array<string,string>>
393+
*/
394+
private static function resolve_runtime_ids(): array {
395+
if ( ! function_exists('apply_filters') ) {
396+
return array();
397+
}
398+
399+
$signatures = apply_filters('datamachine_code_worktree_runtime_signatures', array());
400+
if ( ! is_array($signatures) ) {
401+
return array();
402+
}
403+
404+
$runtime_ids = array();
405+
foreach ( $signatures as $runtime_id => $entry ) {
406+
if ( ! is_string($runtime_id) || '' === $runtime_id || ! is_array($entry) ) {
407+
continue;
408+
}
409+
$resolved = array();
410+
foreach ( $entry as $subkey => $env_var ) {
411+
if ( ! is_string($subkey) || '' === $subkey || ! is_string($env_var) || '' === $env_var ) {
412+
continue;
413+
}
414+
$value = getenv($env_var);
415+
if ( false === $value || '' === trim( (string) $value) ) {
416+
continue;
417+
}
418+
$resolved[ $subkey ] = self::bounded_string( (string) $value, 300);
419+
}
420+
if ( ! empty($resolved) ) {
421+
$runtime_ids[ $runtime_id ] = $resolved;
422+
}
423+
}
424+
425+
return $runtime_ids;
426+
}
427+
368428
private static function default_owner(): string {
369429
$context = self::default_owner_context();
370430
$host = (string) ( $context['host'] ?? 'unknown-host' );

0 commit comments

Comments
 (0)