diff --git a/AGENTS.md b/AGENTS.md index d5fc0e0dcb9..dc23f88044b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -527,7 +527,7 @@ Preserve durable structured identifiers, dependencies, and completion artifact l ## 11. Crewmate briefs -`bin/fm-brief.sh` and its help own scaffold syntax, generated variants, status protocol, delivery-mode definitions of done, and exact safety mechanics. +`bin/fm-brief.sh` and its help own scaffold syntax and generated variants; `bin/fm-status-lib.sh` owns the shared generated status-protocol wording; `bin/fm-dod-lib.sh` owns delivery-mode definitions of done; and the relevant script headers own exact safety mechanics. Use its scaffold as the contract, then fill `## Captain's intent` (`{TASK}`) with the captain's own ask plus the context needed to read it, including the substance of any report, decision, or PR the ask refers to, and fill `## Firstmate spec` (`{FIRSTMATE_SPEC}`) with Firstmate's build instructions. `bin/fm-dod-lib.sh` owns what a no-mistakes worker may pass as `--intent` and its rule that the string must be self-sufficient. Keep additions task-specific rather than repeating lifecycle instructions, and alter generated sections only when the task genuinely differs from the standard shape. diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 0956e5d6968..7a79a202628 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -92,8 +92,12 @@ esac . "$SCRIPT_DIR/fm-classify-lib.sh" # shellcheck source=bin/fm-dod-lib.sh . "$SCRIPT_DIR/fm-dod-lib.sh" +# shellcheck source=bin/fm-status-lib.sh +. "$SCRIPT_DIR/fm-status-lib.sh" PAUSED_VERB=${FM_CLASSIFY_PAUSED_VERB:-$FM_CLASSIFY_PAUSED_VERB_DEFAULT} SILENT_OPERATIONAL_INPUT_RULE=$FM_OPERATIONAL_SILENT_REPLY_RULE +STATUS_WORKING_RULE= +STATUS_NO_PROGRESS_RULE= resolve_directory_input() { local name=$1 path=$2 resolved @@ -186,6 +190,14 @@ elif [ "$MERGE_AUTHORITY_SET" -eq 1 ]; then echo "error: --merge-authority applies only to ship briefs; secondmate charters resolve each project from data/projects.md" >&2 exit 1 fi +if [ "$KIND" = scout ]; then + STATUS_WORKING_RULE=$(fm_status_working_rule scout) +elif [ "$KIND" = ship ]; then + STATUS_WORKING_RULE=$(fm_status_working_rule "$MODE") +fi +if [ "$KIND" != secondmate ]; then + STATUS_NO_PROGRESS_RULE=$(fm_status_no_progress_rule) +fi ID=${POS[0]} if [ "$KIND" = secondmate ] && [ "$HERDR_LAB" -eq 1 ]; then @@ -215,6 +227,8 @@ shell_quote() { STATUS_FILE=$(shell_quote "$STATE/$ID.status") INBOX_DIR=$(shell_quote "$STATE/$ID.inbox") +STATUS_WAKE_REMINDER=$(fm_status_wake_reminder) +STATUS_NO_RESOLVED_ECHO_RULE=$(fm_status_no_resolved_echo_rule) # The receive-and-ack half of the steering-inbox contract, included in every # scaffold kind. The record format, doorbell line, and re-ring ladder are @@ -226,6 +240,7 @@ IFS= read -r -d '' INBOX_SECTION <> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE +$STATUS_WAKE_REMINDER Use \`$PAUSED_VERB: {why}\` (distinct from \`blocked:\`) only when your domain is deliberately idling on a known external wait you expect to clear on its own; use \`blocked:\` when you are stuck and need firstmate to act. Use this only for material phase changes, a captain decision, a real blocker, a failure, work ready for review, or work you landed. Work you landed includes a merge you performed yourself under standing merge authority and one the captain merged on the forge: under that authority nothing is ever \"ready for review\", so a landed merge that goes unreported reaches the captain as silence. @@ -454,9 +470,9 @@ The report is the only thing that survives, so anything worth keeping must be in \`echo "{state}: {one short line}" >> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE - Each append wakes firstmate, so report sparingly: only phase changes a supervisor - would act on and the needs-decision/blocked/paused/done/failed states. No step-by-step - FYI progress lines; firstmate reads your pane for that. + $STATUS_WAKE_REMINDER + $STATUS_WORKING_RULE + $STATUS_NO_PROGRESS_RULE Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. @@ -480,7 +496,7 @@ Write your findings to \`$DATA/$ID/report.md\`. The report must stand alone: what you did, what you found, the evidence (commands run, output, file:line references), and what you recommend. If your deliverable is a visual artifact the captain will review and iterate on, you may host the Lavish review loop yourself (poll, revise, re-serve, staying alive) instead of handing it back to firstmate. Before reporting done, read and follow \`$FM_ROOT/.agents/skills/captain-hold-lifecycle/SKILL.md\` and pass its shared completion gate for the report and any visual review. -When the report is complete, append \`done: {one-line conclusion}\` to the status file and stop. +Only after the report exists and is complete, append \`done: {one-line conclusion}\` to the status file and stop. If your findings reveal work that should ship (e.g. you reproduced a bug and the fix is clear), say so in the report; firstmate may promote this task in place, and you would then receive mode-specific ship instructions as a follow-up message. EOF echo "scaffolded: $BRIEF (scout; replace {TASK} and {FIRSTMATE_SPEC})" @@ -538,14 +554,13 @@ $RULE1 \`echo "{state}: {one short line}" >> $STATUS_FILE\` States: working, needs-decision, blocked, $PAUSED_VERB, done, failed. $SILENT_OPERATIONAL_INPUT_RULE - Each append wakes firstmate, so report sparingly: only phase changes a supervisor - would act on (setup done, bug reproduced, fix implemented, validation passed) and the - needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines; - firstmate reads your pane for that. + $STATUS_WAKE_REMINDER + $STATUS_WORKING_RULE + $STATUS_NO_PROGRESS_RULE Whenever you mention a PR anywhere - a status line, your terminal, a summary - write its full https:// URL exactly as the forge printed it, never a bare number such as "PR 108"; firstmate copies that URL from your line rather than assembling one. - A mid-task \`working:\` line (including setup complete) is nonterminal: do not end the + A mid-task \`working:\` line is nonterminal: do not end the turn after it; continue the same stage until a defined \`done:\` gate under Definition of done. Use \`$PAUSED_VERB: {why}\` - distinct from \`blocked:\` - ONLY when you are deliberately idling on a known external wait you expect to clear on its own (an upstream release, a rate-limit reset, diff --git a/bin/fm-dod-lib.sh b/bin/fm-dod-lib.sh index c5be1b1455c..61a14643e6f 100755 --- a/bin/fm-dod-lib.sh +++ b/bin/fm-dod-lib.sh @@ -203,8 +203,8 @@ EOF # Definition of done Delivery contract: mode=no-mistakes The task is complete only when committed on your branch. -When you believe it is complete, append \`done: {summary}\` to the status file and stop. -Firstmate will then instruct you to run /no-mistakes to validate and ship a PR. +After committing the implementation, append \`working: implementation committed and validation started\` only if that phase change has not already been reported, then run /no-mistakes to validate and ship a PR. +A local commit, local validation pass, or pipeline start is never a \`done:\` event. You drive no-mistakes by responding to its gates, not by implementing fixes. Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and \`no-mistakes axi run --help\` plus the \`help\` lines in each \`axi\` response are authoritative and version-matched to the installed binary. diff --git a/bin/fm-promote.sh b/bin/fm-promote.sh index 7ddf3951a5d..c971c18d9e9 100755 --- a/bin/fm-promote.sh +++ b/bin/fm-promote.sh @@ -34,6 +34,8 @@ DATA="${FM_DATA_OVERRIDE:-$FM_HOME/data}" # shellcheck source=bin/fm-dod-lib.sh . "$SCRIPT_DIR/fm-dod-lib.sh" +# shellcheck source=bin/fm-status-lib.sh +. "$SCRIPT_DIR/fm-status-lib.sh" # shellcheck source=bin/fm-pr-lib.sh . "$SCRIPT_DIR/fm-pr-lib.sh" # shellcheck source=bin/fm-wake-lib.sh @@ -194,6 +196,10 @@ PROMOTION_ASK_USER_BLOCK= if [ "$MODE" = no-mistakes ]; then PROMOTION_ASK_USER_BLOCK=$(fm_ask_user_escalation_block "$DATA" "$ID") fi +PROMOTION_STATUS_WAKE_REMINDER=$(fm_status_wake_reminder) +PROMOTION_STATUS_WORKING_RULE=$(fm_status_working_rule "$MODE") +PROMOTION_STATUS_NO_PROGRESS_RULE=$(fm_status_no_progress_rule) +PROMOTION_STATUS_NO_RESOLVED_ECHO_RULE=$(fm_status_no_resolved_echo_rule) mkdir -p "$DATA/$ID" [ ! -d "$INSTRUCTIONS" ] || { echo "error: ship instructions path is a directory: $INSTRUCTIONS" >&2; exit 1; } TMP="$DATA/$ID/.ship-instructions.md.${BASHPID:-$$}" @@ -213,7 +219,11 @@ EOF 3. Return to a clean default-branch base, then create your branch: \`git checkout -b fm/$ID\`. 4. Carry over only the intended fix changes. Leave scratch commits, debug edits, and experiment files behind. 5. If you reproduced a bug, turn that reproduction into a regression test. -6. These ship instructions supersede the scout delivery rules and report-based Definition of done. Everything else in your original instructions carries over unchanged: the status protocol; the instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule. +6. These ship instructions supersede the scout delivery rules and report-based Definition of done. The shared status protocol rules carry over unchanged, but the mode-specific working-phase rule below supersedes the scout wording. The instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule carry over unchanged. + $PROMOTION_STATUS_WAKE_REMINDER + $PROMOTION_STATUS_WORKING_RULE + $PROMOTION_STATUS_NO_PROGRESS_RULE + $PROMOTION_STATUS_NO_RESOLVED_ECHO_RULE $PROMOTION_ASK_USER_BLOCK 7. Treat the scout-time Firstmate spec and any unmarked legacy \`# Task\` text as investigation context, not captain intent or ship-time instructions. EOF diff --git a/bin/fm-status-lib.sh b/bin/fm-status-lib.sh new file mode 100644 index 00000000000..84acdea1dbe --- /dev/null +++ b/bin/fm-status-lib.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +# Shared generated worker status-protocol wording. + +fm_status_wake_reminder() { + printf '%s\n' \ + 'Each status-file append wakes the supervisor and costs a full supervision turn.' \ + 'Append only when this protocol requires it; never use status as a progress log.' +} + +fm_status_working_rule() { # + local kind=$1 + case "$kind" in + scout) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' + ;; + no-mistakes|direct-PR) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' + ;; + local-only) + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. + printf '%s\n' 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' + ;; + *) + echo "error: fm_status_working_rule: unknown kind '$kind'" >&2 + return 1 + ;; + esac +} + +fm_status_no_progress_rule() { + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. + printf '%s\n' 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' +} + +fm_status_no_resolved_echo_rule() { + # shellcheck disable=SC2016 # Backticks are literal status-protocol text. + printf '%s\n' 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' +} diff --git a/docs/scripts.md b/docs/scripts.md index 4b0bbaebded..07bd3f2d2bf 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -34,7 +34,8 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-backlog-receive.sh` | Idempotently ingest one confined remote handoff outbox through tasks-axi | | `fm-captain-hold.sh` | Hold tasks for the captain, record the captain's answers, gate investigation completion, and report record divergence between the status log and the backlog | | `fm-decision-hold.sh` | One-release compatibility shim mapping the retired decision commands onto fm-captain-hold.sh | -| `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs, with Captain's intent and Firstmate spec subsections on ship/scout | +| `fm-brief.sh` | Scaffold ship (explicit `--mode`), scout, secondmate-charter, and Herdr-lab briefs, with mode-specific status protocol and Captain's intent and Firstmate spec subsections on ship/scout | +| `fm-status-lib.sh` | Shared generated worker status-protocol wording for briefs and promotion handoffs | | `fm-dod-lib.sh` | One owner of the ship definition of done and of the no-mistakes `--intent` contract | | `fm-herdr-lab.sh` | Provision and guardedly operate an isolated, never-default Herdr lab session | | `fm-install-herdr.sh` | Install CI's exact-version Herdr pin with official asset URL, SHA-256, and protocol checks | @@ -135,7 +136,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-pr-merge.sh` | Record PR metadata, merge a task's canonical full GitHub or GitLab URL, then refuse an outcome it cannot prove landed or queued | | `fm-merge-outcome-lib.sh` | Publish a confirmed merge's durable, role-routed supervision outcome | | `fm-parent-channel-lib.sh` | Resolve a secondmate home's parent channel and append a captain-facing outcome line to it at most once | -| `fm-promote.sh` | Promote a scout task in place to a protected ship task with an explicit delivery mode, and write the ship instructions carrying that mode's definition of done | +| `fm-promote.sh` | Promote a scout task in place to a protected ship task with an explicit delivery mode, and write ship instructions carrying that mode's status protocol and definition of done | | `fm-teardown.sh` | Fail-closed teardown: return landed ship worktrees, require completed scout deliverables, retire secondmate homes | | `fm-harness.sh` | Detect the running harness and resolve crew or secondmate harness, model, and effort | | `fm-lock.sh` | Per-home firstmate session lock | diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index b1495a1831a..76bc933c2c8 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -223,13 +223,96 @@ test_ship_modes_generate_clean_briefs() { assert_grep "## Captain's intent" "$brief" "$id: brief missing Captain's intent subsection" assert_grep "## Firstmate spec" "$brief" "$id: brief missing Firstmate spec subsection" assert_grep 'never a bare number such as "PR 108"' "$brief" "$id: brief missing the full-PR-URL rule" - assert_grep "mid-task \`working:\` line (including setup complete) is nonterminal" "$brief" \ - "$id: brief missing nonterminal working:/setup-complete gate protection" + assert_grep "mid-task \`working:\` line is nonterminal" "$brief" \ + "$id: brief missing nonterminal working: gate protection" + assert_no_grep "working:\` line (including setup complete)" "$brief" \ + "$id: brief still permits a setup-complete progress event" assert_no_grep "EOF" "$brief" "$id: brief leaked a heredoc EOF marker (unterminated heredoc)" done pass "fm-brief.sh: no-mistakes/direct-PR/local-only briefs generate cleanly" } +test_status_protocol_is_phase_only() { + local home kind id brief content reminder reminder_count + home="$TMP_ROOT/status-protocol-home" + mkdir -p "$home/data" + reminder='Each status-file append wakes the supervisor and costs a full supervision turn. +Append only when this protocol requires it; never use status as a progress log.' + + for kind in ship scout secondmate; do + id="status-protocol-$kind" + case "$kind" in + ship) + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode no-mistakes >/dev/null 2>&1 + ;; + scout) + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --scout >/dev/null 2>&1 + ;; + secondmate) + FM_HOME="$home" FM_SECONDMATE_CHARTER='Handle routed domain work.' \ + "$ROOT/bin/fm-brief.sh" "$id" --secondmate --no-projects >/dev/null 2>&1 + ;; + esac + brief="$home/data/$id/brief.md" + content=$(cat "$brief") + reminder_count=$(count_literal "$content" "$reminder") + [ "$reminder_count" = 1 ] \ + || fail "$kind scaffold must carry the two-line wake-cost reminder exactly once, found $reminder_count" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Never append a `resolved:` echo of a firstmate steer' "$brief" \ + "$kind scaffold permits a resolved echo of a firstmate steer" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'moving its message into `handled/` is the acknowledgement' "$brief" \ + "$kind scaffold lost inbox-move acknowledgement ownership" + done + + brief="$home/data/status-protocol-ship/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$brief" \ + "ship scaffold lost the closed set of reportable working phases" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Never append `working:` for a sub-step, a verification pass, or the start of re-review.' "$brief" \ + "ship scaffold permits chatty working events" + assert_no_grep 'done: {summary}' "$brief" \ + "no-mistakes scaffold still permits a pre-pipeline done event" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'A local commit, local validation pass, or pipeline start is never a `done:` event.' "$brief" \ + "no-mistakes scaffold does not forbid local done events" + assert_grep 'done: PR {url} checks green' "$brief" \ + "no-mistakes scaffold lost its only terminal done form" + + id="status-protocol-direct" + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode direct-PR >/dev/null 2>&1 + brief="$home/data/$id/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$brief" \ + "direct-PR scaffold lost its mode-specific working rule" + assert_grep 'done: PR {url}' "$brief" \ + "direct-PR scaffold lost its terminal done form" + assert_no_grep 'done: PR {url} checks green' "$brief" \ + "direct-PR scaffold inherited the no-mistakes terminal form" + + id="status-protocol-local-only" + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" firstmate --mode local-only >/dev/null 2>&1 + brief="$home/data/$id/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$brief" \ + "local-only scaffold lost its mode-specific working rule" + assert_no_grep 'implementation committed and validation started, or PR opened' "$brief" \ + "local-only scaffold still permits a PR working phase" + + brief="$home/data/status-protocol-scout/brief.md" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: starting the investigation, entering a distinct research phase, or beginning report writing.' "$brief" \ + "scout scaffold lost its research/report phase-only working rule" + assert_no_grep 'implementation committed and validation started, or PR opened' "$brief" \ + "scout scaffold still names ship-only working phases" + # shellcheck disable=SC2016 # Backticks are literal generated brief prose. + assert_grep 'Only after the report exists and is complete, append `done: {one-line conclusion}`' "$brief" \ + "scout scaffold permits done before its report exists" + pass "fm-brief.sh: generated status protocol wakes only for actionable phases and terminal outcomes" +} + # A blocked headless capture must route a Codex worker to the captain's signed-in # Chrome instead of letting the worker discard or downgrade the reference. test_ship_and_scout_briefs_render_browser_fallback_rule() { @@ -333,7 +416,7 @@ test_ship_mode_is_explicit_not_registry() { brief="$home/data/brief-explicit-a5/brief.md" grep -qx "Delivery contract: mode=no-mistakes" "$brief" \ || fail "registered direct-PR posture overrode the explicit --mode" - assert_grep "Firstmate will then instruct you to run /no-mistakes" "$brief" \ + assert_grep "then run /no-mistakes to validate and ship a PR" "$brief" \ "explicit no-mistakes brief did not render the pipeline definition of done" # An unregistered project is not a blocker either, because nothing is looked up. @@ -1024,6 +1107,7 @@ test_script_parses test_no_heredoc_in_command_substitution test_help_includes_entire_header test_ship_modes_generate_clean_briefs +test_status_protocol_is_phase_only test_ship_and_scout_briefs_render_browser_fallback_rule test_upstream_sync_template_renders_direct_pr_delivery test_ship_mode_is_required_and_closed_set diff --git a/tests/fm-task-delivery.test.sh b/tests/fm-task-delivery.test.sh index f7060a9e3ce..5bcede71a2c 100755 --- a/tests/fm-task-delivery.test.sh +++ b/tests/fm-task-delivery.test.sh @@ -429,6 +429,25 @@ STUB "$mode: promoted worker did not receive the Captain's intent subsection" assert_grep "## Firstmate spec" "$payload" \ "$mode: promoted worker did not receive the Firstmate spec subsection" + assert_grep "The shared status protocol rules carry over unchanged, but the mode-specific working-phase rule below supersedes the scout wording." "$payload" \ + "$mode: promoted worker did not receive the status protocol handoff" + assert_grep "Each status-file append wakes the supervisor and costs a full supervision turn." "$payload" \ + "$mode: promoted worker did not receive the status wake-cost reminder" + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. + assert_grep 'Never append a `resolved:` echo of a firstmate steer; moving its message into `handled/` is the acknowledgement.' "$payload" \ + "$mode: promoted worker did not receive the no-resolved-echo rule" + case "$mode" in + no-mistakes|direct-PR) + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started, implementation committed and validation started, or PR opened.' "$payload" \ + "$mode: promoted worker lost its allowed working phases" ;; + local-only) + # shellcheck disable=SC2016 # Backticks are literal generated delivery prose. + assert_grep 'Append `working:` only for a genuine phase change the supervisor would act on: work started or implementation committed and validation started.' "$payload" \ + "local-only: promoted worker lost its PR-free working phases" + assert_no_grep 'implementation committed and validation started, or PR opened' "$payload" \ + "local-only: promoted worker still permits a PR working phase" ;; + esac # Compare the public outputs of both real generation paths. The promoted # payload ends at its Definition of done, as does an ordinary generated @@ -459,7 +478,7 @@ STUB payload="$TMP_ROOT/promote-dod/payload-promote-dod-direct-pr" assert_grep "supersede the scout delivery rules and report-based Definition of done" "$payload" \ "promoted worker retained the scout delivery contract" - assert_grep "status protocol; the instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule" "$payload" \ + assert_grep "The instruction inbox and its acknowledgement; the escalation rules, including ask-user; and every safety rule carry over unchanged" "$payload" \ "promoted worker lost the scout protocols and safety rules that still apply" # The faster paths keep their own contracts rather than inheriting the pipeline's.