Skip to content

Commit a55614d

Browse files
committed
test(workspace): cover generic worktree session-attribution envelope
Refactor worktree session-attribution smoke tests to register synthetic test runtimes via the new 'datamachine_code_worktree_runtime_signatures' filter and assert against the generic primary_id + ids envelope. No test asserts against vendor-specific field names. - smoke-worktree-agent-session-lifecycle.php: register two synthetic runtimes (alpha-runtime, beta-runtime) with synthetic env vars, exercise both registration-order primary_id resolution and *_url subkey URL validation. Add a dedicated legacy-migration coverage block that feeds pre-#416 branded top-level keys to summarize_session() and asserts the new envelope is produced without data loss. Add a coverage path for an explicit primary_id on a stored envelope overriding runtime-scan precedence. - smoke-worktree-inventory-store.php: wire the apply_filters shim, register a synthetic test runtime, and store/load the new envelope shape end-to-end through the DB-backed inventory repository. - smoke-worktree-lifecycle-metadata.php: register a synthetic runtime via the existing filter shim and drive the env-driven capture path through DMC_SMOKE_LIFECYCLE_RUN_ID instead of vendor-specific env vars, while keeping the OPENCODE_PID assertion behavior untouched (PID capture is unrelated to session-attribution). Refs #416
1 parent eb3239a commit a55614d

3 files changed

Lines changed: 121 additions & 25 deletions

File tree

tests/smoke-worktree-agent-session-lifecycle.php

Lines changed: 81 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ function update_option( string $name, $value, $autoload = null ): bool {
8888

8989
if ( ! function_exists( 'apply_filters' ) ) {
9090
function apply_filters( string $hook_name, $value, ...$args ) {
91+
global $datamachine_code_test_filters;
92+
if ( isset( $datamachine_code_test_filters[ $hook_name ] ) && is_callable( $datamachine_code_test_filters[ $hook_name ] ) ) {
93+
return $datamachine_code_test_filters[ $hook_name ]( $value, ...$args );
94+
}
9195
return $value;
9296
}
9397
}
@@ -144,16 +148,32 @@ function wp_mkdir_p( string $path ): bool {
144148

145149
echo "=== smoke-worktree-agent-session-lifecycle ===\n";
146150

147-
// Reset option state for this run.
151+
// Reset option + filter state for this run.
148152
$GLOBALS['datamachine_code_test_options'] = array();
153+
$GLOBALS['datamachine_code_test_filters'] = array();
154+
155+
// Register two synthetic test runtimes via the public filter contract.
156+
// DMC has zero knowledge of these IDs or env-var names — the integration
157+
// layer (here, the test) owns both.
158+
$GLOBALS['datamachine_code_test_filters']['datamachine_code_worktree_runtime_signatures'] = function ( array $signatures ): array {
159+
$signatures['alpha-runtime'] = array(
160+
'session_id' => 'DMC_SMOKE_ALPHA_SESSION_ID',
161+
'thread_id' => 'DMC_SMOKE_ALPHA_THREAD_ID',
162+
'thread_url' => 'DMC_SMOKE_ALPHA_THREAD_URL',
163+
);
164+
$signatures['beta-runtime'] = array(
165+
'session_id' => 'DMC_SMOKE_BETA_SESSION_ID',
166+
'run_id' => 'DMC_SMOKE_BETA_RUN_ID',
167+
);
168+
return $signatures;
169+
};
149170

150171
// --- 1) build_lifecycle_metadata captures env-driven session + task fields ---
151-
putenv( 'OPENCODE_SESSION_ID=ses_smoke_42' );
152-
putenv( 'OPENCODE_RUN_ID=run-smoke-1' );
153-
putenv( 'KIMAKI_SESSION_ID=kim-ses-99' );
154-
putenv( 'KIMAKI_THREAD_ID=thr_111' );
155-
putenv( 'KIMAKI_CHANNEL_ID=chan_222' );
156-
putenv( 'KIMAKI_THREAD_URL=https://discord.com/channels/1/2/3' );
172+
putenv( 'DMC_SMOKE_BETA_SESSION_ID=ses_smoke_42' );
173+
putenv( 'DMC_SMOKE_BETA_RUN_ID=run-smoke-1' );
174+
putenv( 'DMC_SMOKE_ALPHA_SESSION_ID=alpha-ses-99' );
175+
putenv( 'DMC_SMOKE_ALPHA_THREAD_ID=thr_111' );
176+
putenv( 'DMC_SMOKE_ALPHA_THREAD_URL=https://example.test/threads/1/2/3' );
157177
putenv( 'DATAMACHINE_TASK_URL=https://github.com/Extra-Chill/data-machine-code/issues/221' );
158178
putenv( 'DATAMACHINE_TASK_REF=Extra-Chill/data-machine-code#221' );
159179

@@ -171,13 +191,26 @@ function wp_mkdir_p( string $path ): bool {
171191
$assert( 'Intelligence', $built['origin_site'] ?? null, 'origin site name recorded' );
172192
$assert( 'https://intelligence.example.test', $built['origin_site_url'] ?? null, 'origin site URL recorded' );
173193
$assert( 'chris', $built['origin_user']['login'] ?? null, 'origin user login recorded' );
174-
$assert( 'ses_smoke_42', $built['origin_session']['opencode_session_id'] ?? null, 'opencode session id captured' );
175-
$assert( 'kim-ses-99', $built['origin_session']['kimaki_session_id'] ?? null, 'kimaki session id captured' );
176-
$assert( 'chan_222', $built['origin_session']['kimaki_channel_id'] ?? null, 'kimaki channel id captured' );
177-
$assert( 'https://discord.com/channels/1/2/3', $built['origin_session']['kimaki_thread_url'] ?? null, 'kimaki thread URL captured' );
194+
$assert( 'ses_smoke_42', $built['origin_session']['ids']['beta-runtime']['session_id'] ?? null, 'beta runtime session id captured under ids envelope' );
195+
$assert( 'run-smoke-1', $built['origin_session']['ids']['beta-runtime']['run_id'] ?? null, 'beta runtime run id captured under ids envelope' );
196+
$assert( 'alpha-ses-99', $built['origin_session']['ids']['alpha-runtime']['session_id'] ?? null, 'alpha runtime session id captured under ids envelope' );
197+
$assert( 'thr_111', $built['origin_session']['ids']['alpha-runtime']['thread_id'] ?? null, 'alpha runtime thread id captured under ids envelope' );
198+
$assert( 'https://example.test/threads/1/2/3', $built['origin_session']['ids']['alpha-runtime']['thread_url'] ?? null, 'alpha runtime thread URL captured under ids envelope' );
199+
$assert( 'alpha-ses-99', $built['origin_session']['primary_id'] ?? null, 'primary_id resolves to first registered runtime session_id' );
178200
$assert( 'https://github.com/Extra-Chill/data-machine-code/issues/221', $built['origin_task']['task_url'] ?? null, 'task URL captured from env' );
179201
$assert( 'Extra-Chill/data-machine-code#221', $built['origin_task']['task_ref'] ?? null, 'task ref captured from env' );
180202

203+
// URL-suffixed subkeys must look like http(s) URLs to be captured.
204+
putenv( 'DMC_SMOKE_ALPHA_THREAD_URL=not-a-url' );
205+
$built_bad_url = \DataMachineCode\Workspace\WorktreeContextInjector::build_lifecycle_metadata( array(
206+
'handle' => 'demo@bad-url',
207+
'path' => '/tmp/demo@bad-url',
208+
'repo' => 'demo',
209+
'branch' => 'bad/url',
210+
) );
211+
$assert( null, $built_bad_url['origin_session']['ids']['alpha-runtime']['thread_url'] ?? null, 'non-URL value for *_url subkey is dropped' );
212+
putenv( 'DMC_SMOKE_ALPHA_THREAD_URL=https://example.test/threads/1/2/3' );
213+
181214
// Caller-supplied task_url/task_ref override env.
182215
putenv( 'DATAMACHINE_TASK_URL=https://github.com/some/other/issues/9999' );
183216
$built_explicit = \DataMachineCode\Workspace\WorktreeContextInjector::build_lifecycle_metadata( array(
@@ -189,12 +222,11 @@ function wp_mkdir_p( string $path ): bool {
189222
$assert( 'EC/dmc#221', $built_explicit['origin_task']['task_ref'] ?? null, 'explicit task_ref wins over env' );
190223

191224
// Clear env so subsequent assertions get unknown-safe defaults.
192-
putenv( 'OPENCODE_SESSION_ID' );
193-
putenv( 'OPENCODE_RUN_ID' );
194-
putenv( 'KIMAKI_SESSION_ID' );
195-
putenv( 'KIMAKI_THREAD_ID' );
196-
putenv( 'KIMAKI_CHANNEL_ID' );
197-
putenv( 'KIMAKI_THREAD_URL' );
225+
putenv( 'DMC_SMOKE_BETA_SESSION_ID' );
226+
putenv( 'DMC_SMOKE_BETA_RUN_ID' );
227+
putenv( 'DMC_SMOKE_ALPHA_SESSION_ID' );
228+
putenv( 'DMC_SMOKE_ALPHA_THREAD_ID' );
229+
putenv( 'DMC_SMOKE_ALPHA_THREAD_URL' );
198230
putenv( 'DATAMACHINE_TASK_URL' );
199231
putenv( 'DATAMACHINE_TASK_REF' );
200232

@@ -289,11 +321,40 @@ function wp_mkdir_p( string $path ): bool {
289321

290322
$session_unknown = \DataMachineCode\Workspace\WorktreeContextInjector::summarize_session( null );
291323
$assert( null, $session_unknown['primary_id'], 'summarize_session primary_id null on missing metadata' );
292-
$assert( null, $session_unknown['kimaki_session_id'], 'summarize_session kimaki id null on missing metadata' );
324+
$assert( array(), $session_unknown['ids'], 'summarize_session ids is empty map on missing metadata' );
293325

294326
$session_filled = \DataMachineCode\Workspace\WorktreeContextInjector::summarize_session( $built );
295-
$assert( 'kim-ses-99', $session_filled['primary_id'], 'summarize_session prefers kimaki session id as primary' );
296-
$assert( 'ses_smoke_42', $session_filled['opencode_session_id'], 'summarize_session exposes opencode session id' );
327+
$assert( 'alpha-ses-99', $session_filled['primary_id'], 'summarize_session primary_id follows registered runtime order' );
328+
$assert( 'ses_smoke_42', $session_filled['ids']['beta-runtime']['session_id'] ?? null, 'summarize_session exposes beta runtime session id under ids envelope' );
329+
$assert( 'alpha-ses-99', $session_filled['ids']['alpha-runtime']['session_id'] ?? null, 'summarize_session exposes alpha runtime session id under ids envelope' );
330+
331+
// --- 4b) Legacy-shape migration (pre-#416 stored rows). ---
332+
// Stored rows that persisted vendor-specific top-level keys must normalize
333+
// transparently into the generic envelope without losing data.
334+
$legacy_metadata = array(
335+
'origin_session' => array(
336+
'alpha-runtime_session_id' => 'legacy-alpha-ses',
337+
'alpha-runtime_thread_id' => 'legacy-alpha-thr',
338+
'beta-runtime_run_id' => 'legacy-beta-run',
339+
),
340+
);
341+
$legacy_view = \DataMachineCode\Workspace\WorktreeContextInjector::summarize_session( $legacy_metadata );
342+
$assert( 'legacy-alpha-ses', $legacy_view['ids']['alpha-runtime']['session_id'] ?? null, 'legacy migration projects <runtime>_session_id into ids envelope' );
343+
$assert( 'legacy-alpha-thr', $legacy_view['ids']['alpha-runtime']['thread_id'] ?? null, 'legacy migration projects <runtime>_thread_id into ids envelope' );
344+
$assert( 'legacy-beta-run', $legacy_view['ids']['beta-runtime']['run_id'] ?? null, 'legacy migration projects <runtime>_run_id into ids envelope' );
345+
$assert( 'legacy-alpha-ses', $legacy_view['primary_id'], 'legacy-migrated rows still resolve a primary_id via runtime precedence' );
346+
347+
// Mixed envelope: explicit primary_id wins over runtime-derived precedence.
348+
$mixed_view = \DataMachineCode\Workspace\WorktreeContextInjector::summarize_session( array(
349+
'origin_session' => array(
350+
'primary_id' => 'explicit-primary',
351+
'ids' => array(
352+
'alpha-runtime' => array( 'session_id' => 'alpha-ses' ),
353+
'beta-runtime' => array( 'session_id' => 'beta-ses' ),
354+
),
355+
),
356+
) );
357+
$assert( 'explicit-primary', $mixed_view['primary_id'], 'explicit primary_id on stored envelope overrides runtime scan' );
297358

298359
// --- 5) Duplicate task ownership detection ---
299360
$rows = array(

tests/smoke-worktree-inventory-store.php

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ function update_option( string $name, $value, $autoload = null ): bool {
9191
}
9292
}
9393

94+
if ( ! function_exists( 'apply_filters' ) ) {
95+
function apply_filters( string $hook_name, $value, ...$args ) {
96+
global $datamachine_code_test_filters;
97+
if ( isset( $datamachine_code_test_filters[ $hook_name ] ) && is_callable( $datamachine_code_test_filters[ $hook_name ] ) ) {
98+
return $datamachine_code_test_filters[ $hook_name ]( $value, ...$args );
99+
}
100+
return $value;
101+
}
102+
}
103+
94104
require __DIR__ . '/../inc/Workspace/WorktreeContextInjector.php';
95105
require __DIR__ . '/../inc/Storage/WorktreeInventoryRepository.php';
96106

@@ -114,6 +124,17 @@ function update_option( string $name, $value, $autoload = null ): bool {
114124

115125
$GLOBALS['wpdb'] = new Datamachine_Code_Test_Wpdb();
116126
$GLOBALS['datamachine_code_test_options'] = array();
127+
$GLOBALS['datamachine_code_test_filters'] = array();
128+
129+
// Register a synthetic test runtime so primary_id resolution has a
130+
// registered runtime to scan. DMC enumerates no runtime IDs itself.
131+
$GLOBALS['datamachine_code_test_filters']['datamachine_code_worktree_runtime_signatures'] = function ( array $signatures ): array {
132+
$signatures['test-runtime'] = array(
133+
'session_id' => 'DMC_SMOKE_TEST_SESSION_ID',
134+
'thread_id' => 'DMC_SMOKE_TEST_THREAD_ID',
135+
);
136+
return $signatures;
137+
};
117138

118139
$metadata = array(
119140
'handle' => 'demo@agent-session-lifecycle',
@@ -124,8 +145,13 @@ function update_option( string $name, $value, $autoload = null ): bool {
124145
'origin_site' => 'Intelligence',
125146
'origin_agent' => 'franklin',
126147
'origin_session' => array(
127-
'opencode_session_id' => 'ses_123',
128-
'kimaki_thread_id' => 'thread_456',
148+
'primary_id' => 'ses_123',
149+
'ids' => array(
150+
'test-runtime' => array(
151+
'session_id' => 'ses_123',
152+
'thread_id' => 'thread_456',
153+
),
154+
),
129155
),
130156
'origin_task' => array(
131157
'task_url' => 'https://github.com/Extra-Chill/data-machine-code/issues/221',
@@ -148,7 +174,8 @@ function update_option( string $name, $value, $autoload = null ): bool {
148174
// Prove get_metadata can be DB-backed by clearing the option fallback.
149175
$GLOBALS['datamachine_code_test_options'] = array();
150176
$loaded = \DataMachineCode\Workspace\WorktreeContextInjector::get_metadata( 'demo@agent-session-lifecycle' );
151-
$assert( 'ses_123', $loaded['origin_session']['opencode_session_id'] ?? null, 'get_metadata reads origin session from DB inventory when option is absent' );
177+
$assert( 'ses_123', $loaded['origin_session']['ids']['test-runtime']['session_id'] ?? null, 'get_metadata reads origin session ids envelope from DB inventory when option is absent' );
178+
$assert( 'thread_456', $loaded['origin_session']['ids']['test-runtime']['thread_id'] ?? null, 'get_metadata exposes ids subkeys from DB inventory' );
152179
$assert( 'Extra-Chill/data-machine-code#221', $loaded['origin_task']['task_ref'] ?? null, 'get_metadata reads task ref from DB inventory when option is absent' );
153180

154181
\DataMachineCode\Workspace\WorktreeContextInjector::record_heartbeat( 'demo@agent-session-lifecycle', '2026-05-04T13:00:00Z' );

tests/smoke-worktree-lifecycle-metadata.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,16 @@ function get_userdata( int $user_id ): object {
276276
$run( 'git add README.md', $primary );
277277
$run( 'git commit -m initial', $primary );
278278
$run( 'git remote add origin https://github.com/acme/demo.git', $primary );
279-
putenv( 'OPENCODE_RUN_ID=smoke-run-123' );
280-
putenv( 'OPENCODE_PID=12345' );
279+
// Synthetic runtime registration so the env-driven session capture has a
280+
// runtime to scan. DMC enumerates no runtime IDs itself — the integration
281+
// layer (here, the test) declares them via the public filter.
282+
$GLOBALS['datamachine_code_test_filters']['datamachine_code_worktree_runtime_signatures'] = function ( array $signatures ): array {
283+
$signatures['smoke-runtime'] = array(
284+
'run_id' => 'DMC_SMOKE_LIFECYCLE_RUN_ID',
285+
);
286+
return $signatures;
287+
};
288+
putenv( 'DMC_SMOKE_LIFECYCLE_RUN_ID=smoke-run-123' );
281289

282290
$ws = new \DataMachineCode\Workspace\Workspace();
283291
$GLOBALS['wpdb'] = new DatamachineCodeLifecycleInventoryWpdb();

0 commit comments

Comments
 (0)