From f700177554bf82cf67586eb307d8b67d39cb38b6 Mon Sep 17 00:00:00 2001 From: song Date: Mon, 2 Nov 2026 21:25:57 +0800 Subject: [PATCH 1/2] fix(context): coalesce fully selected initial-reader branches Signed-off-by: song --- docs/reference/required-work-context.md | 8 ++- .../control_plane/quota/context_selection.ts | 47 +++++++++++-- .../control_plane/codex_checkpoint_fixture.py | 16 +++++ .../control_plane/test_context_first_read.py | 69 ++++++++++++++++++- .../context_selection.test.ts | 65 ++++++++++++++++- tests/test_delegation_turn_context.py | 4 +- 6 files changed, 201 insertions(+), 8 deletions(-) diff --git a/docs/reference/required-work-context.md b/docs/reference/required-work-context.md index f48a530000..a8697f6b81 100644 --- a/docs/reference/required-work-context.md +++ b/docs/reference/required-work-context.md @@ -193,7 +193,11 @@ versions remain whole. Initial pages do not repeat pointers already present in their content; the selection digest still prevents a full-input receipt. Its `context-read --initial-read` uses the same exact-address selector; paged continuations retain the short option and pin the selection digest. A changed -profile rejects rather than mixing views. The option is read-only and exclusive with explicit +profile rejects rather than mixing views. Initial navigation can replace fully +selected children with their complete captured parent to avoid repeating addresses. +An unselected child, including optional or unknown material, prevents that merge; +the selected facts and obligations stay identical. Explicit selectors and index +entries keep their original addresses. The option is read-only and exclusive with explicit selectors or discovery; unknown schemas use generic discovery. The concrete first read replaces the broad scalar preview and root index when it fits; unknown schemas or a tight reference budget keep generic discovery. Warm @@ -209,6 +213,8 @@ does not prove complete source delivery, model retention or fresh authority. 执行/结算契约前仍须完整读取。未知 schema、过小引用预算和 warm delta 保留既有路线, 关闭原生传输的输出不变;完整恢复和原 SHA 校验均保留。只读的 `--initial-read` 不与 显式选读或索引混用;续页保留短选项和固定的选读摘要,profile 改变则拒绝混读。 +首读可把已经完整选中的同级内容合并为其完整父节点,减少地址重复;任何未选中的 +可选或未知内容都会阻止合并,选中事实和义务不变。显式选读和索引仍保留原地址。 首读也给出现有验收材料地址和当前结算义务。仅明确 `required=false` 的附件延后完整 说明与模板,使用前须读原附件;未知计划保留全文。首读分页不重复正文中的指针, 选读摘要仍阻止将它记作完整输入。 diff --git a/loopx/control_plane/quota/context_selection.ts b/loopx/control_plane/quota/context_selection.ts index 835a1c1503..6bb8a850b5 100644 --- a/loopx/control_plane/quota/context_selection.ts +++ b/loopx/control_plane/quota/context_selection.ts @@ -3,13 +3,52 @@ import {requireJsonObject} from "../runtime_decode.ts"; import {EffectRuntimeRequestError} from "../effect_runtime_errors.ts"; import type {JsonObject} from "../effect_program.ts"; +/** Initial navigation may share a complete parent instead of repeating every + * child address. The selected facts stay exact; unselected children prevent + * sharing. Explicit selectors and discovery keep their original addresses. */ +function coalescedInitialPointers(context: JsonObject, pointers: string[]): string[] { + if (pointers.length < 2) return pointers; + const selected = new Set(pointers), positions = new Map(), visited = new Set(); + pointers.forEach((pointer, position) => { + if (!positions.has(pointer)) positions.set(pointer, position); + for (let index = 0; index < pointer.length; index++) { + if (pointer[index] !== "/") continue; + const parent = pointer.slice(0, index); + if (!positions.has(parent)) positions.set(parent, position); + } + }); + const visit = (value: unknown, pointer: string): {complete: boolean; pointers: string[]} => { + if (selected.has(pointer)) { + visited.add(pointer); + return {complete: true, pointers: [pointer]}; + } + if (!positions.has(pointer) || value === null || typeof value !== "object") + return {complete: false, pointers: []}; + const children = Object.entries(value).map(([key, child]) => + visit(child, pointer + "/" + key.replaceAll("~", "~0").replaceAll("/", "~1"))); + const complete = children.length > 0 && children.every(child => child.complete); + const fields = children.flatMap(child => child.pointers); + // A single child needs no wrapper; grouping it can increase output bytes. + return {complete, pointers: complete && fields.length > 1 ? [pointer] : fields}; + }; + const result = visit(context, "").pointers; + // Keep malformed/missing or overlapping addresses for the ordinary validator; + // an optimization must not turn a rejected read into a successful subset. + // A merged parent takes its first selected child's navigation position. + return visited.size === selected.size + ? result.sort((left, right) => positions.get(left)! - positions.get(right)!) : pointers; +} + export function capturedContextSelection(request: JsonObject, initialPointers: string[] = []): JsonObject { const context = requireJsonObject(request.context, "captured context"); if (request.initial_read !== undefined && typeof request.initial_read !== "boolean") throw new EffectRuntimeRequestError("initial-read must be boolean"); if (request.initial_read === true && (request.select !== undefined || request.list_fields === true || request.list_depth !== undefined)) throw new EffectRuntimeRequestError("initial-read cannot be combined with select or list"); - const selectors = request.initial_read === true ? initialPointers : request.select; + const rootDiscovery = request.list_fields === true && Array.isArray(request.select) + && request.select.length === 1 && request.select[0] === ""; + const initial = request.initial_read === true || rootDiscovery ? coalescedInitialPointers(context, initialPointers) : []; + const selectors = request.initial_read === true ? initial : request.select; if (request.initial_read === true && !(selectors as string[]).length) throw new EffectRuntimeRequestError("initial-read is unavailable for this capture schema; use generic discovery"); if (!Array.isArray(selectors) || !selectors.length || selectors.some(value => typeof value !== "string")) @@ -64,7 +103,7 @@ export function capturedContextSelection(request: JsonObject, initialPointers: s } return {pointer, path}; }); - const initial = request.list_fields === true && pointers.length === 1 && pointers[0] === "" - ? initialPointers : []; - return {selections, ...(initial.length ? {initial_read_pointers: initial} : {})}; + const navigation = request.list_fields === true && pointers.length === 1 && pointers[0] === "" + ? initial : []; + return {selections, ...(navigation.length ? {initial_read_pointers: navigation} : {})}; } diff --git a/tests/control_plane/codex_checkpoint_fixture.py b/tests/control_plane/codex_checkpoint_fixture.py index f2fcff78cc..c852b5d7ef 100644 --- a/tests/control_plane/codex_checkpoint_fixture.py +++ b/tests/control_plane/codex_checkpoint_fixture.py @@ -11,6 +11,22 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer +def selected_context_values(content): + """Address selected facts independently of complete parent packaging.""" + values = {} + + def visit(pointer, value): + values[pointer] = value + if isinstance(value, (dict, list)): + children = value.items() if isinstance(value, dict) else enumerate(value) + for key, child in children: + visit(pointer + "/" + str(key).replace("~", "~0").replace("/", "~1"), child) + + for item in json.loads(content)["selections"]: + visit(item["pointer"], item["value"]) + return values + + def delivered_context(request, digest): """Independent byte-map oracle over actual native model input.""" import hashlib diff --git a/tests/control_plane/test_context_first_read.py b/tests/control_plane/test_context_first_read.py index 93c25d528e..cba4ecab75 100644 --- a/tests/control_plane/test_context_first_read.py +++ b/tests/control_plane/test_context_first_read.py @@ -13,6 +13,7 @@ from loopx.cli_commands.quota_capture import handle_native_context_command from loopx.control_plane.turn_driver import codex_context from loopx.control_plane.turn_driver.decision_capture import capture_page_range +from tests.control_plane.codex_checkpoint_fixture import selected_context_values @pytest.fixture @@ -68,7 +69,73 @@ def read_selection(command): offset=0, page_bytes=64000, max_page_output_bytes=64000) page = read_context_capture(args) assert page["ok"] and page["eof"] - return page, {item["pointer"]: item["value"] for item in json.loads(page["content"])["selections"]} + return page, selected_context_values(page["content"]) + + +def test_initial_read_coalesces_whole_contract_with_exact_values_and_unknown_obligations(tmp_path, decision): + cli = decision["interaction_contract"]["cli_channel"] + cli["settlement_plan"]["ordered_steps"] = [{ + "kind": "durable_writeback", "precondition": "Keep the original identity and final stop.", + "unknown_obligation": {"allowed": False, "empty": [], "a/b~c": "Preserve this selected constraint."}, + "measurement": [2**60 + 1, 1.0, -0.0], + }] + view = native_context_capture_view(decision, tmp_path) + page, values = read_selection(view["first_read_command"]) + assert values["/interaction_contract/cli_channel"] == cli + pointers = [item["pointer"] for item in json.loads(page["content"])["selections"]] + assert "/interaction_contract/cli_channel" in pointers + assert not any(pointer.startswith("/interaction_contract/cli_channel/") for pointer in pointers) + assert str(2**60 + 1) in page["content"] and "1.0,-0.0" in page["content"] + # The complete source, including unrelated deferred evidence, is unchanged. + raw = Path(view["recovery"]["path"]).read_bytes() + assert json.loads(raw) == decision + assert hashlib.sha256(raw).hexdigest() == page["sha256"] + assert "older-evidence-" not in page["content"] + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_advertised_native_reader_keeps_the_complete_current_contract_across_pages(tmp_path, monkeypatch, provider): + from test_quota_settlement_cli import ( + AGENT_ID, GOAL_ID, TODO_ID, _heartbeat_receipt_count, _initialize_fixture_authority, + _run_cli, _spend_run_count, _write_fixture, + ) + from tests.control_plane.canonical_authority_fixture import isolate_sqlite_runtime + from loopx.control_plane.turn_driver.decision_capture import read_context_read_reference + + isolate_sqlite_runtime(tmp_path, monkeypatch) + monkeypatch.delenv("CODEX_THREAD_ID", raising=False) + project, runtime, registry = _write_fixture(tmp_path) + _initialize_fixture_authority(project, runtime, provider=provider, handoff_mode="hard_lease") + state = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" + original_state = state.read_bytes() + turn_id = "native-first-read" + code, view = _run_cli(registry, runtime, "quota", "should-run", "--codex-app", + "--context-delivery", "on", "--goal-id", GOAL_ID, "--agent-id", AGENT_ID, + "--todo-id", TODO_ID, "--turn-instance-id", turn_id, cwd=project) + assert code == 0 and view["mode"] == "full", view + _, reference = read_context_read_reference(view["recovery"]["reference_file"]) + raw = Path(reference["path"]).read_bytes() + source = json.loads(raw) + words = shlex.split(view["first_read_command"]) + for option in ("--page-bytes", "--max-page-output-bytes"): + words[words.index(option) + 1] = "4500" + content = [] + for _ in range(20): + code, page = _run_cli(registry, runtime, *words[words.index("quota"):], cwd=project) + assert code == 0 and page["ok"], page + assert len((json.dumps(page, ensure_ascii=False, separators=(",", ":")) + "\n").encode()) <= 4500 + assert capture_page_range(page, raw=raw, digest=reference["sha256"]) is None + content.append(page["content"]) + if page["eof"]: + break + words = shlex.split(page["next_command"]) + assert page["eof"] and len(content) > 1 + values = selected_context_values("".join(content)) + assert values["/interaction_contract/cli_channel"] == source["interaction_contract"]["cli_channel"] + assert values["/heartbeat_receipt"] == source["heartbeat_receipt"] + assert values["/selected_todo"] == source["selected_todo"] + assert _heartbeat_receipt_count(runtime, turn_id) == 1 and _spend_run_count(runtime) == 0 + assert Path(reference["path"]).read_bytes() == raw and state.read_bytes() == original_state def test_initial_read_keeps_current_constraints_whole_and_history_reachable(tmp_path, decision): diff --git a/tests/control_plane_ts/context_selection.test.ts b/tests/control_plane_ts/context_selection.test.ts index 856e4ba416..5bc927e599 100644 --- a/tests/control_plane_ts/context_selection.test.ts +++ b/tests/control_plane_ts/context_selection.test.ts @@ -2,6 +2,7 @@ import assert from "node:assert/strict"; import {test} from "node:test"; import {evaluateTurnEnvelope} from "../../loopx/control_plane/quota/turn_envelope.ts"; import {effectRuntimeErrorPayload} from "../../loopx/control_plane/effect_runtime_errors.ts"; +import {capturedContextSelection} from "../../loopx/control_plane/quota/context_selection.ts"; const select = (context: object, pointers: unknown) => evaluateTurnEnvelope({operation: "context_select", context, select: pointers}); @@ -111,7 +112,7 @@ test("full decision discovery points to current controls and whole source obliga for (const pointer of ["/execution_obligation", "/goal_boundary", "/selected_todo", "/interaction_contract/agent_channel/required_reads", "/interaction_contract/cli_channel/spend_allowed_now", "/interaction_contract/agent_channel/work_context/sources/0/content/text"]) - assert.ok(pointers.includes(pointer), pointer); + assert.ok(pointers.some(parent => pointer === parent || pointer.startsWith(parent + "/")), pointer); assert.ok(!pointers.some(pointer => /writeback_results|full_criteria/.test(pointer))); assert.doesNotThrow(() => select(context, pointers)); // Discovery is not a claim that omitted criteria were read or are optional. @@ -122,6 +123,68 @@ test("full decision discovery points to current controls and whole source obliga assert.deepEqual(evaluateTurnEnvelope({operation: "context_select", context, initial_read: true}), select(context, pointers)); }); +// Compare the selected facts independently of their parent/child packaging. +// Empty containers are facts too; neither omission nor accidental expansion is equivalent. +function selectedFacts(context: unknown, selections: {path: (string | number)[]}[]): string[] { + const facts = new Set(); + const visit = (value: unknown, path: (string | number)[]) => { + if (value === null || typeof value !== "object" || !Object.keys(value).length) { + facts.add(JSON.stringify([path, value])); + return; + } + for (const [key, child] of Object.entries(value)) + visit(child, [...path, Array.isArray(value) ? Number(key) : key]); + }; + for (const selection of selections) { + let value = context; + for (const key of selection.path) value = (value as Record)[key]; + visit(value, selection.path); + } + return [...facts].sort(); +} + +test("initial navigation coalesces complete branches without changing selected facts or explicit readers", () => { + const context = {facts: [{"a/b": "Keep the final stop.", "~": null}, {allowed: false, evidence: []}], + single: {only: "A single address need not grow a container."}, unread: {history: "optional body"}}; + // The owner's discovery order differs from object key order. A merged parent + // occupies its first selected child's position, preserving that priority. + const pointers = ["/single/only", "/facts/1/evidence", "/facts/0/a~1b", "/facts/0/~0", "/facts/1/allowed"]; + const before = structuredClone(context); + const explicit = select(context, pointers); + const initial = capturedContextSelection({context, initial_read: true}, pointers); + assert.deepEqual(initial, select(context, ["/facts", "/single/only"])); + assert.deepEqual(selectedFacts(context, initial.selections as {path: (string | number)[]}[]), + selectedFacts(context, explicit.selections as {path: (string | number)[]}[])); + assert.deepEqual((explicit.selections as {pointer: string}[]).map(row => row.pointer), [...pointers].sort()); + const discovery = capturedContextSelection({context, select: [""], list_fields: true}, pointers); + assert.deepEqual(discovery.initial_read_pointers, ["/single/only", "/facts"]); + assert.deepEqual(discovery.selections, [{pointer: "", path: [], children: [ + {pointer: "/facts", path: ["facts"]}, {pointer: "/single", path: ["single"]}, {pointer: "/unread", path: ["unread"]}, + ]}]); + assert.deepEqual(context, before); +}); + +test("ordinary selectors do not inspect initial-navigation data", () => { + const context = {facts: {allowed: false}}; + const unused = new Proxy([], {get() { throw new Error("Initial navigation was not requested"); }}); + assert.deepEqual(capturedContextSelection({context, select: ["/facts"]}, unused), select(context, ["/facts"])); + assert.deepEqual(capturedContextSelection({context, select: ["/facts"], list_fields: true}, unused), + evaluateTurnEnvelope({operation: "context_select", context, select: ["/facts"], list_fields: true})); +}); + +test("partial coverage never expands unknown or optional facts through a parent", () => { + const context = {controls: {allowed: false, instruction: "Preserve this constraint.", unknown: "not selected"}, + attachment: {required: false, command: "read the attachment before use", recipe: "deferred recipe"}, + required: {unknown_obligation: "This selected unknown obligation stays whole."}}; + const pointers = ["/controls/allowed", "/controls/instruction", "/attachment/required", "/attachment/command", "/required"]; + const initial = capturedContextSelection({context, initial_read: true}, pointers); + assert.deepEqual(initial, select(context, pointers)); + const facts = selectedFacts(context, initial.selections as {path: (string | number)[]}[]); + assert.ok(facts.some(value => value.includes("selected unknown obligation"))); + assert.ok(!facts.some(value => /not selected|deferred recipe/.test(value))); + assert.throws(() => capturedContextSelection({context, initial_read: true}, [...pointers, "/missing"]), /does not exist/); +}); + test("unknown decision schemas and ordinary selections keep generic discovery", () => { for (const context of [{value: true}, {interaction_contract: {schema_version: "future"}}]) { const result = evaluateTurnEnvelope({operation: "context_select", context, select: [""], list_fields: true}); diff --git a/tests/test_delegation_turn_context.py b/tests/test_delegation_turn_context.py index cd88e6be24..da7d1d798b 100644 --- a/tests/test_delegation_turn_context.py +++ b/tests/test_delegation_turn_context.py @@ -155,7 +155,9 @@ def test_actual_cli_guards_and_host_envelopes_preserve_the_agenda(tmp_path, monk assert words[0] == "loopx" code, page = run_json_cli_result(*words[1:], registry_path=registry, runtime_root=root) assert code == 0 and page["ok"] and page["eof"], page - values = {row["pointer"]: row["value"] for row in json.loads(page["content"])["selections"]} + from tests.control_plane.codex_checkpoint_fixture import selected_context_values + + values = selected_context_values(page["content"]) pointer = next(key for key in values if key.endswith("/content/unfinished_delegations")) content = {"unfinished_delegations": values[pointer], "read_complete": values[pointer.removesuffix("unfinished_delegations") + "read_complete"]} From 0605a5585d024b8a2508a3f83ebea20413157e0b Mon Sep 17 00:00:00 2001 From: song Date: Tue, 3 Nov 2026 00:12:34 +0800 Subject: [PATCH 2/2] fix(context): preserve discovery for duplicate root selectors Signed-off-by: song --- .../control_plane/quota/context_selection.ts | 8 ++++---- .../context_selection.test.ts | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/loopx/control_plane/quota/context_selection.ts b/loopx/control_plane/quota/context_selection.ts index 6bb8a850b5..f763844db0 100644 --- a/loopx/control_plane/quota/context_selection.ts +++ b/loopx/control_plane/quota/context_selection.ts @@ -45,9 +45,7 @@ export function capturedContextSelection(request: JsonObject, initialPointers: s throw new EffectRuntimeRequestError("initial-read must be boolean"); if (request.initial_read === true && (request.select !== undefined || request.list_fields === true || request.list_depth !== undefined)) throw new EffectRuntimeRequestError("initial-read cannot be combined with select or list"); - const rootDiscovery = request.list_fields === true && Array.isArray(request.select) - && request.select.length === 1 && request.select[0] === ""; - const initial = request.initial_read === true || rootDiscovery ? coalescedInitialPointers(context, initialPointers) : []; + const initial = request.initial_read === true ? coalescedInitialPointers(context, initialPointers) : []; const selectors = request.initial_read === true ? initial : request.select; if (request.initial_read === true && !(selectors as string[]).length) throw new EffectRuntimeRequestError("initial-read is unavailable for this capture schema; use generic discovery"); @@ -103,7 +101,9 @@ export function capturedContextSelection(request: JsonObject, initialPointers: s } return {pointer, path}; }); + // Discovery follows the validated, deduplicated selection. Repeating the root + // cannot drop its navigation; ordinary explicit reads need no initial plan. const navigation = request.list_fields === true && pointers.length === 1 && pointers[0] === "" - ? initial : []; + ? coalescedInitialPointers(context, initialPointers) : []; return {selections, ...(navigation.length ? {initial_read_pointers: navigation} : {})}; } diff --git a/tests/control_plane_ts/context_selection.test.ts b/tests/control_plane_ts/context_selection.test.ts index 5bc927e599..54d7bd4bfe 100644 --- a/tests/control_plane_ts/context_selection.test.ts +++ b/tests/control_plane_ts/context_selection.test.ts @@ -167,9 +167,22 @@ test("initial navigation coalesces complete branches without changing selected f test("ordinary selectors do not inspect initial-navigation data", () => { const context = {facts: {allowed: false}}; const unused = new Proxy([], {get() { throw new Error("Initial navigation was not requested"); }}); - assert.deepEqual(capturedContextSelection({context, select: ["/facts"]}, unused), select(context, ["/facts"])); - assert.deepEqual(capturedContextSelection({context, select: ["/facts"], list_fields: true}, unused), - evaluateTurnEnvelope({operation: "context_select", context, select: ["/facts"], list_fields: true})); + for (const pointers of [["/facts"], ["/facts", "/facts"]]) { + assert.deepEqual(capturedContextSelection({context, select: pointers}, unused), select(context, pointers)); + assert.deepEqual(capturedContextSelection({context, select: pointers, list_fields: true}, unused), + evaluateTurnEnvelope({operation: "context_select", context, select: pointers, list_fields: true})); + } +}); + +test("duplicate root selectors retain the same discovery navigation", () => { + const context = {facts: {allowed: false, stop: "Preserve the final boundary."}, unread: "optional material"}; + const initial = ["/facts/allowed", "/facts/stop"]; + const single = capturedContextSelection({context, select: [""], list_fields: true}, initial); + const repeated = capturedContextSelection({context, select: ["", ""], list_fields: true}, initial); + assert.deepEqual(single.initial_read_pointers, ["/facts"]); + assert.deepEqual(repeated, single); + assert.throws(() => capturedContextSelection({context, select: ["", "", "/missing"], list_fields: true}, initial), + /does not exist/); }); test("partial coverage never expands unknown or optional facts through a parent", () => {