feat: appove and go

This commit is contained in:
thanhnv
2026-07-19 09:37:16 +07:00
parent 13fae3e6c3
commit 709b6cccd6
24 changed files with 1245 additions and 70 deletions
@@ -29,9 +29,79 @@ class Result:
class GoalPatchWorkflowTests(unittest.TestCase):
def setUp(self):
# Keep unit tests independent from production route-health settings.
# Tests that exercise preflight mock model_preflight explicitly.
self.environment = patch.dict(
os.environ, {"CASAN_GOAL_MODEL_PREFLIGHT": "0"}, clear=False,
)
self.environment.start()
self.addCleanup(self.environment.stop)
def test_vietnamese_completion_goal_is_write_intent(self):
self.assertTrue(ORCHESTRATOR.requests_side_effect("Hoàn thành component KeyResultDetail với form update progress đầy đủ"))
def test_phase_labeled_backend_completion_is_write_intent(self):
objective = """PHASE 1 — Hoàn thiện backend Objective và Key Result cho ứng dụng OKR.
Chỉ làm việc trong phạm vi apps/okr/backend.
Tạo patch nhưng không tự apply. Chờ Independent Reviewer phê duyệt.
"""
self.assertTrue(ORCHESTRATOR.requests_side_effect(objective))
def test_read_only_backend_audit_is_not_write_intent(self):
objective = "Rà soát backend và liệt kê các file có rủi ro. Không sửa code, không tạo patch."
self.assertFalse(ORCHESTRATOR.requests_side_effect(objective))
def test_scoped_negative_constraint_does_not_cancel_write_intent(self):
objective = """Hoàn thiện tính nguyên tử của luồng cập nhật tiến độ Key Result trong ứng dụng OKR.
Không thay đổi API contract. Chỉ trả về unified git diff và chờ Independent Reviewer phê duyệt.
"""
self.assertTrue(ORCHESTRATOR.requests_side_effect(objective))
def test_valid_worker_patch_still_reaches_human_approval_when_h3_is_unavailable(self):
diff = "diff --git a/apps/okr/backend/a.ts b/apps/okr/backend/a.ts\n--- a/apps/okr/backend/a.ts\n+++ b/apps/okr/backend/a.ts\n@@ -1 +1 @@\n-a\n+b\n"
manifest = {"file_count": 1, "characters": 10, "truncated": False, "bundle_sha256": "context-sha"}
artifact = {"path": ".specify/state/goals/default/job.patch", "sha256": "patch-sha", "files": ["apps/okr/backend/a.ts"], "status": "awaiting_approval"}
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "job.json")
with open(path, "w", encoding="utf-8") as handle:
json.dump({
"id": "goal-1", "goal": "Hoàn thiện backend nhưng không thay đổi API contract.",
"project": "AINative_OKR_CASAN4", "actor": "owner", "local_provider": "ollama",
"cloud_provider": "omniroute", "workspace": {"context_roots": ["apps/okr/backend"]},
"stages": [],
}, handle)
with (
patch.dict(os.environ, {"CASAN_GOAL_LOCAL_MODEL": "ollama:worker", "CASAN_GOAL_CLOUD_MODEL": "openai-compatible:reviewer"}, clear=False),
patch.object(ORCHESTRATOR, "scan", side_effect=lambda text, mode: (True, text)),
patch.object(ORCHESTRATOR, "build_context", return_value=("snapshot", manifest, "manifest.json")),
patch.object(ORCHESTRATOR, "call_model", return_value=(True, diff, {}, "ok")),
patch.object(ORCHESTRATOR, "validate_write_output", return_value=diff),
patch.object(ORCHESTRATOR, "run_reviewer_chain", return_value=(False, "", {}, "model_output_empty", {"provider": "ollama", "model": "ollama:worker"})),
patch.object(ORCHESTRATOR, "validate_and_store_patch", return_value=artifact),
patch.object(ORCHESTRATOR, "submit_side_effect", return_value={"id": "AP-1", "status": "pending", "action": "goal.workspace.execute"}),
patch.object(ORCHESTRATOR, "emit"), patch.object(ORCHESTRATOR, "metric"),
patch.object(ORCHESTRATOR, "audit", return_value="audit-sha"),
):
exit_code = ORCHESTRATOR.run(path)
with open(path, encoding="utf-8") as handle:
job = json.load(handle)
self.assertEqual(exit_code, 0)
self.assertEqual(job["status"], "requires_approval")
self.assertEqual(job["approval"]["id"], "AP-1")
self.assertIn("Automated H3 review was unavailable", job["result"])
self.assertNotIn("independently reviewed", job["result"])
def test_source_code_delete_method_is_not_promoted_to_security_block(self):
source = "export class Service { async delete(id: number) { return this.repo.delete({ where: { id } }); } }"
with tempfile.TemporaryDirectory() as directory, \
patch.dict(os.environ, {"CASAN_STATE_ROOT": directory, "CASAN_SECURITY_STRICT": "0"}, clear=False):
allowed, safe_source = ORCHESTRATOR.scan(source, "input")
self.assertTrue(allowed)
self.assertIn("repo.delete", safe_source)
def test_extract_patch_requires_unified_diff(self):
with self.assertRaisesRegex(ValueError, "goal_patch_missing"):
ORCHESTRATOR.extract_patch("implementation plan only")
@@ -60,6 +130,31 @@ class GoalPatchWorkflowTests(unittest.TestCase):
self.assertIn("@@ -1,1 +1,1 @@", normalized)
self.assertEqual(check.call_count, 2)
def test_patch_fragment_error_triggers_deterministic_hunk_recount(self):
wrong_counts = (
"diff --git a/a b/a\n--- a/a\n+++ b/a\n"
"@@ -1,5 +1,5 @@\n-old\n+new\n"
"@@ -10,7 +10,7 @@\n-tail-old\n+tail-new\n"
)
with patch.object(
ORCHESTRATOR,
"validate_patch_check",
side_effect=[ValueError("goal_patch_check_failed:error: patch fragment without header at line 7: @@ -10,7 +10,7 @@"), None],
) as check:
normalized = ORCHESTRATOR.validate_write_output(wrong_counts)
self.assertIn("@@ -1,1 +1,1 @@", normalized)
self.assertIn("@@ -10,1 +10,1 @@", normalized)
self.assertEqual(check.call_count, 2)
def test_patch_fragment_error_adds_recount_instruction_to_model_repair(self):
with (
patch.dict(os.environ, {"CASAN_GOAL_PATCH_REPAIR_ATTEMPTS": "1", "CASAN_GOAL_PATCH_REPAIR_MODELS": "openai:gpt"}, clear=False),
patch.object(ORCHESTRATOR, "model_preflight", return_value=(True, "ok")),
patch.object(ORCHESTRATOR, "call_model", return_value=(False, "", {}, "stopped")) as call,
):
ORCHESTRATOR.repair_write_output("openai:gpt", "original", "invalid", "goal_patch_check_failed:patch fragment without header")
self.assertIn("Recompute every `@@ -old,count +new,count @@` header", call.call_args.args[1])
def test_truncated_replacement_is_not_reinterpreted_as_deletion(self):
truncated = "diff --git a/a b/a\n--- a/a\n+++ b/a\n@@ -1,1 +1,1 @@\n-old\n"
self.assertEqual(ORCHESTRATOR.normalize_unified_diff(truncated), truncated)
@@ -80,6 +175,50 @@ class GoalPatchWorkflowTests(unittest.TestCase):
self.assertEqual(manifest["files"][0]["characters"], len(raw.strip()))
self.assertFalse(manifest["files"][0]["truncated"])
def test_explicit_scope_paths_rank_source_ahead_of_general_architecture(self):
project = ORCHESTRATOR.registered_project("AINative_OKR_CASAN4")
objective = """PHASE 1 — Hoàn thiện backend Objective và Key Result.
Nguồn sự thật:
- docs/technical_architecture.md
- apps/okr/domain/input/okr-requirement.md
Chỉ làm việc trong:
- apps/okr/backend/src/objectives/**
- apps/okr/backend/src/key-results/**
- apps/okr/backend/prisma/schema.prisma
Tạo patch nhưng không tự apply.
"""
ranked = ORCHESTRATOR.context_candidates(project, objective)
top_paths = [row[1] for row in ranked[:10]]
self.assertTrue(any(path.startswith("apps/okr/backend/src/objectives/") for path in top_paths))
self.assertTrue(any(path.startswith("apps/okr/backend/src/key-results/") for path in top_paths))
self.assertIn("apps/okr/backend/prisma/schema.prisma", top_paths)
self.assertNotIn("docs/technical_architecture.md", top_paths)
self.assertNotIn("apps/okr/domain/input/okr-requirement.md", top_paths)
def test_explicit_only_scope_excludes_unrequested_context_files(self):
goal = """Hoàn thiện backend.
Chỉ được đọc và thay đổi:
apps/okr/backend/src/key-results/key-results.service.ts
apps/okr/backend/test/services.test.ts
Không sửa bất kỳ file nào khác.
"""
candidates = [
(2000, "apps/okr/backend/src/key-results/key-results.service.ts", "service"),
(2000, "apps/okr/backend/test/services.test.ts", "tests"),
(50, "apps/okr/backend/src/objectives/objectives.service.ts", "unrequested"),
]
project = {"domain": "OKR", "domain_root": "apps/okr", "roots": []}
with tempfile.TemporaryDirectory() as directory, \
patch.object(ORCHESTRATOR, "registered_project", return_value=project), \
patch.object(ORCHESTRATOR, "context_candidates", return_value=candidates), \
patch.object(ORCHESTRATOR, "scan", side_effect=lambda text, mode: (True, text)):
bundle, manifest, _ = ORCHESTRATOR.build_context(os.path.join(directory, "goal.json"), "okr", goal, True)
self.assertEqual([row["path"] for row in manifest["files"]], [
"apps/okr/backend/src/key-results/key-results.service.ts",
"apps/okr/backend/test/services.test.ts",
])
self.assertNotIn("unrequested", bundle)
def test_invalid_write_output_gets_one_bounded_repair_attempt(self):
repaired = "```diff\ndiff --git a/apps/okr/frontend/a.ts b/apps/okr/frontend/a.ts\n--- a/apps/okr/frontend/a.ts\n+++ b/apps/okr/frontend/a.ts\n@@ -1 +1 @@\n-a\n+b\n```"
with patch.dict(os.environ, {"CASAN_GOAL_PATCH_REPAIR_ATTEMPTS": "1"}, clear=False), \
@@ -114,6 +253,53 @@ class GoalPatchWorkflowTests(unittest.TestCase):
self.assertEqual(call.call_args.args[0], "openai:gpt-4o-mini")
self.assertEqual(call.call_args.kwargs["max_output_tokens"], ORCHESTRATOR.patch_output_tokens())
def test_repair_skips_model_that_fails_generation_preflight(self):
repaired = "diff --git a/apps/okr/frontend/a.ts b/apps/okr/frontend/a.ts\n--- a/apps/okr/frontend/a.ts\n+++ b/apps/okr/frontend/a.ts\n@@ -1 +1 @@\n-a\n+b\n"
with (
patch.dict(os.environ, {"CASAN_GOAL_PATCH_REPAIR_ATTEMPTS": "2", "CASAN_GOAL_PATCH_REPAIR_MODELS": "openai-compatible:auto/coding,ollama:ornith:9b"}, clear=False),
patch.object(ORCHESTRATOR, "model_preflight", side_effect=[(False, "gateway_503"), (True, "ok")]),
patch.object(ORCHESTRATOR, "call_model", return_value=(True, repaired, {}, "ok")) as call,
patch.object(ORCHESTRATOR, "validate_write_output", return_value=repaired),
):
ok, _, usage, reason = ORCHESTRATOR.repair_write_output("ollama:ornith:9b", "original", "invalid")
self.assertTrue(ok)
self.assertEqual(reason, "ok")
self.assertEqual(call.call_count, 1)
self.assertEqual(call.call_args.args[0], "ollama:ornith:9b")
self.assertIn("model_preflight_failed", usage["repair_attempts"][0]["reason"])
def test_logged_in_account_worker_is_first_patch_repair_candidate(self):
repaired = "diff --git a/apps/okr/frontend/a.ts b/apps/okr/frontend/a.ts\n--- a/apps/okr/frontend/a.ts\n+++ b/apps/okr/frontend/a.ts\n@@ -1 +1 @@\n-a\n+b\n"
with (
patch.dict(os.environ, {"CASAN_GOAL_PATCH_REPAIR_ATTEMPTS": "2", "CASAN_GOAL_PATCH_REPAIR_MODELS": "openai-compatible:auto/coding,ollama:ornith:9b"}, clear=False),
patch.object(ORCHESTRATOR, "model_preflight", return_value=(True, "ok")),
patch.object(ORCHESTRATOR, "call_account_model", return_value=(True, repaired, {}, "ok")) as account_call,
patch.object(ORCHESTRATOR, "call_model") as routed_call,
patch.object(ORCHESTRATOR, "validate_write_output", return_value=repaired),
):
ok, _, usage, reason = ORCHESTRATOR.repair_write_output("account:codex", "original", "invalid")
self.assertTrue(ok)
self.assertEqual(reason, "ok")
self.assertEqual(account_call.call_args.args[0], "codex")
routed_call.assert_not_called()
self.assertEqual(usage["repair_attempts"][0]["provider"], "codex-account")
def test_account_codex_gets_corrective_pass_before_other_routes(self):
repaired = "diff --git a/apps/okr/frontend/a.ts b/apps/okr/frontend/a.ts\n--- a/apps/okr/frontend/a.ts\n+++ b/apps/okr/frontend/a.ts\n@@ -1 +1 @@\n-a\n+b\n"
with (
patch.dict(os.environ, {"CASAN_GOAL_PATCH_REPAIR_ATTEMPTS": "3", "CASAN_GOAL_PATCH_REPAIR_MODELS": "openai-compatible:auto/coding,ollama:ornith:9b"}, clear=False),
patch.object(ORCHESTRATOR, "model_preflight", return_value=(True, "ok")),
patch.object(ORCHESTRATOR, "call_account_model", side_effect=[(True, "corrupt", {}, "ok"), (True, repaired, {}, "ok")]) as account_call,
patch.object(ORCHESTRATOR, "call_model") as routed_call,
patch.object(ORCHESTRATOR, "validate_write_output", side_effect=[ValueError("goal_patch_check_failed:patch fragment without header"), repaired]),
):
ok, _, usage, reason = ORCHESTRATOR.repair_write_output("account:codex", "original", "invalid")
self.assertTrue(ok)
self.assertEqual(reason, "ok")
self.assertEqual(account_call.call_count, 2)
routed_call.assert_not_called()
self.assertEqual([row["model"] for row in usage["repair_attempts"]], ["account:codex", "account:codex"])
def test_repair_tries_next_stronger_candidate_when_first_patch_is_corrupt(self):
repaired = "diff --git a/apps/okr/frontend/a.ts b/apps/okr/frontend/a.ts\n--- a/apps/okr/frontend/a.ts\n+++ b/apps/okr/frontend/a.ts\n@@ -1 +1 @@\n-a\n+b\n"
with (
@@ -178,6 +364,16 @@ class GoalPatchWorkflowTests(unittest.TestCase):
with self.assertRaisesRegex(ValueError, "goal_patch_outside_workspace"):
ORCHESTRATOR.validate_and_store_patch(path, job, content)
def test_patch_outside_explicit_file_scope_is_denied(self):
job = {
"goal": "Chỉ được đọc và thay đổi:\napps/okr/backend/allowed.ts\nKhông sửa bất kỳ file nào khác.",
"workspace": {"context_roots": ["apps/okr/backend"]},
}
content = "diff --git a/apps/okr/backend/other.ts b/apps/okr/backend/other.ts\n--- a/apps/okr/backend/other.ts\n+++ b/apps/okr/backend/other.ts\n@@ -1 +1 @@\n-a\n+b\n"
with tempfile.TemporaryDirectory() as directory:
with self.assertRaisesRegex(ValueError, "goal_patch_outside_workspace"):
ORCHESTRATOR.validate_and_store_patch(os.path.join(directory, "job.json"), job, content)
def test_patch_rename_source_outside_workspace_is_denied(self):
job = {"workspace": {"context_roots": ["apps/okr/frontend"]}}
content = "diff --git a/package.json b/apps/okr/frontend/package.json\nsimilarity index 100%\nrename from package.json\nrename to apps/okr/frontend/package.json\n"
@@ -199,9 +395,20 @@ class GoalPatchWorkflowTests(unittest.TestCase):
def test_frontend_patch_runs_build_and_tests(self):
commands = EXECUTOR.verification_commands(["apps/okr/frontend/src/pages/KeyResultDetail.tsx"])
self.assertFalse(any(command[:2] == ["git", "diff"] for command in commands))
self.assertIn(["npm", "run", "build", "-w", "@ainative-okr/frontend"], commands)
self.assertIn(["npm", "test", "-w", "@ainative-okr/frontend"], commands)
def test_executor_can_retry_only_a_verified_rollback_failure(self):
artifact = {"path": "job.patch"}
self.assertTrue(EXECUTOR.ready_for_apply({"status": "requires_approval", "patch_artifact": artifact}))
self.assertTrue(EXECUTOR.ready_for_apply({
"status": "failed",
"error": "GOAL_APPLY_VERIFICATION_FAILED_ROLLED_BACK",
"patch_artifact": artifact,
}))
self.assertFalse(EXECUTOR.ready_for_apply({"status": "failed", "error": "OTHER", "patch_artifact": artifact}))
def test_service_desk_patch_uses_service_desk_manifest_commands(self):
commands = EXECUTOR.verification_commands(["apps/service-desk/src/ticket.js"])
self.assertIn(["node", "--check", "apps/service-desk/src/ticket.js"], commands)