diff --git a/README.md b/README.md
index d619656..f7b4289 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
@@ -121,7 +121,7 @@ Phases **0–4** are production skill surface. **5.0–5.3** adds research simul
| | |
|--|--|
| **Version** | **0.4.1** |
-| **Posture** | Hermes skill · Python package (≥3.10) · MIT |
+| **Posture** | Hermes skill · Python package (≥3.10) · LicenseRef-Zero-State-Proprietary-1.0 |
| **Primary store** | `~/.hyperlex/` |
| **Public PyPI** | Not planned |
| **Abraxas** | Wire shapes only — Hyperlex never imports Abraxas |
@@ -367,7 +367,7 @@ bash install.sh --dry-run
## License
-MIT — [LICENSE](./LICENSE).
+LicenseRef-Zero-State-Proprietary-1.0 — [LICENSE](./LICENSE).
Built for operators who want memetic signal **with a receipt trail**, not a mood board.
@@ -377,3 +377,5 @@ Built for operators who want memetic signal **with a receipt trail**, not a mood
Copyright © 2026 Zero State LLC. All Rights Reserved. Licensed under the Zero State Proprietary License v1.0. See [`LICENSE`](LICENSE).
Third-party components remain subject to their respective licenses.
+
+Source, profile, and migration rules: `references/source-and-upgrades.md`.
diff --git a/SKILL.md b/SKILL.md
index e260163..ce47b6f 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -3,7 +3,7 @@ name: hyperlex
description: Catch slang while it is still becoming culture.
version: 0.4.1
author: Daniel Meyer (scrimshawlife-ctrl), Hermes Agent
-license: MIT
+license: LicenseRef-Zero-State-Proprietary-1.0
platforms: [linux, macos]
metadata:
hermes:
@@ -155,3 +155,5 @@ Successful packaging:
Design references: `DESIGN.md`, `docs/brier-calibration.md`, `docs/slang-lineages.md`, `docs/phase5.md`, `docs/modules/simulation.md`, `schemas/`, `examples/slang-families/`, `data/backfill/2026/`, `references/hermes-runtime-contract.md`.
Local stdlib-first CLI. Baseline (`mock`) needs no network. Real ingest may call public web APIs. Score log and receipts are local files under `~/.hyperlex/` or skill `out/`.
+
+Source, profile, and migration rules: `references/source-and-upgrades.md`.
diff --git a/hyperlex.manifest.yaml b/hyperlex.manifest.yaml
index 1c7ee19..48cb5c6 100644
--- a/hyperlex.manifest.yaml
+++ b/hyperlex.manifest.yaml
@@ -5,7 +5,7 @@ kind: hermes-openclaw-skill
description: >
Standalone memetic emergence engine for slang detection, hyperstition
tracking, virality analysis, lineage matching, and settled Brier calibration.
-license: MIT
+license: LicenseRef-Zero-State-Proprietary-1.0
homepage: https://github.com/scrimshawlife-ctrl/Hyperlex
repository: https://github.com/scrimshawlife-ctrl/Hyperlex
diff --git a/install.sh b/install.sh
index cd0879e..22e89e5 100755
--- a/install.sh
+++ b/install.sh
@@ -78,10 +78,18 @@ while [[ $# -gt 0 ]]; do
done
validate_target() {
+ python3 - "$TARGET" <<'PY'
+import sys
+from pathlib import Path
+p = Path(sys.argv[1]).expanduser().absolute()
+if not sys.argv[1].strip() or any(q.is_symlink() for q in (p, *p.parents)):
+ raise SystemExit("refusing empty or symlinked target path")
+PY
+
local parent base resolved home_resolved
parent="$(dirname "$TARGET")"
base="$(basename "$TARGET")"
- mkdir -p "$parent" 2>/dev/null || true
+ # Path resolution and dry-run must not create target parents.
resolved="$(resolve_path "$parent")/${base}"
TARGET="$resolved"
@@ -206,18 +214,13 @@ post_check() {
do_rollback() {
validate_target
- local latest
- latest="$(ls -1dt "${BACKUP_ROOT}"/*/ 2>/dev/null | head -1 || true)"
- [[ -n "$latest" ]] || die "no backups under ${BACKUP_ROOT}"
- log "Rolling back from ${latest}"
if [[ $DRY_RUN -eq 1 ]]; then
- printf '[dry-run] restore %q → %q\n' "$latest" "$TARGET"
- else
- rm -rf "$TARGET"
- mkdir -p "$(dirname "$TARGET")"
- cp -a "$latest" "$TARGET"
+ log "DRY RUN: would validate and restore a backup bound to ${TARGET}"
+ return 0
fi
- log "Rollback complete"
+ local check_args=()
+ [[ $SKIP_SMOKE -eq 1 ]] && check_args+=(--skip-checks)
+ python3 "${ROOT}/scripts/install_transaction.py" "$ROOT" "$TARGET" hyperlex --rollback "${check_args[@]}"
}
if [[ $ROLLBACK -eq 1 ]]; then
@@ -235,9 +238,9 @@ if [[ $DRY_RUN -eq 1 ]]; then
exit 0
fi
-backup_existing
-sync_tree "$TARGET"
-post_check "$TARGET" || true
+CHECK_ARGS=()
+[[ $SKIP_SMOKE -eq 1 ]] && CHECK_ARGS+=(--skip-checks)
+python3 "${ROOT}/scripts/install_transaction.py" "$ROOT" "$TARGET" hyperlex "${CHECK_ARGS[@]}"
if [[ $INSTALL_OPENCLAW -eq 1 ]]; then
mkdir -p "$(dirname "$OPENCLAW_TARGET")"
@@ -245,13 +248,16 @@ if [[ $INSTALL_OPENCLAW -eq 1 ]]; then
_saved="$TARGET"
TARGET="$OPENCLAW_TARGET"
validate_target
- sync_tree "$TARGET"
- post_check "$TARGET" || true
+ python3 "${ROOT}/scripts/install_transaction.py" "$ROOT" "$TARGET" hyperlex "${CHECK_ARGS[@]}"
TARGET="$_saved"
fi
echo ""
-log "Hyperlex v${VERSION} installed"
+if [[ $SKIP_SMOKE -eq 1 ]]; then
+ log "Hyperlex v${VERSION} installed — UNVERIFIED (--skip-smoke)"
+else
+ log "Hyperlex v${VERSION} installed"
+fi
echo " Hermes: ${TARGET}"
[[ $INSTALL_OPENCLAW -eq 1 ]] && echo " OpenClaw: ${OPENCLAW_TARGET}"
echo ""
diff --git a/pyproject.toml b/pyproject.toml
index 26dc5dc..40567a5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,13 +10,13 @@ authors = [
{name = "Applied Alchemy Labs", email = "scrimshawlife@gmail.com"}
]
readme = "README.md"
-license = {text = "MIT"}
+license = {file = "LICENSE"}
requires-python = ">=3.10"
keywords = ["memetics", "slang", "hyperstition", "virality", "brier", "hermes", "forecasting"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
- "License :: OSI Approved :: MIT License",
+ "License :: Other/Proprietary License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
diff --git a/references/source-and-upgrades.md b/references/source-and-upgrades.md
new file mode 100644
index 0000000..cb8dc00
--- /dev/null
+++ b/references/source-and-upgrades.md
@@ -0,0 +1,58 @@
+# Source identity and upgrades
+
+This distribution is `Zero-State-LLC/Hyperlex`, version `0.4.1`, based on commit
+`5eaae2dee88d80b27415bf362a87924fb9c41c2e` before the local audit-fix commit. Record the actual installed commit
+with `git rev-parse HEAD` before installation; do not use a version string alone
+as a source identity. Root and hub packaging are distribution surfaces, not a
+claim that organizational, personal, or legacy embedded variants are identical.
+No personal version is deprecated by this change.
+
+Before switching sources, stop runtime writers, record current source/commit,
+review the destination under `${HERMES_HOME:-$HOME/.hermes}`, compare contracts,
+and retain a separate backup of outputs, sessions, and local customization.
+Do not install two different contracts with the same skill name into one profile.
+A successful compatibility check does not grant deployment/publication authority.
+
+The bundled LICENSE controls this distribution. No license grant is changed by
+these fixes; earlier grants and third-party notices remain intact.
+
+The installer validates a fresh stage on the target filesystem before replacement,
+then reads back the activated contract/version/provenance receipt. Checks use an
+isolated temporary home. Legacy `out` contents (including an out symlink) survive.
+Backups are unique, keyed by canonical destination under the active Hermes home's
+`backups///`; `.install-provenance.json` records source, base
+commit, dirty status, version, destination, and skipped checks. Stop writers during
+upgrades. Two renames have an absent-target window: this is NOT crash-atomic.
+On a reported recovery failure, retain the printed recovery directory and backup;
+do not delete it or retry blindly. Inspect the exact target and restore from the
+named backup only after validating it. No automatic source migration is implied.
+
+## Interrupted installs and stale locks
+
+SIGKILL or power loss can leave `..install-lock` beside the target.
+Locks are never automatically reclaimed: age, an empty directory, or a reused PID
+cannot prove that no installer is active. To recover:
+
+1. Stop install launchers and runtime writers. Confirm no installer is active on
+ this target (including other sessions/hosts sharing the filesystem).
+2. Inspect the exact target, sibling `.-stage-*` recovery directories
+ (especially `previous` and `failed-package`), and target-keyed backups. Retain
+ all recovery data until the original installation and outputs are accounted
+ for. Restore/validate a complete package first if activation was interrupted.
+3. Only then set `lock` to the exact path printed in the error and run
+ `rmdir -- "$lock"`. This removes only an empty lock; never use recursive
+ deletion or remove a lock whose owner/activity is uncertain. Keep launchers
+ stopped through inspection and removal to avoid races, then retry installation.
+
+Backup copies are staged in `.backup-incomplete-*` outside the target's selectable
+backup directory and published by rename after copying and writing the destination
+record. Hard termination can leave these incomplete staging trees; never select
+one for rollback. Inspect them manually after quiescing writers. Rollback skips
+legacy partial entries without a valid matching destination record.
+
+Git is optional. Archive sources, failed Git lookups, and unrelated enclosing
+worktrees record unknown Git fields as JSON `null`, never a false clean claim.
+A source-root worktree or the tracked `skills/neon-genie` hub with matching root
+contract/version supplies Git provenance. Receipts distinguish
+`source_repository_root` from `source_subdirectory` (`.` or `skills/neon-genie`).
+A dirty status lookup failure remains `null` even in a recognized worktree.
diff --git a/scripts/install_transaction.py b/scripts/install_transaction.py
new file mode 100644
index 0000000..c11124e
--- /dev/null
+++ b/scripts/install_transaction.py
@@ -0,0 +1,295 @@
+"""Staged, checked upgrades with target-keyed backups and recovery.
+
+The two renames have an absent-target window; this is not crash-atomic. Stop
+runtime writers during upgrades. A retained recovery directory requires operator
+inspection. No live profile is used by validation subprocesses.
+"""
+
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+import uuid
+from pathlib import Path
+
+CHECKS = {
+ "neon-genie": [("validate_hermes_skill.py",), ("neon_genie.py", "do", "check")],
+ "sigil-forge": [("validate_hermes_skill.py",), ("sigil_forge.py", "check")],
+ "hyperlex": [("hyperlex.py", "check"), ("hyperlex.py", "smoke")],
+}
+IGNORE = shutil.ignore_patterns(
+ ".git",
+ "skills",
+ "out",
+ ".venv",
+ "__pycache__",
+ ".pytest_cache",
+ ".mypy_cache",
+ ".ruff_cache",
+ "*.pyc",
+ ".worktrees",
+ "graft",
+ ".env",
+ ".env.*",
+ ".hermes",
+ "*.pem",
+ "*.key",
+ ".superpowers",
+ "superpowers",
+)
+
+
+def install(source: Path, target: Path, kind: str, skip_checks: bool = False) -> None:
+ # Check the lexical final component before resolve follows a dangling link.
+ if any(p.is_symlink() for p in (target.absolute(), *target.absolute().parents)):
+ raise ValueError("refusing symlink target")
+ source, target = source.resolve(), target.resolve()
+ if source == target or source in target.parents or target in source.parents:
+ raise ValueError("source and target must not overlap")
+ home = Path(os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes")).resolve()
+ if target in (Path("/"), Path.home().resolve(), home, home / "skills"):
+ raise ValueError("refusing installation root")
+ if target.exists() and not target.is_dir():
+ raise ValueError("target must be a directory")
+ key = hashlib.sha256(str(target).encode()).hexdigest()[:20]
+ backups = home / "backups" / kind / key
+ if target == backups or target in backups.parents or backups in target.parents:
+ raise ValueError("backup and target must not overlap")
+ # Reject payload links rather than shipping aliases into private source state.
+ for directory, dirs, files in os.walk(source, followlinks=False):
+ ignored = IGNORE(directory, dirs + files)
+ dirs[:] = [d for d in dirs if d not in ignored]
+ for name in dirs + [f for f in files if f not in ignored]:
+ if (Path(directory) / name).is_symlink():
+ raise ValueError(f"source payload symlink is not portable: {name}")
+ target.parent.mkdir(parents=True, exist_ok=True)
+ lock = target.parent / ("." + target.name + ".install-lock")
+ try:
+ lock.mkdir() # exclusive; never remove another installer's lock
+ except FileExistsError as exc:
+ raise FileExistsError(
+ f"Install lock exists: {lock}. Do not reclaim automatically. "
+ "Stop launchers and confirm no installer or runtime writer is active; "
+ "inspect the target, sibling stage/recovery directories and backups. "
+ "Only after resolving recovery, use rmdir on this exact empty lock "
+ "(never recursive removal), then retry. See references/source-and-upgrades.md."
+ ) from exc
+ workspace = None
+ retain_recovery = False
+ try:
+ workspace = Path(
+ tempfile.mkdtemp(prefix="." + kind + "-stage-", dir=target.parent)
+ )
+ stage = workspace / "package"
+ shutil.copytree(source, stage, ignore=IGNORE)
+ check_home = workspace / "check-home"
+ check_home.mkdir()
+ env = {
+ k: v
+ for k, v in os.environ.items()
+ if not k.startswith(("HYPERLEX_", "SIGIL_FORGE_"))
+ }
+ env.update(
+ HOME=str(check_home),
+ HERMES_HOME=str(check_home / ".hermes"),
+ HERMES_SKILL_DIR=str(stage),
+ SIGIL_FORGE_STATE_DIR=str(check_home / "state"),
+ PYTHONDONTWRITEBYTECODE="1",
+ )
+ if not skip_checks:
+ for command in CHECKS[kind]:
+ subprocess.run(
+ [sys.executable, str(stage / "scripts" / command[0]), *command[1:]],
+ cwd=check_home,
+ env=env,
+ check=True,
+ )
+ if (stage / "out").exists():
+ shutil.rmtree(stage / "out") # NEW staging data only
+ old_out = target / "out"
+ if old_out.is_symlink():
+ (stage / "out").symlink_to(os.readlink(old_out), target_is_directory=True)
+ elif old_out.exists():
+ shutil.copytree(old_out, stage / "out", symlinks=True)
+
+ def git(*args: str) -> str | None:
+ try:
+ r = subprocess.run(
+ check=False,
+ args=["git", "-C", str(source), *args],
+ capture_output=True,
+ text=True,
+ env={
+ k: v for k, v in os.environ.items() if not k.startswith("GIT_")
+ },
+ )
+ except OSError:
+ return None # Git is optional for archive/Python-only installs.
+ return r.stdout.strip() if r.returncode == 0 else None
+
+ # Git searches parents: accept the source root or the tracked Neon hub,
+ # not arbitrary archives nested in an unrelated worktree.
+ top = git("rev-parse", "--show-toplevel")
+ repo_root = Path(top).resolve() if top is not None else None
+ own_checkout = repo_root == source
+ subtree = "." if own_checkout else None
+ if (
+ repo_root is not None
+ and kind == "neon-genie"
+ and source == repo_root / "skills/neon-genie"
+ ):
+ tracked = git(
+ "ls-files",
+ "--error-unmatch",
+ "--",
+ "SKILL.md",
+ "VERSION",
+ str(repo_root / "SKILL.md"),
+ str(repo_root / "VERSION"),
+ )
+ try:
+ # Root/hub grants can legitimately differ; compare the contract
+ # apart from its license metadata, without changing either grant.
+ root_contract = (repo_root / "SKILL.md").read_text().splitlines()
+ hub_contract = (source / "SKILL.md").read_text().splitlines()
+ matching_root = [
+ line for line in root_contract if not line.startswith("license:")
+ ] == [
+ line for line in hub_contract if not line.startswith("license:")
+ ] and (repo_root / "VERSION").read_bytes() == (
+ source / "VERSION"
+ ).read_bytes()
+ except (OSError, UnicodeError):
+ matching_root = False
+ if tracked is not None and matching_root:
+ own_checkout = True
+ subtree = "skills/neon-genie"
+ dirty = git("status", "--porcelain") if own_checkout else None
+ receipt = {
+ "source": str(source),
+ "source_repository_root": str(repo_root) if own_checkout else None,
+ "source_subdirectory": subtree,
+ "repository": git("remote", "get-url", "origin") if own_checkout else None,
+ "source_commit": git("rev-parse", "HEAD") if own_checkout else None,
+ "source_dirty": bool(dirty) if dirty is not None else None,
+ "version": (source / "VERSION").read_text().strip(),
+ "destination": str(target),
+ "status": "UNVERIFIED" if skip_checks else "VALIDATED",
+ "checks_skipped": list(CHECKS[kind]) if skip_checks else [],
+ "validation": "staged runtime; activated contract/version read-back",
+ }
+ receipt_text = json.dumps(receipt, indent=2) + "\n"
+ (stage / ".install-provenance.json").write_text(receipt_text)
+ expected = {
+ p: (stage / p).read_bytes()
+ for p in ("SKILL.md", "VERSION", ".install-provenance.json")
+ }
+ backup = None
+ if target.exists():
+ backups.mkdir(parents=True, exist_ok=True)
+ backup = backups / uuid.uuid4().hex
+ # Incomplete copies live outside the selectable backup namespace.
+ # Publish only after the payload and destination record are complete.
+ with tempfile.TemporaryDirectory(
+ prefix=".backup-incomplete-", dir=backups.parent
+ ) as tmp:
+ pending = Path(tmp) / "backup-payload"
+ shutil.copytree(target, pending, symlinks=True)
+ marker = pending / ".backup-target.json"
+ marker.unlink(missing_ok=True)
+ marker.write_text(json.dumps({"destination": str(target)}) + "\n")
+ os.replace(pending, backup)
+ displaced = workspace / "previous"
+ # Detect a concurrent change of target identity since staging began.
+ if any(p.is_symlink() for p in (target.absolute(), *target.absolute().parents)):
+ raise ValueError("target became a symlink during staging")
+ try:
+ if target.exists():
+ os.replace(target, displaced)
+ os.replace(stage, target)
+ for relative, content in expected.items():
+ if (target / relative).read_bytes() != content:
+ raise OSError(f"activation read-back mismatch: {relative}")
+ except BaseException as original:
+ try:
+ # Infer completed renames from disk even if replace raised after
+ # its side effect. A still-staged package means target is not new.
+ if not stage.exists() and target.exists():
+ os.replace(target, workspace / "failed-package")
+ if displaced.exists():
+ os.replace(displaced, target)
+ except BaseException as recovery_error:
+ retain_recovery = True
+ raise RuntimeError(
+ f"recovery required at {workspace}; backup {backup}; "
+ f"activation: {original}; restoration: {recovery_error}"
+ ) from recovery_error
+ raise
+ print(f"Installed {kind}: {target} ({receipt['status']})")
+ if backup:
+ print(f"Backup: {backup}")
+ finally:
+ if workspace is not None and not retain_recovery:
+ shutil.rmtree(workspace)
+ lock.rmdir()
+
+
+def rollback(target: Path, kind: str, skip_checks: bool = False) -> None:
+ if any(p.is_symlink() for p in (target.absolute(), *target.absolute().parents)):
+ raise ValueError("refusing symlink target")
+ target = target.resolve()
+ home = Path(os.environ.get("HERMES_HOME") or str(Path.home() / ".hermes")).resolve()
+ key = hashlib.sha256(str(target).encode()).hexdigest()[:20]
+ backups = home / "backups" / kind / key
+ candidates = sorted(
+ backups.glob("*"), key=lambda p: p.lstat().st_mtime_ns, reverse=True
+ )
+ backup = None
+ for candidate in candidates:
+ marker = candidate / ".backup-target.json"
+ if candidate.is_symlink() or not candidate.is_dir() or marker.is_symlink():
+ continue
+ try:
+ record = json.loads(marker.read_text())
+ except (OSError, ValueError):
+ continue
+ if isinstance(record, dict) and record.get("destination") == str(target):
+ backup = candidate
+ break
+ if backup is None:
+ raise ValueError(
+ f"no target-bound backup for {target}; legacy backups require manual review"
+ )
+ # Reuse staged runtime checks, backup, activated read-back and failed-rename recovery.
+ install(backup, target, kind, skip_checks)
+
+
+def main() -> int:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("source", type=Path)
+ parser.add_argument("target")
+ parser.add_argument("kind", choices=CHECKS)
+ parser.add_argument("--skip-checks", action="store_true")
+ parser.add_argument("--rollback", action="store_true")
+ args = parser.parse_args()
+ try:
+ if not args.target.strip():
+ raise ValueError("empty target")
+ if args.rollback:
+ rollback(Path(args.target), args.kind, args.skip_checks)
+ else:
+ install(args.source, Path(args.target), args.kind, args.skip_checks)
+ except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as exc:
+ print(f"Installation failed: {exc}", file=sys.stderr)
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/tests_audit/PLAN.md b/tests_audit/PLAN.md
new file mode 100644
index 0000000..b2442b9
--- /dev/null
+++ b/tests_audit/PLAN.md
@@ -0,0 +1,4 @@
+# Audit remediation
+
+User explicitly authorized C1–C6 and relevant A1/A4 fixes, local commits only.
+Plan: reproduce each defect before its fix; stage installs and validate before activation; preserve legacy output; isolate new state; repair session recipe and repository-only CI validation; align only current organizational license metadata; preserve personal variants and advisory boundaries; run sandbox regressions and existing offline checks; parent independently reviews before remote action.
diff --git a/tests_audit/test_hyperlex_install.py b/tests_audit/test_hyperlex_install.py
new file mode 100644
index 0000000..2fbb21d
--- /dev/null
+++ b/tests_audit/test_hyperlex_install.py
@@ -0,0 +1,81 @@
+import json
+import os
+import shutil
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+class HyperlexInstallAudit(unittest.TestCase):
+ def test_real_install_reinstall_and_explicit_skip(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ home = Path(tmp) / "home"
+ home.mkdir()
+ profile = home / "profile"
+ env = dict(os.environ, HOME=str(home), HERMES_HOME=str(profile))
+ dest = profile / "skills/hyperlex"
+ for args in ([], ["--skip-smoke"]):
+ result = subprocess.run(
+ ["bash", str(ROOT / "install.sh"), *args],
+ cwd=tmp,
+ env=env,
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
+ receipt = json.loads((dest / ".install-provenance.json").read_text())
+ self.assertEqual(
+ receipt["status"], "UNVERIFIED" if args else "VALIDATED"
+ )
+ if args:
+ self.assertIn("UNVERIFIED", result.stdout)
+ self.assertEqual((dest / "out/keep").read_text(), "operator output")
+ else:
+ (dest / "out").mkdir(exist_ok=True)
+ (dest / "out/keep").write_text("operator output")
+ self.assertFalse((home / ".hermes").exists())
+
+ def test_smoke_failure_not_just_check_failure(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ source = Path(tmp) / "source"
+ shutil.copytree(
+ ROOT,
+ source,
+ ignore=shutil.ignore_patterns(".git", "out", "__pycache__", ".venv"),
+ )
+ cli = source / "scripts/hyperlex.py"
+ text = cli.read_text()
+ signature = "def cmd_smoke(_args: argparse.Namespace) -> int:\n"
+ self.assertIn(signature, text)
+ cli.write_text(
+ text.replace(
+ signature,
+ signature + " return 42 # injected smoke-only failure\n",
+ 1,
+ )
+ )
+ home = Path(tmp) / "home"
+ home.mkdir()
+ dest = home / "profile/skills/hyperlex"
+ dest.mkdir(parents=True)
+ (dest / "SKILL.md").write_text("previous install")
+ env = dict(os.environ, HOME=str(home), HERMES_HOME=str(home / "profile"))
+ result = subprocess.run(
+ ["bash", str(source / "install.sh")],
+ cwd=tmp,
+ env=env,
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ self.assertNotEqual(result.returncode, 0)
+ self.assertIn("42", result.stderr)
+ self.assertEqual((dest / "SKILL.md").read_text(), "previous install")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests_audit/test_install.py b/tests_audit/test_install.py
new file mode 100644
index 0000000..f67f90b
--- /dev/null
+++ b/tests_audit/test_install.py
@@ -0,0 +1,76 @@
+import os
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+SKILL = "hyperlex"
+
+
+class InstallAudit(unittest.TestCase):
+ def test_failed_check_preserves_previous_install(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ home = Path(tmp) / "home"
+ home.mkdir()
+ profile = home / "profile"
+ dest = profile / "skills" / SKILL
+ dest.mkdir(parents=True)
+ (dest / "SKILL.md").write_text("previous package")
+ (dest / "out/wizard-sessions").mkdir(parents=True)
+ sentinel = dest / "out/wizard-sessions/keep.json"
+ sentinel.write_text("previous session")
+ import shutil
+
+ source = Path(tmp) / "source"
+ shutil.copytree(
+ ROOT,
+ source,
+ ignore=shutil.ignore_patterns(
+ ".git", "skills", "out", "__pycache__", ".venv"
+ ),
+ )
+ script = {
+ "neon-genie": "validate_hermes_skill.py",
+ "sigil-forge": "sigil_forge.py",
+ "hyperlex": "hyperlex.py",
+ }[SKILL]
+ (source / "scripts" / script).write_text("import sys\nsys.exit(42)\n")
+ env = dict(os.environ, HOME=str(home), HERMES_HOME=str(profile))
+ r = subprocess.run(
+ check=False,
+ args=["bash", str(source / "install.sh")],
+ cwd=tmp,
+ env=env,
+ capture_output=True,
+ text=True,
+ )
+ self.assertNotEqual(r.returncode, 0, r.stdout + r.stderr)
+ self.assertEqual((dest / "SKILL.md").read_text(), "previous package")
+ self.assertEqual(sentinel.read_text(), "previous session")
+ self.assertFalse((home / ".hermes").exists())
+
+ def test_profile_skills_symlink_cannot_redirect_install(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ home = Path(tmp) / "home"
+ profile = home / "profile-a"
+ foreign = home / "profile-b/skills"
+ profile.mkdir(parents=True)
+ foreign.mkdir(parents=True)
+ (profile / "skills").symlink_to(foreign, target_is_directory=True)
+ env = dict(os.environ, HOME=str(home), HERMES_HOME=str(profile))
+ r = subprocess.run(
+ ["bash", str(ROOT / "install.sh")],
+ cwd=tmp,
+ env=env,
+ capture_output=True,
+ text=True,
+ check=False,
+ )
+ self.assertNotEqual(r.returncode, 0, r.stdout + r.stderr)
+ self.assertEqual(list(foreign.iterdir()), [])
+ self.assertTrue((profile / "skills").is_symlink())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests_audit/test_install_followup.py b/tests_audit/test_install_followup.py
new file mode 100644
index 0000000..5660e21
--- /dev/null
+++ b/tests_audit/test_install_followup.py
@@ -0,0 +1,191 @@
+"""Regression coverage for installer PR follow-up; shared across distributions."""
+
+import hashlib
+import importlib.util
+import json
+import os
+import shutil
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+ROOT = Path(__file__).resolve().parents[1]
+spec = importlib.util.spec_from_file_location(
+ "transaction_followup", ROOT / "scripts/install_transaction.py"
+)
+assert spec is not None and spec.loader is not None
+transaction = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(transaction)
+
+
+class FollowupTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.tmp.cleanup)
+ self.base = Path(self.tmp.name)
+ self.source = self.base / "source"
+ self.source.mkdir()
+ (self.source / "SKILL.md").write_text("new")
+ (self.source / "VERSION").write_text("1")
+ self.target = self.base / "target"
+ self.home = self.base / "profile"
+ env = patch.dict(os.environ, HERMES_HOME=str(self.home))
+ env.start()
+ self.addCleanup(env.stop)
+ key = hashlib.sha256(str(self.target).encode()).hexdigest()[:20]
+ self.backups = self.home / "backups/hyperlex" / key
+
+ def install(self):
+ transaction.install(self.source, self.target, "hyperlex", True)
+
+ def receipt(self):
+ return json.loads((self.target / ".install-provenance.json").read_text())
+
+ def test_archive_dirty_state_is_unknown(self):
+ self.install()
+ self.assertIsNone(self.receipt()["source_dirty"])
+
+ def test_enclosing_repository_is_not_source_provenance(self):
+ subprocess.run(["git", "init", str(self.base)], check=True, capture_output=True)
+ subprocess.run(
+ [
+ "git",
+ "-C",
+ str(self.base),
+ "-c",
+ "user.name=Test",
+ "-c",
+ "user.email=test@example.invalid",
+ "commit",
+ "--allow-empty",
+ "-m",
+ "unrelated",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ subprocess.run(
+ [
+ "git",
+ "-C",
+ str(self.base),
+ "remote",
+ "add",
+ "origin",
+ "https://example.invalid/unrelated",
+ ],
+ check=True,
+ )
+ self.install()
+ for field in ("repository", "source_commit", "source_dirty"):
+ self.assertIsNone(self.receipt()[field], field)
+
+ def test_missing_git_does_not_block_install(self):
+ with patch.dict(os.environ, PATH=str(self.base / "no-executables")):
+ self.install()
+ for field in ("repository", "source_commit", "source_dirty"):
+ self.assertIsNone(self.receipt()[field], field)
+
+ def test_stale_lock_error_and_docs_are_actionable(self):
+ lock = self.target.parent / ("." + self.target.name + ".install-lock")
+ lock.mkdir()
+ with self.assertRaisesRegex(
+ FileExistsError, "Do not reclaim automatically"
+ ) as error:
+ self.install()
+ self.assertIn(str(lock), str(error.exception))
+ self.assertIn("rmdir", str(error.exception))
+ self.assertTrue(lock.is_dir())
+ self.assertFalse(self.target.exists())
+ docs = (ROOT / "references/source-and-upgrades.md").read_text()
+ for phrase in (
+ "rmdir",
+ "SIGKILL",
+ "no installer",
+ ".install-lock",
+ ".backup-incomplete-",
+ ):
+ self.assertIn(phrase, docs)
+
+ def test_tracked_neon_hub_retains_repository_and_subtree(self):
+ repo = self.base / "neon"
+ hub = repo / "skills/neon-genie"
+ hub.mkdir(parents=True)
+ for directory in (repo, hub):
+ license_id = "MIT" if directory == repo else "Apache-2.0"
+ (directory / "SKILL.md").write_text(
+ f"---\nname: neon-genie\nlicense: {license_id}\n---\n"
+ )
+ (directory / "VERSION").write_text("1")
+ subprocess.run(["git", "init", str(repo)], check=True, capture_output=True)
+ subprocess.run(["git", "-C", str(repo), "add", "."], check=True)
+ subprocess.run(
+ [
+ "git",
+ "-C",
+ str(repo),
+ "-c",
+ "user.name=Test",
+ "-c",
+ "user.email=test@example.invalid",
+ "commit",
+ "-m",
+ "hub",
+ ],
+ check=True,
+ capture_output=True,
+ )
+ transaction.install(hub, self.target, "neon-genie", True)
+ receipt = self.receipt()
+ self.assertIsNotNone(receipt["source_commit"])
+ self.assertIs(receipt["source_dirty"], False)
+ self.assertEqual(receipt["source_subdirectory"], "skills/neon-genie")
+ self.assertEqual(receipt["source_repository_root"], str(repo))
+
+ def test_invalid_utf8_enclosing_root_drops_optional_provenance(self):
+ self.test_tracked_neon_hub_retains_repository_and_subtree()
+ repo = self.base / "neon"
+ hub = repo / "skills/neon-genie"
+ (repo / "SKILL.md").write_bytes(b"\xff")
+ transaction.install(hub, self.target, "neon-genie", True)
+ receipt = self.receipt()
+ for field in (
+ "repository", "source_repository_root", "source_subdirectory",
+ "source_commit", "source_dirty",
+ ):
+ self.assertIsNone(receipt[field], field)
+ for name in ("SKILL.md", "VERSION"):
+ self.assertEqual((self.target / name).read_bytes(), (hub / name).read_bytes())
+
+ def test_partial_backup_never_published(self):
+ self.install()
+ self.install()
+ previous = set(self.backups.iterdir())
+ copytree = shutil.copytree
+
+ def interrupt(src, dst, *args, **kwargs):
+ if Path(src) == self.target:
+ Path(dst).mkdir()
+ (Path(dst) / "truncated").write_text("partial")
+ raise KeyboardInterrupt("backup copy interrupted")
+ return copytree(src, dst, *args, **kwargs)
+
+ with (
+ patch.object(transaction.shutil, "copytree", side_effect=interrupt),
+ self.assertRaises(KeyboardInterrupt),
+ ):
+ self.install()
+ self.assertEqual(set(self.backups.iterdir()), previous)
+ self.assertEqual((self.target / "SKILL.md").read_text(), "new")
+ transaction.rollback(self.target, "hyperlex", True)
+
+ def test_rollback_skips_legacy_partial_backup(self):
+ self.install()
+ self.install()
+ partial = self.backups / "partial"
+ partial.mkdir()
+ os.utime(partial, (2000000000, 2000000000))
+ transaction.rollback(self.target, "hyperlex", True)
+ self.assertEqual((self.target / "SKILL.md").read_text(), "new")
diff --git a/tests_audit/test_license_metadata.py b/tests_audit/test_license_metadata.py
new file mode 100644
index 0000000..2972989
--- /dev/null
+++ b/tests_audit/test_license_metadata.py
@@ -0,0 +1,35 @@
+"""Current distribution metadata must match the unchanged root license."""
+
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+
+
+class LicenseMetadata(unittest.TestCase):
+ def test_current_distribution_metadata(self):
+ self.assertIn("Zero State Proprietary License", (ROOT / "LICENSE").read_text())
+ files = [ROOT / "SKILL.md", ROOT / "README.md"]
+ files += list((ROOT / "skills").glob("*/SKILL.md"))
+ files += list((ROOT / "skills").glob("*/README.md"))
+ files += [
+ p
+ for p in [
+ ROOT / "manifest.json",
+ ROOT / "references/manifest.json",
+ ROOT / "hyperlex.manifest.yaml",
+ ROOT / "pyproject.toml",
+ ]
+ if p.exists()
+ ]
+ for p in files:
+ with self.subTest(path=str(p)):
+ self.assertNotRegex(p.read_text(), r"\bMIT\b")
+ self.assertIn(
+ "license: LicenseRef-Zero-State-Proprietary-1.0",
+ (ROOT / "SKILL.md").read_text(),
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests_audit/test_transaction.py b/tests_audit/test_transaction.py
new file mode 100644
index 0000000..4458239
--- /dev/null
+++ b/tests_audit/test_transaction.py
@@ -0,0 +1,214 @@
+"""Adversarial transactions: sandbox only, no mocked runtime successes."""
+
+import importlib.util
+import io
+import os
+import tempfile
+import unittest
+from pathlib import Path
+from unittest.mock import patch
+
+ROOT = Path(__file__).resolve().parents[1]
+spec = importlib.util.spec_from_file_location(
+ "install_transaction", ROOT / "scripts/install_transaction.py"
+)
+assert spec is not None and spec.loader is not None
+m = importlib.util.module_from_spec(spec)
+spec.loader.exec_module(m)
+
+
+class TransactionAudit(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.TemporaryDirectory()
+ self.addCleanup(self.tmp.cleanup)
+ self.root = Path(self.tmp.name)
+ self.source = self.root / "source"
+ self.source.mkdir()
+ (self.source / "VERSION").write_text("1")
+ (self.source / "SKILL.md").write_text("new")
+ self.target = self.root / "profile/skills/test"
+ self.env = patch.dict(
+ os.environ,
+ HOME=str(self.root / "home"),
+ HERMES_HOME=str(self.root / "profile"),
+ )
+ self.env.start()
+ self.addCleanup(self.env.stop)
+
+ def install(self):
+ m.install(self.source, self.target, "hyperlex", skip_checks=True)
+
+ def previous(self):
+ self.target.mkdir(parents=True)
+ (self.target / "SKILL.md").write_text("old")
+ (self.target / "VERSION").write_text("1")
+
+ def test_final_and_dangling_target_symlinks_rejected(self):
+ self.target.parent.mkdir(parents=True)
+ for exists in (True, False):
+ referent = self.root / str(exists)
+ if exists:
+ referent.mkdir()
+ self.target.symlink_to(referent, target_is_directory=True)
+ with self.assertRaises(ValueError):
+ self.install()
+ self.assertTrue(self.target.is_symlink())
+ self.assertFalse((referent / "SKILL.md").exists())
+ self.target.unlink()
+
+ def test_source_ancestor_rejected(self):
+ self.target = self.root
+ with self.assertRaises(ValueError):
+ self.install()
+
+ def test_secrets_not_packaged(self):
+ (self.source / ".env").write_text("FAKE_SECRET=do-not-copy")
+ (self.source / ".env.local").write_text("do-not-copy")
+ self.install()
+ self.assertFalse((self.target / ".env").exists())
+ self.assertFalse((self.target / ".env.local").exists())
+
+ def test_source_symlink_rejected(self):
+ (self.source / "linked-secret").symlink_to(self.root / "private")
+ with self.assertRaises(ValueError):
+ self.install()
+
+ def test_output_symlink_preserved_without_crawling(self):
+ self.previous()
+ private = self.root / "private"
+ private.mkdir()
+ (private / "keep").write_text("keep")
+ (self.target / "out").symlink_to(private, target_is_directory=True)
+ self.install()
+ self.assertTrue((self.target / "out").is_symlink())
+ self.assertEqual((private / "keep").read_text(), "keep")
+
+ def test_activation_failure_restores_previous(self):
+ self.previous()
+ replace = os.replace
+
+ def fail(src, dst):
+ if Path(src).name == "package":
+ raise OSError("injected activation failure")
+ return replace(src, dst)
+
+ with (
+ patch.object(m.os, "replace", side_effect=fail),
+ self.assertRaises(OSError),
+ ):
+ self.install()
+ self.assertEqual((self.target / "SKILL.md").read_text(), "old")
+
+ def test_restoration_failure_retains_recovery_tree(self):
+ self.previous()
+ replace = os.replace
+
+ def fail(src, dst):
+ if Path(src).name in ("package", "previous"):
+ raise OSError("injected rename failure")
+ return replace(src, dst)
+
+ with (
+ patch.object(m.os, "replace", side_effect=fail),
+ self.assertRaises(RuntimeError),
+ ):
+ self.install()
+ recovered = list(self.target.parent.glob(".hyperlex-stage-*/previous/SKILL.md"))
+ self.assertEqual(len(recovered), 1)
+ self.assertEqual(recovered[0].read_text(), "old")
+
+ def check_interrupted_rename(self, boundary, after, recovery):
+ self.previous()
+ replace = os.replace
+ interrupted = False
+
+ def fail(src, dst):
+ nonlocal interrupted
+ src, dst = Path(src), Path(dst)
+ if src.name == "previous":
+ if recovery == "error":
+ raise OSError("injected restoration failure")
+ if recovery == "interrupt-before":
+ raise KeyboardInterrupt("restoration before rename")
+ if recovery == "interrupt-after":
+ replace(src, dst)
+ raise KeyboardInterrupt("restoration after rename")
+ at_boundary = (
+ dst.name == "previous" if boundary == "displacement"
+ else src.name == "package"
+ )
+ if at_boundary and not interrupted:
+ interrupted = True
+ if after:
+ replace(src, dst) # Real side effect before the exception.
+ raise KeyboardInterrupt("injected installation interruption")
+ return replace(src, dst)
+
+ needs_recovery = boundary == "activation" or after
+ retained = needs_recovery and recovery != "ok"
+ with (
+ patch.object(m.os, "replace", side_effect=fail),
+ patch("sys.stdout", new_callable=io.StringIO) as output,
+ self.assertRaises(RuntimeError if retained else KeyboardInterrupt) as caught,
+ ):
+ self.install() # Explicit skip_checks=True; no fake runtime success.
+ self.assertTrue(interrupted)
+ self.assertNotIn("Installed", output.getvalue())
+ self.assertFalse((self.target.parent / ".test.install-lock").exists())
+ backups = list((self.root / "profile/backups").glob("*/*/*/SKILL.md"))
+ self.assertEqual(len(backups), 1)
+ self.assertEqual(backups[0].read_text(), "old")
+ workspaces = list(self.target.parent.glob(".*-stage-*"))
+ if retained:
+ self.assertEqual(len(workspaces), 1)
+ self.assertIn(str(workspaces[0]), str(caught.exception))
+ self.assertIn(str(backups[0].parent), str(caught.exception))
+ old = (self.target if recovery == "interrupt-after"
+ else workspaces[0] / "previous")
+ self.assertEqual((old / "SKILL.md").read_text(), "old")
+ else:
+ self.assertEqual((self.target / "SKILL.md").read_text(), "old")
+ self.assertEqual(workspaces, [])
+
+ def test_interrupted_displacement_restores_previous(self):
+ self.check_interrupted_rename("displacement", True, "ok")
+
+ def test_interrupted_rename_boundaries(self):
+ for boundary in ("displacement", "activation"):
+ for after in (False, True):
+ for recovery in ("ok", "error", "interrupt-before", "interrupt-after"):
+ with self.subTest(boundary=boundary, after=after, recovery=recovery):
+ # Each fault gets an independent real filesystem sandbox.
+ case = TransactionAudit()
+ case.setUp()
+ try:
+ case.check_interrupted_rename(boundary, after, recovery)
+ finally:
+ case.doCleanups()
+
+ def test_existing_lock_fails_closed(self):
+ self.target.parent.mkdir(parents=True)
+ lock = self.target.parent / ("." + self.target.name + ".install-lock")
+ lock.mkdir()
+ with self.assertRaises(FileExistsError):
+ self.install()
+ self.assertTrue(lock.is_dir())
+
+ def test_rollback_is_target_bound_and_preserves_current_outputs(self):
+ self.previous()
+ self.install()
+ other = self.root / "profile/skills/other"
+ other.mkdir()
+ (other / "SKILL.md").write_text("other-old")
+ (other / "VERSION").write_text("1")
+ m.install(self.source, other, "hyperlex", skip_checks=True)
+ (self.target / "out").mkdir()
+ (self.target / "out/keep").write_text("new-output")
+ m.rollback(self.target, "hyperlex", skip_checks=True)
+ self.assertEqual((self.target / "SKILL.md").read_text(), "old")
+ self.assertEqual((self.target / "out/keep").read_text(), "new-output")
+ self.assertEqual((other / "SKILL.md").read_text(), "new")
+
+
+if __name__ == "__main__":
+ unittest.main()