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: 5 additions & 2 deletions loopx/control_plane/goals/outcome_requirements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ export function refineGoalOutcome(head: JsonObject, goalId: string, goalRef: Wir
requireValue(items.length === outcome.requirements.length && items.every(row => reqs.has(row.id)), "review must address every retained requirement exactly once");
outcome.review = {basis_digest: reviewBasis(outcome, todos), items};
}
const state: OutcomeState = {schema_version: GOAL_OUTCOMES_SCHEMA, goal_id: goalId, goal_ref: goalRef, revision: (previous?.revision ?? 0) + 1,
outcomes: [...(previous?.outcomes.filter(o => o.id !== outcomeId) ?? []), outcome]};
// Refinement changes one outcome, not the positions of unrelated requirements
// in canonical snapshots and their derived full/delta context.
const outcomes = previous?.outcomes.map(row => row.id === outcomeId ? outcome : row) ?? [];
if (!old) outcomes.push(outcome);
const state: OutcomeState = {schema_version: GOAL_OUTCOMES_SCHEMA, goal_id: goalId, goal_ref: goalRef, revision: (previous?.revision ?? 0) + 1, outcomes};
// Preserve declaration/review limits; diagnostics count the failing
// envelope, not the combined snapshot or an individual outcome/source list.
const candidate = outcomeBytes(state);
Expand Down
11 changes: 11 additions & 0 deletions tests/control_plane/test_goal_outcome_refinement.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ def test_cumulative_review_evidence_reuses_real_quota_sources_without_hiding_new
"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
companion = {**declared, "outcome_id": "compatibility", "objective": "Keep the compatible export",
"requirements": [{"id": f"tail{i}", "description": ("Keep the compatibility tail. " * 16).strip()}
for i in range(12)]}
(code, companion_result), _ = record(project, cli, companion, "companion")
assert code == 0, companion_result

def capture():
code, packet = run("quota", "should-run", "--goal-id", "goal-acceptance", "--agent-id", "agent-a")
Expand All @@ -141,11 +146,17 @@ def capture():
review["review"][0]["conclusion"] = "unmet"
assert record(project, cli, review, "negative-review")[0][0] == 0
current = capture()
def outcomes(context):
return next(s["content"]["goal_acceptance_contract"]["outcome_requirements"]["outcomes"]
for s in context["sources"] if s.get("source") == "goal_acceptance")
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"
assert "Keep the compatibility tail" not in json.dumps(delta), "an unrelated outcome must not be retransmitted"
assert [row["id"] for row in outcomes(current)] == [row["id"] for row in outcomes(original)]
assert outcomes(current)[1] == outcomes(original)[1], "another outcome's requirements and evidence stay intact"
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")
Expand Down
24 changes: 24 additions & 0 deletions tests/control_plane_ts/goal_outcome_refinement.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ test("refinement retains original intent, accumulates requirements and cannot in
{...goalRef, goal_instance_id: "ginst_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"}, "author", patch()), /lifetime/);
});

for (const outcomeId of ["tour", "middle", "last"]) test(`refining ${outcomeId} preserves unrelated outcome positions and content`, () => {
const head = fixture();
for (const id of ["tour", "middle", "last"])
head.goal_outcomes = refineGoalOutcome(head, goal, null, "author", patch({outcome_id: id}));
const before = structuredClone(head);
const next = refineGoalOutcome(head, goal, null, "author", {outcome_id: outcomeId,
review: [{id: "opening", conclusion: "unmet", note: "The opening still needs correction.", evidence_refs: ["artifact:check"]}]});
assert.deepEqual(head, before, "refinement cannot mutate its source snapshot");
assert.deepEqual(next.outcomes.map(row => row.id), ["tour", "middle", "last"]);
for (const row of next.outcomes) {
const prior = before.goal_outcomes.outcomes.find((item: JsonObject) => item.id === row.id);
if (row.id !== outcomeId) assert.deepEqual(row, prior);
else {
assert.equal(row.revision, prior.revision + 1);
assert.deepEqual(row.requirements, prior.requirements);
assert.deepEqual(row.sources, prior.sources);
assert.equal(row.review!.items[0].conclusion, "unmet");
}
}
const appended = refineGoalOutcome({...head, goal_outcomes: next}, goal, null, "author", patch({outcome_id: "new"}));
assert.deepEqual(appended.outcomes.map(row => row.id), ["tour", "middle", "last", "new"]);
assert.deepEqual(appended.outcomes.slice(0, 3), next.outcomes, "a new outcome cannot rewrite existing ones");
});

test("only matching current acceptance and work coverage can qualify requirements", () => {
const head = fixture(); head.goal_outcomes = refineGoalOutcome(head, goal, null, "author", patch());
const accepted = {enabled: true, status: "accepted", criteria: [{id: "opening", description: "Keep the opening"}],
Expand Down
Loading