feat: add project-scoped operations
Generic Container CI/CD / test-build-publish (push) Failing after 6s
Server CI/CD v2 / build-test-publish-and-deploy (push) Failing after 6s

This commit is contained in:
2026-10-22 18:40:20 +08:00
parent d320972d57
commit 1d061f6099
73 changed files with 4384 additions and 228 deletions
+20 -1
View File
@@ -69,6 +69,14 @@ async def authorize_query(
if tool.name not in binding.tool_cap:
raise HTTPException(403, "Query is outside the captured tool catalog")
permissions = permissions_for_context(scope.project)
if tool.mutating:
if binding.purpose != "ordinary_chat" or binding.task_id is not None or scope.project.project_role != "member":
raise HTTPException(403, "Writes require a project member's ordinary interactive chat")
required = {tool.permission}
if tool.name == "save_simulation_scenario":
required.add("scada.view")
if not required.issubset(permissions) or not required.issubset(binding.permission_cap):
raise HTTPException(403, "Write and input data permissions are required")
if tool.computation:
if tool.name == "optimize_sensor_placement" and scope.project.project_role != "member":
raise HTTPException(403, "Sensor placement requires project member access")
@@ -123,6 +131,12 @@ async def execute_query(
full_snapshot: bool = False,
) -> QueryResult:
await authorize_query(scope, binding, tool, args)
if tool.mutating:
from app.mcp.mutation_service import execute_mutation
return await execute_mutation(scope, binding, tool, args)
from app.mcp.extended_service import EXTENDED_READ_TOOLS, execute_extended
if tool.name in EXTENDED_READ_TOOLS:
return await execute_extended(scope, binding, tool, args)
name = tool.name
conn = scope.connection
page = None
@@ -162,7 +176,12 @@ async def execute_query(
"run_burst_detection": "Pressure anomalies alone do not confirm a burst or locate a pipe.",
"run_burst_location": "Candidate ranking depends on the leakage assumption, model and sensor coverage; field confirmation is required.",
"analyze_valve_isolation": "Topology-only isolation advice; valve operability, hydraulic impact and field actions are not verified or executed.",
"calculate_demand_allocation": "Pipe-length-weighted allocation only; model demands were not modified. Units follow the current model.",
"run_burst_simulation": "Forward hydraulic simulation for assumed orifice areas, not detection or confirmed burst location; only requested elements returned.",
}[name]]
if name == "calculate_demand_allocation":
truncated = data["result"]["has_more"] or args.offset > 0
page = PageInfo(limit=args.limit, offset=args.offset, total=data["result"]["total"], has_more=data["result"]["has_more"])
if getattr(args, "data_source", None) == "simulation":
gaps.append("Input is an existing analysis run and model baseline, not observed SCADA data.")
elif name == "query_nearby_elements":
@@ -216,7 +235,7 @@ async def execute_query(
"pump-energy": "get_pump_energy_schema",
}
function = getattr(
tjnetwork, mapping.get(args.kind, "get_" + args.kind + "_schema")
tjnetwork, mapping.get(args.kind, "get_" + args.kind.replace("-", "_") + "_schema")
)
data = await asyncio.to_thread(function, scope.project.project_code)
elif name == "get_network_options":