"""c_bug_record_create (03-C §3.10): finalizes a Validator-judged bug. Distinct from c_bug_report_create (03-C §3.1.1, human-submitted feedback with no root cause yet -- owned by U-023, not this Unit): this controller's input_contract requires root_cause_category/root_cause_detail/suggested_route all as required fields, i.e. it's the "record a bug the Validator has already judged" endpoint, called at the end of p_validator_root_cause_trace's judging_root_cause -> bug_recorded transition (04-P §4.3). **skill_defect + skill_iteration_suggestion (resolved 2026-07-31, U-018)**: 03-C §3.10 says root_cause_category=skill_defect "必须同时创建 skill_iteration_suggestion", but skill_iteration.before/after_git_commit_sha are NOT NULL and can't be populated until an actual skill rewrite happens -- that's c_skill_update (src/skill_update.py), a later, separate step per 04-P §4.3's own transition table (bug_recorded is reached by this function alone; skill_iteration_triggered -> c_skill_update is a *subsequent* transition). So `skill_defect` is accepted here with skill_iteration_suggestion_id left null; src/skill_update.py's update_skill() fills it in once the iteration actually exists. See that module's docstring for the full reasoning -- this was originally deferred as an unenforced gap when U-015 was built (U-018 didn't exist yet to close the loop) and has since been resolved, not left open. """ import json import uuid # core_engine deliberately excluded -- see src/schema.py's matching CHECK # constraint and its own comment: PH5-scoped (U-034), not accepted until then. _VALID_ROOT_CAUSE_CATEGORIES = ("planning", "coder", "skill_defect") class InvalidRootCauseCategoryError(Exception): """INVALID_ROOT_CAUSE_CATEGORY -- 03-C §3.10.""" def create_bug_record( conn, task_id, description, root_cause_category, root_cause_detail, suggested_route, repro_steps=None, related_artifact_ids=None, skill_iteration_suggestion_id=None, ): """Creates a bug_record(source='validator', status='routed') -- 'routed' because by the time this is called, triage (root cause + suggested_route) is already complete; there's no separate triaging step for this origin (that's the human_reported branch's job, see claim_human_reported_bug / complete_human_reported_triage below).""" if root_cause_category not in _VALID_ROOT_CAUSE_CATEGORIES: raise InvalidRootCauseCategoryError( "root_cause_category must be one of {}, got {!r}".format( _VALID_ROOT_CAUSE_CATEGORIES, root_cause_category ) ) bug_id = str(uuid.uuid4()) conn.execute( "INSERT INTO bug_record " "(bug_id, task_id, source, description, repro_steps, root_cause_category, root_cause_detail, " "related_artifact_ids, suggested_route, skill_iteration_suggestion_id, status) " "VALUES (?, ?, 'validator', ?, ?, ?, ?, ?, ?, ?, 'routed')", ( bug_id, task_id, description, repro_steps, root_cause_category, root_cause_detail, json.dumps(related_artifact_ids) if related_artifact_ids else None, suggested_route, skill_iteration_suggestion_id, ), ) conn.commit() return {"bug_id": bug_id} def claim_human_reported_bug(conn, bug_id): """04-P §4.3: human_reported -> tracing_lineage. Validator claims an open, source=human bug_record. status: open -> triaging.""" row = conn.execute("SELECT source, status FROM bug_record WHERE bug_id = ?", (bug_id,)).fetchone() if row is None: raise ValueError("no such bug_record: " + bug_id) source, status = row if source != "human" or status != "open": raise ValueError( "bug_record {} is not claimable (source={}, status={}); expected source=human, status=open".format( bug_id, source, status ) ) conn.execute("UPDATE bug_record SET status = 'triaging' WHERE bug_id = ?", (bug_id,)) conn.commit() def complete_human_reported_triage(conn, bug_id, root_cause_category, root_cause_detail, suggested_route): """04-P §4.3 补充说明: human_reported 起点的 judging_root_cause->bug_recorded 是对已存在 bug_record 的字段补全式更新,不产生重复记录 -- status: triaging->routed.""" if root_cause_category not in _VALID_ROOT_CAUSE_CATEGORIES: raise InvalidRootCauseCategoryError( "root_cause_category must be one of {}, got {!r}".format( _VALID_ROOT_CAUSE_CATEGORIES, root_cause_category ) ) conn.execute( "UPDATE bug_record SET root_cause_category = ?, root_cause_detail = ?, suggested_route = ?, " "status = 'routed' WHERE bug_id = ?", (root_cause_category, root_cause_detail, suggested_route, bug_id), ) conn.commit()