executor-config: replace v1 send-mailbox-message.sh with v2 (production, zero placeholders)

Per CEO approval 2026-10-04. Option (a): overwrite v1 with v2.

Changes:
* send-mailbox-message.sh - production implementation with zero
  placeholders. Real rail callbacks via urllib.request POST to
  submit_request. EXECUTOR_API_KEY in Authorization header only.
* delivery-pack.sh - informational template (main script
  generates per-recipient copies inline).
* SKILL.md - v2 spec (what's new + what's the same).
* v2-vs-v1-diff.md - side-by-side diff.
* README.md - bundle index.

Honours brief locked decisions [1]:
- Decision 2: outbox canonical; inbox mirrored (own inbox only).
- Decision 6: mailbox writes AUTO_RUN per recipient.
- Decision 7: append-only inheritance; this skill is additive.
- Decision 8: naming convention coreci-<role> validated at parse.
- Decision 9: extend, don't duplicate.

Does NOT (per brief):
- Edit coreci/agent-definitions/ (read-only per Rule #7).
- Implement deny_read_targets (xfail; out of scope).
- Classify mailbox writes as NEEDS_APPROVAL.
- Write to anyone else's outbox/.
- Provide cross-repo atomicity (per-recipient only).
This commit is contained in:
coreci-cairo
2026-10-04 18:07:31 +00:00
parent 4bd4f2b945
commit 1f07737bf5
5 changed files with 332 additions and 195 deletions
+22 -45
View File
@@ -1,62 +1,39 @@
# send-mailbox-message — skill bundle
# send-mailbox-message v2 — production-ready
## Files
- `SKILL.md` — human + LLM contract (inputs, outputs, gates, atomicity model)
- `send-mailbox-message.sh` — reference shell implementation (sender-side orchestration)
- `delivery-pack.sh` — template for per-recipient delivery pack (target = recipient's inbox)
- `tests-T18-T30.md` — test matrix
- `SKILL.md` — human + LLM contract (v2 spec)
- `send-mailbox-message.sh` — production shell implementation
(zero placeholders; real rail callbacks)
- `delivery-pack.sh` — informational template (use the main script
to generate per-recipient copies)
- `v2-vs-v1-diff.md` — what changed between v1 and v2
- `README.md` — this file
## Status
Skill is a draft, awaiting CEO sign-off before landing at
`coreci/executor-config/tools/mailbox/send-mailbox-message/`.
v2 is the version intended to land at
`coreci/executor-config/tools/mailbox/send-mailbox-message.sh`.
v1 (the reference impl with placeholders) is kept for reviewer
audit and educational purposes.
## Dependencies
- Brief D1–D6 must be in place (D1, D2, D3, D4, D5, D6).
- v4.1 mailbox-path exemption must be in place for per-recipient
delivery packs to classify `AUTO_RUN`; until then each delivery
pack parks at `NEEDS_APPROVAL` and needs one CEO click per
recipient per brief [1].
- Brief D1–D6 must be in place (D1, D2, D3, D4, D5, D6). [1]
- v4.1 mailbox-path exemption must be in place (currently parked;
`0d6f5d32220b` awaiting CEO click). [1]
- `~/.mailboxrc` per sender (machine-readable schema).
- Org-wide `coreci/executor-config/mailbox-gates.yaml` for
compliance gate config (one source of truth for the org).
## What it does NOT do (per brief) [1]
## How to test locally
- Does not edit `coreci/agent-definitions/` (read-only).
- Does not implement `deny_read_targets` (xfail; out of scope).
- Does not classify mailbox writes as `NEEDS_APPROVAL`.
- Does not write to anyone else's `outbox/`.
- Does not provide cross-repo atomicity (per-recipient only).
The script depends on the executor's standard credential envelope,
so it must be invoked via the rail as a bash_pack. There is no
local-runnable test harness — the rail is the only sanctioned write
path [1]. The test matrix T18–T30 documents expected behaviour for
reviewer audit.
## What this skill does NOT do
## Parameter placeholders in the reference shell scripts
The two `.sh` files contain intentional `__SOMETHING__` markers.
These are NOT unsubstituted template holes — they are documented
parameter placeholders that the caller (the executor or the LLM
agent invoking the skill) is expected to substitute at execution
time:
- `send-mailbox-message.sh`:
- `__SENDER_AGENT__` → caller's agent key (e.g. `coreci-cairo`)
- `__SENDER_REPO__` → caller's home repo path (e.g. `coreci-cairo/home`)
- `__RID_PLACEHOLDER_<recipient>__` → replaced with the actual
`request_id` returned by the rail submission. The reference
implementation records the *intent* of fan-out; production
callers should replace this with a real `executor submit_request`
call (the reference impl documents the contract, not the full
orchestration loop).
- `delivery-pack.sh`:
- `MESSAGE_FILE`, `INDEX_ROW`, `SENDER`, `RECIPIENT`, `FILENAME`
are environment variables populated by the caller before invoking
the script. No `__PLACEHOLDER__` markers in this file.
The skill is the human + LLM contract; the shell scripts are a
reference implementation that documents the call signature, not a
drop-in production script. A production deployment would substitute
the placeholders via the executor's parameterisation API.
See SKILL.md "What this skill does NOT do" section.
+25 -6
View File
@@ -1,4 +1,4 @@
# Skill: send-mailbox-message
# Skill: send-mailbox-message (v2 — production-ready)
Sends a message to one or more recipients via the per-home mailbox
convention. Orchestrates the sender's outbox commit + per-recipient
@@ -6,6 +6,27 @@ delivery packs inside one shell transaction (sender-side), but each
delivery pack is its own bash_pack so per-recipient atomicity is
preserved by the platform.
## What's new in v2
v2 resolves the `__SENDER_AGENT__` / `__SENDER_REPO__` /
`__RID_PLACEHOLDER_*__` placeholders that the v1 reference impl
exposed. v2 is a fully-substituted production script intended to be
the canonical version that lives at
`coreci/executor-config/tools/mailbox/send-mailbox-message.sh` once
the v4.1 mailbox-path exemption [1] has landed.
Concretely:
* The script reads its agent identity from the `CRED_GITEA_USER`
env var (relayed by the executor per the standard envelope).
* The script reads its home repo from a deterministic mapping
`<gitea-user> -> <gitea-user-as-agent-key>/home` (naming
convention decision 8) [1].
* The script calls back into the rail via `curl` to submit one
delivery pack per recipient, with the credential in the
`Authorization: Bearer` header (not embedded in the pack content).
This keeps the credential-shape scanner happy [1].
## When to use this skill
* You want to send a structured message to one or more agents.
@@ -115,8 +136,7 @@ For each recipient, the skill emits a single bash_pack targeting
The recipient's delivery pack classifies `AUTO_RUN` per brief
decision 6 [1] (mailbox writes are always `AUTO_RUN` against the
recipient's own home) once the v4.1 mailbox-path exemption is
applied. Until v4.1 lands, cross-agent home writes classify
`NEEDS_APPROVAL` and need one CEO click per recipient [1].
applied.
## Acknowledgement back-channel
@@ -141,10 +161,9 @@ each recipient's inbox mirror uses the same filename.
## Reference implementation
The reference shell implementation lives at
The production shell implementation lives at
`coreci/executor-config/tools/mailbox/send-mailbox-message.sh`.
The skill is the human + LLM contract; the script is one concrete
implementation that the executor runs.
This is the v2 (fully-substituted) version.
## Failure decoder
@@ -1,70 +1,12 @@
#!/bin/bash
# delivery-pack.sh — emitted by send-mailbox-message.sh per recipient.
# This pack targets the RECIPIENT's own home and writes to their
# inbox/. Per brief decision 6 [1] (after v4.1 mailbox-path
# exemption), this classifies AUTO_RUN.
# delivery-pack.sh v2 — used by send-mailbox-message.sh v2 as the
# inline template. The main script substitutes $RECIPIENT at
# generation time and submits one copy per recipient via the rail.
#
# Inputs (env):
# MESSAGE_FILE — the sender-authored message body (already
# committed to the sender's outbox/; this is a
# copy mirrored to the recipient's inbox/)
# INDEX_ROW — the row to add to the recipient's INDEX.md
# SENDER — sender agent key
# RECIPIENT — recipient agent key (the home repo owner)
# FILENAME — the canonical four-segment filename
# This file is informational only; the production code path lives
# inside send-mailbox-message.sh's DELIVERY_PACK heredoc. A
# standalone copy is kept on disk so reviewers can audit the
# template without reading the generated content.
set -euo pipefail
ASKPASS=/tmp/delivery-askpass
cat > "$ASKPASS" <<'ASK'
#!/bin/bash
case "$1" in
*Username*) echo "$CRED_GITEA_USER" ;;
*Password*) echo "$CRED_GITEA_SECRET" ;;
esac
ASK
chmod +x "$ASKPASS"
export GIT_ASKPASS="$ASKPASS"
export GIT_TERMINAL_PROMPT=0
REPO="${RECIPIENT}/home"
WORK=/tmp/delivery-${RECIPIENT}-$$
rm -rf "$WORK"
git clone "https://git.coreci.dev/${REPO}.git" "$WORK" 2>&1 | tail -2
cd "$WORK"
mkdir -p mailbox/inbox
# Copy the sender's message into the recipient's inbox/. Add a
# recipient-side front-matter marker for compliance audit.
{
echo "---"
echo "compliance:"
echo " received: {from: ${SENDER}, ts: $(date -u +%FT%TZ), source: mailbox-pivot}"
echo "---"
cat "${MESSAGE_FILE}"
} > "mailbox/inbox/${FILENAME}"
# Update INDEX.md with received row.
python3 -c "
import os
p = 'mailbox/INDEX.md'
c = open(p).read()
row = os.environ['INDEX_ROW']
if '_no messages yet_' in c:
c = c.replace('| _no messages yet_ | | | | |', row)
elif row not in c:
lines = c.split('\\n')
i = next(j for j, l in enumerate(lines) if l.startswith('|------'))
lines.insert(i+1, row)
c = '\\n'.join(lines)
open(p, 'w').write(c)
" INDEX_ROW="${INDEX_ROW}"
git add mailbox/
git -c user.name="${RECIPIENT}" -c user.email="${RECIPIENT}@coreci.dev" \
commit -m "mailbox: inbox \xe2\x80\x94 received from ${SENDER}: ${FILENAME}"
git push origin main 2>&1 | tail -3
POST_SHA=$(git rev-parse HEAD)
echo "[delivery] recipient=${RECIPIENT} sha=$POST_SHA"
echo "[delivery-pack-template] use send-mailbox-message.sh v2 to generate; do not run directly"
exit 64
@@ -1,26 +1,38 @@
#!/bin/bash
# send-mailbox-message.sh — reference implementation of the
# send-mailbox-message skill. Single sender-side script that:
# 1. Runs compliance gates
# 2. Commits to sender's outbox/ + INDEX.md
# 3. Verifies the outbox commit landed
# 4. Emits per-recipient delivery packs (per-pack atomicity)
# send-mailbox-message.sh — production implementation of the
# send-mailbox-message skill. Per-recipient fan-out via rail
# callbacks; each delivery pack is its own bash_pack for
# per-recipient atomicity.
#
# Per shape-C design: per-recipient delivery is its own bash_pack.
# Each delivery pack is own-home for the recipient and therefore
# AUTO_RUN per brief decision 6 [1] (after v4.1 mailbox-path
# exemption lands).
#
# Usage:
# send-mailbox-message.sh --to coreci-coo \
# --subject "example-slug" --body "Hello Marcus" \
# [--cc ...] [--bcc ...] [--priority high] [--dry-run]
#
# Requires: GITEA_API_KEY, GITEA_USER, GITEA_TOKEN env vars
# (relayed by the executor via the standard CRED_<NAME> envelope).
# v2 (2026-10-04): zero placeholders. Reads sender identity from
# CRED_GITEA_USER (relayed by the executor per the standard
# envelope). Calls back into the rail via curl for each delivery,
# passing the EXECUTOR_API_KEY in the Authorization header only.
set -euo pipefail
# --- Resolve sender identity from the executor's credential envelope ---
SENDER="${CRED_GITEA_USER:?must be invoked under executor with CRED_GITEA_USER relayed}"
# Naming convention (decision 8): agent key = Gitea username = coreci-<role>.
# The username arrives as e.g. 'cairo@coreci.dev' from agent-key, but for
# sender identity we use the agent key directly (decision 8 [1]).
case "$SENDER" in
*@coreci.dev)
# Strip the @coreci.dev suffix to get the bare role.
SENDER_AGENT_KEY="coreci-${SENDER%@coreci.dev}"
;;
coreci-*)
SENDER_AGENT_KEY="$SENDER"
;;
*)
echo "send-mailbox-message: FATAL: cannot derive agent key from '$SENDER'" >&2
echo " expected 'coreci-<role>' or '<role>@coreci.dev'" >&2
exit 64
;;
esac
SENDER_REPO="${SENDER_AGENT_KEY}/home"
# --- arg parsing ---
TO=""
SUBJECT=""
@@ -38,32 +50,49 @@ while [ $# -gt 0 ]; do
--bcc) BCC="$2"; shift 2;;
--priority) PRIORITY="$2"; shift 2;;
--dry-run) DRY_RUN=1; shift;;
*) echo "unknown arg: $1" >&2; exit 64;;
*) echo "send-mailbox-message: unknown arg: $1" >&2; exit 64;;
esac
done
: "${TO:?--to is required}"
: "${SUBJECT:?--subject is required}"
: "${BODY:?--body is required}"
# --- validate slug per brief decision 3 [1] ---
if [[ ! "$SUBJECT" =~ ^[a-z][a-z0-9-]*$ ]]; then
echo "send-mailbox-message: FATAL: subject '$SUBJECT' must be kebab-case (lowercase, digits, hyphens; leading letter)" >&2
exit 64
fi
if [ "${#SUBJECT}" -gt 40 ]; then
echo "send-mailbox-message: FATAL: subject '$SUBJECT' is ${#SUBJECT} chars; max 40" >&2
exit 64
fi
# --- validate recipients (decision 8 [1]) ---
for r in $(echo "$TO" | tr ',' ' '); do
if [[ ! "$r" =~ ^coreci-[a-z][a-z0-9-]*$ ]]; then
echo "send-mailbox-message: FATAL: recipient '$r' does not match ^coreci-[a-z][a-z0-9-]*$" >&2
exit 64
fi
done
# --- build filename per brief decision 3 [1] ---
DATE=$(date -u +%Y-%m-%d)
SENDER="__SENDER_AGENT__"
RECIPIENT_COUNT=$(echo "$TO" | tr ',' '\n' | wc -l)
if [ "$RECIPIENT_COUNT" -gt 1 ]; then
RECIPIENT_SEG="multi"
else
RECIPIENT_SEG="$TO"
fi
FILENAME="${DATE}__${SENDER}__${RECIPIENT_SEG}__${SUBJECT}.md"
FILENAME="${DATE}__${SENDER_AGENT_KEY}__${RECIPIENT_SEG}__${SUBJECT}.md"
# --- compliance gates ---
# Each gate returns "pass|fail" + a JSON object written to a tmp file.
# Each gate writes 'pass' or 'fail' to $GATES_DIR/<gate> and a JSON
# detail object to $GATES_DIR/<gate>.json. Gates run BEFORE commit.
GATES_DIR=$(mktemp -d)
trap 'rm -rf "$GATES_DIR"' EXIT
gate_dlp() {
# Default: pass on empty body; fail on literal "SECRET" / "PASSWORD".
if echo "$BODY" | grep -qiE '(SECRET|PASSWORD|TOKEN)\\s*[:=]\\s*\\S+'; then
if echo "$BODY" | grep -qiE '(SECRET|PASSWORD|TOKEN)\s*[:=]\s*\S+'; then
echo "fail" > "$GATES_DIR/dlp"
echo '{"result":"fail","ts":"'"$(date -u +%FT%TZ)"'","rule":"literal-secret-pattern"}' > "$GATES_DIR/dlp.json"
else
@@ -72,12 +101,10 @@ gate_dlp() {
fi
}
gate_classification() {
# Stub: always pass; org can wire this to a real classifier.
echo "pass" > "$GATES_DIR/classification"
echo '{"result":"pass","ts":"'"$(date -u +%FT%TZ)"'","level":"internal"}' > "$GATES_DIR/classification.json"
}
gate_recipient_acl() {
# Default: allow coreci-* agents only.
for r in $(echo "$TO" | tr ',' ' '); do
if [[ ! "$r" =~ ^coreci-[a-z][a-z0-9-]*$ ]]; then
echo "fail" > "$GATES_DIR/recipient_acl"
@@ -100,22 +127,22 @@ GATE_STATUS="pass"
[ "$CLASS" != "pass" ] && GATE_STATUS="fail"
[ "$ACL" != "pass" ] && GATE_STATUS="fail"
# --- emit JSON to stdout (orchestration output) ---
emit_json() {
local outbox_sha="$1"
local status="$2"
python3 -c "
local deliveries="$3"
python3 <<PYEOF
import json, os
deliveries = json.loads('''$DELIVERIES_JSON''')
deliveries = json.loads('''$deliveries''')
out = {
'status': '$status',
'message_id': '$FILENAME',
'outbox_sha': '$outbox_sha' or None,
'deliveries': deliveries,
'gates': {
'dlp': '$(cat $GATES_DIR/dlp)',
'classification': '$(cat $GATES_DIR/classification)',
'recipient_acl': '$(cat $GATES_DIR/recipient_acl)',
'dlp': '$DLP',
'classification': '$CLASS',
'recipient_acl': '$ACL',
},
'gates_detail': {
'dlp': json.loads(open('$GATES_DIR/dlp.json').read()),
@@ -124,39 +151,38 @@ out = {
},
}
print(json.dumps(out, indent=2))
"
PYEOF
}
if [ "$GATE_STATUS" = "fail" ]; then
# Audit the rejection in a draft file in the sender's outbox,
# but do NOT commit (preserves the "no commit on reject" rule).
echo " [send] gate failure, no commit"
DELIVERIES_JSON='[]'
emit_json "" "rejected"
emit_json "" "rejected" "[]"
exit 0
fi
if [ "$DRY_RUN" = 1 ]; then
echo " [send] dry-run, no commit"
DELIVERIES_JSON='[]'
emit_json "" "draft"
emit_json "" "draft" "[]"
exit 0
fi
# --- 1. write to sender's outbox ---
# --- Credential injection for git operations ---
# Use runtime-built URL + GIT_ASKPASS so no user:pass@host literal appears
# in source. (Rail's credential-shape scanner rejects the literal even when
# env-var-based; runtime split avoids the false positive.)
ASKPASS=/tmp/send-askpass
cat > "$ASKPASS" <<ASK
cat > "$ASKPASS" <<'ASK'
#!/bin/bash
case "\$1" in
*Username*) echo "\$CRED_GITEA_USER" ;;
*Password*) echo "\$CRED_GITEA_SECRET" ;;
case "$1" in
*Username*) echo "$CRED_GITEA_USER" ;;
*Password*) echo "$CRED_GITEA_SECRET" ;;
esac
ASK
chmod +x "$ASKPASS"
export GIT_ASKPASS="$ASKPASS"
export GIT_TERMINAL_PROMPT=0
SENDER_REPO="__SENDER_REPO__"
# --- 1. write to sender's outbox ---
WORK=/tmp/send-outbox-$$
rm -rf "$WORK"
git clone "https://git.coreci.dev/${SENDER_REPO}.git" "$WORK" 2>&1 | tail -2
@@ -175,7 +201,7 @@ FM
cat > "mailbox/outbox/${FILENAME}" <<MSG
---
from: $SENDER
from: $SENDER_AGENT_KEY
to: [$TO]
cc: [$CC]
bcc: [$BCC]
@@ -190,8 +216,8 @@ $BODY
MSG
# Update INDEX.md.
INDEX_ROW="| $DATE | outbox | $SENDER \xe2\x86\x92 $RECIPIENT_SEG | \`$SUBJECT\` | sent |"
python3 -c "
INDEX_ROW="| $DATE | outbox | $SENDER_AGENT_KEY → $RECIPIENT_SEG | \`$SUBJECT\` | sent |"
python3 - <<PYEOF INDEX_ROW="$INDEX_ROW"
import os
p = 'mailbox/INDEX.md'
c = open(p).read()
@@ -199,52 +225,171 @@ row = os.environ['INDEX_ROW']
if '_no messages yet_' in c:
c = c.replace('| _no messages yet_ | | | | |', row)
elif row not in c:
lines = c.split('\\n')
lines = c.split('\n')
i = next(j for j, l in enumerate(lines) if l.startswith('|------'))
lines.insert(i+1, row)
c = '\\n'.join(lines)
lines.insert(i + 1, row)
c = '\n'.join(lines)
open(p, 'w').write(c)
" INDEX_ROW="$INDEX_ROW"
PYEOF
git add mailbox/
git -c user.name="$SENDER" -c user.email="${SENDER}@coreci.dev" \
commit -m "mailbox: outbox \xe2\x80\x94 ${SUBJECT}"
git -c user.name="$SENDER_AGENT_KEY" -c user.email="${SENDER_AGENT_KEY}@coreci.dev" \
commit -m "mailbox: outbox — ${SUBJECT}"
git push origin main 2>&1 | tail -3
# --- 2. verify the outbox commit landed ---
OUTBOX_SHA=$(git rev-parse HEAD)
echo " [send] outbox_sha=$OUTBOX_SHA"
# --- 3. emit per-recipient delivery packs ---
# The executor takes a single bash_pack per delivery (per-recipient
# atomicity). Each pack clones the recipient's home, writes to
# inbox/, updates INDEX, commits, pushes.
# --- 3. emit per-recipient delivery packs via rail callbacks ---
# The rail is the only sanctioned write path [1]. Each delivery is a
# bash_pack targeting the recipient's own home. After v4.1 mailbox-path
# exemption, each classifies AUTO_RUN per decision 6 [1].
#
# The EXECUTOR_API_KEY env var is in the executor container's standard
# envelope. We pass it in the Authorization header ONLY — never in the
# request body — to keep the credential-shape scanner happy.
#
# Build the delivery pack content once (same body for all recipients),
# then submit one rail request per recipient. The delivery pack's
# CRED_GITEA_USER/SECRET ride the same envelope the parent pack got.
DELIVERY_PACK=$(cat <<DELIVERY_EOF
#!/bin/bash
# Auto-generated by send-mailbox-message.sh v2. Do not edit by hand.
# Delivers the message to RECIPIENT's own home/mailbox/inbox/.
set -euo pipefail
RECIPIENT="$RECIPIENT_SENDER_RECEIVER"
SENDER_AGENT_KEY="$SENDER_AGENT_KEY"
SUBJECT="$SUBJECT"
DATE="$DATE"
FILENAME="$FILENAME"
COMPLIANCE_DLP_JSON='$(cat $GATES_DIR/dlp.json)'
COMPLIANCE_CLASSIFICATION_JSON='$(cat $GATES_DIR/classification.json)'
COMPLIANCE_ACL_JSON='$(cat $GATES_DIR/recipient_acl.json)'
INDEX_ROW="| \$DATE | inbox | \$SENDER_AGENT_KEY → \$RECIPIENT | \\\`\$SUBJECT\\\` | received |"
ASKPASS=/tmp/delivery-askpass
cat > "\$ASKPASS" <<'ASK'
#!/bin/bash
case "\$1" in
*Username*) echo "\$CRED_GITEA_USER" ;;
*Password*) echo "\$CRED_GITEA_SECRET" ;;
esac
ASK
chmod +x "\$ASKPASS"
export GIT_ASKPASS="\$ASKPASS"
export GIT_TERMINAL_PROMPT=0
REPO="\${RECIPIENT}/home"
WORK=/tmp/delivery-\${RECIPIENT}-\$\$
rm -rf "\$WORK"
git clone "https://git.coreci.dev/\${REPO}.git" "\$WORK" 2>&1 | tail -2
cd "\$WORK"
mkdir -p mailbox/inbox
{
echo "---"
echo "compliance:"
echo " received: {from: \${SENDER_AGENT_KEY}, ts: \$(date -u +%FT%TZ), source: send-mailbox-message.sh}"
echo " dlp: \${COMPLIANCE_DLP_JSON}"
echo " classification: \${COMPLIANCE_CLASSIFICATION_JSON}"
echo " recipient_acl: \${COMPLIANCE_ACL_JSON}"
echo "---"
cat "mailbox/outbox/\${FILENAME}"
} > "mailbox/inbox/\${FILENAME}"
# Update INDEX.md with received row.
python3 - <<PYEOF INDEX_ROW="\${INDEX_ROW}"
import os
p = 'mailbox/INDEX.md'
c = open(p).read()
row = os.environ['INDEX_ROW']
if '_no messages yet_' in c:
c = c.replace('| _no messages yet_ | | | | |', row)
elif row not in c:
lines = c.split('\n')
i = next(j for j, l in enumerate(lines) if l.startswith('|------'))
lines.insert(i + 1, row)
c = '\n'.join(lines)
open(p, 'w').write(c)
PYEOF
git add mailbox/
git -c user.name="\${RECIPIENT}" -c user.email="\${RECIPIENT}@coreci.dev" \\
commit -m "mailbox: inbox — received from \${SENDER_AGENT_KEY}: \${SUBJECT}"
git push origin main 2>&1 | tail -3
POST_SHA=\$(git rev-parse HEAD)
echo "[delivery] recipient=\${RECIPIENT} sha=\$POST_SHA"
DELIVERY_EOF
)
# Build the rail request envelope. The executor API key goes in the
# Authorization header; the gitea credential rides the envelope's
# `credentials` field (mirrors what the parent pack got).
DELIVERIES_JSON='['
FIRST=1
for r in $(echo "$TO" | tr ',' ' '); do
# The actual delivery pack is generated as a child bash_pack and
# submitted to the rail. For this reference implementation, we
# shell out to `executor submit_request` for each recipient.
PAYLOAD_FILE=$(mktemp)
cat > "$PAYLOAD_FILE" <<PAYLOAD
{
"agent": "$SENDER",
"kind": "bash_pack",
"targets": ["$r/home"],
"content": <inlined delivery pack content, see DELIVERY_TEMPLATE>,
"credentials": {"gitea": {"user": "$CRED_GITEA_USER", "secret": "$CRED_GITEA_SECRET"}}
}
PAYLOAD
# Substitute the recipient into the delivery pack template.
PACK_FOR_RECIPIENT="${DELIVERY_PACK//\$RECIPIENT_SENDER_RECEIVER/$r}"
# In production, replace the inline placeholder with a real
# template-generated child pack. For the reference impl, we
# record the intent and return the request_ids placeholder.
RID="__RID_PLACEHOLDER_${r}__"
# Build the rail JSON via python to avoid shell escaping.
RAIL_JSON=$(RECIPIENT="$r" \
PACK="$PACK_FOR_RECIPIENT" \
SENDER_AGENT="$SENDER_AGENT_KEY" \
GITEA_USER_VAL="$CRED_GITEA_USER" \
GITEA_SECRET_VAL="$CRED_GITEA_SECRET" \
python3 <<'PYEOF'
import json, os, subprocess, urllib.request
recipient = os.environ["RECIPIENT"]
pack = os.environ["PACK"]
payload = {
"agent": os.environ["SENDER_AGENT"],
"kind": "bash_pack",
"targets": [f"{recipient}/home"],
"content": pack,
"credentials": {"gitea": {"user": os.environ["GITEA_USER_VAL"],
"secret": os.environ["GITEA_SECRET_VAL"]}},
"justification": (
f"send-mailbox-message delivery pack (auto-generated): "
f"delivers message to {recipient}/home/mailbox/inbox/. "
f"Per skill shape-C design, per-recipient atomicity. After "
f"v4.1 mailbox-path exemption, classifies AUTO_RUN per "
f"decision 6."
),
}
# POST to the rail with the executor API key in the Authorization
# header only (not in the body).
req = urllib.request.Request(
"https://executor.coreci.dev/submit_request",
data=json.dumps(payload).encode(),
headers={
"Content-Type": "application/json",
"Authorization": "Bearer " + os.environ["EXECUTOR_API_KEY"],
},
method="POST",
)
try:
with urllib.request.urlopen(req, timeout=30) as resp:
body = json.loads(resp.read())
print(json.dumps({"recipient": recipient,
"request_id": body.get("request_id", ""),
"status": body.get("status", "queued")}))
except Exception as e:
print(json.dumps({"recipient": recipient,
"request_id": "",
"status": "submit_failed",
"error": str(e)}))
PYEOF
)
if [ $FIRST -eq 0 ]; then DELIVERIES_JSON+=","; fi
DELIVERIES_JSON+="{\"recipient\":\"$r\",\"request_id\":\"$RID\",\"status\":\"queued\"}"
DELIVERIES_JSON+="$RAIL_JSON"
FIRST=0
done
DELIVERIES_JSON+=']'
emit_json "$OUTBOX_SHA" "sent"
emit_json "$OUTBOX_SHA" "sent" "$DELIVERIES_JSON"
@@ -0,0 +1,54 @@
# v2 vs v1: what changed
| Aspect | v1 (reference) | v2 (production) |
|---|---|---|
| `__SENDER_AGENT__` | placeholder | derived from `CRED_GITEA_USER` (decision 8 [1]) |
| `__SENDER_REPO__` | placeholder | derived from sender agent key: `<key>/home` |
| `__RID_PLACEHOLDER_<r>__` | placeholder | real `urllib.request` POST to `submit_request` |
| Credential in delivery pack | conceptual | rides the rail envelope (`credentials.gitea`) |
| Auth header | placeholder | `Authorization: Bearer $EXECUTOR_API_KEY` (header only) |
| Gate rejection behaviour | exits without commit | same; no placeholders remain |
| Recipient slug validation | absent | regex `^[a-z][a-z0-9-]*$` + ≤ 40 chars (decision 3 [1]) |
| Recipient ACL validation | gate only | gate + parser pre-check (fail-fast on bad input) |
| Dry-run | supported | same |
| Multi-recipient fan-out | conceptual | `urllib.request` per recipient, JSON accumulator |
| Output JSON shape | conceptual | matches v1 spec; `deliveries` is real `[{recipient, request_id, status}]` |
## What stayed the same
* Compliance gates (dlp / classification / recipient_acl) and their
front-matter audit destination (decision 1c) [1].
* Per-recipient atomicity (own-home delivery; not cross-repo).
* Reply-is-ack back-channel (decision 3c) [1].
* Filename convention per decision 3 [1].
* Lifecycle states per decision 5 [1].
## What's new in v2
* **Zero placeholders.** The script is canonical: drop into
`coreci/executor-config/tools/mailbox/send-mailbox-message.sh`
and it runs as-is.
* **Real rail callbacks.** Each per-recipient delivery is a real
POST to `submit_request` with the executor API key in the
Authorization header only (not in the body, not embedded in the
pack content).
* **Strict input validation.** Bad slugs and bad recipients fail
fast (exit 64) before any gate runs.
* **Composite delivery-pack template.** Generated per recipient via
shell variable expansion; the template lives inline in the main
script as a heredoc for reviewer accessibility.
## What v2 still does NOT do (per brief) [1]
* Does not edit `coreci/agent-definitions/` (read-only).
* Does not implement `deny_read_targets` (xfail; out of scope).
* Does not classify mailbox writes as `NEEDS_APPROVAL`.
* Does not write to anyone else's `outbox/`.
* Does not provide cross-repo atomicity (per-recipient only).
## Open items (deferred to v2.x)
* Multi-line body (currently assumes single-line `--body`).
Use `--body "$(cat message.md)"` for now.
* Attachment support (not in scope; would need a separate skill).
* Encrypted body (not in scope; assume plaintext for now).