bug_record.py
6.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""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."""
class InvalidParamError(Exception):
"""INVALID_PARAM -- 03-C §3.1.1: description is empty, or project_id/
related_task_id don't reference existing rows."""
def create_bug_report(conn, project_id, description, repro_steps=None, related_task_id=None):
"""c_bug_report_create (03-C §3.1.1): console-submitted human bug report.
03-C's own input_contract for this controller lists only
{description, repro_steps, related_task_id} -- no project_id -- but
execution_event.project_id is NOT NULL (01-M 1.9) and bug_record has no
project_id column of its own to derive one from when related_task_id is
left empty ("留空则视为新发现的独立问题", 02-V §2.4). Requiring
project_id here (like c_task_create already does per its own contract)
is the only way to satisfy that constraint for a task-less report; this
is a real 03-C gap, not a design choice, flagged rather than silently
routed around -- see development-plan.md's 2026-07-31 note on this Unit.
"""
if not description:
raise InvalidParamError("description is required")
if conn.execute("SELECT 1 FROM project WHERE project_id = ?", (project_id,)).fetchone() is None:
raise InvalidParamError("project_id does not exist: " + str(project_id))
if related_task_id is not None and conn.execute(
"SELECT 1 FROM task WHERE task_id = ?", (related_task_id,)
).fetchone() is None:
raise InvalidParamError("related_task_id does not exist: " + str(related_task_id))
bug_id = str(uuid.uuid4())
conn.execute(
"INSERT INTO bug_record (bug_id, task_id, source, description, repro_steps, status) "
"VALUES (?, ?, 'human', ?, ?, 'open')",
(bug_id, related_task_id, description, repro_steps),
)
conn.execute(
"INSERT INTO execution_event (event_id, project_id, task_id, event_type, payload) "
"VALUES (?, ?, ?, 'bug_recorded', ?)",
(str(uuid.uuid4()), project_id, related_task_id, json.dumps({"bug_id": bug_id, "source": "human"})),
)
conn.commit()
return {"bug_id": bug_id, "status": "open"}
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()