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
11 changes: 11 additions & 0 deletions docs/reference/required-work-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ branch identity, selection, status, resume facts and orchestration gates remain
current. Reordering, refutation, removed evidence and changed guidance reconstruct
the exact current source; no timestamp, revision or text is normalized away.

Within successfully read cumulative Goal outcomes, source summaries, review notes
and string evidence references also use exact reuse. They are provenance and
explanations, not current acceptance decisions. Requirement status, review
conclusion, basis currency, independent verification, revisions and unknown fields
remain literal current input. Changed, removed or reordered evidence reconstructs
the complete current source; failed or incomplete sources retain full delivery.

Object edits may group complete current fields in `values`, alongside recursive
`changed` edits and `removed` keys. Array edits use exact indices in `items` and
`values`, with `length` for resizing. These literals are present in the current
Expand Down Expand Up @@ -434,6 +441,10 @@ TurnEnvelope。在基线连续性成立的口径内,两入口仍逐轮发送 `
条件、身份、命令与未知控制仍逐轮发送。Explore owner 登记分支证据审计与证据关联
模板的精确复用;分支身份、选择、状态、恢复事实与编排门仍是当前输入。重排、反证、
移除与指导变更完整重建当前来源,不规范化时间戳、修订或正文。
已成功完整读取的累计 Goal outcome 中,来源说明、复核笔记及字符串证据引用也按精确
差分传输。它们是来源与解释;要求状态、复核结论、依据是否当前、独立验证标记、修订
及未知字段仍逐轮完整发送。证据变化、移除或重排后仍须精确还原当前来源;来源失败或
读取不完整时保留完整投递。
对象编辑可以在 `values` 中合并携带本轮完整字段,同时保留递归 `changed` 与删除
`removed`;数组的 `items` 与 `values` 使用精确索引,`length` 调整长度。
字段仍在本轮输入中,只减少重复 wrapper;codec 比较普通编辑、合并字段
Expand Down
22 changes: 20 additions & 2 deletions loopx/control_plane/quota/context_delivery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function parentAt(root: JsonObject, path: Path): Record<string | number, unknown
}

/** Registered requirement fields and capability evidence subtrees are reusable. Revisions, source
* failures, claims, reviews, grants, current gates and unknown fields stay fresh.
* failures, claims, review conclusions, grants, current gates and unknown fields stay fresh.
* Source-array order is transport layout, never the identity of a requirement. */
function splitContext(context: JsonObject): Split {
const fresh = clone(context), identities = new Set<string>();
Expand Down Expand Up @@ -112,6 +112,16 @@ function splitContext(context: JsonObject): Split {
seen.add(outcome.id);
const path = [...prefix, "goal_acceptance_contract", "outcome_requirements", "outcomes", j];
add(JSON.stringify([kind, outcome.id, "objective"]), [...path, "objective"]);
// Cumulative provenance and review explanations are evidence, not a
// current acceptance decision. Reuse only these registered fields;
// conclusions, basis currency, independent verification and unknown
// controls still reach every input literally.
if (Array.isArray(outcome.sources)) {
outcome.sources.forEach((entry, index) => {
if (jsonObject(entry)) add(JSON.stringify([kind, outcome.id, "source_summary", index]),
[...path, "sources", index, "summary"]);
});
}
const requirements = outcome.requirements;
if (!Array.isArray(requirements)) continue;
const requirementIds = new Set<string>();
Expand All @@ -121,7 +131,15 @@ function splitContext(context: JsonObject): Split {
valid = false; continue;
}
requirementIds.add(requirement.id);
add(JSON.stringify([kind, outcome.id, requirement.id, "description"]), [...path, "requirements", k, "description"]);
const requirementPath = [...path, "requirements", k];
const identity = [kind, outcome.id, requirement.id];
add(JSON.stringify([...identity, "description"]), [...requirementPath, "description"]);
const review = jsonObject(requirement.agent_review);
if (review) {
add(JSON.stringify([...identity, "review_note"]), [...requirementPath, "agent_review", "note"]);
if (Array.isArray(review.evidence_refs) && review.evidence_refs.every(ref => typeof ref === "string"))
add(JSON.stringify([...identity, "review_evidence"]), [...requirementPath, "agent_review", "evidence_refs"], true);
}
}
}
} else {
Expand Down
38 changes: 38 additions & 0 deletions tests/control_plane/test_goal_outcome_refinement.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,44 @@ def test_review_only_cli_retains_origins_and_requires_provenance_for_new_require
assert cli("inspect")[1] == before


def test_cumulative_review_evidence_reuses_real_quota_sources_without_hiding_new_judgment(acceptance_goal):
from loopx.control_plane.quota.turn_envelope import prepare_context_delivery, restore_context_delivery

project, _, cli, run = acceptance_goal
declared = refinement([{"id": f"r{i}", "description": "Retain the original requirement"}
for i in range(12)], "message:first")
assert record(project, cli, declared, "declare")[0][0] == 0
review = {"outcome_id": "tour", "review": [{"id": r["id"], "conclusion": "uncertain",
"evidence_refs": ["artifact:validation/" + "segment/" * 12],
"note": ("Retain the measured result and its applicability. " * 10).strip()} for r in declared["requirements"]]}
(code, result), _ = record(project, cli, review, "review")
assert code == 0, result

def capture():
code, packet = run("quota", "should-run", "--goal-id", "goal-acceptance", "--agent-id", "agent-a")
assert code == 0, packet
return packet["interaction_contract"]["agent_channel"]["work_context"]

original = capture()
scope = dict(goal_id="goal-acceptance", agent_id="agent-a", session_id="fixture", context_epoch="cycle")
baseline = prepare_context_delivery(original, scope=scope, capture_id="original")
review["review"][0]["conclusion"] = "unmet"
assert record(project, cli, review, "negative-review")[0][0] == 0
current = capture()
full = prepare_context_delivery(current, scope=scope, capture_id="current")
delta = prepare_context_delivery(current, scope=scope, capture_id="current", baseline=baseline,
retained={"complete": True, "target": baseline["target"]})
assert delta["mode"] == "delta", delta.get("reason")
assert "segment/" not in json.dumps(delta), "unchanged evidence must not be retransmitted"
restored = restore_context_delivery(delta, baseline=baseline, current=full)
assert restored["context"] == current
acceptance = next(s["content"] for s in restored["context"]["sources"] if s.get("source") == "goal_acceptance")
row = acceptance["goal_acceptance_contract"]["outcome_requirements"]["outcomes"][0]["requirements"][0]
assert row["agent_review"]["conclusion"] == "unmet" and row["agent_review"]["evidence_refs"] == review["review"][0]["evidence_refs"]
assert not row["planning_satisfied"] and row["status"] != "accepted"
assert restore_context_delivery(delta, baseline=None, current=full)["context"] == current


def test_typed_outcome_gaps_are_transported_only_to_their_agent():
gap = {"kind": "goal_outcome_uncovered", "agent_id": "author", "outcome_id": "tour", "requirement_ids": ["opening"]}
summary = {"goal_acceptance_contract": {"enabled": False, "outcome_requirements": {"acceptance_gaps": [gap]}}}
Expand Down
55 changes: 55 additions & 0 deletions tests/control_plane_ts/context_delivery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,61 @@ test("fresh control with exact warm bodies; changed source revisions do not rese
assert.deepEqual(baseline.context, original);
});

test("cumulative acceptance evidence reuses exact history while current judgments stay inline on every surface", () => {
const evidence = "artifact:validation/" + "evidence-segment/".repeat(80);
const outcome = {id: "result", objective: "Preserve the original goal.", revision: 1,
sources: [{ref: "owner:original", summary: long, unknown_source_control: {must_read: true}}],
requirements: Array.from({length: 12}, (_, i) => ({id: `r${i}`, description: "Verify the result.",
status: "unverified", planning_satisfied: false,
agent_review: {id: `r${i}`, conclusion: "uncertain", basis_current: false,
independently_verified: false, note: long, evidence_refs: [evidence + i], unknown_review_control: true}}))};
const work = {complete: true, sources: [{source: "goal_acceptance", content: {ok: true,
provider_revision: "current", goal_acceptance_contract: {outcome_requirements: {revision: 1, outcomes: [outcome]}}}}]};
for (const surface of ["work_context", "quota_packet", "turn_envelope"]) {
const prefix = surface === "quota_packet" ? ["interaction_contract", "agent_channel", "work_context"]
: surface === "turn_envelope" ? ["work_context"] : [];
const wrap = (value: unknown): JsonObject => surface === "quota_packet"
? {interaction_contract: {agent_channel: {work_context: value}}}
: surface === "turn_envelope" ? {work_context: value} : value as JsonObject;
const original = wrap(work), baseline = prepareContextDelivery({context: original, scope, capture_id: "a", surface});
const current = structuredClone(work), updated = current.sources[0].content.goal_acceptance_contract.outcome_requirements;
updated.revision = 2;
updated.outcomes[0].requirements[0].agent_review.conclusion = "unmet";
const args = {context: wrap(current), scope, capture_id: "b", surface};
const delta = prepareContextDelivery({...args, baseline, retained: {complete: true, target: baseline.target}});
assert.equal(delta.mode, "delta");
assert.ok(JSON.stringify(delta).length < JSON.stringify(original).length / 8);
const root = [...prefix, "sources", 0, "content", "goal_acceptance_contract", "outcome_requirements"];
assert.equal(inline(delta, [...root, "revision"]), 2);
const row = [...root, "outcomes", 0, "requirements", 0];
for (const [field, expected] of Object.entries(updated.outcomes[0].requirements[0].agent_review))
if (!["note", "evidence_refs"].includes(field)) assert.deepEqual(inline(delta, [...row, "agent_review", field]), expected);
assert.equal(inline(delta, [...row, "status"]), "unverified");
assert.equal(inline(delta, [...row, "planning_satisfied"]), false);
assert.deepEqual(inline(delta, [...root, "outcomes", 0, "sources", 0, "unknown_source_control"]), {must_read: true});
assert.deepEqual(restoreContextDelivery({delivery: delta, baseline, current: prepareContextDelivery(args)}).context, wrap(current));
assert.deepEqual(baseline.context, original);
// Changed, reordered, removed and empty evidence never inherit an old claim.
updated.outcomes[0].sources[0].summary = "A new owner correction.";
updated.outcomes[0].requirements.reverse();
updated.outcomes[0].requirements[0].agent_review.note = "";
updated.outcomes[0].requirements[0].agent_review.evidence_refs = ["artifact:new"];
updated.outcomes[0].requirements[1].agent_review.evidence_refs = [];
delete (updated.outcomes[0].requirements[2].agent_review as JsonObject).note;
const edited = {...args, context: wrap(current)};
const next = prepareContextDelivery({...edited, baseline, retained: {complete: true, target: baseline.target}});
assert.deepEqual(restoreContextDelivery({delivery: next, baseline, current: prepareContextDelivery(edited)}).context, wrap(current));
}
// An unavailable source is not historical evidence eligible for omission.
const unavailable = structuredClone(work) as JsonObject;
((unavailable.sources as JsonObject[])[0].content as JsonObject).read_complete = false;
const initial = full(work), delta = warm(unavailable, initial);
const result = delta.mode === "full" ? delta.context : recover(delta, initial, unavailable).context;
assert.deepEqual(result, unavailable);
if (delta.mode === "delta") assert.equal(inline(delta, ["sources", 0, "content", "goal_acceptance_contract",
"outcome_requirements", "outcomes", 0, "requirements", 0, "agent_review", "note"]), long);
});

test("relocated reusable strings reference the exact base while current controls stay inline", () => {
const original = {registry: {authoring: long}, current_control: {instruction: long}};
const current = {registry: {attachment: {guidance: long}}, current_control: {instruction: long}};
Expand Down
Loading