133 lines
5.0 KiB
Python
133 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
import uuid
|
|
from copy import deepcopy
|
|
from pathlib import Path
|
|
|
|
from server.agent_core import harness
|
|
from server.agent_core.approval_store import utc_now
|
|
from server.aps_domain.drawing_understanding import build_drawing_link_evidence_records
|
|
from server.aps_domain.workflow import execute_confirmed
|
|
from server.auth.context import get_identity
|
|
from server.state.seed import seed_world
|
|
from server.state.store import WorldStore
|
|
|
|
|
|
def _selected_candidates() -> list[dict]:
|
|
return [
|
|
{"candidateType": "MATERIAL", "candidateId": "m-1", "code": "CTRL-A",
|
|
"name": "Controller A", "revision": "e", "confidence": 0.9},
|
|
{"candidateType": "BOM_REFERENCE", "candidateId": "b-1",
|
|
"componentReference": "PCB-001", "parentMaterialCode": "CTRL-A",
|
|
"confidence": 0.72, "revision": "e"},
|
|
{"candidateType": "ROUTING_OPERATION", "candidateId": "r-1",
|
|
"operationCode": "ASSEMBLY", "productCode": "CTRL-A",
|
|
"confidence": 0.63, "revision": "e"},
|
|
]
|
|
|
|
|
|
def _stage_drawing_confirmation(store: WorldStore, selected: list[dict]) -> str:
|
|
params = {
|
|
"drawingId": "drawing-1",
|
|
"sourceSha256": "0" * 64,
|
|
"selectedCandidates": selected,
|
|
"candidateIds": [row["candidateId"] for row in selected],
|
|
"executionSupported": True,
|
|
}
|
|
identity = get_identity()
|
|
confirm_id = f"drawing-link-{uuid.uuid4().hex[:12]}"
|
|
now = time.time()
|
|
record = {
|
|
"confirmId": confirm_id,
|
|
"sessionId": "drawing-test",
|
|
"action": "drawing.master.apply",
|
|
"power": "P2",
|
|
"params": deepcopy(params),
|
|
"paramsHash": harness._params_fingerprint(params),
|
|
"tenantUuid": store.tenant_uuid,
|
|
"ownerUserId": identity.user_id,
|
|
"delegateUserId": None,
|
|
"projectId": "default",
|
|
"worldKey": store.world_key,
|
|
"evidenceRefs": [],
|
|
"beforeSnapshot": None,
|
|
"beforeFingerprint": None,
|
|
"requester": {"userId": identity.user_id, "username": identity.username},
|
|
"approvals": [],
|
|
"approvalStep": 0,
|
|
"requiredApprovals": 1,
|
|
"createdAt": utc_now(now),
|
|
"createdAtEpoch": now,
|
|
"expiresAt": utc_now(now + 3600),
|
|
"expiresAtEpoch": now + 3600,
|
|
}
|
|
record["envelopeHash"] = harness._confirmation_envelope_fingerprint(record)
|
|
assert harness._approval_store.stage(record, now_epoch=now)
|
|
return confirm_id
|
|
|
|
|
|
def test_link_evidence_builder_adds_bom_routing_order_without_master_write():
|
|
world = seed_world()
|
|
before = deepcopy(world)
|
|
counter = {"n": 0}
|
|
|
|
def next_id(kind: str) -> int:
|
|
counter["n"] += 1
|
|
return counter["n"]
|
|
|
|
records = build_drawing_link_evidence_records(
|
|
world,
|
|
drawing_id="drawing-1",
|
|
source_sha256="0" * 64,
|
|
selected_candidates=_selected_candidates(),
|
|
actor="tester",
|
|
confirm_id="confirm-1",
|
|
next_id=next_id,
|
|
)
|
|
|
|
assert world == before
|
|
assert {row["linkType"] for row in records} == {"BOM", "ORDER", "ROUTING"}
|
|
bom = next(row for row in records if row["linkType"] == "BOM")
|
|
routing = next(row for row in records if row["linkType"] == "ROUTING")
|
|
orders = [row for row in records if row["linkType"] == "ORDER"]
|
|
assert bom["status"] == "PENDING_REVIEW"
|
|
assert bom["masterCommitted"] is False
|
|
assert bom["confidence"] == 0.72
|
|
assert bom["evidence"]["confirmId"] == "confirm-1"
|
|
assert routing["status"] == "PENDING_REVIEW"
|
|
assert routing["masterCommitted"] is False
|
|
assert orders
|
|
assert all(row["status"] == "CONFIRMED" for row in orders)
|
|
assert all(row["masterCommitted"] is True for row in orders)
|
|
|
|
|
|
def test_drawing_master_apply_persists_extended_drawing_link_evidence(tmp_path: Path):
|
|
store = WorldStore(path=tmp_path / "world.json")
|
|
selected = _selected_candidates()
|
|
before_master = {
|
|
key: deepcopy(store.data.get(key, []))
|
|
for key in ("boms", "bomItems", "routings", "routingSteps")
|
|
}
|
|
confirm_id = _stage_drawing_confirmation(store, selected)
|
|
|
|
message = execute_confirmed(store, confirm_id, True, actor="tester")
|
|
|
|
assert "Drawing review applied:" in message
|
|
assert "links=" in message
|
|
after_master = {
|
|
key: deepcopy(store.data.get(key, [])) for key in before_master
|
|
}
|
|
assert after_master == before_master
|
|
links = store.data.get("drawingLinks", [])
|
|
assert {"BOM", "MATERIAL", "ROUTING"} <= {row["linkType"] for row in links}
|
|
material = next(row for row in links if row["linkType"] == "MATERIAL")
|
|
bom = next(row for row in links if row["linkType"] == "BOM")
|
|
routing = next(row for row in links if row["linkType"] == "ROUTING")
|
|
assert material["masterCommitted"] is True
|
|
assert material["evidence"]["contractVersion"] == "drawing-link-evidence.v1"
|
|
assert bom["masterCommitted"] is False
|
|
assert bom["status"] == "PENDING_REVIEW"
|
|
assert routing["masterCommitted"] is False
|
|
assert routing["status"] == "PENDING_REVIEW"
|