From fc7016b1ede30d6097cafb6730abf2fd08fc564b Mon Sep 17 00:00:00 2001 From: duang777 Date: Sat, 24 Oct 2026 11:42:25 +0800 Subject: [PATCH 1/3] fix(control-plane): reuse the canonical input digest matcher Remove the duplicate whole-value SHA-256 matcher that fails the content-digest ownership guard. Register trajectory input usage as a canonical consumer while preserving its accepted digest format. Signed-off-by: duang777 --- loopx/control_plane/capabilities/trajectory_input_usage.ts | 3 ++- tests/control_plane_ts/content_digest_single_owner.test.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/loopx/control_plane/capabilities/trajectory_input_usage.ts b/loopx/control_plane/capabilities/trajectory_input_usage.ts index 90d992fb27..9d8e6f550d 100644 --- a/loopx/control_plane/capabilities/trajectory_input_usage.ts +++ b/loopx/control_plane/capabilities/trajectory_input_usage.ts @@ -2,6 +2,7 @@ * This estimates only explicitly recorded LoopX Turn packets. It never labels * arbitrary prose, a quoted packet, tool output or all compaction as LoopX. */ import type {TrajectoryLoopXInput, TrajectoryRequestCoverage} from './trajectory_assessment_types.ts'; +import {BARE_SHA256_PATTERN} from '../content_digest.ts'; const method = 'tiktoken_o200k_base_input_estimate_v1'; class InputObservationError extends Error {} @@ -21,7 +22,7 @@ function count(value: unknown): number { ? value : fail('Invalid input token estimate.'); } function digest(value: unknown): string { - return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value) ? value : fail('Missing input source digest.'); + return typeof value === 'string' && BARE_SHA256_PATTERN.test(value) ? value : fail('Missing input source digest.'); } export function estimateTrajectoryLoopXInput(value: unknown, session: string, goal: unknown, diff --git a/tests/control_plane_ts/content_digest_single_owner.test.ts b/tests/control_plane_ts/content_digest_single_owner.test.ts index 1a059dc52c..f8cf6a6bee 100644 --- a/tests/control_plane_ts/content_digest_single_owner.test.ts +++ b/tests/control_plane_ts/content_digest_single_owner.test.ts @@ -104,6 +104,7 @@ const CANONICAL_CONSUMERS = [ "control_plane/capabilities/external_evidence.ts", "control_plane/capabilities/goal_capability_organization.ts", "control_plane/capabilities/trajectory_assessment.ts", + "control_plane/capabilities/trajectory_input_usage.ts", "control_plane/collaboration/chat_mode.ts", "control_plane/collaboration/delegation.ts", "control_plane/collaboration/delegation_result_use.ts", From b150d75db2e8b4d0a4cd8df0a9d9c385dc136dd9 Mon Sep 17 00:00:00 2001 From: duang777 Date: Sat, 24 Oct 2026 13:52:50 +0800 Subject: [PATCH 2/3] fix(control-plane): share the report recovery digest rule Reuse the canonical enveloped SHA-256 matcher for native report Turn recovery. Register the managed steward as a real consumer so the ownership guard continues to enforce the full package contract. Signed-off-by: duang777 --- loopx/control_plane/collaboration/steward_managed_mode.ts | 3 ++- tests/control_plane_ts/content_digest_single_owner.test.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/loopx/control_plane/collaboration/steward_managed_mode.ts b/loopx/control_plane/collaboration/steward_managed_mode.ts index dc2e205a5e..6d8c3933fc 100644 --- a/loopx/control_plane/collaboration/steward_managed_mode.ts +++ b/loopx/control_plane/collaboration/steward_managed_mode.ts @@ -8,6 +8,7 @@ import { createHash } from "node:crypto"; import type { JsonObject } from "../runtime_decode.ts"; import { requireJsonObject, requireNonEmptyString } from "../runtime_decode.ts"; import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts"; +import { ENVELOPED_SHA256_PATTERN } from "../content_digest.ts"; export const STEWARD_MANAGED_ACTOR_SCHEMA = "loopx_steward_managed_actor_v0"; const AGENT_ID = "steward"; @@ -152,7 +153,7 @@ export function admitStewardManagedTurn(params: JsonObject): JsonObject { throw new EffectRuntimeRequestError("report execution requires the original live subscription, window and task"); } if (request.resume_turn_key !== undefined) { - if (typeof request.resume_turn_key !== "string" || !/^sha256:[a-f0-9]{64}$/.test(request.resume_turn_key)) { + if (typeof request.resume_turn_key !== "string" || !ENVELOPED_SHA256_PATTERN.test(request.resume_turn_key)) { throw new EffectRuntimeRequestError("report recovery requires the original native Turn key"); } // An already accepted worker retains its ordinary active-Turn fence. diff --git a/tests/control_plane_ts/content_digest_single_owner.test.ts b/tests/control_plane_ts/content_digest_single_owner.test.ts index f8cf6a6bee..bb5887b2f6 100644 --- a/tests/control_plane_ts/content_digest_single_owner.test.ts +++ b/tests/control_plane_ts/content_digest_single_owner.test.ts @@ -115,6 +115,7 @@ const CANONICAL_CONSUMERS = [ "control_plane/collaboration/result_publication.ts", "control_plane/collaboration/return_delivery.ts", "control_plane/collaboration/semantic_request.ts", + "control_plane/collaboration/steward_managed_mode.ts", "control_plane/collaboration/turn_context.ts", "control_plane/coordination/authority_archive_read.ts", "control_plane/coordination/authority_source.ts", From 35e99bf18b5df593283c6aec16bf604a2b765b89 Mon Sep 17 00:00:00 2001 From: duang777 Date: Sat, 24 Oct 2026 13:48:44 +0800 Subject: [PATCH 3/3] fix(tests): close analysis explicitly before changing its scope Escape correctly dismisses an open context-help popover before its parent reader. Use the close control and wait for the analysis reader to disappear before asserting that selecting an unanalysed source does not open or execute analysis. Signed-off-by: duang777 (cherry picked from commit a85c4e72cbb79a25d8b0f2620754e8995b3753a2) --- examples/personal-workspace-browser/research-history.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/personal-workspace-browser/research-history.mjs b/examples/personal-workspace-browser/research-history.mjs index ccdec6dc2a..cbc7ac2622 100644 --- a/examples/personal-workspace-browser/research-history.mjs +++ b/examples/personal-workspace-browser/research-history.mjs @@ -927,7 +927,8 @@ Path(sys.argv[1]).write_text(json.dumps({'goal_id':goal,'display_name':'Syntheti assert.match(await progress.innerText(), /原生测量.*未采集/); await history.getByRole("button", {name: "查看分析", exact: true}).click(); await analysis.locator("header p").first().filter({hasText: /^Synthetic analysis C$/}).waitFor(); - await analysis.press('Escape'); + await analysis.getByRole('button', {name: '关闭分析', exact: true}).click(); + await analysis.waitFor({state: 'hidden'}); await progress.getByRole('combobox').selectOption('d'.repeat(64)); await progress.locator(`.research-progress-scope[data-pack-id="${"d".repeat(64)}"]`).waitFor(); await progress.locator('.research-progress-basis > summary').click();