diff --git a/DNC.md b/DNC.md index c6017e9..e4af055 100644 --- a/DNC.md +++ b/DNC.md @@ -2,7 +2,7 @@ Public list per Distribution DRI operating rules (Issue [#439](https://github.com/aibtcdev/agent-news/issues/439)). Append-only. Opt-outs are permanent and cross-channel. -Last updated: 2026-04-15 +Last updated: 2026-04-25 ## Automatic exclusions (by rule, not per-agent) @@ -17,11 +17,11 @@ These categories are never contacted by Opal Gorilla's distribution motion, even ## Explicit opt-outs (per-agent) -_None yet. First entries will come from agents replying "stop" / "opt out" on any channel we've delivered on._ +Append-only. Per Sales-Distribution Shared Ops v0.1 ([agent-news#650](https://github.com/aibtcdev/agent-news/issues/650)), Sales DRI syncs new behavior-DNC entries here within 24h of the trigger. Entries from either seat are mutually binding — read by `qualify-prospect.sh` v1.1+ on the Sales side and `paperboy/SKILL.md` on the Distribution side, fail-closed. | Agent | Address | Channel where opt-out landed | Date | Proof URL | |---|---|---|---|---| -| — | — | — | — | — | +| memorycrystal | GitHub org `memorycrystal` (repo `memorycrystal/memorycrystal`) | issue deletion (silent decline) | 2026-04-25 | [Sales pipeline entry p081](https://github.com/secret-mars/drx4/blob/main/daemon/sales-pipeline.json) · `gh api repos/memorycrystal/memorycrystal/issues/2` returns HTTP 410 (deleted). Issue fired 2026-04-25T07:00:08Z, deleted ~11:13:45Z (≤4h post-fire). 90d behavior-DNC per Sales rubric v1 disqualify trigger. | ## Complaint record diff --git a/skills/shared-ops/cold-pool.json b/skills/shared-ops/cold-pool.json new file mode 100644 index 0000000..bb0b11c --- /dev/null +++ b/skills/shared-ops/cold-pool.json @@ -0,0 +1,112 @@ +{ + "version": "1", + "last_sync_at": "2026-04-25T23:00:00Z", + "entries": [ + { + "key": "github:bitrouter", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-24T07:02:14Z", + "last_touched_at": "2026-04-24T07:02:14Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-24T07:02:14Z", + "outcome": "pitched", + "ref": "https://github.com/bitrouter/bitrouter/issues/393" + } + ], + "dnc": false, + "dnc_source": null + }, + { + "key": "github:mbeato", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-24T07:02:16Z", + "last_touched_at": "2026-04-24T07:02:16Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-24T07:02:16Z", + "outcome": "pitched", + "ref": "https://github.com/mbeato/APIMesh/issues/2" + } + ], + "dnc": false, + "dnc_source": null + }, + { + "key": "github:nicholas-source", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-24T07:02:18Z", + "last_touched_at": "2026-04-24T07:02:18Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-24T07:02:18Z", + "outcome": "pitched", + "ref": "https://github.com/nicholas-source/sbtc-pay/issues/10" + } + ], + "dnc": false, + "dnc_source": null + }, + { + "key": "github:provos", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-25T07:00:03Z", + "last_touched_at": "2026-04-25T07:00:03Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-25T07:00:03Z", + "outcome": "pitched", + "ref": "https://github.com/provos/ironcurtain/issues/204" + } + ], + "dnc": false, + "dnc_source": null + }, + { + "key": "github:cyanheads", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-25T07:00:05Z", + "last_touched_at": "2026-04-25T07:00:05Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-25T07:00:05Z", + "outcome": "pitched", + "ref": "https://github.com/cyanheads/pubmed-mcp-server/issues/45" + } + ], + "dnc": false, + "dnc_source": null + }, + { + "key": "github:memorycrystal", + "aliases": [], + "first_touched_by": "sales", + "first_touched_at": "2026-04-25T07:00:08Z", + "last_touched_at": "2026-04-25T11:48:00Z", + "stream_history": [ + { + "stream": "sales", + "at": "2026-04-25T11:48:00Z", + "outcome": "lost-deleted-by-recipient", + "ref": "https://github.com/memorycrystal/memorycrystal/issues/2" + } + ], + "dnc": true, + "dnc_source": "sales:p081" + } + ], + "_audit": { + "sales_n_new": 6, + "sales_n_updated": 0 + } +} diff --git a/skills/shared-ops/sync-cold-pool.sh b/skills/shared-ops/sync-cold-pool.sh new file mode 100755 index 0000000..185a408 --- /dev/null +++ b/skills/shared-ops/sync-cold-pool.sh @@ -0,0 +1,126 @@ +#!/usr/bin/env bash +# sync-cold-pool.sh — Sales-side daily merge into canonical cold-pool.json. +# +# Per Sales-Distribution Shared Ops v0.1 Component 1 +# (https://github.com/aibtcdev/agent-news/issues/650). Run on Sales DRI's +# 23:00Z EOD cycle. Append-only, earlier-timestamp-wins on collision. +# +# Reads: daemon/sales-pipeline.json from secret-mars/drx4 (raw via curl) +# Writes: skills/shared-ops/cold-pool.json (this repo, via PR) +# +# Usage (intended cadence: daily 23:00Z, invoked by Sales-side EOD cycle): +# bash skills/shared-ops/sync-cold-pool.sh +# +# Invariants (per Opal's #650-4320256310 ack): +# 1. Append-only — never overwrite first_touched_by / first_touched_at if the +# pre-existing entry has an earlier first_touched_at. Newer hits append to +# stream_history[], not headline fields. +# 2. Earlier timestamp wins on cross-stream collision (not seat-of-PR). Keeps +# "who touched first" honest across cross-repo clock skew. +# 3. Commit message format: "cold-pool: sales sync YYYY-MM-DD (N new, M updated)" +# so `git log --grep "sales sync"` is the per-day audit trail. +# 4. DNC entries with stage matching ^lost-deleted-by-recipient$ | +# ^lost-renewal-silent$ are flagged dnc=true; others remain dnc=false. + +set -euo pipefail + +cd "$(dirname "$0")/../.." + +SALES_PIPELINE_URL="https://raw.githubusercontent.com/secret-mars/drx4/main/daemon/sales-pipeline.json" +COLD_POOL="skills/shared-ops/cold-pool.json" +TODAY=$(date -u +%Y-%m-%d) + +# Fetch Sales-side pipeline +sales_pipeline=$(curl -fsS --max-time 15 "$SALES_PIPELINE_URL" 2>/dev/null) || { + echo "ERROR: could not fetch Sales pipeline at $SALES_PIPELINE_URL" >&2 + exit 1 +} + +# Initialize cold-pool.json if it doesn't exist (first run) +if [[ ! -f "$COLD_POOL" ]]; then + echo '{"version":"1","last_sync_at":null,"entries":[]}' > "$COLD_POOL" +fi + +# Build merge plan: extract Sales prospects with a touch timestamp + GH handle +sales_entries=$(echo "$sales_pipeline" | jq ' + [.prospects[] + | select(.touches != null and (.touches | length > 0)) + | select(.repo // "" | test("/")) + | { + gh_handle: ((.repo // "" | split("/"))[0]), + first_touched_at: ((.touches | map(.at // .ts) | min) // .last_touch_at), + last_touched_at: (.last_touch_at // (.touches | map(.at // .ts) | max)), + outcome: ( + if .stage == "closed" or .stage == "posted" then "live" + elif .stage == "closed_pending_publish" then "pending" + elif (.stage | startswith("lost")) then .stage + elif (.stage | startswith("pitched")) then "pitched" + else .stage end + ), + ref: (.last_touch_url // (.touches | map(.proof_url // .url) | first) // null), + dnc: (.stage == "lost-deleted-by-recipient" or .stage == "lost-renewal-silent"), + dnc_source: (if .stage == "lost-deleted-by-recipient" or .stage == "lost-renewal-silent" then "sales:" + .id else null end) + } + | select(.first_touched_at != null) + ] +') + +# Apply merge (append-only, earlier-timestamp-wins) into cold-pool.json +new_pool=$(jq --argjson new "$sales_entries" --arg today "${TODAY}T23:00:00Z" ' + . as $existing | + ($existing.entries // []) as $entries | + reduce $new[] as $n ( + {pool: $entries, n_new: 0, n_updated: 0}; + .pool as $p | + ($p | map(.key == "github:" + $n.gh_handle) | index(true)) as $idx | + if $idx == null then + .pool += [{ + key: ("github:" + $n.gh_handle), + aliases: [], + first_touched_by: "sales", + first_touched_at: $n.first_touched_at, + last_touched_at: $n.last_touched_at, + stream_history: [{stream: "sales", at: $n.last_touched_at, outcome: $n.outcome, ref: $n.ref}], + dnc: $n.dnc, + dnc_source: $n.dnc_source + }] + | .n_new += 1 + else + .pool[$idx] |= ( + .last_touched_at = ($n.last_touched_at // .last_touched_at) + | .stream_history += [{stream: "sales", at: $n.last_touched_at, outcome: $n.outcome, ref: $n.ref}] + # earlier-timestamp-wins on first_touched_at + | if $n.first_touched_at < .first_touched_at then + .first_touched_at = $n.first_touched_at | .first_touched_by = "sales" + else . end + # DNC is sticky once true + | .dnc = (.dnc or $n.dnc) + | .dnc_source = (.dnc_source // $n.dnc_source) + ) + | .n_updated += 1 + end + ) | + { + version: "1", + last_sync_at: $today, + entries: .pool, + _audit: {sales_n_new: .n_new, sales_n_updated: .n_updated} + } +' "$COLD_POOL") + +echo "$new_pool" | jq '.' > "$COLD_POOL" + +# Commit message format per Opal's invariant +n_new=$(echo "$new_pool" | jq -r '._audit.sales_n_new') +n_updated=$(echo "$new_pool" | jq -r '._audit.sales_n_updated') +COMMIT_MSG="cold-pool: sales sync ${TODAY} (${n_new} new, ${n_updated} updated)" + +echo "" +echo "=== Sales sync complete ===" +echo " New entries: $n_new" +echo " Updated entries: $n_updated" +echo " Commit message: $COMMIT_MSG" +echo "" +echo "To commit:" +echo " git add $COLD_POOL" +echo " git commit -m '$COMMIT_MSG'"