From 66a3d7a538358f378fbc1d35184626bfda702c80 Mon Sep 17 00:00:00 2001 From: agent-b883b4bc188823a2 Date: Sun, 26 Apr 2026 22:11:17 +0200 Subject: [PATCH] build(agent): c3po#b883b4 iteration --- .gitignore | 21 ++++ AGENTS.md | 32 +++++ README.md | 55 ++++++++- pyproject.toml | 20 ++++ src/idea153_aidmesh_mobile_first/__init__.py | 21 ++++ src/idea153_aidmesh_mobile_first/adapters.py | 38 ++++++ src/idea153_aidmesh_mobile_first/contracts.py | 110 ++++++++++++++++++ src/idea153_aidmesh_mobile_first/replay.py | 22 ++++ src/idea153_aidmesh_mobile_first/solver.py | 47 ++++++++ src/idea153_aidmesh_mobile_first/sync.py | 85 ++++++++++++++ test.sh | 5 + tests/test_aidmesh.py | 72 ++++++++++++ 12 files changed, 526 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 pyproject.toml create mode 100644 src/idea153_aidmesh_mobile_first/__init__.py create mode 100644 src/idea153_aidmesh_mobile_first/adapters.py create mode 100644 src/idea153_aidmesh_mobile_first/contracts.py create mode 100644 src/idea153_aidmesh_mobile_first/replay.py create mode 100644 src/idea153_aidmesh_mobile_first/solver.py create mode 100644 src/idea153_aidmesh_mobile_first/sync.py create mode 100755 test.sh create mode 100644 tests/test_aidmesh.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +node_modules/ +.npmrc +.env +.env.* +__tests__/ +coverage/ +.nyc_output/ +dist/ +build/ +.cache/ +*.log +.DS_Store +tmp/ +.tmp/ +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +READY_TO_PUBLISH diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ef00c1e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# AGENTS.md + +## Architecture + +- `idea153_aidmesh_mobile_first` is a small Python package for offline-first disaster-response planning. +- Core pieces: + - `contracts.py`: parses a tiny ContractScript dialect and compiles it into a canonical portable module. + - `solver.py`: deterministic bounded solver for allocating scarce resources to tasks. + - `sync.py`: signed plan deltas, merge policy, and append-only plan ledger. + - `replay.py`: deterministic replay from signed logs. + - `adapters.py`: starter adapters for field inventory and drone routing. + +## Tech Stack + +- Python 3.11+ +- `pydantic` for validation +- `pytest` for tests +- `setuptools` for packaging + +## Rules + +- Keep changes deterministic and replayable. +- Prefer minimal, explicit data structures over hidden state. +- Preserve canonical JSON ordering when hashing or signing. +- Add tests for any behavior change in parsing, solving, syncing, or replay. +- Keep public APIs small and stable. + +## Test Commands + +- `bash test.sh` +- `PYTHONPATH=src python3 -m pytest` +- `python3 -m build` diff --git a/README.md b/README.md index 774ce7c..46c9fc8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,54 @@ -# idea153-aidmesh-mobile-first +# AidMesh -Source logic for Idea #153 \ No newline at end of file +AidMesh is a small offline-first planning engine for disaster response and humanitarian logistics. + +This repository currently ships a production-shaped core slice focused on deterministic field coordination: + +- `ContractScript` parsing into a canonical portable module +- bounded resource allocation for scarce field supplies +- signed plan deltas with replayable merge semantics +- starter adapters for inventory and drone routing +- deterministic replay for after-action review + +## What It Does + +- Models a site as capacities plus prioritized tasks. +- Compiles a tiny ContractScript into canonical JSON bytes and a stable digest. +- Solves tasks greedily under resource limits. +- Signs and verifies plan deltas with HMAC-SHA256. +- Merges and replays offline updates deterministically. + +## Project Layout + +- `src/idea153_aidmesh_mobile_first/contracts.py` +- `src/idea153_aidmesh_mobile_first/solver.py` +- `src/idea153_aidmesh_mobile_first/sync.py` +- `src/idea153_aidmesh_mobile_first/replay.py` +- `src/idea153_aidmesh_mobile_first/adapters.py` +- `tests/test_aidmesh.py` + +## Install + +```bash +python3 -m pip install -e . +``` + +## Test + +```bash +bash test.sh +``` + +## ContractScript Example + +```text +site "field-clinic-7" +capacity meds 10 +capacity water 6 +task triage priority 10 demand meds=4, water=2 +task shelter priority 4 demand water=4 +``` + +## Package Metadata + +The distribution name is `idea153-aidmesh-mobile-first` and the long description is sourced from this `README.md`. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f679aed --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea153-aidmesh-mobile-first" +version = "0.1.0" +description = "AidMesh is a mobile-first offline planning engine for disaster response." +readme = "README.md" +requires-python = ">=3.11" +dependencies = ["pydantic>=2.13"] + +[project.optional-dependencies] +test = ["pytest>=8"] + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/idea153_aidmesh_mobile_first/__init__.py b/src/idea153_aidmesh_mobile_first/__init__.py new file mode 100644 index 0000000..d078ce9 --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/__init__.py @@ -0,0 +1,21 @@ +"""AidMesh: offline-first planning and reconciliation primitives.""" + +from .adapters import inventory_to_contract, route_to_contract +from .contracts import ContractProgram, ContractScriptCompiler +from .replay import replay_ledger +from .solver import PlanAction, PlanResult, solve_program +from .sync import PlanDelta, PlanLedger, merge_deltas + +__all__ = [ + "ContractProgram", + "ContractScriptCompiler", + "PlanAction", + "PlanResult", + "PlanDelta", + "PlanLedger", + "inventory_to_contract", + "merge_deltas", + "replay_ledger", + "route_to_contract", + "solve_program", +] diff --git a/src/idea153_aidmesh_mobile_first/adapters.py b/src/idea153_aidmesh_mobile_first/adapters.py new file mode 100644 index 0000000..9331915 --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/adapters.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from .contracts import ContractProgram, TaskSpec + + +@dataclass(frozen=True) +class InventoryItem: + name: str + quantity: int + criticality: int + + +@dataclass(frozen=True) +class RouteWaypoint: + name: str + x: float + y: float + priority: int + + +def inventory_to_contract(site: str, items: list[InventoryItem]) -> ContractProgram: + capacities = {item.name: item.quantity for item in items} + tasks = [TaskSpec(name=f"restock_{item.name}", priority=item.criticality, demand={item.name: max(1, item.quantity // 4 or 1)}) for item in items] + return ContractProgram(site=site, capacities=capacities, tasks=tasks, notes=["generated from inventory adapter"]) + + +def route_to_contract(site: str, origin: tuple[float, float], waypoints: list[RouteWaypoint]) -> ContractProgram: + route_budget = max(1, len(waypoints)) + capacities = {"route_steps": route_budget} + ordered = sorted(waypoints, key=lambda wp: (-wp.priority, _distance(origin, (wp.x, wp.y)), wp.name)) + tasks = [TaskSpec(name=f"visit_{wp.name}", priority=wp.priority, demand={"route_steps": 1}) for wp in ordered] + return ContractProgram(site=site, capacities=capacities, tasks=tasks, notes=["generated from route adapter"]) + + +def _distance(a: tuple[float, float], b: tuple[float, float]) -> float: + return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2) ** 0.5 diff --git a/src/idea153_aidmesh_mobile_first/contracts.py b/src/idea153_aidmesh_mobile_first/contracts.py new file mode 100644 index 0000000..130a31f --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/contracts.py @@ -0,0 +1,110 @@ +from __future__ import annotations + +import json +import re +from dataclasses import dataclass, field +from hashlib import sha256 +from typing import Any + +from pydantic import BaseModel, Field, field_validator + + +class TaskSpec(BaseModel): + name: str + priority: int = Field(ge=0) + demand: dict[str, int] + tags: list[str] = Field(default_factory=list) + + @field_validator("demand") + @classmethod + def _validate_demand(cls, value: dict[str, int]) -> dict[str, int]: + if not value: + raise ValueError("task demand must not be empty") + if any(amount <= 0 for amount in value.values()): + raise ValueError("task demand values must be positive") + return value + + +class ContractProgram(BaseModel): + site: str + capacities: dict[str, int] + tasks: list[TaskSpec] + notes: list[str] = Field(default_factory=list) + + @field_validator("capacities") + @classmethod + def _validate_capacities(cls, value: dict[str, int]) -> dict[str, int]: + if not value: + raise ValueError("capacities must not be empty") + if any(amount < 0 for amount in value.values()): + raise ValueError("capacity values must be non-negative") + return value + + +@dataclass(frozen=True) +class PortableModule: + """Canonical portable representation of a compiled contract.""" + + program: ContractProgram + module_bytes: bytes + digest: str + metadata: dict[str, Any] = field(default_factory=dict) + + +class ContractScriptCompiler: + """Parse a tiny ContractScript dialect into a deterministic module.""" + + _capacity_re = re.compile(r"^capacity\s+(?P[A-Za-z_][A-Za-z0-9_]*)\s+(?P\d+)$") + _task_re = re.compile( + r"^task\s+(?P[A-Za-z_][A-Za-z0-9_]*)\s+priority\s+(?P\d+)\s+demand\s+(?P.+)$" + ) + _site_re = re.compile(r'^site\s+"(?P.+)"$') + _note_re = re.compile(r'^note\s+"(?P.+)"$') + + def compile(self, source: str) -> PortableModule: + site = "" + capacities: dict[str, int] = {} + tasks: list[TaskSpec] = [] + notes: list[str] = [] + + for raw_line in source.splitlines(): + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if match := self._site_re.match(line): + site = match.group("site") + continue + if match := self._note_re.match(line): + notes.append(match.group("note")) + continue + if match := self._capacity_re.match(line): + capacities[match.group("name")] = int(match.group("amount")) + continue + if match := self._task_re.match(line): + tasks.append( + TaskSpec( + name=match.group("name"), + priority=int(match.group("priority")), + demand=_parse_demand(match.group("demand")), + ) + ) + continue + raise ValueError(f"unrecognized ContractScript line: {raw_line}") + + if not site: + raise ValueError("missing site declaration") + + program = ContractProgram(site=site, capacities=capacities, tasks=sorted(tasks, key=lambda t: (-t.priority, t.name)), notes=notes) + payload = program.model_dump(mode="json") + canonical = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + digest = sha256(canonical).hexdigest() + module_bytes = canonical + return PortableModule(program=program, module_bytes=module_bytes, digest=digest, metadata={"format": "canonical-json", "version": 1}) + + +def _parse_demand(raw: str) -> dict[str, int]: + demand: dict[str, int] = {} + for item in raw.split(","): + name, amount = item.strip().split("=") + demand[name.strip()] = int(amount.strip()) + return demand diff --git a/src/idea153_aidmesh_mobile_first/replay.py b/src/idea153_aidmesh_mobile_first/replay.py new file mode 100644 index 0000000..8713497 --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/replay.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from .sync import PlanDelta, apply_delta + + +@dataclass(frozen=True) +class ReplayResult: + state: dict + applied: list[str] + + +def replay_ledger(initial_state: dict, deltas: list[PlanDelta], secret: str) -> ReplayResult: + state = dict(initial_state) + applied: list[str] = [] + for delta in sorted(deltas, key=lambda d: (d.sequence, d.device_id)): + if not delta.verify(secret): + raise ValueError(f"invalid signature for {delta.device_id}:{delta.sequence}") + state = apply_delta(state, delta) + applied.append(f"{delta.device_id}:{delta.sequence}") + return ReplayResult(state=state, applied=applied) diff --git a/src/idea153_aidmesh_mobile_first/solver.py b/src/idea153_aidmesh_mobile_first/solver.py new file mode 100644 index 0000000..c191c6d --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/solver.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from .contracts import ContractProgram, TaskSpec + + +@dataclass(frozen=True) +class PlanAction: + task: str + allocations: dict[str, int] + score: float + + +@dataclass(frozen=True) +class PlanResult: + site: str + selected_tasks: list[PlanAction] + remaining_capacity: dict[str, int] + audit_log: list[str] = field(default_factory=list) + + +def solve_program(program: ContractProgram) -> PlanResult: + remaining = dict(program.capacities) + selected: list[PlanAction] = [] + audit_log: list[str] = [] + + for task in program.tasks: + if _fits(task, remaining): + for resource, amount in task.demand.items(): + remaining[resource] -= amount + score = _task_score(task) + selected.append(PlanAction(task=task.name, allocations=dict(task.demand), score=score)) + audit_log.append(f"selected {task.name} score={score:.3f}") + else: + audit_log.append(f"skipped {task.name} insufficient capacity") + + return PlanResult(site=program.site, selected_tasks=selected, remaining_capacity=remaining, audit_log=audit_log) + + +def _fits(task: TaskSpec, remaining: dict[str, int]) -> bool: + return all(remaining.get(resource, 0) >= amount for resource, amount in task.demand.items()) + + +def _task_score(task: TaskSpec) -> float: + demand_weight = sum(task.demand.values()) + return task.priority / max(demand_weight, 1) diff --git a/src/idea153_aidmesh_mobile_first/sync.py b/src/idea153_aidmesh_mobile_first/sync.py new file mode 100644 index 0000000..7cc78e6 --- /dev/null +++ b/src/idea153_aidmesh_mobile_first/sync.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import hmac +import json +from dataclasses import dataclass, field +from hashlib import sha256 +from typing import Any + + +def _canonical_bytes(payload: dict[str, Any]) -> bytes: + return json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +@dataclass(frozen=True) +class PlanDelta: + device_id: str + sequence: int + parents: list[str] + changes: dict[str, Any] + summary: str + timestamp: str + signature: str = "" + + def payload(self) -> dict[str, Any]: + return { + "device_id": self.device_id, + "sequence": self.sequence, + "parents": list(self.parents), + "changes": self.changes, + "summary": self.summary, + "timestamp": self.timestamp, + } + + def sign(self, secret: str) -> "PlanDelta": + digest = hmac.new(secret.encode("utf-8"), _canonical_bytes(self.payload()), sha256).hexdigest() + return PlanDelta( + device_id=self.device_id, + sequence=self.sequence, + parents=list(self.parents), + changes=dict(self.changes), + summary=self.summary, + timestamp=self.timestamp, + signature=digest, + ) + + def verify(self, secret: str) -> bool: + expected = hmac.new(secret.encode("utf-8"), _canonical_bytes(self.payload()), sha256).hexdigest() + return hmac.compare_digest(expected, self.signature) + + +@dataclass +class PlanLedger: + secret: str + state: dict[str, Any] = field(default_factory=dict) + entries: list[PlanDelta] = field(default_factory=list) + + def append(self, delta: PlanDelta) -> None: + if not delta.verify(self.secret): + raise ValueError("invalid plan delta signature") + self.state = apply_delta(self.state, delta) + self.entries.append(delta) + + +def merge_deltas(base_state: dict[str, Any], deltas: list[PlanDelta], secret: str) -> dict[str, Any]: + state = dict(base_state) + for delta in sorted(deltas, key=lambda d: (d.sequence, d.device_id)): + if not delta.verify(secret): + raise ValueError(f"invalid signature for {delta.device_id}:{delta.sequence}") + state = apply_delta(state, delta) + return state + + +def apply_delta(state: dict[str, Any], delta: PlanDelta) -> dict[str, Any]: + next_state = json.loads(json.dumps(state, sort_keys=True)) + for key, value in delta.changes.items(): + if isinstance(value, dict) and isinstance(next_state.get(key), dict): + merged = dict(next_state[key]) + merged.update(value) + next_state[key] = merged + else: + next_state[key] = value + next_state.setdefault("audit", []) + next_state["audit"] = list(next_state["audit"]) + [f"{delta.device_id}:{delta.sequence}:{delta.summary}"] + next_state["last_hash"] = sha256(_canonical_bytes(delta.payload())).hexdigest() + return next_state diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..61be3e9 --- /dev/null +++ b/test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +PYTHONPATH=src python3 -m pytest +python3 -m build diff --git a/tests/test_aidmesh.py b/tests/test_aidmesh.py new file mode 100644 index 0000000..327e5a6 --- /dev/null +++ b/tests/test_aidmesh.py @@ -0,0 +1,72 @@ +from __future__ import annotations + +from idea153_aidmesh_mobile_first import ContractScriptCompiler, PlanDelta, PlanLedger, merge_deltas, replay_ledger, solve_program +from idea153_aidmesh_mobile_first.adapters import InventoryItem, RouteWaypoint, inventory_to_contract, route_to_contract + + +def test_compiler_and_solver_select_high_priority_tasks(): + source = """ + site "field-clinic-7" + capacity meds 10 + capacity water 6 + task triage priority 10 demand meds=4, water=2 + task shelter priority 4 demand water=4 + """ + module = ContractScriptCompiler().compile(source) + assert module.digest + result = solve_program(module.program) + assert [action.task for action in result.selected_tasks] == ["triage", "shelter"] + assert result.remaining_capacity == {"meds": 6, "water": 0} + + +def test_plan_deltas_sign_merge_and_replay(): + secret = "shared-key" + delta_a = PlanDelta( + device_id="truck-1", + sequence=1, + parents=[], + changes={"inventory": {"meds": 12}}, + summary="restocked meds", + timestamp="2026-04-26T20:00:00Z", + ).sign(secret) + delta_b = PlanDelta( + device_id="drone-2", + sequence=2, + parents=[delta_a.signature], + changes={"routes": {"drone-2": ["clinic", "depot"]}}, + summary="published route", + timestamp="2026-04-26T20:01:00Z", + ).sign(secret) + + merged = merge_deltas({}, [delta_b, delta_a], secret) + assert merged["inventory"]["meds"] == 12 + assert merged["routes"]["drone-2"] == ["clinic", "depot"] + + replay = replay_ledger({}, [delta_a, delta_b], secret) + assert replay.state["inventory"]["meds"] == 12 + assert replay.applied == ["truck-1:1", "drone-2:2"] + + +def test_adapters_generate_valid_contracts(): + inventory_program = inventory_to_contract( + "warehouse-1", + [InventoryItem(name="bandages", quantity=20, criticality=8), InventoryItem(name="saline", quantity=12, criticality=6)], + ) + route_program = route_to_contract( + "airstrip-a", + (0.0, 0.0), + [RouteWaypoint(name="north-hill", x=2.0, y=3.0, priority=9), RouteWaypoint(name="river-base", x=1.0, y=1.0, priority=4)], + ) + assert solve_program(inventory_program).selected_tasks + assert solve_program(route_program).selected_tasks + ledger = PlanLedger(secret="shared-key") + delta = PlanDelta( + device_id="inventory-1", + sequence=1, + parents=[], + changes={"inventory": {"bandages": 20}}, + summary="inventory snapshot", + timestamp="2026-04-26T20:02:00Z", + ).sign("shared-key") + ledger.append(delta) + assert ledger.state["inventory"]["bandages"] == 20