feat: add production Core runtime modes
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""`casan init` / `casan verify-harness` (Plan-21 hybrid adoption).
|
||||
"""`casan init` / `casan verify-harness` (Plan-21 production adoption).
|
||||
|
||||
Adopt CASAN into an EXISTING project by writing only per-project config — the
|
||||
shared harness stays under $CASAN_HOME and is NOT copied into the repo. This is
|
||||
the codegraph-style flow: global install once, then `casan init` per project.
|
||||
Adopt CASAN into an EXISTING project using an explicit runtime contract:
|
||||
managed mode pins the shared Core under $CASAN_HOME; vendored mode installs the
|
||||
same production-only Core under `.casan/runtime/casan-core`.
|
||||
|
||||
What init writes into the target repo:
|
||||
.casan/config.json project id, enforcement/integration mode, clients
|
||||
@@ -13,6 +13,8 @@ What init writes into the target repo:
|
||||
.specify/ runtime state root marker (logs/traces/admissions)
|
||||
.claude/settings.json Plan-20 Claude Code hooks (--client claude|all)
|
||||
.codex/hooks.json+config Plan-20 Codex hooks (--client codex|all)
|
||||
.casan/runtime/casan-core
|
||||
optional self-contained Core (--runtime vendored)
|
||||
|
||||
`verify` recomputes the resolved harness gate-code hash and compares it to
|
||||
version.lock — the pin+VERIFY half. Drift/tamper of the global harness relative
|
||||
@@ -107,6 +109,8 @@ def _render_init(result):
|
||||
("Location", result["target"]),
|
||||
("Level", "%s (%s)" % (
|
||||
result["target_level_name"].capitalize(), result["target_level"])),
|
||||
("Runtime", "%s — %s" % (
|
||||
result["runtime_mode"].capitalize(), result["runtime_path"])),
|
||||
("Mode", result["enforcement_mode"]),
|
||||
("Clients", _client_names(result["clients"])),
|
||||
("Files", "%d created or updated" % len(result["created"])),
|
||||
@@ -149,6 +153,10 @@ def _render_level(result):
|
||||
result.get("project_target_level_name"),
|
||||
result.get("project_target_level")))
|
||||
if result.get("project_target_level") else "not initialized"),
|
||||
("Runtime", ("%s — %s" % (
|
||||
result.get("project_runtime_mode"),
|
||||
result.get("project_runtime_path")))
|
||||
if result.get("project_target_level") else "not initialized"),
|
||||
("Status", str(result.get("project_level_status") or "unknown")),
|
||||
])
|
||||
print()
|
||||
@@ -270,7 +278,10 @@ def _copy_tree_missing(src_dir, dst_dir, target, created):
|
||||
|
||||
|
||||
def resolve_harness(explicit):
|
||||
for cand in (explicit, os.environ.get("CASAN_HARNESS_ROOT")):
|
||||
for cand in (
|
||||
explicit,
|
||||
os.environ.get("CASAN_HARNESS_ROOT"),
|
||||
os.environ.get("CASAN_GLOBAL_HARNESS_ROOT")):
|
||||
if cand and os.path.isdir(os.path.join(cand, "scripts", "bash")):
|
||||
return os.path.abspath(cand)
|
||||
install = os.environ.get("CASAN_INSTALL_ROOT")
|
||||
@@ -290,8 +301,9 @@ def install_root(harness):
|
||||
|
||||
|
||||
def harness_version(harness):
|
||||
for p in (os.path.join(install_root(harness), "VERSION"),
|
||||
os.path.join(harness, "..", "..", "VERSION")):
|
||||
local_root = os.path.abspath(os.path.join(harness, "..", ".."))
|
||||
for p in (os.path.join(local_root, "VERSION"),
|
||||
os.path.join(install_root(harness), "VERSION")):
|
||||
try:
|
||||
with open(p, "r", encoding="utf-8") as fh:
|
||||
v = fh.read().strip()
|
||||
@@ -317,17 +329,38 @@ def compute_harness_hash(harness):
|
||||
"""For PINNING at init: use the value recorded at install time if present
|
||||
(it equals a live compute of the same files), else compute live. Verify must
|
||||
NOT use this — it must call compute_live() to detect drift."""
|
||||
recorded = os.path.join(install_root(harness), ".harness-hash")
|
||||
try:
|
||||
with open(recorded, "r", encoding="utf-8") as fh:
|
||||
v = fh.read().strip()
|
||||
if v:
|
||||
return v, "recorded"
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
local_root = os.path.abspath(os.path.join(harness, "..", ".."))
|
||||
for recorded in (
|
||||
os.path.join(local_root, ".harness-hash"),
|
||||
os.path.join(install_root(harness), ".harness-hash")):
|
||||
try:
|
||||
with open(recorded, "r", encoding="utf-8") as fh:
|
||||
v = fh.read().strip()
|
||||
if v:
|
||||
return v, "recorded"
|
||||
except (OSError, IOError):
|
||||
continue
|
||||
return compute_live(harness)
|
||||
|
||||
|
||||
def resolve_project_harness(target, explicit=None):
|
||||
lock = _load_json_or(
|
||||
os.path.join(target, ".casan", "version.lock"), {})
|
||||
runtime_path = lock.get("runtime_path")
|
||||
if lock.get("runtime_mode") == "vendored" and isinstance(runtime_path, str):
|
||||
root = (
|
||||
None if os.path.isabs(runtime_path)
|
||||
else _safe_project_path(target, runtime_path)
|
||||
)
|
||||
if not root:
|
||||
return None
|
||||
candidate = os.path.join(root, "packages", "casan-harness")
|
||||
if os.path.isdir(os.path.join(candidate, "scripts", "bash")):
|
||||
return os.path.abspath(candidate)
|
||||
return None
|
||||
return resolve_harness(explicit)
|
||||
|
||||
|
||||
def _write(path, text, backups):
|
||||
with owner_writable(path):
|
||||
if os.path.exists(path):
|
||||
@@ -366,8 +399,10 @@ def _safe_project_path(target, relative):
|
||||
if not normalized or os.path.isabs(str(relative)):
|
||||
return None
|
||||
candidate = os.path.abspath(os.path.join(target, *normalized.split("/")))
|
||||
target_real = os.path.realpath(target)
|
||||
candidate_real = os.path.realpath(candidate)
|
||||
try:
|
||||
if os.path.commonpath((target, candidate)) != target:
|
||||
if os.path.commonpath((target_real, candidate_real)) != target_real:
|
||||
return None
|
||||
except ValueError:
|
||||
return None
|
||||
@@ -387,6 +422,110 @@ def _file_sha256(path):
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _install_vendored_core(target, source_harness):
|
||||
"""Atomically install the production Core runtime inside one project."""
|
||||
source_root = os.path.abspath(os.path.join(source_harness, "..", ".."))
|
||||
copier = os.path.join(source_root, "scripts", "copy-runtime.py")
|
||||
source_bin = os.path.join(source_root, "bin", "casan")
|
||||
source_version = os.path.join(source_root, "VERSION")
|
||||
for required in (source_bin, source_version):
|
||||
if not os.path.isfile(required):
|
||||
raise RuntimeError(
|
||||
"vendored runtime source is incomplete; missing %s" % required)
|
||||
|
||||
destination = _safe_project_path(
|
||||
target, ".casan/runtime/casan-core")
|
||||
if not destination:
|
||||
raise RuntimeError(
|
||||
"vendored runtime path escapes the project through a symlink")
|
||||
parent = os.path.dirname(destination)
|
||||
os.makedirs(parent, exist_ok=True)
|
||||
staging = tempfile.mkdtemp(prefix=".casan-core-", dir=parent)
|
||||
try:
|
||||
if os.path.isfile(copier):
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
copier,
|
||||
"--source-root", source_root,
|
||||
"--destination-root", staging,
|
||||
"--component", "harness",
|
||||
"--clean",
|
||||
],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(
|
||||
result.stderr.strip() or result.stdout.strip() or
|
||||
"production runtime copy failed")
|
||||
else:
|
||||
shutil.copytree(
|
||||
source_harness,
|
||||
os.path.join(staging, "packages", "casan-harness"))
|
||||
vendored_harness = os.path.join(
|
||||
staging, "packages", "casan-harness")
|
||||
forbidden = [
|
||||
relative for relative in (
|
||||
"tests",
|
||||
"level5",
|
||||
"scripts/bash/ci-harness-gate.sh",
|
||||
"scripts/bash/security-gate.sh",
|
||||
"scripts/bash/dashboard-server.py",
|
||||
)
|
||||
if os.path.exists(os.path.join(
|
||||
vendored_harness, *relative.split("/")))
|
||||
]
|
||||
if forbidden:
|
||||
raise RuntimeError(
|
||||
"source is not a production runtime; forbidden paths: %s" %
|
||||
", ".join(forbidden))
|
||||
os.makedirs(os.path.join(staging, "bin"), exist_ok=True)
|
||||
shutil.copy2(source_bin, os.path.join(staging, "bin", "casan"))
|
||||
shutil.copy2(source_version, os.path.join(staging, "VERSION"))
|
||||
with open(os.path.join(staging, ".casan-level"), "w",
|
||||
encoding="utf-8") as fh:
|
||||
fh.write("core\n")
|
||||
vendored_hash, hash_source = compute_live(vendored_harness)
|
||||
if hash_source == "error":
|
||||
raise RuntimeError(
|
||||
"cannot verify staged vendored Core: %s" % vendored_hash)
|
||||
with open(os.path.join(staging, ".harness-hash"), "w",
|
||||
encoding="utf-8") as fh:
|
||||
fh.write(vendored_hash + "\n")
|
||||
if os.path.isdir(destination) and not os.path.islink(destination):
|
||||
shutil.rmtree(destination)
|
||||
elif os.path.exists(destination) or os.path.islink(destination):
|
||||
os.unlink(destination)
|
||||
os.replace(staging, destination)
|
||||
staging = None
|
||||
finally:
|
||||
if staging and os.path.isdir(staging):
|
||||
shutil.rmtree(staging)
|
||||
|
||||
file_count = sum(
|
||||
len(files) for _root, _dirs, files in os.walk(destination))
|
||||
return destination, file_count
|
||||
|
||||
|
||||
def _remove_vendored_core(target):
|
||||
destination = _safe_project_path(
|
||||
target, ".casan/runtime/casan-core")
|
||||
if not destination:
|
||||
return False
|
||||
if not os.path.exists(destination) and not os.path.islink(destination):
|
||||
return False
|
||||
with owner_writable(destination):
|
||||
if os.path.isdir(destination) and not os.path.islink(destination):
|
||||
shutil.rmtree(destination)
|
||||
else:
|
||||
os.unlink(destination)
|
||||
_prune_empty_parents(destination, target)
|
||||
return True
|
||||
|
||||
|
||||
def _backup_once(path, backups):
|
||||
if backups is None:
|
||||
return
|
||||
@@ -831,8 +970,38 @@ def cmd_init(args):
|
||||
except ValueError as error:
|
||||
sys.stderr.write("casan init: %s\n" % error)
|
||||
return 64
|
||||
version = harness_version(harness)
|
||||
hhash, hsource = compute_harness_hash(harness)
|
||||
|
||||
previous_config = _load_json_or(
|
||||
os.path.join(target, ".casan", "config.json"), {})
|
||||
previous_runtime_mode = previous_config.get("runtime_mode")
|
||||
runtime_mode = (
|
||||
args.runtime or
|
||||
(previous_runtime_mode
|
||||
if previous_runtime_mode in ("managed", "vendored") else "managed")
|
||||
)
|
||||
runtime_removed = False
|
||||
runtime_files = 0
|
||||
if runtime_mode == "vendored":
|
||||
try:
|
||||
runtime_root, runtime_files = _install_vendored_core(
|
||||
target, harness)
|
||||
except (OSError, RuntimeError) as error:
|
||||
sys.stderr.write(
|
||||
"casan init: cannot install vendored Core runtime: %s\n" %
|
||||
error)
|
||||
return 1
|
||||
active_harness = os.path.join(
|
||||
runtime_root, "packages", "casan-harness")
|
||||
runtime_path = os.path.relpath(
|
||||
runtime_root, target).replace(os.sep, "/")
|
||||
else:
|
||||
runtime_removed = _remove_vendored_core(target)
|
||||
active_harness = harness
|
||||
runtime_root = install_root(harness)
|
||||
runtime_path = runtime_root
|
||||
|
||||
version = harness_version(active_harness)
|
||||
hhash, hsource = compute_harness_hash(active_harness)
|
||||
created = []
|
||||
backups = []
|
||||
previous_manifest = _load_json_or(
|
||||
@@ -889,7 +1058,11 @@ def cmd_init(args):
|
||||
"vscode-native-copilot": "unsupported_global_interception",
|
||||
},
|
||||
"harness_version": version,
|
||||
"adoption_model": "hybrid-global",
|
||||
"adoption_model": (
|
||||
"managed-global" if runtime_mode == "managed"
|
||||
else "vendored-project"),
|
||||
"runtime_mode": runtime_mode,
|
||||
"runtime_path": runtime_path,
|
||||
"target_level": lvl,
|
||||
"target_level_name": lvl_name,
|
||||
}
|
||||
@@ -903,7 +1076,9 @@ def cmd_init(args):
|
||||
"harness_hash": hhash,
|
||||
"hash_algo": "sha256",
|
||||
"hash_source": hsource,
|
||||
"install_root": install_root(harness),
|
||||
"install_root": runtime_root,
|
||||
"runtime_mode": runtime_mode,
|
||||
"runtime_path": runtime_path,
|
||||
"recorded_at": now_iso(),
|
||||
}
|
||||
p = os.path.join(cfg_dir, "version.lock")
|
||||
@@ -924,7 +1099,7 @@ def cmd_init(args):
|
||||
_write(p, "\n".join(env_lines), backups); created_add(p)
|
||||
|
||||
# Stable project-local bootstrap. It loads the config above, resolves the
|
||||
# global harness and verifies version.lock before dispatching an adapter.
|
||||
# managed/vendored Core and verifies version.lock before dispatching.
|
||||
bootstrap_source = os.path.join(devkit_root(), "templates", "project",
|
||||
"casan-hook.py")
|
||||
bootstrap_target = os.path.join(cfg_dir, "casan-hook.py")
|
||||
@@ -1067,6 +1242,10 @@ def cmd_init(args):
|
||||
"target_level_name": lvl_name,
|
||||
"harness_version": version,
|
||||
"harness_hash": hhash,
|
||||
"runtime_mode": runtime_mode,
|
||||
"runtime_path": runtime_path,
|
||||
"runtime_files": runtime_files,
|
||||
"runtime_removed": runtime_removed,
|
||||
"enforcement_mode": args.mode,
|
||||
"clients": clients,
|
||||
"created": created,
|
||||
@@ -1076,8 +1255,11 @@ def cmd_init(args):
|
||||
"level_extras": level_extras,
|
||||
"level_removed": level_removed,
|
||||
"level_retained": level_retained,
|
||||
"note": ("harness NOT copied into repo (hybrid model); selected client hooks "
|
||||
"MERGED and unselected CASAN hooks removed; run `casan doctor`"),
|
||||
"note": (
|
||||
"managed runtime is referenced by version/hash lock"
|
||||
if runtime_mode == "managed" else
|
||||
"production-only Core runtime vendored under .casan/runtime/casan-core"
|
||||
),
|
||||
}
|
||||
_emit_json_or_human(args, result, _render_init)
|
||||
if legacy_migration.get("manual_review"):
|
||||
@@ -1096,11 +1278,11 @@ def cmd_init(args):
|
||||
|
||||
|
||||
def cmd_verify(args):
|
||||
harness = resolve_harness(args.harness)
|
||||
if not harness:
|
||||
sys.stderr.write("casan verify-harness: cannot locate the harness.\n")
|
||||
return 1
|
||||
target = os.path.abspath(args.target or os.getcwd())
|
||||
harness = resolve_project_harness(target, args.harness)
|
||||
if not harness:
|
||||
sys.stderr.write("casan verify-harness: cannot locate the project runtime.\n")
|
||||
return 1
|
||||
lock_path = os.path.join(target, ".casan", "version.lock")
|
||||
if not os.path.exists(lock_path):
|
||||
sys.stderr.write("casan verify-harness: no .casan/version.lock (run `casan init` first).\n")
|
||||
@@ -1120,8 +1302,8 @@ def cmd_verify(args):
|
||||
}
|
||||
_emit_json_or_human(args, result, _render_verify)
|
||||
if not ok:
|
||||
sys.stderr.write("HARNESS_INTEGRITY_DRIFT — the resolved harness does not match the "
|
||||
"project pin. The global harness changed or was tampered.\n")
|
||||
sys.stderr.write("HARNESS_INTEGRITY_DRIFT — the resolved Core runtime does not match "
|
||||
"the project pin. The runtime changed or was tampered.\n")
|
||||
return 3
|
||||
return 0
|
||||
|
||||
@@ -1144,6 +1326,8 @@ def cmd_level(args):
|
||||
"installed_level": installed,
|
||||
"project_target_level": tl,
|
||||
"project_target_level_name": cfg.get("target_level_name"),
|
||||
"project_runtime_mode": cfg.get("runtime_mode", "managed"),
|
||||
"project_runtime_path": cfg.get("runtime_path"),
|
||||
"project_level_status": status_map.get(tl, "unknown") if tl else None,
|
||||
"levels": {
|
||||
"1 core": "implemented — harness + gates + CLI",
|
||||
@@ -1217,7 +1401,7 @@ def cmd_doctor(args):
|
||||
else:
|
||||
clients = config.get("clients") or []
|
||||
|
||||
harness = resolve_harness(args.harness)
|
||||
harness = resolve_project_harness(target, args.harness)
|
||||
lock = _load_json_or(os.path.join(target, ".casan", "version.lock"), {})
|
||||
expected = lock.get("harness_hash")
|
||||
actual, source = compute_live(harness) if harness else ("unavailable", "error")
|
||||
@@ -1502,6 +1686,8 @@ def cmd_uninstall(args):
|
||||
|
||||
removed, retained = _remove_owned_project_artifacts(
|
||||
target, owned_files, owned_file_hashes)
|
||||
if _remove_vendored_core(target):
|
||||
removed.append(".casan/runtime/casan-core")
|
||||
|
||||
for relative in (
|
||||
".casan/config.json",
|
||||
@@ -1567,6 +1753,11 @@ def main(argv=None):
|
||||
"(default auto: install when `code` is available)"))
|
||||
pi.add_argument("--level", default="core",
|
||||
help="packaging level to adopt: 1|core (default), 2|devkit, 3|platform (preview), 4|enterprise (refused)")
|
||||
pi.add_argument(
|
||||
"--runtime", choices=["managed", "vendored"],
|
||||
help=("Core runtime placement: managed uses the pinned global install "
|
||||
"(default for new adoption); vendored copies a production-only "
|
||||
"Core into the project; re-init preserves the current mode"))
|
||||
pi.add_argument("--mode", choices=["observe", "enforce"], default="enforce",
|
||||
help="agentic policy mode (default: enforce; use observe for a telemetry-only pilot)")
|
||||
pi.add_argument("--integration-mode", dest="integration_mode",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Project-local bootstrap for CASAN's globally installed agentic adapters.
|
||||
"""Project-local bootstrap for CASAN's managed or vendored agentic adapters.
|
||||
|
||||
This file is intentionally small and stdlib-only. It is the stable command
|
||||
target committed by `casan init`; the policy implementation remains in the
|
||||
versioned global CASAN installation. On every hook invocation it:
|
||||
target committed by `casan init`; the policy implementation is resolved from
|
||||
the project lock. On every hook invocation it:
|
||||
|
||||
1. locates the project and loads `.casan/config.json`;
|
||||
2. applies the project's enforcement/integration settings to the process;
|
||||
3. resolves the pinned global harness and verifies its live integrity hash;
|
||||
3. resolves the pinned managed/vendored Core and verifies its live integrity hash;
|
||||
4. dispatches stdin/stdout to the selected client adapter.
|
||||
|
||||
The bootstrap never calls a model.
|
||||
@@ -46,8 +46,22 @@ def find_project_root(start):
|
||||
return None
|
||||
|
||||
|
||||
def harness_candidates(lock):
|
||||
def harness_candidates(lock, project_root):
|
||||
values = []
|
||||
runtime_path = lock.get("runtime_path")
|
||||
if lock.get("runtime_mode") == "vendored":
|
||||
if not isinstance(runtime_path, str) or os.path.isabs(runtime_path):
|
||||
return values
|
||||
normalized = os.path.normpath(runtime_path)
|
||||
candidate = os.path.abspath(os.path.join(project_root, normalized))
|
||||
try:
|
||||
inside_project = os.path.commonpath(
|
||||
(project_root, candidate)) == project_root
|
||||
except ValueError:
|
||||
inside_project = False
|
||||
if not inside_project:
|
||||
return values
|
||||
return [os.path.join(candidate, "packages", "casan-harness")]
|
||||
explicit = os.environ.get("CASAN_HARNESS_ROOT")
|
||||
if explicit:
|
||||
values.append(explicit)
|
||||
@@ -69,8 +83,8 @@ def harness_candidates(lock):
|
||||
return values
|
||||
|
||||
|
||||
def resolve_harness(lock):
|
||||
for candidate in harness_candidates(lock):
|
||||
def resolve_harness(lock, project_root):
|
||||
for candidate in harness_candidates(lock, project_root):
|
||||
root = os.path.abspath(os.path.expanduser(candidate))
|
||||
if os.path.isfile(os.path.join(root, "scripts", "python",
|
||||
"agentic_bridge.py")):
|
||||
@@ -178,7 +192,7 @@ def main(argv=None):
|
||||
"vscode-copilot": "vscode"}.get(item, item) for item in enabled)
|
||||
|
||||
lock = load_json(os.path.join(root, ".casan", "version.lock"))
|
||||
harness = resolve_harness(lock)
|
||||
harness = resolve_harness(lock, root)
|
||||
if not harness:
|
||||
return emit_failure(client, event, "pinned global harness not found",
|
||||
enforce)
|
||||
|
||||
@@ -181,11 +181,14 @@ PID=$(python3 -c 'import json;print(json.load(open("'"$PROJ2"'/.casan/config.jso
|
||||
MODE=$(python3 -c 'import json;print(json.load(open("'"$PROJ2"'/.casan/config.json"))["enforcement_mode"])' 2>/dev/null)
|
||||
[[ "$MODE" == "enforce" ]] && pass "production init defaults to enforce mode" || fail "default mode is not enforce ($MODE)"
|
||||
DEFAULT_LEVEL=$(python3 -c 'import json;print(json.load(open("'"$PROJ2"'/.casan/config.json"))["target_level"])' 2>/dev/null)
|
||||
DEFAULT_RUNTIME=$(python3 -c 'import json;print(json.load(open("'"$PROJ2"'/.casan/config.json"))["runtime_mode"])' 2>/dev/null)
|
||||
[[ "$DEFAULT_LEVEL" == "1" ]] \
|
||||
&& [[ "$DEFAULT_RUNTIME" == "managed" ]] \
|
||||
&& [ ! -d "$PROJ2/.casan/runtime" ] \
|
||||
&& [ ! -d "$PROJ2/.gitea" ] \
|
||||
&& [ ! -d "$PROJ2/apps" ] \
|
||||
&& pass "existing-project init defaults to core without CI/domain scaffold" \
|
||||
|| fail "default init did not stay at core (level=$DEFAULT_LEVEL)"
|
||||
&& pass "existing-project init defaults to managed Core without vendoring" \
|
||||
|| fail "default init mode is unclear (level=$DEFAULT_LEVEL runtime=$DEFAULT_RUNTIME)"
|
||||
|
||||
echo "===== ⑦ init MERGES into an existing shell (agents/skills/hooks preserved) ====="
|
||||
EXP="$WORK/existing"; mkdir -p "$EXP/.claude/agents" "$EXP/.claude/skills" "$EXP/.codex"
|
||||
@@ -254,6 +257,53 @@ LVL_DOWN=$( ( cd "$L2" && "$DKC" level show --json ) | python3 -c 'import json,s
|
||||
&& pass "default init downgrades existing DevKit adoption to clean core" \
|
||||
|| fail "default init left Level 2 artifacts after core downgrade"
|
||||
|
||||
VENDORED="$WORK/vendored-core"; mkdir -p "$VENDORED"
|
||||
VENDORED_OUT=$(cd "$VENDORED" && "$DKC" init --runtime vendored --project vendored-core --client claude --non-interactive)
|
||||
VENDORED_ROOT="$VENDORED/.casan/runtime/casan-core"
|
||||
echo "$VENDORED_OUT" | grep -q "Runtime.*Vendored" \
|
||||
&& pass "vendored init clearly reports runtime placement" \
|
||||
|| fail "vendored init output does not explain runtime mode ($VENDORED_OUT)"
|
||||
[ -x "$VENDORED_ROOT/bin/casan" ] \
|
||||
&& [ -f "$VENDORED_ROOT/packages/casan-harness/scripts/bash/casan-harness.sh" ] \
|
||||
&& [ ! -d "$VENDORED_ROOT/packages/casan-harness/tests" ] \
|
||||
&& [ ! -d "$VENDORED_ROOT/packages/casan-harness/level5" ] \
|
||||
&& pass "vendored mode installs only production Core inside the project" \
|
||||
|| fail "vendored Core layout is incomplete or contains source-only files"
|
||||
( cd "$VENDORED" && "$VENDORED_ROOT/bin/casan" version >/dev/null ) \
|
||||
&& pass "project-vendored Core includes a working local CLI" \
|
||||
|| fail "project-vendored CLI cannot resolve its Core runtime"
|
||||
( cd "$VENDORED" && "$DKC" verify-harness >/dev/null ) \
|
||||
&& pass "global launcher resolves and verifies the project-vendored Core" \
|
||||
|| fail "vendored Core is not honored by the global launcher"
|
||||
( cd "$VENDORED" && "$DKC" doctor --client claude >/dev/null ) \
|
||||
&& pass "project hooks execute against the verified vendored Core" \
|
||||
|| fail "project hook cannot execute against vendored Core"
|
||||
mv "$VENDORED_ROOT" "$VENDORED_ROOT.missing"
|
||||
VENDORED_MISSING_RC=0
|
||||
( cd "$VENDORED" && "$DKC" version >/dev/null 2>&1 ) || VENDORED_MISSING_RC=$?
|
||||
mv "$VENDORED_ROOT.missing" "$VENDORED_ROOT"
|
||||
[ "$VENDORED_MISSING_RC" -ne 0 ] \
|
||||
&& pass "vendored contract fails closed instead of falling back to global Core" \
|
||||
|| fail "missing vendored Core silently fell back to the global runtime"
|
||||
( cd "$VENDORED" && "$DKC" init --project vendored-core --client none --non-interactive >/dev/null )
|
||||
[ -d "$VENDORED_ROOT/packages/casan-harness" ] \
|
||||
&& pass "re-init preserves the project's selected vendored runtime mode" \
|
||||
|| fail "re-init silently changed the project's runtime mode"
|
||||
( cd "$VENDORED" && "$DKC" init --runtime managed --project vendored-core --client none --non-interactive >/dev/null )
|
||||
[ ! -d "$VENDORED/.casan/runtime" ] \
|
||||
&& pass "switching back to managed mode removes the old vendored runtime" \
|
||||
|| fail "managed re-init left a stale vendored runtime"
|
||||
|
||||
VENDORED_ESCAPE="$WORK/vendored-escape"; VENDORED_OUTSIDE="$WORK/vendored-outside"
|
||||
mkdir -p "$VENDORED_ESCAPE/.casan" "$VENDORED_OUTSIDE"
|
||||
ln -s "$VENDORED_OUTSIDE" "$VENDORED_ESCAPE/.casan/runtime"
|
||||
VENDORED_ESCAPE_RC=0
|
||||
( cd "$VENDORED_ESCAPE" && "$DKC" init --runtime vendored --project vendored-escape --client none --non-interactive >/dev/null 2>&1 ) || VENDORED_ESCAPE_RC=$?
|
||||
[ "$VENDORED_ESCAPE_RC" -ne 0 ] \
|
||||
&& [ ! -e "$VENDORED_OUTSIDE/casan-core" ] \
|
||||
&& pass "vendored install rejects a runtime path escaping through symlink" \
|
||||
|| fail "vendored install followed an unsafe project symlink"
|
||||
|
||||
echo "===== ⑨ SAFETY: init refuses to adopt a CASAN source hub into itself ====="
|
||||
HUB="$WORK/fakehub"
|
||||
mkdir -p "$HUB/packages/casan-harness/scripts/bash" \
|
||||
@@ -370,6 +420,9 @@ INIT_OUT=$(cd "$UN" && "$DKC" init --level devkit --project uninstall-project --
|
||||
echo "$INIT_OUT" | grep -q "CASAN initialized" \
|
||||
&& pass "init defaults to concise human-readable output" \
|
||||
|| fail "init human output missing ($INIT_OUT)"
|
||||
echo "$INIT_OUT" | grep -q "Runtime.*Managed" \
|
||||
&& pass "init output clearly identifies managed runtime and path" \
|
||||
|| fail "init output hides runtime placement ($INIT_OUT)"
|
||||
if echo "$INIT_OUT" | head -1 | grep -q '^[[:space:]]*{'; then
|
||||
fail "init still defaults to raw JSON"
|
||||
else
|
||||
@@ -432,6 +485,14 @@ PY
|
||||
&& pass "legacy uninstall preserves user Gitea workflow and removes CASAN workflow" \
|
||||
|| fail "uninstall removed a user workflow or retained the CASAN workflow"
|
||||
|
||||
UN_VENDORED="$WORK/uninstall-vendored"; mkdir -p "$UN_VENDORED"
|
||||
( cd "$UN_VENDORED" && "$DKC" init --runtime vendored --project uninstall-vendored --client none --non-interactive >/dev/null )
|
||||
( cd "$UN_VENDORED" && "$DKC" uninstall >/dev/null )
|
||||
[ ! -d "$UN_VENDORED/.casan/runtime" ] \
|
||||
&& [ ! -f "$UN_VENDORED/.casan/config.json" ] \
|
||||
&& pass "uninstall removes the complete project-vendored Core" \
|
||||
|| fail "uninstall left vendored Core or active project config"
|
||||
|
||||
echo ""
|
||||
echo "===== HYBRID INSTALL SUMMARY: PASS=$PASS FAIL=$FAIL ====="
|
||||
[[ "$FAIL" -eq 0 ]] || exit 1
|
||||
|
||||
Reference in New Issue
Block a user