From ec5a2563dcd352b0d47b184901052251d8c31af7 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 27 Oct 2026 18:07:05 +0800 Subject: [PATCH] fix: admit native child reports on verified replan work Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../capabilities/native_child_admission.ts | 9 +++- .../quota/heartbeat_receipt_identity.ts | 10 ++--- .../quota/settlement_readback.ts | 2 +- .../test_native_child_replan_guard_cli.py | 4 +- .../native_child_admission.test.ts | 42 +++++++++++++++++++ 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/loopx/control_plane/capabilities/native_child_admission.ts b/loopx/control_plane/capabilities/native_child_admission.ts index 03c1d0a1cc..c17143d871 100644 --- a/loopx/control_plane/capabilities/native_child_admission.ts +++ b/loopx/control_plane/capabilities/native_child_admission.ts @@ -13,13 +13,15 @@ export interface NativeChildReportAdmission extends JsonObject { } /** Consume committed admission facts, not a whitelist of current status labels. - * Identity and phase must first be verified by the settlement readback owner. */ + * Identity, phase and any semantic replan obligation must first be verified by + * the settlement readback owner. Reporting does not grant ordinary delivery. */ export function nativeChildReportAdmission( guardDetails: JsonObject, guardStatus: unknown, effectId: string, phase: ReceiptBoundReplayPhase, closeoutStarted: boolean, + semanticReplanObligationId: string | null = null, ): NativeChildReportAdmission { const projectionAbsent = ["must_attempt_work", "delivery_allowed", "quiet_noop_allowed"] .every((field) => !Object.hasOwn(guardDetails, field)); @@ -29,9 +31,12 @@ export function nativeChildReportAdmission( (guardStatus === "normal_run" || guardStatus === "turn_run_once") && (guardDetails.ok === undefined || guardDetails.ok === true) && (guardDetails.should_run === undefined || guardDetails.should_run === true); + const workRouteAdmitted = guardDetails.delivery_allowed === true || + (guardDetails.delivery_allowed === false && guardDetails.interaction_mode === "autonomous_replan" && + semanticReplanObligationId !== null); const admitted = legacyAdmission || ( guardDetails.ok === true && guardDetails.should_run === true && - guardDetails.must_attempt_work === true && guardDetails.delivery_allowed === true && + guardDetails.must_attempt_work === true && workRouteAdmitted && guardDetails.quiet_noop_allowed === false ); const open = phase === "open" && !closeoutStarted; diff --git a/loopx/control_plane/quota/heartbeat_receipt_identity.ts b/loopx/control_plane/quota/heartbeat_receipt_identity.ts index 193a47b06e..0166797905 100644 --- a/loopx/control_plane/quota/heartbeat_receipt_identity.ts +++ b/loopx/control_plane/quota/heartbeat_receipt_identity.ts @@ -207,13 +207,13 @@ export function heartbeatWorkRequalification( if (identity.todo_id !== null && currentDetails.selected_todo_id !== identity.todo_id) return replay; // No legacy/status-label compatibility for new qualifications: all modern // work facts must be explicit. This reuses the report-admission predicate. - if (nativeChildReportAdmission(currentDetails, null, identity.effect_id, phase, false) - .report_permission !== "new_operation") return replay; const oldDetails = heartbeatReceiptDetails(original); - if (nativeChildReportAdmission(oldDetails, original.status, identity.effect_id, phase, false) - .report_permission === "new_operation") return replay; const originalReplan = normalizeHeartbeatReplanObligationId(oldDetails.semantic_replan_obligation_id); const currentReplan = normalizeHeartbeatReplanObligationId(currentDetails.semantic_replan_obligation_id); + if (nativeChildReportAdmission(currentDetails, null, identity.effect_id, phase, false, currentReplan) + .report_permission !== "new_operation") return replay; + if (nativeChildReportAdmission(oldDetails, original.status, identity.effect_id, phase, false, originalReplan) + .report_permission === "new_operation") return replay; if (currentReplan !== null && currentReplan !== originalReplan) { throw receiptIdentityConflict("work requalification cannot replace the original semantic replan guard"); } @@ -221,7 +221,7 @@ export function heartbeatWorkRequalification( // The first positive work admission owns these facts. A repair-only // receipt's earlier selection cannot describe the newly admitted work. for (const field of ["ok", "should_run", "must_attempt_work", "delivery_allowed", "quiet_noop_allowed", - "admission_resume_when", "admission_resume_ready"]) { + "interaction_mode", "admission_resume_when", "admission_resume_ready"]) { if (currentDetails[field] !== undefined) qualified[field] = currentDetails[field]!; else delete qualified[field]; } diff --git a/loopx/control_plane/quota/settlement_readback.ts b/loopx/control_plane/quota/settlement_readback.ts index 7cd43d8e5f..bc8c459636 100644 --- a/loopx/control_plane/quota/settlement_readback.ts +++ b/loopx/control_plane/quota/settlement_readback.ts @@ -1342,7 +1342,7 @@ function readQuotaSettlementFromRequest( receiptDetails.should_run === true && receiptDetails.delivery_allowed === false, native_child_admission: nativeChildReportAdmission( receiptDetails, heartbeatReceipt.status, identity.effect_id, replayPhase, - closeoutStarted, + closeoutStarted, normalizeReplanObligationId(semanticReplanGuard.selected_obligation_id), ), ...(request.heartbeat_reentry_guard === null ? {} : { heartbeat_reentry_qualification: heartbeatWorkRequalification(heartbeatReceipt, diff --git a/tests/control_plane/test_native_child_replan_guard_cli.py b/tests/control_plane/test_native_child_replan_guard_cli.py index 7c4572f696..ec132cd4e0 100644 --- a/tests/control_plane/test_native_child_replan_guard_cli.py +++ b/tests/control_plane/test_native_child_replan_guard_cli.py @@ -124,7 +124,9 @@ def test_legal_replan_reports_native_child_without_settling_parent( identity = guard["heartbeat_receipt"]["settlement_identity"] assert identity.get("todo_id") == (TODO if todo_bound else None) assert bool(identity.get("replan_obligation_id")) is not todo_bound - assert guard["interaction_contract"]["agent_channel"]["delivery_allowed"] is True + assert guard["interaction_contract"]["agent_channel"]["delivery_allowed"] is False + assert guard["interaction_contract"]["agent_channel"]["must_attempt"] is True + assert guard["heartbeat_receipt"]["semantic_replan_obligation_id"] original_index = index.read_bytes() base = ("native-child", "--goal-id", GOAL, "--agent-id", AGENT, "--turn-instance-id", TURN) diff --git a/tests/control_plane_ts/native_child_admission.test.ts b/tests/control_plane_ts/native_child_admission.test.ts index df50bf8540..e210b76cd9 100644 --- a/tests/control_plane_ts/native_child_admission.test.ts +++ b/tests/control_plane_ts/native_child_admission.test.ts @@ -38,6 +38,31 @@ test("late facts are allowed but no new operation once closeout has begun", () = .report_permission, "existing_operation_only"); }); +test("an admitted semantic replan can report without ordinary delivery authority", () => { + const replan = {...proof, interaction_mode: "autonomous_replan", delivery_allowed: false}; + const obligation = "replan-0000000000000001"; + for (const status of ["autonomous_replan_required", "future_work_label"]) { + assert.equal(nativeChildReportAdmission(replan, status, "original", "open", false, obligation) + .report_permission, "new_operation"); + assert.equal(nativeChildReportAdmission(replan, status, "original", "open", true, obligation) + .report_permission, "existing_operation_only"); + for (const phase of ["settlement_pending", "settled"] as const) { + assert.equal(nativeChildReportAdmission(replan, status, "original", phase, false, obligation) + .report_permission, "existing_operation_only"); + } + } + for (const denied of [ + {...replan, ok: false}, {...replan, should_run: false}, {...replan, must_attempt_work: false}, + {...replan, quiet_noop_allowed: true}, {...replan, delivery_allowed: "false"}, + {...replan, interaction_mode: "successor_replan_required"}, + ]) { + assert.equal(nativeChildReportAdmission(denied, "autonomous_replan_required", "original", "open", false, obligation) + .report_permission, "not_admitted"); + } + assert.equal(nativeChildReportAdmission(replan, "autonomous_replan_required", "original", "open", false) + .report_permission, "not_admitted"); +}); + test("legacy ordinary compatibility never overrides partial or negative facts", async (t) => { for (const status of ["normal_run", "turn_run_once"]) { assert.equal(nativeChildReportAdmission({}, status, "original", "open", false) @@ -103,6 +128,23 @@ test("exact-Turn original binding is read without selecting or inferring new wor } }); +test("readback validates a semantic replan obligation before report admission", async () => { + for (const replan of [false, true]) { + const {root, request, identity, event, log} = await fixture(replan); + try { + const details = {...event.details, delivery_allowed: false, interaction_mode: "autonomous_replan", + semantic_replan_obligation_id: "replan-0000000000000001"}; + await writeFile(log, JSON.stringify({...event, details}) + "\n"); + const result = await readQuotaSettlement(request); + assert.deepEqual(result.identity.result.value, settlementIdentityPayload(identity)); + assert.equal(result.native_child_admission.report_permission, "new_operation"); + assert.equal(result.heartbeat_receipt.details.delivery_allowed, false); + await writeFile(log, JSON.stringify({...event, details: {...details, semantic_replan_obligation_id: "malformed"}}) + "\n"); + await assert.rejects(readQuotaSettlement(request), /semantic replan guard is malformed/); + } finally {await rm(root, {recursive: true, force: true});} + } +}); + test("exact binding resolution preserves explicit denials and conflicts", async (t) => { for (const [label, mutate] of [ ["other actor", {agent_id: "other-coordinator"}],