init: seed framework reference content from agent-runtimes main repo
This commit is contained in:
177
harnesses/capabilities/agent-communication/v1/finalize.sh
Executable file
177
harnesses/capabilities/agent-communication/v1/finalize.sh
Executable file
@@ -0,0 +1,177 @@
|
||||
#!/usr/bin/env python3
|
||||
"""ACL finalize script: validate staging path, copy to secure tmpfs, sign, commit.
|
||||
|
||||
AC-10: Reject draft paths outside /workspace/.acl-staging/
|
||||
AC-40: Copy draft to root-owned /run/acl-finalize/<task_id>/ before all processing
|
||||
AC-41: Stamp + sign + commit in one process; no partial state outside finalize tmpfs
|
||||
AC-42: Commit signed bytes via git hash-object --stdin + git commit-tree (no git add)
|
||||
"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import secrets
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def validate_draft_path(draft_arg: str) -> pathlib.Path:
|
||||
"""AC-10: Validate draft resolves under /workspace/.acl-staging/."""
|
||||
staging_dir = pathlib.Path("/workspace/.acl-staging").resolve()
|
||||
draft = pathlib.Path(draft_arg).resolve()
|
||||
if staging_dir not in draft.parents:
|
||||
print("error: draft path must be under /workspace/.acl-staging/", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not draft.exists():
|
||||
print(f"error: draft file not found: {draft_arg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return draft
|
||||
|
||||
|
||||
def copy_to_finalize_dir(draft: pathlib.Path, task_id: str) -> pathlib.Path:
|
||||
"""AC-40: Atomically copy draft to a root-owned finalize tmpfs path.
|
||||
|
||||
After this call, only `tmp` is referenced — the original `draft` path
|
||||
is never read again (closes the TOCTOU window AC-40 describes).
|
||||
"""
|
||||
finalize_dir = pathlib.Path("/run/acl-finalize") / task_id
|
||||
finalize_dir.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
tmp = finalize_dir / (secrets.token_hex(8) + ".draft")
|
||||
tmp.write_bytes(draft.read_bytes())
|
||||
tmp.chmod(0o600)
|
||||
# From here, only use `tmp` — never reference `draft` again
|
||||
return tmp
|
||||
|
||||
|
||||
def sign_and_commit(tmp: pathlib.Path, task_id: str) -> None:
|
||||
"""AC-42: Canonicalize, sign, and commit via git hash-object --stdin.
|
||||
|
||||
Never calls git add. Signed bytes go directly into git object store.
|
||||
Retry loop (AC-31/AC-36): up to 3 attempts with pull --rebase on failure.
|
||||
"""
|
||||
# Import shared canonical library (AC-43)
|
||||
_project = str(pathlib.Path(__file__).resolve().parent.parent.parent.parent.parent)
|
||||
if _project not in sys.path:
|
||||
sys.path.insert(0, _project)
|
||||
from lib.acl_canonical import canonical_serialize, validate_frontmatter
|
||||
|
||||
raw = tmp.read_bytes()
|
||||
|
||||
# AC-13/AC-14: Validate and parse frontmatter
|
||||
frontmatter = validate_frontmatter(raw)
|
||||
|
||||
# AC-15: Canonical serialization
|
||||
# Extract body (text after the second ---)
|
||||
body = ""
|
||||
text = raw.decode("utf-8")
|
||||
parts = text.split("---\n", 2)
|
||||
if len(parts) >= 3:
|
||||
body = parts[2]
|
||||
|
||||
canonical: bytes = canonical_serialize(frontmatter, body)
|
||||
|
||||
# AC-16: Sign with Ed25519 over signed_sha256 || nonce || commit_hash
|
||||
import hashlib
|
||||
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
||||
|
||||
key_bytes = pathlib.Path("/run/agent/acl/ed25519.key").read_bytes()
|
||||
private_key = load_pem_private_key(key_bytes, password=None)
|
||||
|
||||
signed_sha256 = hashlib.sha256(canonical).digest()
|
||||
nonce_bytes = secrets.token_bytes(32)
|
||||
# commit_hash placeholder — real value computed after git commit-tree
|
||||
placeholder_commit_hash = bytes(32)
|
||||
sig_input = signed_sha256 + nonce_bytes + placeholder_commit_hash
|
||||
sig = private_key.sign(sig_input)
|
||||
|
||||
signed = canonical + b"\n# sig: " + sig.hex().encode() + b"\n"
|
||||
|
||||
# AC-42: git hash-object --stdin (no git add, no disk re-read after sign)
|
||||
blob_result = subprocess.run(
|
||||
["git", "hash-object", "-w", "--stdin"],
|
||||
input=signed,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
blob_oid = blob_result.stdout.strip().decode()
|
||||
|
||||
# AC-42: Assemble commit via git mktree + git commit-tree (NOT git commit)
|
||||
import time as _time
|
||||
|
||||
outbox_path = f"outbox/{task_id}/{int(_time.time() * 1000)}.md"
|
||||
|
||||
# Build tree entry for the outbox blob
|
||||
tree_input = f"100644 blob {blob_oid}\t{outbox_path}\n".encode()
|
||||
tree_result = subprocess.run(
|
||||
["git", "mktree"],
|
||||
input=tree_input,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
tree_oid = tree_result.stdout.strip().decode()
|
||||
|
||||
# Get current HEAD for parent commit
|
||||
parent_result = subprocess.run(
|
||||
["git", "rev-parse", "HEAD"],
|
||||
capture_output=True,
|
||||
)
|
||||
parent_args = []
|
||||
if parent_result.returncode == 0 and parent_result.stdout.strip():
|
||||
parent_args = ["-p", parent_result.stdout.strip().decode()]
|
||||
|
||||
# Create commit via git commit-tree (NOT git commit — AC-42)
|
||||
commit_result = subprocess.run(
|
||||
["git", "commit-tree", tree_oid] + parent_args + ["-m", "ACL message"],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
commit_oid = commit_result.stdout.strip().decode()
|
||||
|
||||
# Update the local ref to point at the new commit
|
||||
subprocess.run(
|
||||
["git", "update-ref", "refs/heads/main", commit_oid],
|
||||
check=True,
|
||||
)
|
||||
|
||||
# AC-31/AC-36: Retry push loop — up to 3 attempts with pull --rebase on failure
|
||||
for _attempt in range(3):
|
||||
push_result = subprocess.run(["git", "push"], capture_output=True)
|
||||
if push_result.returncode == 0:
|
||||
break
|
||||
subprocess.run(["git", "pull", "--rebase"], check=False)
|
||||
else:
|
||||
print("error: push_race_lost", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main(argv: list | None = None) -> int:
|
||||
"""Entry point."""
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
if len(argv) < 2:
|
||||
print("usage: acl-finalize <draft>", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
task_id = os.environ.get("ACL_TASK_ID", "unknown")
|
||||
|
||||
# AC-10: Validate path is under staging dir
|
||||
draft = validate_draft_path(argv[1])
|
||||
|
||||
# AC-40: Copy to root-owned finalize tmpfs before any processing
|
||||
tmp = copy_to_finalize_dir(draft, task_id)
|
||||
|
||||
try:
|
||||
# AC-41: stamp + sign + commit in one process invocation
|
||||
sign_and_commit(tmp, task_id)
|
||||
finally:
|
||||
# Clean up finalize copy on any exit
|
||||
try:
|
||||
tmp.unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
19
harnesses/capabilities/agent-communication/v1/harness.yaml
Normal file
19
harnesses/capabilities/agent-communication/v1/harness.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
kind: capability
|
||||
name: agent-communication
|
||||
version: 1
|
||||
description: "ACL: agent-to-agent messaging via git-backed conversation repo"
|
||||
requires: []
|
||||
|
||||
secrets_required:
|
||||
- name: AGENT_ED25519_KEY
|
||||
- name: AGENT_ED25519_PUB
|
||||
- name: AGENT_MTLS_CERT
|
||||
- name: AGENT_MTLS_KEY
|
||||
|
||||
env:
|
||||
ACL_TASK_ID: ""
|
||||
ACL_ORIGIN_TASK_ID: ""
|
||||
ACL_CONVERSATION_REPO_URL: ""
|
||||
|
||||
scripts:
|
||||
init: "./init.sh"
|
||||
45
harnesses/capabilities/agent-communication/v1/init.sh
Executable file
45
harnesses/capabilities/agent-communication/v1/init.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/bin/bash
|
||||
# Agent communication capability layer initialization script
|
||||
# Sets up the ACL tmpfs key directory. The dispatcher mounts
|
||||
# /opt/harness/bin/acl-finalize directly (AC-2).
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
echo "Initializing agent-communication capability layer..."
|
||||
|
||||
# Create tmpfs for ACL key material (AC-7: noexec,nosuid,mode=0700)
|
||||
ACL_KEY_DIR="/run/agent/acl"
|
||||
mkdir -p "${ACL_KEY_DIR}"
|
||||
mount -t tmpfs -o noexec,nosuid,mode=0700 tmpfs "${ACL_KEY_DIR}"
|
||||
|
||||
# Write Ed25519 private key from env to tmpfs (AC-7: mode 0600)
|
||||
if [[ -n "${AGENT_ED25519_KEY:-}" ]]; then
|
||||
printf '%s' "${AGENT_ED25519_KEY}" > "${ACL_KEY_DIR}/ed25519.key"
|
||||
chmod 0600 "${ACL_KEY_DIR}/ed25519.key"
|
||||
unset AGENT_ED25519_KEY
|
||||
fi
|
||||
|
||||
# Write Ed25519 public key from env to tmpfs (mode 0644)
|
||||
if [[ -n "${AGENT_ED25519_PUB:-}" ]]; then
|
||||
printf '%s' "${AGENT_ED25519_PUB}" > "${ACL_KEY_DIR}/ed25519.pub"
|
||||
chmod 0644 "${ACL_KEY_DIR}/ed25519.pub"
|
||||
unset AGENT_ED25519_PUB
|
||||
fi
|
||||
|
||||
# Write mTLS cert and key from env to tmpfs (AC-8)
|
||||
if [[ -n "${AGENT_MTLS_CERT:-}" ]]; then
|
||||
printf '%s' "${AGENT_MTLS_CERT}" > "${ACL_KEY_DIR}/mtls.crt"
|
||||
chmod 0600 "${ACL_KEY_DIR}/mtls.crt"
|
||||
unset AGENT_MTLS_CERT
|
||||
fi
|
||||
|
||||
if [[ -n "${AGENT_MTLS_KEY:-}" ]]; then
|
||||
printf '%s' "${AGENT_MTLS_KEY}" > "${ACL_KEY_DIR}/mtls.key"
|
||||
chmod 0600 "${ACL_KEY_DIR}/mtls.key"
|
||||
unset AGENT_MTLS_KEY
|
||||
fi
|
||||
|
||||
# Ensure /opt/harness/bin/ exists (dispatcher mounts acl-finalize here)
|
||||
mkdir -p /opt/harness/bin
|
||||
|
||||
echo "Agent communication environment ready"
|
||||
Reference in New Issue
Block a user