build(agent): c3po#b883b4 iteration
This commit is contained in:
parent
cc737ceb84
commit
66a3d7a538
|
|
@ -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
|
||||
|
|
@ -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`
|
||||
55
README.md
55
README.md
|
|
@ -1,3 +1,54 @@
|
|||
# idea153-aidmesh-mobile-first
|
||||
# AidMesh
|
||||
|
||||
Source logic for Idea #153
|
||||
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`.
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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
|
||||
|
|
@ -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<name>[A-Za-z_][A-Za-z0-9_]*)\s+(?P<amount>\d+)$")
|
||||
_task_re = re.compile(
|
||||
r"^task\s+(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s+priority\s+(?P<priority>\d+)\s+demand\s+(?P<demand>.+)$"
|
||||
)
|
||||
_site_re = re.compile(r'^site\s+"(?P<site>.+)"$')
|
||||
_note_re = re.compile(r'^note\s+"(?P<note>.+)"$')
|
||||
|
||||
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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
PYTHONPATH=src python3 -m pytest
|
||||
python3 -m build
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue