chore(consistency): sync task status from DB [auto]
Updated by fix-consistency on 2026-10-11: - update .custodian-brief.md for the-custodian
This commit is contained in:
parent
ebaa38bd56
commit
6fe7201fd5
15 changed files with 411 additions and 10 deletions
52
scripts/check_generated_only_commit.py
Normal file
52
scripts/check_generated_only_commit.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Reject commits consisting only of generated indexes/projection refreshes.
|
||||
|
||||
Applies to every author: automation must accompany an actual source change.
|
||||
--staged supports a pre-commit hook; BASE..HEAD checks every commit for CI.
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import PurePosixPath
|
||||
import subprocess
|
||||
|
||||
|
||||
def generated(path):
|
||||
parsed = PurePosixPath(path.lower())
|
||||
name = parsed.name
|
||||
return (name in {"work-records.md", ".custodian-brief.md", "source-index.md",
|
||||
"publication-index.md", "index.generated.md"}
|
||||
or name.startswith("source-inventory")
|
||||
or name.startswith("decision-packets")
|
||||
or name.startswith("publication-index") or name.startswith("projection-refresh")
|
||||
or str(parsed).startswith("generated/")
|
||||
or str(parsed).startswith("docs/attended-window/")
|
||||
or any(part in {"evidence", "decision-packets", "evidence-packets"}
|
||||
for part in parsed.parts[:-1]))
|
||||
|
||||
|
||||
def standalone(paths):
|
||||
return bool(paths) and all(generated(path) for path in paths)
|
||||
|
||||
|
||||
def git(*args):
|
||||
return subprocess.check_output(["git", *args])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--staged", action="store_true")
|
||||
parser.add_argument("--range", default="HEAD^..HEAD")
|
||||
args = parser.parse_args()
|
||||
commits = [None] if args.staged else git("rev-list", "--reverse", args.range).decode().splitlines()
|
||||
failed = []
|
||||
for commit in commits:
|
||||
raw = git("diff", "--cached", "--name-only", "-z") if commit is None else git(
|
||||
"diff-tree", "--root", "--no-commit-id", "-r", "--name-only", "-z", commit)
|
||||
paths = [path.decode() for path in raw.split(b"\0") if path]
|
||||
if standalone(paths):
|
||||
failed.append(commit or "staged")
|
||||
if failed:
|
||||
parser.exit(1, "Generated-only commits rejected: " + ", ".join(failed) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5
scripts/generated-pre-commit.sh
Normal file
5
scripts/generated-pre-commit.sh
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# Install as .git/hooks/pre-commit; rejects generated/evidence-only auto commits too.
|
||||
set -eu
|
||||
REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||
exec python3 "$REPO_ROOT/scripts/check_generated_only_commit.py" --staged
|
||||
79
scripts/security_flow_daily.py
Normal file
79
scripts/security_flow_daily.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Daily decision sync and security report, with per-recipient delivery checkpoints.
|
||||
|
||||
Dry-run by default. --apply is the scheduled mode. No credentials are stored.
|
||||
"""
|
||||
import argparse
|
||||
import fcntl
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
from urllib.request import Request, urlopen
|
||||
|
||||
from security_flow_report import DEFAULT_REPOS
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def notifications(report, state, agent, founder):
|
||||
pending = []
|
||||
ready = {key: report[key] for key in ("READY", "UNBLOCKED")}
|
||||
if ready != state.get("agent"):
|
||||
pending.append(("agent", agent, ready))
|
||||
# ATTENDED is the daily operator reminder; empty lists never message the founder.
|
||||
if report["ATTENDED"]:
|
||||
pending.append(("founder", founder, {"ATTENDED": report["ATTENDED"]}))
|
||||
return pending
|
||||
|
||||
|
||||
def post(hub, recipient, payload):
|
||||
body = {"from_agent": "the-custodian", "to_agent": recipient,
|
||||
"subject": "Daily security flow", "body": json.dumps(payload, sort_keys=True)}
|
||||
request = Request(hub.rstrip("/") + "/messages/", data=json.dumps(body).encode(),
|
||||
headers={"Content-Type": "application/json"}, method="POST")
|
||||
with urlopen(request, timeout=30) as response:
|
||||
return json.load(response)["id"]
|
||||
|
||||
|
||||
def save(path, data):
|
||||
temporary = path.with_suffix(path.suffix + ".tmp")
|
||||
temporary.write_text(json.dumps(data, indent=2) + "\n")
|
||||
temporary.replace(path)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--root", default=str(Path.home()))
|
||||
parser.add_argument("--repos", default=",".join(["the-custodian", *DEFAULT_REPOS]))
|
||||
parser.add_argument("--hub", default="http://127.0.0.1:8000")
|
||||
parser.add_argument("--agent", default="the-custodian")
|
||||
parser.add_argument("--founder", default="human")
|
||||
parser.add_argument("--state-dir", type=Path, default=Path.home() / ".local/state/custodian-security-flow")
|
||||
parser.add_argument("--apply", action="store_true")
|
||||
args = parser.parse_args()
|
||||
args.state_dir.mkdir(parents=True, exist_ok=True)
|
||||
with (args.state_dir / "run.lock").open("w") as lock:
|
||||
fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
command = [sys.executable, str(SCRIPTS / "sync_resolved_decisions.py"),
|
||||
"--root", args.root, "--hub", args.hub]
|
||||
subprocess.run(command + (["--apply"] if args.apply else []), check=True)
|
||||
raw = subprocess.check_output([sys.executable, str(SCRIPTS / "security_flow_report.py"),
|
||||
"--root", args.root, "--repos", args.repos, "--json"], text=True)
|
||||
report = json.loads(raw)
|
||||
state_path = args.state_dir / "delivery.json"
|
||||
state = json.loads(state_path.read_text()) if state_path.exists() else {}
|
||||
save(args.state_dir / "report.json", report)
|
||||
for key, recipient, payload in notifications(report, state, args.agent, args.founder):
|
||||
if not args.apply:
|
||||
print(json.dumps({"dry_run": True, "to_agent": recipient, "report": payload}))
|
||||
continue
|
||||
message_id = post(args.hub, recipient, payload)
|
||||
state[key] = payload
|
||||
state[key + "_message_id"] = message_id
|
||||
save(state_path, state) # A later recipient failure does not replay earlier sends.
|
||||
print(raw)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue