fix: placement verification and sidechat policy enforcement

- dm.py: placement-aware verify (verify_placement), checked_uuid logging, placement_mismatch events
- main-chat-watchdog.py: P0 alerts on placement_mismatch
- box-ctl.py: notify routes to sidechat, box policy command
- jobs: ops-audit and pipe-demo use sidechats
- response-harvester.py: chain deduplication

Session: sidechat/ops-restore
This commit is contained in:
operator-main
2026-10-04 18:29:13 +00:00
parent ae1bf17de5
commit 550e30901b
14 changed files with 1122 additions and 47 deletions
+27 -3
View File
@@ -9,6 +9,10 @@ Reads /home/super/Projects/NetVM/jobs/<job_name>.json,
renders the prompt template, sends DM via dm.py, logs to job-log.jsonl.
Part of the JOB system (see docs/JOB-SPEC.md).
Sidechat-first policy (2026-10-04): a job that resolves to target "main"
without explicit opt-in fails loudly instead of saturating main threads.
Opt in via job JSON "allow_main_chat": true, or --allow-main-chat.
"""
import sys
@@ -202,9 +206,11 @@ def build_followup_tags(followup):
return args
def send_dm(agent, target, message, dry_run=False, followup_tags=None):
def send_dm(agent, target, message, dry_run=False, followup_tags=None,
allow_main_chat=False):
"""Send DM via dm.py. followup_tags: flat ['--tag', 'k=v', ...] list
from build_followup_tags(), or None."""
from build_followup_tags(), or None. allow_main_chat passes the explicit
main-chat opt-in through to dm.py (sidechat-first policy)."""
if dry_run:
print(f"[DRY RUN] Would send to {agent} ({target}):")
if followup_tags:
@@ -218,6 +224,7 @@ def send_dm(agent, target, message, dry_run=False, followup_tags=None):
cmd = ([str(DM_PY), "send", "--agent", "opm", "--to", agent,
"--target", target]
+ (["--allow-main-chat"] if (target == "main" and allow_main_chat) else [])
+ (followup_tags or []) + [message])
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
@@ -286,6 +293,8 @@ def main():
help="Pipeline run ID if running as part of a pipeline")
p.add_argument("--step-n", type=int, default=int(os.environ.get("CHAIN_STEP_N", "1")),
help="Step sequence number in the pipeline")
p.add_argument("--allow-main-chat", action="store_true",
help="Explicit opt-in: allow this job to dispatch to main chat (refused by default per sidechat-first policy)")
args = p.parse_args()
job_name = args.job_name
@@ -370,6 +379,20 @@ def main():
sc_name = render_prompt(sc_cfg.get("name_template", "job-{job_name}-{date}"), variables)
target = sc_name
# Sidechat-first policy (2026-10-04): refuse to dispatch to main chat
# unless the job explicitly opts in. Never fall back to main silently.
allow_main = bool(job.get("allow_main_chat")) or args.allow_main_chat
if target == "main" and not allow_main:
print(f"ERROR: Job '{job_name}' resolves to main chat; refusing by sidechat-first policy. "
f"Set a sidechat target (dm_target/target/sidechat.create) in the job JSON, "
f"set \"allow_main_chat\": true, or pass --allow-main-chat.", file=sys.stderr)
log_event("job_failed", {
"job_id": job_id,
"error": "main_chat_blocked_by_policy",
"pipeline_run_id": pipeline_run_id,
})
sys.exit(1)
# Log job_sent
log_event("job_sent", {
"job_id": job_id,
@@ -383,7 +406,8 @@ def main():
# Dispatch via dm.py (handles main or sidechat with auto-provisioning and verification)
msg_id = send_dm(agent, target, dm_message,
dry_run=dry_run, followup_tags=followup_tags)
dry_run=dry_run, followup_tags=followup_tags,
allow_main_chat=allow_main)
if msg_id and not dry_run:
print(f"Dispatched job {job_id} to {agent}/{target} (DM: {msg_id})")