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):
|
class DecisionCreate(LegacyWorkstreamIdBodyMixin, BaseModel):
|
||||||
|
id: uuid.UUID | None = None
|
||||||
topic_id: uuid.UUID | None = None
|
topic_id: uuid.UUID | None = None
|
||||||
workplan_id: uuid.UUID | None = Field(
|
workplan_id: uuid.UUID | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
|
|
@ -65,4 +66,4 @@ class DecisionRead(OptionalWorkplanIdCompatMixin, BaseModel):
|
||||||
escalation_note: str | None = None
|
escalation_note: str | None = None
|
||||||
superseded_by: uuid.UUID | None = None
|
superseded_by: uuid.UUID | None = None
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from api.models.intake import IntakeLane, IntakeOutcome, IntakeStatus
|
||||||
|
|
||||||
|
|
||||||
class IntakeCreate(BaseModel):
|
class IntakeCreate(BaseModel):
|
||||||
|
id: uuid.UUID | None = None
|
||||||
topic_id: uuid.UUID | None = None
|
topic_id: uuid.UUID | None = None
|
||||||
workplan_id: uuid.UUID | None = None
|
workplan_id: uuid.UUID | None = None
|
||||||
repo_id: uuid.UUID | None = None
|
repo_id: uuid.UUID | None = None
|
||||||
|
|
|
||||||
|
|
@ -987,7 +987,12 @@ def _inject_yaml_block_field(
|
||||||
return False
|
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
|
"""C-32: register kind: intake / kind: decision YAML blocks found
|
||||||
anywhere in the repo against the hub (CUST-WP-0061-T02, work-record
|
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
|
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]
|
id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind]
|
||||||
existing = str(meta.get(id_field, "")).strip().strip('"')
|
existing = str(meta.get(id_field, "")).strip().strip('"')
|
||||||
if existing and existing not in ("~", "null", "None", "none"):
|
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)
|
rel = md.relative_to(repo_dir)
|
||||||
if kind not in _WORK_RECORD_CREATE_ENDPOINT_BY_KIND:
|
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(
|
report.add(
|
||||||
severity="WARN", check_id="C-32",
|
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_path=str(rel),
|
||||||
file_value=rid,
|
file_value=rid,
|
||||||
fixable=True,
|
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)
|
_check_unregistered_work_records(repo_dir, report)
|
||||||
|
|
||||||
# C-32: intake/decision work-record registration (CUST-WP-0061-T02)
|
# 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)
|
# C-33: generated per-repo work-record index (CUST-WP-0061-T04)
|
||||||
_check_work_record_index_freshness(repo_dir, repo_slug, report)
|
_check_work_record_index_freshness(repo_dir, repo_slug, report)
|
||||||
|
|
@ -3101,6 +3125,7 @@ def fix_repo(
|
||||||
meta = ctx["meta"]
|
meta = ctx["meta"]
|
||||||
id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind]
|
id_field = _WORK_RECORD_ID_FIELD_BY_KIND[kind]
|
||||||
title = str(meta.get("title") or rid)
|
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}")
|
repo_record = _api_get(api_base, f"/repos/{repo_slug}")
|
||||||
if not repo_record:
|
if not repo_record:
|
||||||
|
|
@ -3111,6 +3136,7 @@ def fix_repo(
|
||||||
created = None
|
created = None
|
||||||
if kind == "intake":
|
if kind == "intake":
|
||||||
payload = {
|
payload = {
|
||||||
|
"id": desired_record_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"repo_id": repo_record.get("id"),
|
"repo_id": repo_record.get("id"),
|
||||||
"description": meta.get("description"),
|
"description": meta.get("description"),
|
||||||
|
|
@ -3138,6 +3164,7 @@ def fix_repo(
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
payload = {
|
payload = {
|
||||||
|
"id": desired_record_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"topic_id": topic_id,
|
"topic_id": topic_id,
|
||||||
"decision_type": meta.get("decision_type", "pending"),
|
"decision_type": meta.get("decision_type", "pending"),
|
||||||
|
|
@ -3154,6 +3181,17 @@ def fix_repo(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
new_id = created["id"]
|
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):
|
if _inject_yaml_block_field(md_path, id_field, new_id, rid):
|
||||||
report.fixes_applied.append(
|
report.fixes_applied.append(
|
||||||
f"C-32 fixed: created {kind} {new_id[:8]}… for {rid}, "
|
f"C-32 fixed: created {kind} {new_id[:8]}… for {rid}, "
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,17 @@ class TestIntakeCreateAndRead:
|
||||||
assert body["lane"] == "green"
|
assert body["lane"] == "green"
|
||||||
assert body["outcome"] is None
|
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):
|
async def test_get_unknown_intake_404s(self, client):
|
||||||
r = await client.get("/intakes/00000000-0000-0000-0000-000000000000")
|
r = await client.get("/intakes/00000000-0000-0000-0000-000000000000")
|
||||||
assert r.status_code == 404
|
assert r.status_code == 404
|
||||||
|
|
|
||||||
|
|
@ -407,6 +407,17 @@ class TestTasks:
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
class TestDecisions:
|
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):
|
async def test_create_and_resolve(self, client):
|
||||||
await _create_domain(client)
|
await _create_domain(client)
|
||||||
topic = await _create_topic(client)
|
topic = await _create_topic(client)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue