refactor(structure): promote app to repo root + remove redundant workspace cruft
Standard production layout: the OKR app (was nested under AINative_OKR_CASAN5/) is now
the repository root. No more wrapper directory.
- Promote AINative_OKR_CASAN5/* -> repo root (backend/ frontend/ packages/ apps/
.specify/ docs/ infra/ nginx/ scripts/ + configs). Merge tool dirs: .gitea (kept the
active deploy ci.yml, added harness-ci.yml + runbooks), .claude (agents/commands +
launch.json), .github moved up.
- Remove redundant: 00_SUBMISSION_PACKAGE, scattered root notes (FPT_CASAN_Full.md,
tu-tuong-casan.md, casan-tu-sinh..., casan_harness_assessment.md, source-review...,
README_CASAN5_REFINED.md), casan-next-plans/ and optimize-docs/ (competition/planning
artifacts — roadmap + design history preserved in git log / commit messages).
- Update all references to the old layout:
- .gitea/workflows/{ci,harness-ci}.yml, .github/workflows/{ci,deploy}.yml:
working-directory .; drop AINative_OKR_CASAN5/ prefix; .specify/{tests,scripts}
-> packages/casan-harness/... (.specify/logs state kept)
- .claude/launch.json, .gitea/*-runbook.md: path prefixes
- CLAUDE.md, README.md: docs/input -> apps/okr/domain/input
- policy-bundle.yaml: 8 policy paths -> packages/casan-harness/...; manifest re-signed
- secrets-scan.sh: fixture excludes -> new package/domain paths.
Full gate from the new root: PASS=64 FAIL=0 SKIP=3.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
7101af9fd4
commit
36a4812ef3
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CASAN Meta-loop — self-improving loop policy (Plan-17 Track 5, harness-owned).
|
||||
|
||||
The loop that improves the loop. It reads a loop trace (and optional AgentOps
|
||||
metrics) and PROPOSES loop-policy changes (dry-run, never writes). Applying a
|
||||
proposal is governed exactly like every other security-sensitive change:
|
||||
* proposal != application (17.17): `propose` only prints JSON,
|
||||
* apply needs a real approval + Separation of Duties (proposer != approver) (17.18),
|
||||
* apply routes through control-plane-settings.py so the change is versioned,
|
||||
audited (hash-chain) and rollback-able,
|
||||
* a loosen proposal that exceeds the org hard cap is refused (17.19) — the
|
||||
meta-loop cannot grant itself unbounded budget.
|
||||
|
||||
Subcommands:
|
||||
propose --loop-trace R|FILE [--agentops JSONL] [--profile prod|dev]
|
||||
apply --proposals FILE --id ID --proposer P --approver A [--approval TOK]
|
||||
[--allow-untrusted]
|
||||
|
||||
Exit codes:
|
||||
0 proposed / applied ok
|
||||
3 DENY (SoD violation, missing approval, exceeds org ceiling, untrusted, governed-set failed)
|
||||
1 unreadable proposals / unknown id
|
||||
2 usage error
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import loop_common as lc
|
||||
|
||||
|
||||
def _emit(payload):
|
||||
json.dump(payload, sys.stdout, ensure_ascii=False, indent=2, sort_keys=True)
|
||||
sys.stdout.write("\n")
|
||||
|
||||
|
||||
def _load_trace(ref):
|
||||
"""Accept either a run-id (resolved to its trace.jsonl) or a direct file path."""
|
||||
path = ref
|
||||
if not os.path.isfile(path):
|
||||
candidate = os.path.join(lc.run_dir(ref), "trace.jsonl")
|
||||
if os.path.isfile(candidate):
|
||||
path = candidate
|
||||
else:
|
||||
raise lc.PolicyError("trace_not_found")
|
||||
rows = []
|
||||
with open(path, encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
rows.append(json.loads(line))
|
||||
except ValueError:
|
||||
raise lc.PolicyError("trace_corrupt")
|
||||
# entries are {seq, prev_hash, iteration, hash}; return the iterations
|
||||
return [r.get("iteration", r) for r in rows]
|
||||
|
||||
|
||||
def build_proposals(iterations, agentops):
|
||||
proposals = []
|
||||
steps = [it.get("step", 0) for it in iterations if isinstance(it.get("step"), int)]
|
||||
max_step = max(steps) if steps else 0
|
||||
decisions = [str(it.get("decision") or "") for it in iterations]
|
||||
verdicts = [str(it.get("gate_verdict") or "") for it in iterations]
|
||||
progresses = [it.get("progress") for it in iterations if isinstance(it.get("progress"), (int, float))]
|
||||
|
||||
# (a) The loop repeatedly hit its budget ceiling -> propose loosening max_steps.
|
||||
# Loosening is security-sensitive and is capped by org_ceiling at apply time.
|
||||
if any(d == "HALT" for d in decisions):
|
||||
proposals.append({
|
||||
"id": "P-LOOSEN-STEPS",
|
||||
"key": "loop.max_steps",
|
||||
"value": max_step + 10,
|
||||
"direction": "loosen",
|
||||
"security_sensitive": True,
|
||||
"reason": f"run halted on budget at step {max_step}; propose raising max_steps to {max_step + 10}",
|
||||
})
|
||||
|
||||
# (b) Oscillation/thrash seen -> propose TIGHTENING oscillation_repeat (detect sooner).
|
||||
# Tightening is safe but still governed (propose != apply).
|
||||
if any(v == "OSCILLATING" for v in verdicts) or (agentops or {}).get("oscillations"):
|
||||
proposals.append({
|
||||
"id": "P-TIGHTEN-OSC",
|
||||
"key": "loop.oscillation_repeat",
|
||||
"value": 2,
|
||||
"direction": "tighten",
|
||||
"security_sensitive": False,
|
||||
"reason": "oscillation observed; tighten oscillation_repeat to 2 to break loops sooner",
|
||||
})
|
||||
|
||||
# (c) Progress stagnated across the trace -> propose tightening the no-progress window.
|
||||
if len(progresses) >= 3 and max(progresses) <= min(progresses):
|
||||
proposals.append({
|
||||
"id": "P-TIGHTEN-NOPROG",
|
||||
"key": "loop.no_progress_window",
|
||||
"value": 2,
|
||||
"direction": "tighten",
|
||||
"security_sensitive": False,
|
||||
"reason": "no forward progress across the trace; tighten no_progress_window to 2",
|
||||
})
|
||||
return proposals
|
||||
|
||||
|
||||
def cmd_propose(args):
|
||||
iterations = _load_trace(args.loop_trace)
|
||||
agentops = None
|
||||
if args.agentops and os.path.isfile(args.agentops):
|
||||
try:
|
||||
agentops = json.load(open(args.agentops, encoding="utf-8"))
|
||||
except ValueError:
|
||||
agentops = None
|
||||
# AgentOps/loop-trace here is local, unsigned telemetry -> untrusted by default
|
||||
# (ARCH-08 parity with self-improve). Enforced apply of an untrusted proposal
|
||||
# requires --allow-untrusted.
|
||||
proposals = build_proposals(iterations, agentops)
|
||||
for p in proposals:
|
||||
p["source_trust"] = "untrusted"
|
||||
_emit({"proposals": proposals, "count": len(proposals), "source_trust": "untrusted"})
|
||||
return 0
|
||||
|
||||
|
||||
def cmd_apply(args):
|
||||
try:
|
||||
data = json.load(open(args.proposals, encoding="utf-8"))
|
||||
except (OSError, ValueError):
|
||||
_emit({"decision": "DENY", "reason": "proposals_unreadable", "proposals": args.proposals})
|
||||
return 1
|
||||
proposal = next((p for p in data.get("proposals", []) if p.get("id") == args.id), None)
|
||||
if proposal is None:
|
||||
_emit({"decision": "DENY", "reason": "unknown_proposal", "id": args.id})
|
||||
return 1
|
||||
|
||||
# Separation of Duties (17.18): the proposer can never be their own approver.
|
||||
if not args.proposer or not args.approver or args.proposer == args.approver:
|
||||
_emit({"decision": "DENY", "reason": "sod_violation",
|
||||
"proposer": args.proposer, "approver": args.approver})
|
||||
return 3
|
||||
|
||||
# Applying ALWAYS requires an approval (propose != apply).
|
||||
if not (args.approval or "").strip():
|
||||
_emit({"decision": "DENY", "reason": "approval_required", "id": args.id})
|
||||
return 3
|
||||
|
||||
# ARCH-08: refuse untrusted telemetry-derived proposals in enforced mode.
|
||||
enforced = (os.environ.get("CASAN_PROFILE") == "prod"
|
||||
or os.environ.get("CASAN_METALOOP_STRICT") == "1")
|
||||
if proposal.get("source_trust", "untrusted") == "untrusted" and enforced and not args.allow_untrusted:
|
||||
_emit({"decision": "DENY", "reason": "untrusted_source", "id": args.id})
|
||||
return 3
|
||||
|
||||
key = proposal.get("key")
|
||||
value = proposal.get("value")
|
||||
if key not in lc._GOV_BUDGET_MAP and key not in lc._GOV_CONV_MAP:
|
||||
_emit({"decision": "DENY", "reason": "key_not_governable", "key": key})
|
||||
return 3
|
||||
|
||||
# Org hard cap (17.19): a loosen can never exceed the org ceiling.
|
||||
if proposal.get("direction") == "loosen" and key in lc._GOV_BUDGET_MAP:
|
||||
try:
|
||||
policy = lc.load_policy()
|
||||
except lc.PolicyError as exc:
|
||||
_emit({"decision": "DENY", "reason": "fail_closed", "detail": str(exc)})
|
||||
return 3
|
||||
oc = lc.org_ceiling(policy)
|
||||
bk = lc._GOV_BUDGET_MAP[key]
|
||||
if bk in oc and isinstance(value, (int, float)) and value > oc[bk]:
|
||||
_emit({"decision": "DENY", "reason": "exceeds_org_ceiling",
|
||||
"key": key, "value": value, "org_ceiling": oc[bk]})
|
||||
return 3
|
||||
|
||||
# Governed apply: route through the control-plane store (versioned + audit +
|
||||
# rollback + its own approval verification in enforced mode). CASAN_APPROVER is
|
||||
# honoured by control-plane-settings.py's enforced approval check.
|
||||
cps = os.path.join(os.path.dirname(__file__), "control-plane-settings.py")
|
||||
env = dict(os.environ)
|
||||
env["CASAN_APPROVER"] = args.approver
|
||||
cmd = [
|
||||
sys.executable, cps, "set", key, json.dumps(value),
|
||||
"--actor", args.proposer,
|
||||
"--reason", f"meta-loop {args.id} ({proposal.get('direction')})",
|
||||
"--approval", args.approval,
|
||||
]
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, env=env)
|
||||
if result.returncode != 0:
|
||||
_emit({"decision": "DENY", "reason": "governed_set_failed",
|
||||
"detail": result.stderr.strip(), "id": args.id})
|
||||
return 3
|
||||
lc.append_audit({"kind": "metaloop_applied", "id": args.id, "key": key,
|
||||
"value": value, "proposer": args.proposer, "approver": args.approver})
|
||||
_emit({"decision": "APPLIED", "id": args.id, "key": key, "value": value,
|
||||
"proposer": args.proposer, "approver": args.approver})
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser():
|
||||
p = argparse.ArgumentParser(description="CASAN Meta-loop (Plan-17 T5)")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
pr = sub.add_parser("propose", help="Propose loop-policy changes (dry-run)")
|
||||
pr.add_argument("--loop-trace", required=True, help="run-id or trace.jsonl path")
|
||||
pr.add_argument("--agentops", default=None)
|
||||
pr.add_argument("--profile", default=None)
|
||||
pr.set_defaults(func=cmd_propose)
|
||||
|
||||
ap = sub.add_parser("apply", help="Apply an approved proposal (governed + SoD)")
|
||||
ap.add_argument("--proposals", required=True)
|
||||
ap.add_argument("--id", required=True)
|
||||
ap.add_argument("--proposer", required=True)
|
||||
ap.add_argument("--approver", required=True)
|
||||
ap.add_argument("--approval", default="")
|
||||
ap.add_argument("--allow-untrusted", action="store_true")
|
||||
ap.set_defaults(func=cmd_apply)
|
||||
return p
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
args = build_parser().parse_args(argv)
|
||||
try:
|
||||
return args.func(args)
|
||||
except lc.PolicyError as exc:
|
||||
_emit({"decision": "DENY", "reason": "fail_closed", "detail": str(exc)})
|
||||
return 3
|
||||
except Exception as exc: # never fail open
|
||||
json.dump({"decision": "DENY", "reason": "internal_error", "detail": str(exc)}, sys.stderr)
|
||||
sys.stderr.write("\n")
|
||||
return 3
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user