feat(pmo): ping-charles SMS script (curl SMTP relay, offline queue, 0600 config)
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
# TASK: FINAL crush turn — harness runtime container + tool-widening (Redmine 521 + bootstrap)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Charles 2026-08-29: "alpha asap, then kill the screen/crush stack; bootstrap
|
||||||
|
the harness (docker container) FIRST." This is the LAST crush turn on the
|
||||||
|
harness repo. After it: the loop runs containerized AND its turns can do
|
||||||
|
build work — the self-hosted stack builds everything to beta/prod itself.
|
||||||
|
|
||||||
|
## Part A — runtime container (bootstrap)
|
||||||
|
1. `deploy/Dockerfile.runtime` — FROM alpine (digest-pinned), copies the
|
||||||
|
static release binary, dumb-init or tini entrypoint (or Go signal
|
||||||
|
handling — SIGINT graceful stop already exists).
|
||||||
|
2. `deploy/compose.yaml` — service `mopac-loop`: image built from
|
||||||
|
Dockerfile.runtime, `restart: unless-stopped`, env-file (0600) for
|
||||||
|
HARNESS_REDMINE_KEY / HARNESS_LITELLM_KEY, bind mounts: repo reports/
|
||||||
|
+ state/ (persistent), harness.toml read-only. Healthcheck: loop
|
||||||
|
exposes nothing by default — add `--healthz-port` flag or reuse events
|
||||||
|
healthz; simplest: a `loop --status-file` heartbeat the healthcheck
|
||||||
|
reads (age < 3x poll interval).
|
||||||
|
3. Verify LIVE: compose up, confirm scan + at least one autonomous turn
|
||||||
|
(the alpha queue has open tasks), `docker inspect` restart policy,
|
||||||
|
then leave the container RUNNING (it replaces the dev.sh screen).
|
||||||
|
4. Kill switch documented: `docker compose -f deploy/compose.yaml down`.
|
||||||
|
|
||||||
|
## Part B — tool-widening (ticket 521: loop turns do build work)
|
||||||
|
1. File tools behind the existing gate discipline (deny-first, scoped to
|
||||||
|
work_root, logged): read (offset/limit), exact-match edit, write (new
|
||||||
|
files), ls/glob/grep. Same denial-count + feedback semantics as the
|
||||||
|
bash tool.
|
||||||
|
2. Bash gate: verify `./dev.sh {build,vet,test}` and `git commit/push`
|
||||||
|
pass the allow-list from work_root; extend the allow-list minimally if
|
||||||
|
not (no broadening beyond dev.sh + git).
|
||||||
|
3. Per-turn wall-clock timeout (config `[loop] turn_timeout_secs`,
|
||||||
|
default 1800) — expired turn = partial REPORT + release.
|
||||||
|
4. Tests: file-tool gate denials, edit exact-match semantics, timeout
|
||||||
|
expiry, and an end-to-end loop turn against the fake Redmine that
|
||||||
|
edits a file + runs a no-op command + REPORTs.
|
||||||
|
|
||||||
|
## Discipline
|
||||||
|
TDD red/green, docker dev builder only, atomic conventional commits,
|
||||||
|
push origin main, NEVER broad pkill (docker stop by name is fine).
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
`REPORT-20260829-1500-bootstrap.md` in `~/.coordinate/inbox-pmo/`:
|
||||||
|
container instructions (up/down/logs), healthcheck design, tool surface
|
||||||
|
added, test results, and confirmation the container processed a real
|
||||||
|
alpha task autonomously.
|
||||||
@@ -87,3 +87,4 @@
|
|||||||
2026-08-29T06:19:29-05:00 | PMO | 497 corrected: BW machine accounts map to CLOUDRON accounts (per-vertical API identities), not raw Linux accounts
|
2026-08-29T06:19:29-05:00 | PMO | 497 corrected: BW machine accounts map to CLOUDRON accounts (per-vertical API identities), not raw Linux accounts
|
||||||
2026-08-29T06:20:17-05:00 | PMO | identity model ticket filed (linux runtime + cloudron acting, N per account); 497 corrected
|
2026-08-29T06:20:17-05:00 | PMO | identity model ticket filed (linux runtime + cloudron acting, N per account); 497 corrected
|
||||||
2026-08-29T06:21:06-05:00 | PMO | identity model FINAL: 1:1 linux->BW vault (cloudron+3rd-party entries); acting identity selected per task/turn, stamped on all actions + usage
|
2026-08-29T06:21:06-05:00 | PMO | identity model FINAL: 1:1 linux->BW vault (cloudron+3rd-party entries); acting identity selected per task/turn, stamped on all actions + usage
|
||||||
|
2026-08-29T06:26:47-05:00 | PMO | ALPHA LIVE: loop autonomous (520 done New->Resolved+note, 519 running); q12 killed; q14 CLI keeps running; tool-widening ticket filed Immediate
|
||||||
|
|||||||
@@ -8,3 +8,30 @@ harness: loop: status New -> Resolved on #520
|
|||||||
harness: loop: noted REPORT on #520
|
harness: loop: noted REPORT on #520
|
||||||
harness: loop: dispatch 519 (updated 2026-08-29T06:23:26-05:00): "Alpha: draft briefing v0 format as note on 495"
|
harness: loop: dispatch 519 (updated 2026-08-29T06:23:26-05:00): "Alpha: draft briefing v0 format as note on 495"
|
||||||
harness: task 519 (redmine): "Alpha: draft briefing v0 format as note on 495" class=study -> mopac-study -> glm-4.7-flash
|
harness: task 519 (redmine): "Alpha: draft briefing v0 format as note on 495" class=study -> mopac-study -> glm-4.7-flash
|
||||||
|
harness: REPORT reports/REPORT-demo-519-20260829-112832.md
|
||||||
|
harness: loop: status transition failed for 519: redmine HTTP 502: <!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||||
|
|
||||||
|
<!-- generated from dashboard/public/img/app_down.svg -->
|
||||||
|
<link rel="icon" href="da...
|
||||||
|
harness: loop: note writeback failed for 519: redmine HTTP 502: <!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||||
|
|
||||||
|
<!-- generated from dashboard/public/img/app_down.svg -->
|
||||||
|
<link rel="icon" href="da...
|
||||||
|
harness: loop: refresh failed for 519: redmine HTTP 502: <!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||||
|
|
||||||
|
<!-- generated from dashboard/public/img/app_down.svg -->
|
||||||
|
<link rel="icon" href="da... (a re-dispatch may follow)
|
||||||
|
harness: loop: dispatch 518 (updated 2026-08-29T06:23:26-05:00): "Alpha: triage open MOPAC tickets — next-action note on each New ticket"
|
||||||
|
harness: task 518 (redmine): "Alpha: triage open MOPAC tickets — next-action note on each New ticket" class=study -> mopac-study -> glm-4.7-flash
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
I have full context: sibling pattern (mopac-bitwarden-go), harness fake-server test style, PMO python glue to replace, creds at `~/.creds/redmine.env`, Redmine at projects.knownelement.com. Setting up the work plan.Toolchain verified. Creating the repo via Gitea API (idempotent check first):Repo created. Cloning and seeding with LICENSE:Harness done. Now TDD config package — tests first (red):Red confirmed. Implementation:Green. Two atomic commits:
|
I have full context: sibling pattern (mopac-bitwarden-go), harness fake-server test style, PMO python glue to replace, creds at `~/.creds/redmine.env`, Redmine at projects.knownelement.com. Setting up the work plan.Toolchain verified. Creating the repo via Gitea API (idempotent check first):Repo created. Cloning and seeding with LICENSE:Harness done. Now TDD config package — tests first (red):Red confirmed. Implementation:Green. Two atomic commits:Now the core piece. First the stateful fake Redmine server (test infrastructure, harness-style):Now the library test suite (red first):
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
[info 08-29 06:29 MOPAC] alpha loop live, bootstrap turn dispatched, CLI building
|
||||||
Executable
+50
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ping-charles.sh — SMS Charles via T-Mobile email-to-SMS gateway.
|
||||||
|
# Usage: ping-charles.sh "short message" [severity]
|
||||||
|
# Relay: curl SMTP (Cloudron mail submission). Config: /home/reachableceo/.coordinate/secrets/ping-charles.env (0600)
|
||||||
|
# PING_SMTP_HOST=mail.knownelement.com:587
|
||||||
|
# PING_SMTP_USER=...
|
||||||
|
# PING_SMTP_PASS=...
|
||||||
|
# PING_FROM=...
|
||||||
|
# Until wired, messages queue in ~/.coordinate/outbox/ping-queue.txt (flushed on first successful send).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
DEST="8182807059@tmomail.net"
|
||||||
|
CONF="$HOME/.coordinate/secrets/ping-charles.env"
|
||||||
|
QUEUE="$HOME/.coordinate/outbox/ping-queue.txt"
|
||||||
|
MSG="${1:?usage: ping-charles.sh \"message\" [severity]}"
|
||||||
|
SEV="${2:-info}"
|
||||||
|
STAMP="$(date '+%m-%d %H:%M')"
|
||||||
|
LINE="[$SEV $STAMP MOPAC] $MSG"
|
||||||
|
|
||||||
|
if [ ! -f "$CONF" ]; then
|
||||||
|
mkdir -p "$(dirname "$QUEUE")"
|
||||||
|
echo "$LINE" >> "$QUEUE"
|
||||||
|
echo "ping queued (no relay config): $LINE" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
set -a; . "$CONF"; set +a
|
||||||
|
|
||||||
|
send_one() {
|
||||||
|
printf 'From: %s\nTo: %s\nSubject: MOPAC %s\nContent-Type: text/plain; charset=UTF-8\n\n%s\n' \
|
||||||
|
"$PING_FROM" "$DEST" "$SEV" "$1" \
|
||||||
|
| curl -sS --max-time 30 --url "smtp://$PING_SMTP_HOST" \
|
||||||
|
--mail-from "$PING_FROM" --mail-rcpt "$DEST" \
|
||||||
|
-u "$PING_SMTP_USER:$PING_SMTP_PASS" \
|
||||||
|
-T - && return 0 || return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "$QUEUE" ] && [ -s "$QUEUE" ]; then
|
||||||
|
while IFS= read -r queued; do
|
||||||
|
send_one "$queued" && sed -i '1d' "$QUEUE" || break
|
||||||
|
done < /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if send_one "$LINE"; then
|
||||||
|
echo "sms sent: $LINE" >&2
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
mkdir -p "$(dirname "$QUEUE")"; echo "$LINE" >> "$QUEUE"
|
||||||
|
echo "sms send FAILED, queued: $LINE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
Reference in New Issue
Block a user