chore(consistency): sync task status from DB [auto]
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Reject generated-only commits / generated-commit-guard (push) Successful in 7s
Python Tests / pytest (push) Successful in 25s

Updated by fix-consistency on 2026-10-11:
  - update .custodian-brief.md for the-custodian
This commit is contained in:
custodian-sync 2026-10-11 21:30:44 +02:00
parent ebaa38bd56
commit 6fe7201fd5
15 changed files with 411 additions and 10 deletions

View 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()

View 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

View 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()