fix(family): the owed task is tagged by the matching System only, and the ground fixtures build without duplicate keys (#4990)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 44s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m4s
CI & Build / Python tests (push) Successful in 1m57s
CI & Build / Build & push image (push) Successful in 44s
assess_family_adoption took system_ids for the task it files, which made the family tool module look like a System-tagging door and tripped the parity registry. It is not one: the owed task is tagged to the project's System matching the idea's canonical area, and update_task retags it. The unit test for each conflict ground passed evidence/conditions twice. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -253,7 +253,6 @@ async def assess_family_adoption(
|
|||||||
reason: str,
|
reason: str,
|
||||||
evidence: list[str] | None = None,
|
evidence: list[str] | None = None,
|
||||||
precedent_ids: list[int] | None = None,
|
precedent_ids: list[int] | None = None,
|
||||||
system_ids: list[int] | None = None,
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Answer one canon family idea for one project. YOU decide; nobody
|
"""Answer one canon family idea for one project. YOU decide; nobody
|
||||||
approves. Judge in this order and stop at the first that holds:
|
approves. Judge in this order and stop at the first that holds:
|
||||||
@@ -268,8 +267,10 @@ async def assess_family_adoption(
|
|||||||
3. adopted — it applies and the project does it. `evidence` names where
|
3. adopted — it applies and the project does it. `evidence` names where
|
||||||
(a file, a commit, a task, a CI run).
|
(a file, a commit, a task, a CI run).
|
||||||
4. owed — none of the above. A task is filed in THIS project naming the
|
4. owed — none of the above. A task is filed in THIS project naming the
|
||||||
gap and the reference implementation for its language. Nothing edits
|
gap and the reference implementation for its language, filed under
|
||||||
another repository; the project picks the task up itself.
|
the project's System matching the idea's area (retag it with
|
||||||
|
update_task if that guess is wrong). Nothing edits another
|
||||||
|
repository; the project picks the task up itself.
|
||||||
|
|
||||||
Read get_family_adoption first: answer consistently with its precedents
|
Read get_family_adoption first: answer consistently with its precedents
|
||||||
unless this project differs in a way you can name in `reason`. The
|
unless this project differs in a way you can name in `reason`. The
|
||||||
@@ -286,11 +287,10 @@ async def assess_family_adoption(
|
|||||||
reason: why — required for every outcome.
|
reason: why — required for every outcome.
|
||||||
evidence: where it is done (required for adopted), or what you checked.
|
evidence: where it is done (required for adopted), or what you checked.
|
||||||
precedent_ids: earlier family decisions you followed, if any.
|
precedent_ids: earlier family decisions you followed, if any.
|
||||||
system_ids: Systems for an owed task. Omit to match the idea's own.
|
|
||||||
"""
|
"""
|
||||||
return await adoption_svc.assess(
|
return await adoption_svc.assess(
|
||||||
current_user_id(), project_id, idea_id, outcome=outcome, reason=reason,
|
current_user_id(), project_id, idea_id, outcome=outcome, reason=reason,
|
||||||
evidence=evidence, precedent_ids=precedent_ids, system_ids=system_ids,
|
evidence=evidence, precedent_ids=precedent_ids,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -519,8 +519,7 @@ async def _matching_systems(session, project_id: int, note_ids: list[int]) -> li
|
|||||||
return list(dict.fromkeys(direct + list(mapped)))
|
return list(dict.fromkeys(direct + list(mapped)))
|
||||||
|
|
||||||
|
|
||||||
async def _file_owed_task(user_id: int, row_id: int, reason: str,
|
async def _file_owed_task(user_id: int, row_id: int, reason: str) -> Note:
|
||||||
system_ids: list[int] | None) -> Note:
|
|
||||||
from scribe.services import notes as notes_svc
|
from scribe.services import notes as notes_svc
|
||||||
from scribe.services import systems as systems_svc
|
from scribe.services import systems as systems_svc
|
||||||
|
|
||||||
@@ -531,7 +530,7 @@ async def _file_owed_task(user_id: int, row_id: int, reason: str,
|
|||||||
refs = await _references(session, row.idea_id)
|
refs = await _references(session, row.idea_id)
|
||||||
langs = await _project_languages(session, row.project_id)
|
langs = await _project_languages(session, row.project_id)
|
||||||
platforms = await family_svc._platform_slugs(session, row.idea_id)
|
platforms = await family_svc._platform_slugs(session, row.idea_id)
|
||||||
systems = system_ids or await _matching_systems(
|
systems = await _matching_systems(
|
||||||
session, row.project_id, [row.idea_id] + [r["id"] for r in refs])
|
session, row.project_id, [row.idea_id] + [r["id"] for r in refs])
|
||||||
project_id, idea_id, version = row.project_id, row.idea_id, idea.canon_version
|
project_id, idea_id, version = row.project_id, row.idea_id, idea.canon_version
|
||||||
body = (
|
body = (
|
||||||
@@ -568,8 +567,7 @@ async def _set_task_status(user_id: int, task: Note, status: str, log: str) -> N
|
|||||||
await task_logs.create_log(user_id, task.id, log)
|
await task_logs.create_log(user_id, task.id, log)
|
||||||
|
|
||||||
|
|
||||||
async def _sync_owed_task(user_id: int, row_id: int, *, reason: str,
|
async def _sync_owed_task(user_id: int, row_id: int, *, reason: str) -> dict | None:
|
||||||
system_ids: list[int] | None = None) -> dict | None:
|
|
||||||
"""Make the owed task agree with the answer. Idempotent: an `owed` row
|
"""Make the owed task agree with the answer. Idempotent: an `owed` row
|
||||||
with an open task, or a settled row with a closed one, is left alone.
|
with an open task, or a settled row with a closed one, is left alone.
|
||||||
A task the caller cannot write is left alone too, and said so."""
|
A task the caller cannot write is left alone too, and said so."""
|
||||||
@@ -594,7 +592,7 @@ async def _sync_owed_task(user_id: int, row_id: int, *, reason: str,
|
|||||||
user_id, task, TaskStatus.todo.value,
|
user_id, task, TaskStatus.todo.value,
|
||||||
f"Reopened: family idea #{idea_id} was assessed owed again — {reason}")
|
f"Reopened: family idea #{idea_id} was assessed owed again — {reason}")
|
||||||
return ref(task, TaskStatus.todo.value, "reopened")
|
return ref(task, TaskStatus.todo.value, "reopened")
|
||||||
task = await _file_owed_task(user_id, row_id, reason, system_ids)
|
task = await _file_owed_task(user_id, row_id, reason)
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
row = await session.get(FamilyAdoption, row_id)
|
row = await session.get(FamilyAdoption, row_id)
|
||||||
row.owed_task_id = task.id
|
row.owed_task_id = task.id
|
||||||
@@ -645,7 +643,7 @@ def _answer(row: FamilyAdoption, *, status: str, reason: str, version: int,
|
|||||||
async def assess(
|
async def assess(
|
||||||
user_id: int, project_id: int, idea_id: int, *, outcome: str, reason: str,
|
user_id: int, project_id: int, idea_id: int, *, outcome: str, reason: str,
|
||||||
evidence: list[str] | None = None, precedent_ids: list[int] | None = None,
|
evidence: list[str] | None = None, precedent_ids: list[int] | None = None,
|
||||||
system_ids: list[int] | None = None, decided_via: str = "agent",
|
decided_via: str = "agent",
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Record one project's answer to one canon idea.
|
"""Record one project's answer to one canon idea.
|
||||||
|
|
||||||
@@ -692,7 +690,7 @@ async def assess(
|
|||||||
if decision is not None:
|
if decision is not None:
|
||||||
await session.refresh(decision)
|
await session.refresh(decision)
|
||||||
row_id = row.id
|
row_id = row.id
|
||||||
task = await _sync_owed_task(user_id, row_id, reason=reason, system_ids=system_ids)
|
task = await _sync_owed_task(user_id, row_id, reason=reason)
|
||||||
rows = await list_adoptions(user_id, project_id=project_id, idea_id=idea_id)
|
rows = await list_adoptions(user_id, project_id=project_id, idea_id=idea_id)
|
||||||
return {
|
return {
|
||||||
"changed": changed,
|
"changed": changed,
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ GOOD = {
|
|||||||
|
|
||||||
@pytest.mark.parametrize("ground", CONFLICT_KEYS)
|
@pytest.mark.parametrize("ground", CONFLICT_KEYS)
|
||||||
def test_each_ground_holds_when_its_needs_are_met(ground):
|
def test_each_ground_holds_when_its_needs_are_met(ground):
|
||||||
kw = dict(evidence=None, conditions=None, **{**GOOD[ground]})
|
kw = {"evidence": None, "conditions": None, **GOOD[ground]}
|
||||||
assert conflict_problems(ground=ground, grounds_checked=_checked(ground),
|
assert conflict_problems(ground=ground, grounds_checked=_checked(ground),
|
||||||
fold="their reasoning", **kw) == []
|
fold="their reasoning", **kw) == []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user