bug_record.py
8.27 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
"""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 added in U-035 -- U-034 deliberately left this app-layer
# whitelist unchanged even though it already widened the schema-level CHECK
# constraint, since classifying a change as core_engine is U-035's
# path-based rule (src/core_engine_classify.py), not something the
# Validator's existing 3-perspective judge_root_cause can produce on its own.
_VALID_ROOT_CAUSE_CATEGORIES = ("planning", "coder", "skill_defect", "core_engine")
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 record_core_engine_change(conn, task_id, description):
"""U-035 (05 §5.8): audit-trail record for a self-target task that
touched core-engine territory (`.claude/agents/*.md`/`src/`/`supervisor/`)
and passed Validator on its first attempt -- NOT the Validator-FAIL
root-cause-judgment path `create_bug_record()` serves. There is no
"route" decision here (`suggested_route` stays null, unlike
`create_bug_record`'s required field) and no open triage step -- the
change already passed validation, so `status='resolved'` from creation.
Exists so 03-C §3.11's `c_org_loop_route` core_engine branch and 04-P
§4.6's `p_deploy_supervisor_blue_green` trigger text -- both phrased in
terms of "a bug_record with root_cause_category=core_engine" -- have a
real row to reference, satisfying that text even though this is a
PASS-path audit record rather than a FAIL-path bug attribution.
Returns {"bug_id"}."""
bug_id = str(uuid.uuid4())
conn.execute(
"INSERT INTO bug_record "
"(bug_id, task_id, source, description, root_cause_category, status) "
"VALUES (?, ?, 'validator', ?, 'core_engine', 'resolved')",
(bug_id, task_id, description),
)
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()