Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .agentmesh/policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"public_private": {
"content_scan_exempt_globs": [
"tests/fixtures/secrets/**"
]
}
}
44 changes: 44 additions & 0 deletions .github/workflows/nightly-agentmesh-simulations.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Nightly QA Simulations

on:
schedule:
# 03:15 UTC daily, offset from other nightly workflows
- cron: "15 3 * * *"
workflow_dispatch:

jobs:
full-suite:
name: Full test suite
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run full test suite
run: pytest tests/ -v

# Contention tests with extended timeout for heavier concurrency pressure
contention-heavy:
name: Contention tests (extended)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run contention tests with extended timeout
run: pytest tests/test_contention.py -v
38 changes: 38 additions & 0 deletions .github/workflows/pr-agentmesh-qa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: PR QA – Contention & Secret Detection

on:
pull_request:
branches: [main]

jobs:
contention:
name: Contention tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run contention tests
run: pytest tests/test_contention.py -v

secret-detection:
name: Secret detection tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run secret detection tests
run: pytest tests/test_secret_detection.py -v
57 changes: 57 additions & 0 deletions .github/workflows/release-agentmesh-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Release Check

on:
release:
types: [created]
workflow_dispatch:

jobs:
build-and-verify:
name: Build, install, and verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Build wheel
run: |
pip install build
python -m build

- name: Install wheel in clean venv
run: |
python -m venv .release-venv
. .release-venv/bin/activate
pip install dist/*.whl

- name: Verify CLI entry points
run: |
. .release-venv/bin/activate
agentmesh --version
agentmesh --help
agentmesh doctor --help

- name: Upload wheel artifact
uses: actions/upload-artifact@v4
with:
name: release-wheel
path: dist/

test-suite:
name: Full test suite against source
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run full test suite
run: pytest tests/ -v
175 changes: 175 additions & 0 deletions src/agentmesh/assay_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,178 @@ def emit_bridge_event(
)

return BridgeResult(status=status, gate_report=gate_report, reason=reason, envelope=envelope)


# ---------------------------------------------------------------------------
# Proof Posture bridge
# ---------------------------------------------------------------------------

_POSTURE_OK = "POSTURE_OK"
_POSTURE_DEGRADED = "POSTURE_DEGRADED"


@dataclass(frozen=True)
class PostureResult:
status: str # _POSTURE_OK | _POSTURE_DEGRADED
posture: dict[str, Any] # Full posture dict from assay posture --json
text: str # Human-readable rendered summary
reason: str # Empty on OK, human-readable on degraded


def _find_proof_packs(repo_path: Path) -> list[Path]:
"""Find proof pack directories in a repo, newest first."""
packs: list[Path] = []
for candidate in repo_path.iterdir():
if candidate.is_dir() and candidate.name.startswith("proof_pack_"):
if (candidate / "pack_manifest.json").exists():
packs.append(candidate)
return sorted(packs, key=lambda p: p.stat().st_mtime, reverse=True)


def _run_assay_posture(
pack_dir: Path,
*,
require_falsifiers: bool = False,
) -> tuple[str, dict[str, Any], str, str]:
"""Run ``assay posture`` and return (status, posture_dict, text, reason)."""
if shutil.which("assay") is None:
return _POSTURE_DEGRADED, {}, "", "assay CLI not found on PATH"

cmd = ["assay", "posture", str(pack_dir), "--json"]
if require_falsifiers:
cmd.append("--require-falsifiers")

try:
proc = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
)
except subprocess.TimeoutExpired:
return _POSTURE_DEGRADED, {}, "", "assay posture timed out"
except OSError as exc:
return _POSTURE_DEGRADED, {}, "", f"failed to start assay: {exc}"

if proc.returncode == 3:
return _POSTURE_DEGRADED, {}, "", f"assay posture: bad input ({proc.stderr.strip()})"

try:
posture = json.loads(proc.stdout)
except (json.JSONDecodeError, ValueError):
return _POSTURE_DEGRADED, {}, "", "assay posture returned non-JSON output"

# Also get the text version for PR comments
text_cmd = ["assay", "posture", str(pack_dir)]
if require_falsifiers:
text_cmd.append("--require-falsifiers")
try:
text_proc = subprocess.run(
text_cmd,
capture_output=True,
text=True,
timeout=30,
)
text = text_proc.stdout.strip()
except Exception:
text = ""

# Exit 0 (verified/supported) and 1 (incomplete/blocked) are both valid.
return _POSTURE_OK, posture, text, ""


def _post_pr_comment(pr_ref: str, body: str, repo_path: Path) -> bool:
"""Post a comment to a PR via ``gh pr comment``. Returns True on success."""
if shutil.which("gh") is None:
return False
try:
subprocess.run(
["gh", "pr", "comment", pr_ref, "--body", body],
capture_output=True,
text=True,
timeout=30,
cwd=str(repo_path),
)
return True
except Exception:
Comment on lines +295 to +308
return False


def emit_posture_comment(
*,
task_id: str,
pr_ref: str,
repo_path: Path | None = None,
pack_dir: Path | None = None,
require_falsifiers: bool = False,
agent_id: str = "",
episode_id: str = "",
data_dir: Path | None = None,
) -> PostureResult:
"""Run proof posture and post a PR comment.

AgentMesh calls Assay, does not reinterpret Assay.
Assay owns posture semantics. AgentMesh owns when/where to attach.
"""
Comment on lines +312 to +327
# Resolve repo path
if repo_path is None:
repo_path = _find_repo_path(task_id, data_dir)
if repo_path is None or not repo_path.is_dir():
return PostureResult(
status=_POSTURE_DEGRADED,
posture={},
text="",
reason="no repo path found for task",
)

# Resolve proof pack
if pack_dir is None:
packs = _find_proof_packs(repo_path)
if not packs:
return PostureResult(
status=_POSTURE_DEGRADED,
posture={},
text="",
reason=f"no proof packs found in {repo_path}",
)
pack_dir = packs[0] # newest

# Run assay posture
status, posture, text, reason = _run_assay_posture(
pack_dir, require_falsifiers=require_falsifiers,
)

if status == _POSTURE_DEGRADED:
result = PostureResult(status=status, posture=posture, text=text, reason=reason)
else:
# Format the PR comment
disposition = posture.get("disposition", "unknown")
header = f"**Proof Posture: {disposition.upper().replace('_', ' ')}**"
comment_body = f"{header}\n\n```\n{text}\n```\n\n<sub>Generated by assay posture | pack: {pack_dir.name}</sub>"

posted = _post_pr_comment(pr_ref, comment_body, repo_path)

result = PostureResult(
status=_POSTURE_OK,
posture=posture,
text=text,
reason="" if posted else "posture computed but gh pr comment failed",
)

# Emit event
events.append_event(
kind=EventKind.ASSAY_RECEIPT,
agent_id=agent_id,
payload={
"task_id": task_id,
"action": "posture_comment",
"pr_ref": pr_ref,
"bridge_status": result.status,
"disposition": posture.get("disposition", ""),
"pack_dir": str(pack_dir) if pack_dir else "",
"degraded_reason": result.reason,
Comment on lines +378 to +384
},
data_dir=data_dir,
)

return result
20 changes: 19 additions & 1 deletion src/agentmesh/orchestrator.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,29 @@ def advance_task(
agent_id=agent_id,
data_dir=data_dir,
)
return transition_task(
task = transition_task(
task_id,
to_state,
agent_id=agent_id,
reason=reason,
data_dir=data_dir,
**update_kwargs,
)

# PR_OPEN side effect: emit proof posture comment if PR is available.
# AgentMesh calls Assay, does not reinterpret Assay.
if to_state == TaskState.PR_OPEN:
pr_ref = task.pr_url or task.branch
if pr_ref:
try:
assay_bridge.emit_posture_comment(
task_id=task_id,
pr_ref=pr_ref,
agent_id=agent_id,
episode_id=task.episode_id,
data_dir=data_dir,
)
Comment on lines +479 to +491
except Exception:
pass # Posture is best-effort, never blocks transition
Comment on lines +484 to +493

return task
3 changes: 2 additions & 1 deletion src/agentmesh/public_private.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ def classify_path(
private_globs = _policy_list(cfg.get("private_path_globs")) or _DEFAULT_PRIVATE_GLOBS
review_globs = _policy_list(cfg.get("review_path_globs")) or _DEFAULT_REVIEW_GLOBS
private_patterns = _policy_list(cfg.get("private_content_patterns")) or _DEFAULT_PRIVATE_PATTERNS
content_scan_exempt = _policy_list(cfg.get("content_scan_exempt_globs"))

rel = _rel_path(path, repo_root)
reasons: list[str] = []
Expand All @@ -125,7 +126,7 @@ def classify_path(
reasons.append("path matches private pattern")

content_marker = None
if path.exists() and path.is_file():
if path.exists() and path.is_file() and not _has_match(rel, content_scan_exempt):
try:
text = path.read_text(errors="ignore")
except OSError:
Expand Down
2 changes: 2 additions & 0 deletions tests/fixtures/secrets/aws_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This file contains a leaked AWS access key for testing secret detection.
AWS_ACCESS_KEY_ID = "AKIAIOSFODNN7EXAMPLE"
5 changes: 5 additions & 0 deletions tests/fixtures/secrets/clean_public.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""A module with no secrets or sensitive content."""


def greet(name: str) -> str:
return f"Hello, {name}!"
6 changes: 6 additions & 0 deletions tests/fixtures/secrets/edge_ghp_in_comment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Utility module with an accidentally pasted token in a comment."""


def do_work() -> None:
# TODO: remove this token ghp_A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6
pass
2 changes: 2 additions & 0 deletions tests/fixtures/secrets/ghp_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# This file contains a leaked GitHub PAT for testing secret detection.
API_TOKEN = "ghp_R8x2mN4vL6pQ9wK1jT3yF5bA7cE0hU2sG4nM"
Comment on lines +1 to +2
3 changes: 3 additions & 0 deletions tests/fixtures/secrets/pricing_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Internal Strategy

Our pricing model targets enterprise customers at $25K per seat.
Loading
Loading