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:
+27
-3
@@ -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})")
|
||||
|
||||
Reference in New Issue
Block a user