feat(consistency): rebuild authoritative intake IDs
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
This commit is contained in:
parent
03c7924b7f
commit
059de9358e
5 changed files with 68 additions and 6 deletions
|
|
@ -9,6 +9,7 @@ from pydantic import AliasChoices, Field
|
|||
|
||||
|
||||
class DecisionCreate(LegacyWorkstreamIdBodyMixin, BaseModel):
|
||||
id: uuid.UUID | None = None
|
||||
topic_id: uuid.UUID | None = None
|
||||
workplan_id: uuid.UUID | None = Field(
|
||||
default=None,
|
||||
|
|
@ -65,4 +66,4 @@ class DecisionRead(OptionalWorkplanIdCompatMixin, BaseModel):
|
|||
escalation_note: str | None = None
|
||||
superseded_by: uuid.UUID | None = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
updated_at: datetime
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from api.models.intake import IntakeLane, IntakeOutcome, IntakeStatus
|
|||
|
||||
|
||||
class IntakeCreate(BaseModel):
|
||||
id: uuid.UUID | None = None
|
||||
topic_id: uuid.UUID | None = None
|
||||
workplan_id: uuid.UUID | None = None
|
||||
repo_id: uuid.UUID | None = None
|
||||
|
|
|
|||
|
|
@ -987,7 +987,12 @@ def _inject_yaml_block_field(
|
|||
return False
|
||||
|
||||
|
||||
def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport") -> None:
|
||||
def _check_work_record_registration(
|
||||
api_base: str,
|
||||
repo_dir: Path,
|
||||
report: "ConsistencyReport",
|
||||
bootstrap_empty_projection: bool = False,
|
||||
) -> None:
|
||||
"""C-32: register kind: intake / kind: decision YAML blocks found
|
||||
anywhere in the repo against the hub (CUST-WP-0061-T02, work-record
|
||||
stage 3) — writes state_hub_intake_id / state_hub_decision_id back into
|
||||
|
|
@ -1020,7 +1025,11 @@ def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport")
|
|||
id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind]
|
||||
existing = str(meta.get(id_field, "")).strip().strip('"')
|
||||
if existing and existing not in ("~", "null", "None", "none"):
|
||||
continue
|
||||
if not bootstrap_empty_projection:
|
||||
continue
|
||||
endpoint = _WORK_RECORD_CREATE_ENDPOINT_BY_KIND.get(kind)
|
||||
if endpoint and _api_get(api_base, f"{endpoint}/{existing}") is not None:
|
||||
continue
|
||||
|
||||
rel = md.relative_to(repo_dir)
|
||||
if kind not in _WORK_RECORD_CREATE_ENDPOINT_BY_KIND:
|
||||
|
|
@ -1036,11 +1045,21 @@ def _check_work_record_registration(repo_dir: Path, report: "ConsistencyReport")
|
|||
|
||||
report.add(
|
||||
severity="WARN", check_id="C-32",
|
||||
message=f"[{rel}] {kind} '{rid}' has no {id_field} — not indexed in DB",
|
||||
message=(
|
||||
f"[{rel}] {kind} '{rid}' is absent from the explicitly empty projection"
|
||||
if existing
|
||||
else f"[{rel}] {kind} '{rid}' has no {id_field} — not indexed in DB"
|
||||
),
|
||||
file_path=str(rel),
|
||||
file_value=rid,
|
||||
fixable=True,
|
||||
_fix_context={"md_path": md, "kind": kind, "rid": rid, "meta": meta},
|
||||
_fix_context={
|
||||
"md_path": md,
|
||||
"kind": kind,
|
||||
"rid": rid,
|
||||
"meta": meta,
|
||||
"desired_record_id": existing or None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -1251,7 +1270,12 @@ def check_repo(
|
|||
_check_unregistered_work_records(repo_dir, report)
|
||||
|
||||
# C-32: intake/decision work-record registration (CUST-WP-0061-T02)
|
||||
_check_work_record_registration(repo_dir, report)
|
||||
_check_work_record_registration(
|
||||
api_base,
|
||||
repo_dir,
|
||||
report,
|
||||
bootstrap_empty_projection=bootstrap_empty_projection,
|
||||
)
|
||||
|
||||
# C-33: generated per-repo work-record index (CUST-WP-0061-T04)
|
||||
_check_work_record_index_freshness(repo_dir, repo_slug, report)
|
||||
|
|
@ -3101,6 +3125,7 @@ def fix_repo(
|
|||
meta = ctx["meta"]
|
||||
id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind]
|
||||
title = str(meta.get("title") or rid)
|
||||
desired_record_id = ctx.get("desired_record_id")
|
||||
|
||||
repo_record = _api_get(api_base, f"/repos/{repo_slug}")
|
||||
if not repo_record:
|
||||
|
|
@ -3111,6 +3136,7 @@ def fix_repo(
|
|||
created = None
|
||||
if kind == "intake":
|
||||
payload = {
|
||||
"id": desired_record_id,
|
||||
"title": title,
|
||||
"repo_id": repo_record.get("id"),
|
||||
"description": meta.get("description"),
|
||||
|
|
@ -3138,6 +3164,7 @@ def fix_repo(
|
|||
)
|
||||
continue
|
||||
payload = {
|
||||
"id": desired_record_id,
|
||||
"title": title,
|
||||
"topic_id": topic_id,
|
||||
"decision_type": meta.get("decision_type", "pending"),
|
||||
|
|
@ -3154,6 +3181,17 @@ def fix_repo(
|
|||
continue
|
||||
|
||||
new_id = created["id"]
|
||||
if desired_record_id and new_id != desired_record_id:
|
||||
report.fixes_applied.append(
|
||||
f"C-32 FAIL {rid}: projection returned unexpected UUID {new_id}"
|
||||
)
|
||||
continue
|
||||
if desired_record_id:
|
||||
report.fixes_applied.append(
|
||||
f"C-32 fixed: created {kind} {new_id[:8]}… for {rid} "
|
||||
"with the authoritative ID"
|
||||
)
|
||||
continue
|
||||
if _inject_yaml_block_field(md_path, id_field, new_id, rid):
|
||||
report.fixes_applied.append(
|
||||
f"C-32 fixed: created {kind} {new_id[:8]}… for {rid}, "
|
||||
|
|
|
|||
|
|
@ -48,6 +48,17 @@ class TestIntakeCreateAndRead:
|
|||
assert body["lane"] == "green"
|
||||
assert body["outcome"] is None
|
||||
|
||||
async def test_create_preserves_explicit_authoritative_id(self, client):
|
||||
await _create_domain(client)
|
||||
topic = await _create_topic(client)
|
||||
expected = "11111111-1111-4111-8111-111111111111"
|
||||
r = await client.post(
|
||||
"/intakes/",
|
||||
json={"id": expected, "title": "file intake", "topic_id": topic["id"]},
|
||||
)
|
||||
assert r.status_code == 201
|
||||
assert r.json()["id"] == expected
|
||||
|
||||
async def test_get_unknown_intake_404s(self, client):
|
||||
r = await client.get("/intakes/00000000-0000-0000-0000-000000000000")
|
||||
assert r.status_code == 404
|
||||
|
|
|
|||
|
|
@ -407,6 +407,17 @@ class TestTasks:
|
|||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestDecisions:
|
||||
async def test_create_preserves_explicit_authoritative_id(self, client):
|
||||
await _create_domain(client)
|
||||
topic = await _create_topic(client)
|
||||
expected = "22222222-2222-4222-8222-222222222222"
|
||||
r = await client.post(
|
||||
"/decisions/",
|
||||
json={"id": expected, "title": "File decision", "topic_id": topic["id"]},
|
||||
)
|
||||
assert r.status_code == 201
|
||||
assert r.json()["id"] == expected
|
||||
|
||||
async def test_create_and_resolve(self, client):
|
||||
await _create_domain(client)
|
||||
topic = await _create_topic(client)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue