Merge pull request 'fix Codex hook trust detection' (#10) from codex/fix-codex-trust-doctor into main
Reviewed-on: http://161.33.139.73:3000/admin/casan5/pulls/10
This commit is contained in:
@@ -216,6 +216,14 @@ casan doctor --client codex
|
|||||||
casan doctor --client vscode-copilot
|
casan doctor --client vscode-copilot
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Với Codex, `doctor` đọc trạng thái hiệu lực bằng API `hooks/list` của chính
|
||||||
|
`codex app-server`. Nếu toàn bộ CASAN hook của project đang `trusted`, kết quả
|
||||||
|
là `READY` và không yêu cầu Allow lại. Action `/hooks` chỉ xuất hiện khi hook
|
||||||
|
thực sự `untrusted`/`modified`, bị tắt, hoặc máy hiện tại không thể xác minh
|
||||||
|
trust state. Hash toàn file `.codex/hooks.json` trong `init-manifest.json` là
|
||||||
|
checksum ownership phục vụ uninstall an toàn; nó không phải per-hook
|
||||||
|
`trusted_hash` mà Codex dùng để quyết định trust.
|
||||||
|
|
||||||
### Gỡ khỏi project
|
### Gỡ khỏi project
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -32,12 +32,14 @@ from contextlib import contextmanager
|
|||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import queue
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import stat
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
PROJECT_RE = re.compile(r"^[a-z][a-z0-9-]{1,62}$")
|
PROJECT_RE = re.compile(r"^[a-z][a-z0-9-]{1,62}$")
|
||||||
@@ -1490,6 +1492,185 @@ def _commands_in_hooks(path):
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
|
def _casan_codex_hook_count(path):
|
||||||
|
doc = _load_json_or(path, {})
|
||||||
|
count = 0
|
||||||
|
for groups in (doc.get("hooks") or {}).values():
|
||||||
|
if not isinstance(groups, list):
|
||||||
|
continue
|
||||||
|
for group in groups:
|
||||||
|
handlers = (group.get("hooks") or []) if isinstance(group, dict) else []
|
||||||
|
for handler in handlers:
|
||||||
|
if not isinstance(handler, dict):
|
||||||
|
continue
|
||||||
|
commands = (
|
||||||
|
str(handler.get("command", "")),
|
||||||
|
str(handler.get("commandWindows", "")),
|
||||||
|
)
|
||||||
|
if any(
|
||||||
|
"casan-hook.py" in command and "--client codex" in command
|
||||||
|
for command in commands
|
||||||
|
):
|
||||||
|
count += 1
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
|
def _codex_project_hook_trust(target, timeout=10):
|
||||||
|
"""Read Codex's effective per-hook trust state via its app-server API."""
|
||||||
|
hooks_path = os.path.realpath(os.path.join(target, ".codex", "hooks.json"))
|
||||||
|
expected = _casan_codex_hook_count(hooks_path)
|
||||||
|
codex = shutil.which("codex")
|
||||||
|
result = {
|
||||||
|
"verified": False,
|
||||||
|
"source": "codex_app_server",
|
||||||
|
"codex_cli": codex,
|
||||||
|
"expected_hooks": expected,
|
||||||
|
"discovered_hooks": 0,
|
||||||
|
"trusted_hooks": 0,
|
||||||
|
"untrusted_hooks": 0,
|
||||||
|
"modified_hooks": 0,
|
||||||
|
"disabled_hooks": 0,
|
||||||
|
"status": "unavailable",
|
||||||
|
"review_required": True,
|
||||||
|
"operational": False,
|
||||||
|
"reason": None,
|
||||||
|
}
|
||||||
|
if not codex:
|
||||||
|
result["reason"] = "codex_cli_not_found"
|
||||||
|
return result
|
||||||
|
if not expected:
|
||||||
|
result["reason"] = "casan_project_hooks_not_found"
|
||||||
|
return result
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
{
|
||||||
|
"method": "initialize",
|
||||||
|
"id": 1,
|
||||||
|
"params": {
|
||||||
|
"clientInfo": {
|
||||||
|
"name": "casan_doctor",
|
||||||
|
"title": "CASAN Doctor",
|
||||||
|
"version": "1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"method": "initialized", "params": {}},
|
||||||
|
{"method": "hooks/list", "id": 2, "params": {"cwds": [target]}},
|
||||||
|
]
|
||||||
|
process = None
|
||||||
|
lines = queue.Queue()
|
||||||
|
|
||||||
|
def read_stdout(stream):
|
||||||
|
try:
|
||||||
|
for line in stream:
|
||||||
|
lines.put(line)
|
||||||
|
finally:
|
||||||
|
lines.put(None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
[codex, "app-server", "--stdio"],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.DEVNULL,
|
||||||
|
text=True,
|
||||||
|
cwd=target,
|
||||||
|
)
|
||||||
|
for message in messages:
|
||||||
|
process.stdin.write(
|
||||||
|
json.dumps(message, ensure_ascii=False, separators=(",", ":")) + "\n"
|
||||||
|
)
|
||||||
|
process.stdin.flush()
|
||||||
|
threading.Thread(
|
||||||
|
target=read_stdout, args=(process.stdout,), daemon=True
|
||||||
|
).start()
|
||||||
|
|
||||||
|
deadline = time.monotonic() + timeout
|
||||||
|
response = None
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
remaining = max(0.01, deadline - time.monotonic())
|
||||||
|
try:
|
||||||
|
line = lines.get(timeout=remaining)
|
||||||
|
except queue.Empty:
|
||||||
|
break
|
||||||
|
if line is None:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
message = json.loads(line)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
continue
|
||||||
|
if message.get("id") == 2:
|
||||||
|
response = message
|
||||||
|
break
|
||||||
|
if not response:
|
||||||
|
result["reason"] = "codex_hooks_list_timeout"
|
||||||
|
return result
|
||||||
|
if response.get("error"):
|
||||||
|
result["reason"] = "codex_hooks_list_error"
|
||||||
|
return result
|
||||||
|
|
||||||
|
hooks = []
|
||||||
|
for item in (response.get("result") or {}).get("data") or []:
|
||||||
|
for hook in item.get("hooks") or []:
|
||||||
|
source_path = hook.get("sourcePath")
|
||||||
|
command = str(hook.get("command") or "")
|
||||||
|
if (
|
||||||
|
isinstance(source_path, str)
|
||||||
|
and os.path.normcase(os.path.realpath(source_path))
|
||||||
|
== os.path.normcase(hooks_path)
|
||||||
|
and "casan-hook.py" in command
|
||||||
|
and "--client codex" in command
|
||||||
|
):
|
||||||
|
hooks.append(hook)
|
||||||
|
|
||||||
|
result["discovered_hooks"] = len(hooks)
|
||||||
|
if len(hooks) != expected:
|
||||||
|
result["reason"] = "codex_project_hooks_incomplete"
|
||||||
|
return result
|
||||||
|
|
||||||
|
statuses = [str(hook.get("trustStatus") or "").lower() for hook in hooks]
|
||||||
|
result["trusted_hooks"] = sum(
|
||||||
|
status in ("trusted", "managed") for status in statuses
|
||||||
|
)
|
||||||
|
result["untrusted_hooks"] = statuses.count("untrusted")
|
||||||
|
result["modified_hooks"] = statuses.count("modified")
|
||||||
|
result["disabled_hooks"] = sum(
|
||||||
|
hook.get("enabled") is False for hook in hooks
|
||||||
|
)
|
||||||
|
result["verified"] = True
|
||||||
|
result["review_required"] = any(
|
||||||
|
status not in ("trusted", "managed") for status in statuses
|
||||||
|
)
|
||||||
|
result["operational"] = (
|
||||||
|
not result["review_required"] and not result["disabled_hooks"]
|
||||||
|
)
|
||||||
|
if result["modified_hooks"]:
|
||||||
|
result["status"] = "modified"
|
||||||
|
elif result["untrusted_hooks"]:
|
||||||
|
result["status"] = "untrusted"
|
||||||
|
elif result["disabled_hooks"]:
|
||||||
|
result["status"] = "disabled"
|
||||||
|
elif result["operational"]:
|
||||||
|
result["status"] = "trusted"
|
||||||
|
else:
|
||||||
|
result["status"] = "unknown"
|
||||||
|
return result
|
||||||
|
except (OSError, ValueError, TypeError):
|
||||||
|
result["reason"] = "codex_hooks_list_unavailable"
|
||||||
|
return result
|
||||||
|
finally:
|
||||||
|
if process is not None and process.poll() is None:
|
||||||
|
try:
|
||||||
|
process.terminate()
|
||||||
|
try:
|
||||||
|
process.wait(timeout=2)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
process.kill()
|
||||||
|
process.wait()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _smoke_bootstrap(target, client):
|
def _smoke_bootstrap(target, client):
|
||||||
bootstrap = os.path.join(target, ".casan", "casan-hook.py")
|
bootstrap = os.path.join(target, ".casan", "casan-hook.py")
|
||||||
event = "Begin" if client == "vscode-copilot" else "UserPromptSubmit"
|
event = "Begin" if client == "vscode-copilot" else "UserPromptSubmit"
|
||||||
@@ -1600,13 +1781,17 @@ def cmd_doctor(args):
|
|||||||
item["vscode_extension_installed"] = (
|
item["vscode_extension_installed"] = (
|
||||||
"anthropic.claude-code" in installed_extensions if code else None)
|
"anthropic.claude-code" in installed_extensions if code else None)
|
||||||
elif client == "codex":
|
elif client == "codex":
|
||||||
commands = _commands_in_hooks(
|
hooks_path = os.path.join(target, ".codex", "hooks.json")
|
||||||
os.path.join(target, ".codex", "hooks.json"))
|
commands = _commands_in_hooks(hooks_path)
|
||||||
item["hook_present"] = any(
|
item["hook_present"] = any(
|
||||||
"casan-hook.py" in command and "--client codex" in command
|
"casan-hook.py" in command and "--client codex" in command
|
||||||
for command in commands)
|
for command in commands)
|
||||||
item["trust_review_required"] = True
|
trust = _codex_project_hook_trust(target)
|
||||||
item["trust_action"] = "Open /hooks in Codex and trust the current project hook hash."
|
item["trust"] = trust
|
||||||
|
item["trust_review_required"] = trust["review_required"]
|
||||||
|
if trust["review_required"]:
|
||||||
|
item["trust_action"] = (
|
||||||
|
"Open /hooks in Codex and trust the current project hooks.")
|
||||||
item["vscode_extension_installed"] = (
|
item["vscode_extension_installed"] = (
|
||||||
"openai.chatgpt" in installed_extensions if code else None)
|
"openai.chatgpt" in installed_extensions if code else None)
|
||||||
elif client == "vscode-copilot":
|
elif client == "vscode-copilot":
|
||||||
@@ -1628,6 +1813,8 @@ def cmd_doctor(args):
|
|||||||
else:
|
else:
|
||||||
item["smoke"] = {"ok": False, "reason": "configuration_missing"}
|
item["smoke"] = {"ok": False, "reason": "configuration_missing"}
|
||||||
client_ready = bool(item.get("hook_present") and item["smoke"].get("ok"))
|
client_ready = bool(item.get("hook_present") and item["smoke"].get("ok"))
|
||||||
|
if client == "codex" and item["trust"].get("verified"):
|
||||||
|
client_ready = client_ready and bool(item["trust"].get("operational"))
|
||||||
if client == "vscode-copilot":
|
if client == "vscode-copilot":
|
||||||
client_ready = client_ready and bool(item.get("extension_installed"))
|
client_ready = client_ready and bool(item.get("extension_installed"))
|
||||||
item["ready"] = client_ready
|
item["ready"] = client_ready
|
||||||
@@ -1635,13 +1822,33 @@ def cmd_doctor(args):
|
|||||||
checks["client_checks"][client] = item
|
checks["client_checks"][client] = item
|
||||||
|
|
||||||
if "codex" in clients:
|
if "codex" in clients:
|
||||||
checks["warnings"].append(
|
codex_check = checks["client_checks"].get("codex", {})
|
||||||
"Codex project hooks do not run until their exact hash is reviewed and trusted via /hooks.")
|
trust = codex_check.get("trust", {})
|
||||||
|
if trust.get("review_required"):
|
||||||
|
if trust.get("verified"):
|
||||||
|
warning = (
|
||||||
|
"Codex reports one or more CASAN project hooks as "
|
||||||
|
"%s; their current hashes require review." %
|
||||||
|
trust.get("status", "untrusted"))
|
||||||
|
else:
|
||||||
|
warning = (
|
||||||
|
"CASAN could not verify Codex project-hook trust state "
|
||||||
|
"(%s)." % (trust.get("reason") or "unavailable"))
|
||||||
|
checks["warnings"].append(warning)
|
||||||
checks["required_actions"].append({
|
checks["required_actions"].append({
|
||||||
"code": "codex_hook_trust",
|
"code": "codex_hook_trust",
|
||||||
"message": (
|
"message": (
|
||||||
"Open /hooks in a local Codex client, review the exact hook hash, "
|
"Open /hooks in a local Codex client, review the current "
|
||||||
"and trust it before treating Codex as operational."),
|
"project hooks, and trust any new or modified entries."),
|
||||||
|
})
|
||||||
|
elif trust.get("disabled_hooks"):
|
||||||
|
checks["warnings"].append(
|
||||||
|
"Codex reports one or more CASAN project hooks as disabled.")
|
||||||
|
checks["required_actions"].append({
|
||||||
|
"code": "codex_hook_enable",
|
||||||
|
"message": (
|
||||||
|
"Open /hooks in a local Codex client and enable the disabled "
|
||||||
|
"CASAN project hooks."),
|
||||||
})
|
})
|
||||||
if "vscode-copilot" in clients:
|
if "vscode-copilot" in clients:
|
||||||
checks["warnings"].append(
|
checks["warnings"].append(
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import contextlib
|
||||||
|
import importlib.util
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[3]
|
||||||
|
|
||||||
|
|
||||||
|
def module(name: str, path: Path):
|
||||||
|
spec = importlib.util.spec_from_file_location(name, path)
|
||||||
|
value = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
INIT = module("casan_init_test", ROOT / "packages/casan-devkit/casan-init.py")
|
||||||
|
|
||||||
|
|
||||||
|
def codex_hooks():
|
||||||
|
hooks = {}
|
||||||
|
for event in ("UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop"):
|
||||||
|
hooks[event] = [{
|
||||||
|
"hooks": [{
|
||||||
|
"type": "command",
|
||||||
|
"command": (
|
||||||
|
'python3 ".casan/casan-hook.py" '
|
||||||
|
f"--client codex --event {event}"
|
||||||
|
),
|
||||||
|
"timeout": 15,
|
||||||
|
}],
|
||||||
|
}]
|
||||||
|
return {"hooks": hooks}
|
||||||
|
|
||||||
|
|
||||||
|
class FakeProcess:
|
||||||
|
def __init__(self, response):
|
||||||
|
self.stdin = io.StringIO()
|
||||||
|
self.stdout = io.StringIO(json.dumps(response) + "\n")
|
||||||
|
self.returncode = None
|
||||||
|
|
||||||
|
def poll(self):
|
||||||
|
return self.returncode
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
self.returncode = 0
|
||||||
|
|
||||||
|
def wait(self, timeout=None):
|
||||||
|
self.returncode = 0
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def kill(self):
|
||||||
|
self.returncode = -9
|
||||||
|
|
||||||
|
|
||||||
|
class CasanInitTests(unittest.TestCase):
|
||||||
|
def trust_response(self, target: str, statuses):
|
||||||
|
source = str(Path(target, ".codex", "hooks.json").resolve())
|
||||||
|
hooks = []
|
||||||
|
for index, (event, status) in enumerate(zip(
|
||||||
|
("user_prompt_submit", "pre_tool_use", "post_tool_use", "stop"),
|
||||||
|
statuses,
|
||||||
|
)):
|
||||||
|
hooks.append({
|
||||||
|
"key": f"{source}:{event}:0:0",
|
||||||
|
"sourcePath": source,
|
||||||
|
"command": (
|
||||||
|
'python3 ".casan/casan-hook.py" '
|
||||||
|
f"--client codex --event {event}"
|
||||||
|
),
|
||||||
|
"enabled": True,
|
||||||
|
"trustStatus": status,
|
||||||
|
"displayOrder": index,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
"id": 2,
|
||||||
|
"result": {
|
||||||
|
"data": [{
|
||||||
|
"cwd": target,
|
||||||
|
"hooks": hooks,
|
||||||
|
"warnings": [],
|
||||||
|
"errors": [],
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def test_codex_trust_uses_effective_app_server_state(self):
|
||||||
|
with tempfile.TemporaryDirectory() as target:
|
||||||
|
hooks_path = Path(target, ".codex", "hooks.json")
|
||||||
|
hooks_path.parent.mkdir()
|
||||||
|
hooks_path.write_text(json.dumps(codex_hooks()), encoding="utf-8")
|
||||||
|
response = self.trust_response(target, ["trusted"] * 4)
|
||||||
|
process = FakeProcess(response)
|
||||||
|
|
||||||
|
with patch.object(INIT.shutil, "which", return_value="/bin/codex"), \
|
||||||
|
patch.object(INIT.subprocess, "Popen", return_value=process):
|
||||||
|
result = INIT._codex_project_hook_trust(target)
|
||||||
|
|
||||||
|
self.assertTrue(result["verified"])
|
||||||
|
self.assertEqual(result["status"], "trusted")
|
||||||
|
self.assertEqual(result["trusted_hooks"], 4)
|
||||||
|
self.assertFalse(result["review_required"])
|
||||||
|
self.assertTrue(result["operational"])
|
||||||
|
requests = [
|
||||||
|
json.loads(line) for line in process.stdin.getvalue().splitlines()
|
||||||
|
]
|
||||||
|
self.assertEqual(requests[-1]["method"], "hooks/list")
|
||||||
|
self.assertEqual(requests[-1]["params"]["cwds"], [target])
|
||||||
|
|
||||||
|
def test_codex_trust_detects_modified_hook(self):
|
||||||
|
with tempfile.TemporaryDirectory() as target:
|
||||||
|
hooks_path = Path(target, ".codex", "hooks.json")
|
||||||
|
hooks_path.parent.mkdir()
|
||||||
|
hooks_path.write_text(json.dumps(codex_hooks()), encoding="utf-8")
|
||||||
|
response = self.trust_response(
|
||||||
|
target, ["trusted", "modified", "trusted", "trusted"])
|
||||||
|
|
||||||
|
with patch.object(INIT.shutil, "which", return_value="/bin/codex"), \
|
||||||
|
patch.object(
|
||||||
|
INIT.subprocess, "Popen",
|
||||||
|
return_value=FakeProcess(response),
|
||||||
|
):
|
||||||
|
result = INIT._codex_project_hook_trust(target)
|
||||||
|
|
||||||
|
self.assertTrue(result["verified"])
|
||||||
|
self.assertEqual(result["status"], "modified")
|
||||||
|
self.assertEqual(result["modified_hooks"], 1)
|
||||||
|
self.assertTrue(result["review_required"])
|
||||||
|
self.assertFalse(result["operational"])
|
||||||
|
|
||||||
|
def test_doctor_does_not_warn_when_codex_hooks_are_trusted(self):
|
||||||
|
with tempfile.TemporaryDirectory() as target:
|
||||||
|
root = Path(target)
|
||||||
|
(root / ".casan").mkdir()
|
||||||
|
(root / ".codex").mkdir()
|
||||||
|
(root / ".casan/config.json").write_text(json.dumps({
|
||||||
|
"project_id": "trusted-project",
|
||||||
|
"enforcement_mode": "enforce",
|
||||||
|
"integration_mode": "project_hook",
|
||||||
|
"clients": ["codex"],
|
||||||
|
}), encoding="utf-8")
|
||||||
|
(root / ".casan/version.lock").write_text(
|
||||||
|
json.dumps({"harness_hash": "sha256:harness"}),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
(root / ".casan/casan-hook.py").write_text(
|
||||||
|
"# test bootstrap\n", encoding="utf-8")
|
||||||
|
(root / ".codex/hooks.json").write_text(
|
||||||
|
json.dumps(codex_hooks()), encoding="utf-8")
|
||||||
|
trusted = {
|
||||||
|
"verified": True,
|
||||||
|
"review_required": False,
|
||||||
|
"operational": True,
|
||||||
|
"status": "trusted",
|
||||||
|
"disabled_hooks": 0,
|
||||||
|
}
|
||||||
|
args = argparse.Namespace(
|
||||||
|
target=target, harness=None, client=None, json=True)
|
||||||
|
|
||||||
|
with patch.object(
|
||||||
|
INIT, "resolve_project_harness", return_value="/harness"
|
||||||
|
), patch.object(
|
||||||
|
INIT, "compute_live",
|
||||||
|
return_value=("sha256:harness", "live"),
|
||||||
|
), patch.object(
|
||||||
|
INIT, "_legacy_instruction_conflicts", return_value=[]
|
||||||
|
), patch.object(
|
||||||
|
INIT, "_smoke_bootstrap",
|
||||||
|
return_value={"ok": True, "decision": "allow"},
|
||||||
|
), patch.object(
|
||||||
|
INIT, "_codex_project_hook_trust", return_value=trusted
|
||||||
|
), patch.object(
|
||||||
|
INIT.shutil, "which", return_value=None
|
||||||
|
), contextlib.redirect_stdout(io.StringIO()) as output:
|
||||||
|
returncode = INIT.cmd_doctor(args)
|
||||||
|
|
||||||
|
result = json.loads(output.getvalue())
|
||||||
|
self.assertEqual(returncode, 0)
|
||||||
|
self.assertEqual(result["status"], "ready")
|
||||||
|
self.assertEqual(result["operational_status"], "ready")
|
||||||
|
self.assertEqual(result["warnings"], [])
|
||||||
|
self.assertEqual(result["required_actions"], [])
|
||||||
|
self.assertFalse(
|
||||||
|
result["client_checks"]["codex"]["trust_review_required"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -24,6 +24,9 @@ trap cleanup EXIT
|
|||||||
CASAN="$CASAN_HOME/bin/casan"
|
CASAN="$CASAN_HOME/bin/casan"
|
||||||
|
|
||||||
echo "===== ① global install ====="
|
echo "===== ① global install ====="
|
||||||
|
python3 "$REPO_ROOT/packages/casan-devkit/tests/casan-init-tests.py" >/dev/null \
|
||||||
|
&& pass "casan init/doctor unit contracts pass" \
|
||||||
|
|| fail "casan init/doctor unit contracts failed"
|
||||||
if sh "$REPO_ROOT/install.sh" >/dev/null 2>&1; then pass "install.sh completes"; else fail "install.sh failed"; fi
|
if sh "$REPO_ROOT/install.sh" >/dev/null 2>&1; then pass "install.sh completes"; else fail "install.sh failed"; fi
|
||||||
[[ -x "$CASAN" ]] && pass "global launcher created" || fail "launcher missing"
|
[[ -x "$CASAN" ]] && pass "global launcher created" || fail "launcher missing"
|
||||||
[[ -f "$CASAN_HOME/current/.harness-hash" ]] && pass "integrity hash recorded at install" || fail "no .harness-hash"
|
[[ -f "$CASAN_HOME/current/.harness-hash" ]] && pass "integrity hash recorded at install" || fail "no .harness-hash"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ set -euo pipefail
|
|||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
|
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
|
||||||
|
|
||||||
|
python3 "$CASAN_APP_ROOT/packages/casan-devkit/tests/casan-init-tests.py"
|
||||||
python3 "$CASAN_APP_ROOT/packages/casan-devkit/tests/project-scaffold-tests.py"
|
python3 "$CASAN_APP_ROOT/packages/casan-devkit/tests/project-scaffold-tests.py"
|
||||||
bash "$CASAN_APP_ROOT/packages/casan-devkit/tests/adoption-install-tests.sh"
|
bash "$CASAN_APP_ROOT/packages/casan-devkit/tests/adoption-install-tests.sh"
|
||||||
python3 "$CASAN_HARNESS_ROOT/scripts/bash/project_manifest.py" validate \
|
python3 "$CASAN_HARNESS_ROOT/scripts/bash/project_manifest.py" validate \
|
||||||
|
|||||||
Reference in New Issue
Block a user