Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions loopx/capabilities/manager_context/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,13 @@ evidence records; requested detail and material gaps remain in the answer.
This is authoring guidance, not a summary service or a delivery-time rewrite,
and does not certify the quality of a receiver's result.

For a legacy v1 request, `manager-inbox report --request-revision 1` is equivalent
to omitting the revision. It publishes the receiver's original result without
creating request-control state or granting correction authority; other explicit
revisions are rejected. An `--update-id` still requires an initial conclusion.
This compatibility does not replay an earlier failed command or relax original
delivery proof, audience, immutable-result or unknown-effect checks.

For longer work, `--phase decision` optionally returns a meaningful intermediate
update. A ready conclusion supersedes an unsent intermediate update. Do not send
one notification per poll, quote private deliberation, or claim an implementation
Expand Down
7 changes: 6 additions & 1 deletion loopx/control_plane/collaboration/result_publication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,12 @@ export function collaborationResultObservations(request: JsonObject, observation
export function planCollaborationResult(params: JsonObject): JsonObject {
const request = requireJsonObject(params.request, "result request");
const phase = requireStringLiteral(params.phase, ["decision", "conclusion"], "result phase");
const controlled = request.request_control !== undefined || params.request_revision != null;
const legacyOriginal = request.schema_version === "loopx_manager_context_entry_v1"
&& request.request_control === undefined;
if (legacyOriginal && params.request_revision != null && params.request_revision !== 1) {
throw new EffectRuntimeRequestError("legacy requests only support original result revision 1");
}
const controlled = !legacyOriginal && (request.request_control !== undefined || params.request_revision != null);
// Old commands always name original revision 1. They never inherit the new
// head merely because a correction arrived while their result was in flight.
const context = controlled ? requestRevisionReceiptContext(request, params.request_revision, params.legacy_assessment ?? null) : null;
Expand Down
30 changes: 30 additions & 0 deletions tests/control_plane_ts/collaboration_result_publication.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,36 @@ function observation(key: string, receipt: JsonObject): JsonObject {
}
const input = { request, phase: "conclusion", text: "Review finished.", decision: "adopt", update_id: "review-finished", results: [observation("conclusion", initial)] };

test("legacy original revision is an optional result selector, not request control", () => {
const legacy = {...request, schema_version: "loopx_manager_context_entry_v1"};
for (const phase of ["decision", "conclusion"]) {
const source = {...input, request: legacy, phase, update_id: null, results: []};
const expected = planCollaborationResult(source);
const explicit = planCollaborationResult({...source, request_revision: 1});
assert.deepEqual(explicit, expected);
const value = explicit.value as JsonObject;
assert.equal(Object.hasOwn(value, "request_revision"), false);
assert.equal(Object.hasOwn(value, "request_revision_digest"), false);
assert.equal(planCollaborationResult({...source, request_revision: 1,
results: [observation(phase, value)]}).replay, true);
assert.equal(planCollaborationResult({...source,
results: [observation(phase, value)]}).replay, true);
}
});

test("legacy revision selection cannot create corrections or bypass immutable results", () => {
const source = {...input, request: {...request, schema_version: "loopx_manager_context_entry_v1"},
request_revision: 1};
for (const request_revision of [0, 2, -1, 1.5, "1", true, {}]) {
assert.throws(() => planCollaborationResult({...source, request_revision}), /original result revision 1/);
}
assert.equal(planCollaborationResult(source).result_key, "conclusion-00000001");
assert.throws(() => planCollaborationResult({...source, update_id: null}), /conflicting/);
assert.throws(() => planCollaborationResult({...source, results: []}), /initial conclusion/);
assert.throws(() => planCollaborationResult({...source,
request: {...source.request, request_control: {}}}), /exact-instance/);
});

test("a later result appends without replacing the initial conclusion", () => {
const plan = planCollaborationResult(input);
assert.equal(plan.result_key, "conclusion-00000001");
Expand Down
47 changes: 47 additions & 0 deletions tests/test_manager_context_roundtrip.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,53 @@ def test_owner_return_does_not_create_source_policy(flow, project):
assert not list(_root(root).glob("policy*"))


def test_cli_legacy_explicit_original_revision_returns_once(flow, capsys):
from loopx.cli import main

root, registry, store, create = flow
session, turn, receipt = create()
rid = receipt["request_id"]
acknowledge(root, "research", "worker", rid, "adopt", "Checked the original request")
text = "Checked the original request and completed its existing work."
argv = ["--registry", str(registry), "--runtime-root", str(root), "--format", "json",
"manager-inbox", "report", "--goal-id", "research", "--agent-id", "worker",
"--request-id", rid, "--reply-text", text]
assert main([*argv, "--request-revision", "1"]) == 0
assert json.loads(capsys.readouterr().out)["status"] == "queued_for_original_conversation"
assert main(argv) == 0
assert json.loads(capsys.readouterr().out)["ok"] is True
assert drain(root, registry, store, None) == 1
assert drain(root, registry, ChatSessionStore(root), None) == 0
returned = [row for row in store.messages(session["session_id"])
if row.get("origin") == "manager_followup"]
assert len(returned) == 1 and returned[0]["turn_id"] == turn["turn_id"]
assert returned[0]["text"].split("\n\n", 1)[1] == text
assert reply_status(root, receipt)[0]["status"] == "delivered"
assert not list(_root(root).glob("policy*"))
entry = next((_root(root) / "entries").glob("*/*.json"))
assert "request_control" not in json.loads(entry.read_text())


def test_legacy_original_revision_does_not_repair_mismatched_initial_delivery(flow):
root, registry, store, create = flow
session, turn, receipt = create(True)
rid = receipt["request_id"]
store.update_turn(session["session_id"], turn["turn_id"], response={
"context_handoff_receipt": {**receipt, "agent_id": "another-worker"},
})
acknowledge(root, "research", "worker", rid, "adopt", "Checked")
report(root, "research", "worker", rid, "conclusion", "Checked the original request.",
registry=registry, request_revision=1)
calls = []
drain(root, registry, store, lambda *args: calls.append(args))
assert calls == []
assert not any(row.get("origin") == "manager_followup"
for row in store.messages(session["session_id"]))
assert reply_status(root, receipt)[0]["status"] != "delivered"
assert store.load_turn(session["session_id"], turn["turn_id"])["response"][
"context_handoff_receipt"]["agent_id"] == "another-worker"


@pytest.mark.parametrize("audience", ["local_owner", "bound_owner"])
@pytest.mark.parametrize("drift", ["channel", "goal", "context", "context_missing", "context_null"])
def test_recovered_project_identity_cannot_publish_as_a_manager(flow, audience, drift):
Expand Down
Loading