feat: ship hitridge-business-o1 v0.1.0 — signed depo package + toolchain
- package: manifest v1.0 (schema, package_id, layers: odoo_modules/agents/docs/config/brain) - agents: 4 neutral cards + prompts (sellers-recruiter, test-operator, closer, compliance) no script.run in v0.1.0; odoo.* + file.* primitives only - modules: aura_business_core + hitridge_venture_o1 stubs (menus, ACLs, models) - docs: playbook, test-checklist, price-guide-starter, agreement-template.odt - config: channels (olx-bg/forums-local), pricing-rules (BGN, A/B/C/reject, drop ladder) - brain: empty-seed capability contract + seeds README (memories never ship) - toolchain: depo-sign.sh (ed25519, PEM+raw), validate-depo.sh (schema/files/cards/tools/ secrets/sig gates -> writes VALIDATE), tool-registry.json - install.sh: target-side verify (hash + ed25519 + layer files); install is phase 2 - public-keys/depo-signing.pub committed (private key stays out of repo) - VALIDATE: PASS (signed + verified, tamper-rejection tested)
This commit is contained in:
Executable
+166
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bash
|
||||
# validate-depo.sh — validate a package against manifest-spec v1.0 + agent-card-spec v1.0.
|
||||
# Rejects: malformed manifest, missing layers/files, unregistered tools, missing
|
||||
# verification, secrets, missing signature/hash.
|
||||
#
|
||||
# Usage:
|
||||
# validate-depo.sh <package_dir> [--key <pubkey>] [--registry <path>]
|
||||
# Writes <package_dir>/VALIDATE on success (the spec-mandated gate file).
|
||||
set -euo pipefail
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "usage: validate-depo.sh <package_dir> [--key <pubkey>] [--registry <path>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
PKG_DIR="$1"
|
||||
shift
|
||||
PUBKEY=""
|
||||
REGISTRY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/tool-registry.json"
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--key) PUBKEY="${2:?}"; shift 2 ;;
|
||||
--registry) REGISTRY="${2:?}"; shift 2 ;;
|
||||
*) echo "validate-depo: unknown arg $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
MANIFEST="$PKG_DIR/manifest.json"
|
||||
[ -f "$MANIFEST" ] || { echo "validate-depo: no manifest.json in $PKG_DIR" >&2; exit 1; }
|
||||
|
||||
# Signature + hash gate (refusal per spec: No signature = refuse)
|
||||
for art in "$MANIFEST.sig" "$MANIFEST.sha256"; do
|
||||
[ -f "$art" ] || { echo "validate-depo: FAIL — missing $art (refusal per spec)" >&2; exit 1; }
|
||||
done
|
||||
GOT="$(sha256sum "$MANIFEST" | cut -d' ' -f1)"
|
||||
WANT="$(cat "$MANIFEST.sha256")"
|
||||
[ "$GOT" = "$WANT" ] || { echo "validate-depo: FAIL — manifest hash mismatch (modified after signing)" >&2; exit 1; }
|
||||
if [ -n "$PUBKEY" ]; then
|
||||
python3 - "$PUBKEY" "$MANIFEST.sig" "$MANIFEST" <<'PY' || { echo "validate-depo: FAIL — bad signature" >&2; exit 1; }
|
||||
import base64, sys
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
pub = Ed25519PublicKey.from_public_bytes(open(sys.argv[1], "rb").read())
|
||||
sig = base64.b64decode(open(sys.argv[2], "rb").read())
|
||||
data = open(sys.argv[3], "rb").read()
|
||||
pub.verify(sig, data)
|
||||
PY
|
||||
echo "validate-depo: signature OK"
|
||||
fi
|
||||
|
||||
python3 - "$MANIFEST" "$PKG_DIR" "$REGISTRY" <<'PY' || { echo "validate-depo: FAIL — semantic checks" >&2; exit 1; }
|
||||
import json, os, re, sys
|
||||
manifest_path, root, registry_path = sys.argv[1], os.path.abspath(sys.argv[2]), sys.argv[3]
|
||||
m = json.load(open(manifest_path))
|
||||
errs = []
|
||||
|
||||
def req(obj, key, where):
|
||||
if key not in obj or obj[key] in (None, "", [], {}):
|
||||
errs.append(f"{where}: missing/invalid '{key}'")
|
||||
|
||||
req(m, "schema_version", "manifest")
|
||||
if m.get("schema_version") != "1.0":
|
||||
errs.append("manifest: schema_version must be '1.0'")
|
||||
req(m, "package_id", "manifest")
|
||||
req(m, "display_name", "manifest")
|
||||
req(m, "version", "manifest")
|
||||
req(m, "license", "manifest")
|
||||
req(m, "requires", "manifest")
|
||||
req(m, "layers", "manifest")
|
||||
req(m, "install", "manifest")
|
||||
if not m.get("install", {}).get("order"):
|
||||
errs.append("manifest.install: missing/invalid 'order'")
|
||||
|
||||
if "agents" not in m.get("layers", {}):
|
||||
errs.append("manifest.layers: no agents layer (spec requires it)")
|
||||
for name in ("odoo_modules", "agents", "docs", "config"):
|
||||
if name not in m.get("layers", {}):
|
||||
errs.append(f"manifest.layers: missing '{name}'")
|
||||
|
||||
# Every layer file must exist on disk.
|
||||
# odoo_modules entries are module names -> modules/<name>/__manifest__.py
|
||||
# agents entries are card ids -> agents/<id>.json
|
||||
# docs/config/brain entries are plain paths relative to the package root
|
||||
for layer, files in m.get("layers", {}).items():
|
||||
for f in (files if isinstance(files, list) else [files]):
|
||||
if layer == "odoo_modules":
|
||||
p = os.path.join(root, "modules", f, "__manifest__.py")
|
||||
label = f"modules/{f}/__manifest__.py"
|
||||
elif layer == "agents":
|
||||
p = os.path.join(root, "agents", f"{f}.json")
|
||||
label = f"agents/{f}.json"
|
||||
else:
|
||||
p = os.path.join(root, f)
|
||||
label = f
|
||||
if not os.path.exists(p):
|
||||
errs.append(f"manifest.layers.{layer}: file missing on disk: {label}")
|
||||
|
||||
# Agent cards must satisfy agent-card-spec v1.0
|
||||
registry = {}
|
||||
if os.path.exists(registry_path):
|
||||
registry = json.load(open(registry_path)).get("tools", {})
|
||||
agents_dir = os.path.join(root, "agents")
|
||||
for aid in m.get("layers", {}).get("agents", []):
|
||||
card = os.path.join(agents_dir, f"{aid}.json")
|
||||
if not os.path.exists(card):
|
||||
errs.append(f"agent card missing: {aid}.json")
|
||||
continue
|
||||
try:
|
||||
c = json.load(open(card))
|
||||
except json.JSONDecodeError as e:
|
||||
errs.append(f"agent card {aid}: invalid JSON ({e})")
|
||||
continue
|
||||
req(c, "schema_version", f"agent {aid}")
|
||||
req(c, "agent_id", f"agent {aid}")
|
||||
if c.get("agent_id") != aid:
|
||||
errs.append(f"agent {aid}: agent_id mismatch ({c.get('agent_id')})")
|
||||
req(c, "name", f"agent {aid}")
|
||||
req(c, "role", f"agent {aid}")
|
||||
req(c, "description", f"agent {aid}")
|
||||
req(c, "tools", f"agent {aid}")
|
||||
req(c, "gates", f"agent {aid}")
|
||||
req(c, "verification", f"agent {aid}")
|
||||
req(c, "memory", f"agent {aid}")
|
||||
if "definition_of_done" not in c.get("verification", {}) and "check" not in c.get("verification", {}):
|
||||
errs.append(f"agent {aid}: verification must contain definition_of_done or check")
|
||||
for t in c.get("tools", []):
|
||||
if registry and t not in registry:
|
||||
errs.append(f"agent {aid}: unregistered tool '{t}' (not in tool-registry.json)")
|
||||
if t == "script.run":
|
||||
errs.append(f"agent {aid}: script.run tool requires explicit allowlist — use the file.* + odoo.* primitives")
|
||||
for g in c.get("gates", []):
|
||||
if g not in ("hours", "irreversible-action", "none"):
|
||||
errs.append(f"agent {aid}: unknown gate '{g}'")
|
||||
|
||||
# No secrets (lenient scan — real scan hooks into the target's secrets_scan)
|
||||
BANNED = re.compile(r"(api[_-]?key|secret|password|token)\s*[:=]\s*['\"][A-Za-z0-9_\-]{8,}['\"]", re.I)
|
||||
for dirpath, _, files in os.walk(root):
|
||||
for fn in files:
|
||||
if fn.endswith((".sig", ".sha256")):
|
||||
continue
|
||||
try:
|
||||
text = open(os.path.join(dirpath, fn), "rb").read().decode("utf-8", "ignore")
|
||||
except Exception:
|
||||
continue
|
||||
for mt in BANNED.finditer(text):
|
||||
errs.append(f"possible secret in {os.path.relpath(os.path.join(dirpath, fn), root)}: {mt.group(0)[:40]}")
|
||||
|
||||
if errs:
|
||||
for e in errs:
|
||||
print(" -", e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print("manifest + agent cards + files + tools + secrets scan: PASS")
|
||||
PY
|
||||
|
||||
# Write the spec-mandated VALIDATE gate file
|
||||
cat > "$PKG_DIR/VALIDATE" <<EOF
|
||||
# VALIDATE — $MANIFEST
|
||||
Status: PASS
|
||||
Checked: $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||
- manifest schema v1.0 fields: OK
|
||||
- all layer files present: OK
|
||||
- agent cards (schema/tools/gates/verification/memory): OK
|
||||
- tools registered: OK
|
||||
- secrets scan: OK
|
||||
- sha256 matches signed manifest: OK
|
||||
$( [ -n "$PUBKEY" ] && echo "- ed25519 signature verified: OK")
|
||||
EOF
|
||||
echo "validate-depo: PASS — VALIDATE written to $PKG_DIR/VALIDATE"
|
||||
Reference in New Issue
Block a user