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
|
|
@ -2,22 +2,22 @@
|
|||
# Custodian Brief — the-custodian
|
||||
|
||||
**Domain:** infotech
|
||||
**Last synced:** 2026-10-11 19:10 UTC
|
||||
**Last synced:** 2026-10-11 19:30 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
### Deterministic security flow: standing authorizations, flow report, and agent re-engagement
|
||||
Progress: 2/7 done | workplan_id: `ab288f92-d364-5c24-9362-aee451f266c3`
|
||||
Progress: 3/7 done | workplan_id: `ab288f92-d364-5c24-9362-aee451f266c3`
|
||||
|
||||
**Open tasks:**
|
||||
- ! Schedule the flow report and decision sync `1be0569c`
|
||||
*(wait: Awaiting daily schedule owner acceptance; workstation user service bus unavailable. Handoff 117e538e-da06-48b8-ba68-b82300a832e7.)*
|
||||
- ! Encode the standing change rule as a policy-nexus guardrail `5dd52156`
|
||||
*(wait: policy-nexus owns guardrail publication; handoff written as PNEX-WP-0006.)*
|
||||
- ! Attended window #1 `e3da807f`
|
||||
*(wait: Founder runs docs/attended-window/2026-10-11-checklist.md (about 2 h).)*
|
||||
- ► Schedule the flow report and decision sync `1be0569c`
|
||||
- ► Stop agent-authored index and sync commits `8a3dc219`
|
||||
- · Re-engage the coding agent with the renewed goal `91ec924c`
|
||||
|
||||
### Separate agent credentials and establish supervised privileged execution
|
||||
Progress: 2/5 done | workplan_id: `a98a9f34-83b4-5c8c-8107-e05f7806d50d`
|
||||
|
|
|
|||
33
.forgejo/workflows/generated-commit-guard.yaml
Normal file
33
.forgejo/workflows/generated-commit-guard.yaml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
name: Reject generated-only commits
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
generated-commit-guard:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check every proposed commit
|
||||
env:
|
||||
PUSH_BEFORE: ${{ github.event.before }}
|
||||
PR_BASE: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -eu
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq git python3 >/dev/null
|
||||
WORK="$(mktemp -d)"
|
||||
trap 'rm -rf "$WORK"' EXIT
|
||||
git clone --quiet "https://forgejo.coulomb.social/${GITHUB_REPOSITORY}.git" "$WORK/repo"
|
||||
cd "$WORK/repo"
|
||||
git fetch --quiet origin "$HEAD_SHA"
|
||||
git checkout --quiet --detach "$HEAD_SHA"
|
||||
BASE="${PR_BASE:-${PUSH_BEFORE:-}}"
|
||||
if [ -z "$BASE" ] || [ "$BASE" = 0000000000000000000000000000000000000000 ]; then
|
||||
BASE="$(git rev-parse HEAD^)"
|
||||
fi
|
||||
git fetch --quiet origin "$BASE"
|
||||
python3 scripts/check_generated_only_commit.py --range "$BASE..$HEAD_SHA"
|
||||
|
|
@ -12,6 +12,7 @@ on:
|
|||
- main
|
||||
paths:
|
||||
- "tools/**"
|
||||
- "scripts/**"
|
||||
- "tests/**"
|
||||
- "canon/standards/**"
|
||||
- ".forgejo/workflows/python-tests.yaml"
|
||||
|
|
@ -26,7 +27,7 @@ jobs:
|
|||
run: |
|
||||
set -eu
|
||||
apt-get update -qq >/dev/null
|
||||
apt-get install -y -qq python3 python3-pip python3-yaml \
|
||||
apt-get install -y -qq git python3 python3-pip python3-yaml \
|
||||
python3-jsonschema python3-pytest wget >/dev/null
|
||||
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
|
||||
REF="${GITHUB_SHA:-main}"; SHORT="${REF:0:7}"
|
||||
|
|
|
|||
10
Makefile
10
Makefile
|
|
@ -1,4 +1,14 @@
|
|||
# the-custodian top-level Makefile
|
||||
|
||||
.PHONY: security-flow-daily security-flow-check generated-commit-check
|
||||
security-flow-daily: ## Sync resolved decisions and notify changed actionable work
|
||||
python3 scripts/security_flow_daily.py --apply
|
||||
|
||||
security-flow-check: ## Run the deterministic security flow and generated commit tests
|
||||
python3 -m pytest tests/test_security_flow_daily.py tests/test_generated_commit_guard.py -q
|
||||
|
||||
generated-commit-check: ## Reject staged generated-only changes
|
||||
python3 scripts/check_generated_only_commit.py --staged
|
||||
#
|
||||
# Custodian agent SSH identity
|
||||
# ----------------------------
|
||||
|
|
|
|||
|
|
@ -498,9 +498,9 @@
|
|||
| task | CUST-WP-0078-T04 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
|
||||
| task | CUST-WP-0079-T01 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T02 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T03 | todo | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T03 | progress | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T04 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T05 | todo | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T05 | progress | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T06 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | CUST-WP-0079-T07 | todo | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
|
||||
| task | THE-WP-0001-T01 | done | — | workplans/THE-WP-0001-federation-interface.md |
|
||||
|
|
|
|||
34
docs/attended-window/2026-10-11-daily-red-batch.md
Normal file
34
docs/attended-window/2026-10-11-daily-red-batch.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Daily red-lane batch — 2026-10-11
|
||||
|
||||
One package for founder disposition; no per-item approval request has been sent.
|
||||
This package contains the new publication item prepared during the renewed
|
||||
critical-path run. It does not replace existing decisions or orange-window acts.
|
||||
|
||||
## PNEX-WP-0006 — publish build-mode authorization
|
||||
|
||||
Publish the accepted Custodian build-mode change rule as a Policy Nexus page
|
||||
and its canon-derived JSON artifact:
|
||||
|
||||
- `/standards/build-mode-change-authorization/v0.1/`
|
||||
- `/guardrails/build-mode-change-authorization.json`
|
||||
|
||||
Prepared implementation: `policy-nexus/tools/guardrails.py`, publication entry,
|
||||
expiry header and digest-bound release verification. Sixty tests pass. Local
|
||||
build renders 74 documents and the JSON equals the canonical YAML block.
|
||||
Native publication has not run; both tasks remain waiting.
|
||||
|
||||
Review diff: `/tmp/pnex6/reviewed.patch`; validation:
|
||||
`policy-nexus/docs/evidence/2026-10-11-build-mode-guardrail/validation.json`.
|
||||
Canon SHA-256: `7135ad8e36041f6dc9633c029109b4751be275f912cec44fc462c9f55befaab8`.
|
||||
Artifact SHA-256: `a86462a57bb1df5804d3088881af22569082d43ff68bcab2b67cbe56b6fc46cd`.
|
||||
|
||||
Abort owner: policy-nexus. Before execution, bind the release image and exact
|
||||
source commit, review the resulting native diff, and retain the prior deployment
|
||||
revision. Rollback restores that image/revision and retains immutable publication
|
||||
paths. Verify the live expiry, JSON equality, artifact digest and installed source
|
||||
revision. No new spend ceiling or credential/access scope is proposed.
|
||||
|
||||
Release remains blocked independently by an unreviewed
|
||||
`net-kingdom/canon/standards/security-layer-model_v0.9.md` source-inventory entry.
|
||||
The source owner must resolve its publication disposition before the full audit
|
||||
passes; batch acceptance cannot bypass that release check.
|
||||
9
infra/security-flow/custodian-security-flow.service
Normal file
9
infra/security-flow/custodian-security-flow.service
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[Unit]
|
||||
Description=Custodian daily security decision sync and flow report
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
WorkingDirectory=%h/the-custodian
|
||||
ExecStart=/usr/bin/python3 %h/the-custodian/scripts/security_flow_daily.py --apply
|
||||
NoNewPrivileges=yes
|
||||
UMask=0077
|
||||
10
infra/security-flow/custodian-security-flow.timer
Normal file
10
infra/security-flow/custodian-security-flow.timer
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[Unit]
|
||||
Description=Daily Custodian security flow
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 08:00:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=10m
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
12
infra/security-flow/scheduling-handoff.json
Normal file
12
infra/security-flow/scheduling-handoff.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"case_id": "case:cross-owner:CUST-WP-0079-T03:activity-core",
|
||||
"scenario_type": "cross_owner_wait",
|
||||
"beneficiary_workplan_id": "CUST-WP-0079",
|
||||
"beneficiary_task_id": "CUST-WP-0079-T03",
|
||||
"obligor_repo": "activity-core",
|
||||
"obligor_workplan_id": null,
|
||||
"state": "waiting",
|
||||
"reason": "Awaiting schedule owner acceptance and named non-residual owner task; workstation user service bus unavailable. No founder act requested.",
|
||||
"proposal_message_id": "117e538e-da06-48b8-ba68-b82300a832e7",
|
||||
"progress_receipt_id": "8edea261-4841-4db7-bc0e-1db8021106a6"
|
||||
}
|
||||
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()
|
||||
49
tests/test_generated_commit_guard.py
Normal file
49
tests/test_generated_commit_guard.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import importlib.util
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
SCRIPT = Path(__file__).resolve().parents[1] / "scripts/check_generated_only_commit.py"
|
||||
spec = importlib.util.spec_from_file_location("guard", SCRIPT)
|
||||
guard = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(guard)
|
||||
|
||||
|
||||
def test_source_change_is_required_with_generated_indexes():
|
||||
assert guard.standalone(["WORK-RECORDS.md", "docs/publication-index.md"])
|
||||
assert guard.standalone(["docs/source-inventory.json", "generated/index.json"])
|
||||
assert not guard.standalone(["WORK-RECORDS.md", "scripts/issuer.py"])
|
||||
assert not guard.standalone([])
|
||||
assert not guard.standalone(["canon/standards/work-record-types_v0.1.md"])
|
||||
|
||||
|
||||
def test_evidence_packets_and_generated_brief_need_source_change():
|
||||
assert guard.standalone(["docs/evidence/run/receipt.json", ".custodian-brief.md"])
|
||||
assert guard.standalone(["docs/decision-packets/daily-batch.md"])
|
||||
assert guard.standalone(["docs/decision-packets-2026-10-03.md"])
|
||||
assert guard.standalone(["docs/attended-window/2026-10-11-daily-red-batch.md"])
|
||||
assert guard.standalone(["docs/evidence-packets/live-proof.md", "docs/source-index.md"])
|
||||
assert not guard.standalone(["docs/evidence/run/receipt.json", "scripts/verify_live.py"])
|
||||
assert not guard.standalone(["docs/implementation/delivery-protocol.md"])
|
||||
assert not guard.standalone(["docs/decisions/authorization.md"])
|
||||
|
||||
|
||||
def test_ci_checks_each_commit_in_range(tmp_path):
|
||||
def git(*args):
|
||||
return subprocess.check_output(["git", *args], cwd=tmp_path).decode().strip()
|
||||
git("init", "-q")
|
||||
git("config", "user.name", "Test")
|
||||
git("config", "user.email", "test@example.test")
|
||||
(tmp_path / "source.py").write_text("source\n")
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "source")
|
||||
base = git("rev-parse", "HEAD")
|
||||
(tmp_path / "WORK-RECORDS.md").write_text("generated\n")
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "generated only")
|
||||
(tmp_path / "source.py").write_text("changed\n")
|
||||
git("add", ".")
|
||||
git("commit", "-qm", "source again")
|
||||
result = subprocess.run(["python3", str(SCRIPT), "--range", base + "..HEAD"],
|
||||
cwd=tmp_path, capture_output=True, text=True)
|
||||
assert result.returncode == 1
|
||||
assert "Generated-only commits rejected" in result.stderr
|
||||
80
tests/test_security_flow_daily.py
Normal file
80
tests/test_security_flow_daily.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
SCRIPTS = Path(__file__).resolve().parents[1] / "scripts"
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import security_flow_daily as daily
|
||||
import security_flow_report as flow
|
||||
|
||||
|
||||
def test_notifications_compare_entire_actionable_lists():
|
||||
report = {"READY": [{"id": "T1", "status": "todo"}], "UNBLOCKED": [], "ATTENDED": []}
|
||||
first = daily.notifications(report, {}, "coding", "human")
|
||||
assert first == [("agent", "coding", {"READY": report["READY"], "UNBLOCKED": []})]
|
||||
assert daily.notifications(report, {"agent": first[0][2]}, "coding", "human") == []
|
||||
report["READY"] = []
|
||||
assert daily.notifications(report, {"agent": first[0][2]}, "coding", "human")
|
||||
|
||||
|
||||
def test_attended_goes_only_to_founder_and_is_daily():
|
||||
report = {"READY": [], "UNBLOCKED": [], "ATTENDED": [{"id": "T2"}]}
|
||||
state = {"agent": {"READY": [], "UNBLOCKED": []}}
|
||||
assert daily.notifications(report, state, "coding", "human") == [
|
||||
("founder", "human", {"ATTENDED": report["ATTENDED"]})]
|
||||
|
||||
|
||||
def test_atomic_checkpoint_retains_prior_recipient(tmp_path):
|
||||
checkpoint = tmp_path / "delivery.json"
|
||||
daily.save(checkpoint, {"agent_message_id": "receipt-1"})
|
||||
assert checkpoint.read_text().find("receipt-1") >= 0
|
||||
assert not checkpoint.with_suffix(".json.tmp").exists()
|
||||
|
||||
|
||||
def test_retry_does_not_replay_successful_agent_delivery(tmp_path, monkeypatch):
|
||||
report = {"READY": [{"id": "T1"}], "UNBLOCKED": [], "ATTENDED": [{"id": "T2"}]}
|
||||
monkeypatch.setattr(sys, "argv", ["security_flow_daily", "--apply", "--state-dir", str(tmp_path)])
|
||||
commands = []
|
||||
monkeypatch.setattr(daily.subprocess, "run", lambda command, **kw: commands.append(command))
|
||||
monkeypatch.setattr(daily.subprocess, "check_output", lambda *a, **kw: json.dumps(report))
|
||||
sent = []
|
||||
def fail_founder(hub, recipient, payload):
|
||||
sent.append(recipient)
|
||||
if recipient == "human":
|
||||
raise OSError("hub interrupted")
|
||||
return "agent-receipt"
|
||||
monkeypatch.setattr(daily, "post", fail_founder)
|
||||
with pytest.raises(OSError):
|
||||
daily.main()
|
||||
assert "--apply" in commands[0]
|
||||
assert json.loads((tmp_path / "delivery.json").read_text())["agent_message_id"] == "agent-receipt"
|
||||
monkeypatch.setattr(daily, "post", lambda hub, recipient, payload: sent.append(recipient) or "receipt")
|
||||
daily.main()
|
||||
assert sent == ["the-custodian", "human", "human"]
|
||||
|
||||
|
||||
def test_sync_failure_prevents_notifications(tmp_path, monkeypatch):
|
||||
monkeypatch.setattr(sys, "argv", ["security_flow_daily", "--apply", "--state-dir", str(tmp_path)])
|
||||
def fail_sync(*args, **kwargs):
|
||||
raise daily.subprocess.CalledProcessError(1, args[0])
|
||||
monkeypatch.setattr(daily.subprocess, "run", fail_sync)
|
||||
with pytest.raises(daily.subprocess.CalledProcessError):
|
||||
daily.main()
|
||||
assert not (tmp_path / "delivery.json").exists()
|
||||
|
||||
|
||||
def test_report_classifies_terminal_unknown_and_human_dependencies():
|
||||
def task(tid, status, deps=(), human=False, flavor="implementation"):
|
||||
return {"id": tid, "status": status, "deps": deps, "human": human,
|
||||
"blocked_on": "", "priority": "high",
|
||||
"wp": {"status": "active", "flavor": flavor}}
|
||||
tasks = {"X-T01": task("X-T01", "done"),
|
||||
"X-T02": task("X-T02", "wait", ["X-T01"]),
|
||||
"X-T03": task("X-T03", "wait", ["missing"]),
|
||||
"X-T04": task("X-T04", "wait", ["X-T01"], human=True),
|
||||
"Y-T01": task("Y-T01", "todo", flavor="residual")}
|
||||
report = flow.classify(tasks)
|
||||
assert [t["id"] for t in report["UNBLOCKED"]] == ["X-T02"]
|
||||
assert [t["id"] for t in report["ATTENDED"]] == ["X-T04"]
|
||||
assert not report["READY"]
|
||||
|
|
@ -59,9 +59,11 @@ resolved or accepted).
|
|||
|
||||
```task
|
||||
id: CUST-WP-0079-T03
|
||||
status: todo
|
||||
status: wait
|
||||
priority: high
|
||||
state_hub_task_id: "1be0569c-221d-5925-a0ff-fd2af88e52b1"
|
||||
blocked_on: message-from:activity-core
|
||||
blocking_reason: "Awaiting daily schedule owner acceptance; workstation user service bus unavailable. Handoff 117e538e-da06-48b8-ba68-b82300a832e7."
|
||||
```
|
||||
|
||||
Run `sync_resolved_decisions.py --apply` and `security_flow_report.py --json` daily (activity-core
|
||||
|
|
@ -69,6 +71,17 @@ schedule, or a workstation timer until activity-core has the lane). Post a hub m
|
|||
coding agent only when UNBLOCKED or READY changed, and to the founder only when ATTENDED is
|
||||
non-empty. Agents must not poll wait tasks themselves.
|
||||
|
||||
Implementation (2026-10-11): `scripts/security_flow_daily.py` runs both commands, includes
|
||||
`the-custodian` in the default security estate, checkpoints each delivered recipient, and
|
||||
notifies the coding agent only when READY/UNBLOCKED changes. Empty ATTENDED lists never
|
||||
message the founder. User timer units are in `infra/security-flow/`; `make security-flow-check`
|
||||
passes nine tests including interrupted delivery and retry. Live hub dry-run succeeds.
|
||||
Schedule activation handoff `117e538e-da06-48b8-ba68-b82300a832e7` to `activity-core`
|
||||
is attached to `case:cross-owner:CUST-WP-0079-T03:activity-core`
|
||||
(`infra/security-flow/scheduling-handoff.json`); no owner acceptance or foreign dependency is
|
||||
invented. Schedule activation remains pending: workstation `systemctl --user` cannot connect to its
|
||||
user bus (`No data available`), so checked-in units alone do not establish daily execution.
|
||||
|
||||
## Encode the standing change rule as a policy-nexus guardrail
|
||||
|
||||
```task
|
||||
|
|
@ -87,7 +100,7 @@ Canon source written 2026-10-11: `canon/standards/build-mode-change-authorizatio
|
|||
|
||||
```task
|
||||
id: CUST-WP-0079-T05
|
||||
status: todo
|
||||
status: progress
|
||||
priority: medium
|
||||
state_hub_task_id: "8a3dc219-8982-5903-9407-e1b549d4544c"
|
||||
```
|
||||
|
|
@ -97,6 +110,14 @@ regenerated by a `make` target in a pre-commit hook or a Forgejo Action, not by
|
|||
Start with the security repos. Done when a week of git log shows no standalone index-only commits
|
||||
from agents.
|
||||
|
||||
Implementation (2026-10-11): `scripts/check_generated_only_commit.py` checks staged files
|
||||
or every commit in a revision range. Forgejo workflow `generated-commit-guard.yaml` rejects
|
||||
standalone WORK-RECORDS, source-inventory, publication-index, projection-refresh,
|
||||
generated briefs and explicit evidence/decision-packet commits regardless of author; a later source commit cannot hide an earlier
|
||||
generated-only commit. `make generated-commit-check` provides the local hook entry point.
|
||||
The CI suite covers the rejection using a real temporary git repository. Consumer generator
|
||||
adoption and the required week of clean logs remain open; this task is not claimed complete.
|
||||
|
||||
## Attended window #1
|
||||
|
||||
```task
|
||||
|
|
@ -112,10 +133,16 @@ state_hub_task_id: "e3da807f-9a6e-513f-82dc-3714a489e7bf"
|
|||
|
||||
```task
|
||||
id: CUST-WP-0079-T07
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "91ec924c-b305-5a63-925d-087a3b927d60"
|
||||
```
|
||||
|
||||
Replace the Sol goal with the renewed text in the assessment history file (section "Renewed
|
||||
goal"). Re-engage now for the READY lane. It does not depend on T06.
|
||||
|
||||
Completed 2026-10-11: the renewed critical-path goal is active in thread
|
||||
`01a126e1-76f8-7643-8cf3-e93336539ade`; its attached text was read before
|
||||
execution. Work resumed from READY/UNBLOCKED, with three workers pinned to
|
||||
eligible workplans and the accepted build-mode authorization in force. This
|
||||
closes re-engagement only, not the live critical-path goal.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue