pi-crew 0.11.37 → 0.11.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +89 -0
- package/agents/critic.md +13 -0
- package/agents/oracle.md +14 -0
- package/dist/index.mjs +531 -526
- package/package.json +4 -3
- package/skills/delegation-patterns/SKILL.md +16 -0
- package/skills/real-test-pi-crew/SKILL.md +13 -2
- package/src/config/env-allowlist-registry.ts +22 -1
- package/src/config/env-vars.ts +23 -0
- package/src/extension/registration/foreground-run-controller.ts +21 -1
- package/src/extension/registration/lifecycle-handlers.ts +12 -0
- package/src/extension/team-tool/doctor.ts +125 -1
- package/src/prompt/prompt-runtime.ts +67 -5
- package/src/runtime/child-pi/child-pi-kill.ts +68 -2
- package/src/runtime/child-pi/child-pi-spawn.ts +20 -0
- package/src/runtime/child-pi/child-pi.ts +3 -1
- package/src/runtime/foreground-watchdog.ts +394 -17
- package/src/runtime/heartbeat/worker-heartbeat.ts +4 -0
- package/src/runtime/model/model-fallback.ts +5 -0
- package/src/runtime/process/orphan-reaper.ts +197 -22
- package/src/runtime/process/proc-stat.ts +8 -0
- package/src/runtime/process/reap-group.ts +499 -0
- package/src/runtime/process/run-descendant-sweep.ts +744 -0
- package/src/runtime/recovery/crash-recovery.ts +202 -0
- package/src/runtime/rpc/frame-client.ts +21 -1
- package/src/runtime/stale-reconciler.ts +343 -2
- package/src/runtime/task-runner/child-executor.ts +15 -1
- package/src/runtime/task-runner/post-execution.ts +112 -1
- package/src/runtime/task-runner/state-helpers.ts +28 -1
- package/src/runtime/verification/verify-gate-memo.ts +127 -0
- package/src/runtime/verification/verify-gate.ts +127 -1
- package/src/state/coordination/mailbox.ts +68 -0
- package/src/state/stores/state-store.ts +8 -0
- package/src/state/types.ts +61 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,92 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.39] — fix wave 11: worker descendant reaping (2026-10-24)
|
|
6
|
+
|
|
7
|
+
Closes the leak that motivated the wave: run `team_20261022110510_cefb9164af126460` reached `completed` on
|
|
8
|
+
2026-10-22 and six grandchild processes (`dpi-backend` ×5 + `caddy`, ~390 MB RSS) were still alive 24 h later.
|
|
9
|
+
Root cause (SPEC `fixwave11-worker-reaping-2026-10-24` GAP-0): pi's bash tool spawns EVERY command with
|
|
10
|
+
`detached: true`, so a worker's bash grandchildren own a process group that a group kill rooted at the worker can
|
|
11
|
+
never reach.
|
|
12
|
+
|
|
13
|
+
**11a — identity-proven group kill (F1–F4).**
|
|
14
|
+
|
|
15
|
+
- `src/runtime/process/reap-group.ts` is now the ONE place that may signal a process group: a refusal ladder
|
|
16
|
+
(POSIX only, `pgid > 1`, never our own real pgrp, live-identity-or-leader-proof, same uid, member of the requested
|
|
17
|
+
group, starttime present + matching) plus a bounded SIGTERM → SIGKILL escalation. SIGTERM is delivered
|
|
18
|
+
synchronously; the SIGKILL rung is handed back as `pending` and owned by `drainReapEscalations()`
|
|
19
|
+
(stale reconciler) or `flushReapEscalationsSync()` (shutdown), never dropped.
|
|
20
|
+
- `child-pi-kill.ts` (F1): an exited ROOT no longer means "nothing left" — when the snapshot proves the root led its
|
|
21
|
+
own group, the group is reaped.
|
|
22
|
+
- `orphan-reaper.ts` (F2): recorded workers are reaped through the guarded group ladder when a pgid is known,
|
|
23
|
+
identity-proven candidates bypass the zombie-scan gate, and a scanner throw is now fail-open for proven candidates
|
|
24
|
+
instead of a blanket no-op.
|
|
25
|
+
- `state/types.ts` + `child-executor.ts` (F3): `pgid`/`starttime`/`uid` are persisted on the checkpoint and worker
|
|
26
|
+
heartbeat (all optional — old records load unchanged), which is what makes identity proof real on the next run.
|
|
27
|
+
- `frame-client.ts` (F4): the prototype RPC frame client spawns detached. The production RPC path uses the SDK
|
|
28
|
+
`RpcClient`, which exposes no spawn knob — those workers stay non-leaders.
|
|
29
|
+
|
|
30
|
+
**11b — run-scoped descendant sweep (F7, the actual cure for GAP-0).**
|
|
31
|
+
|
|
32
|
+
- New `src/runtime/process/run-descendant-sweep.ts`: `collectRunDescendants()` / `reapRunDescendants()` enumerate
|
|
33
|
+
`/proc`, match the pi-crew env markers a descendant INHERITED, and reap only through a group that can be proven.
|
|
34
|
+
Markers used (verified in src, none invented): `PI_CREW_BROKER_RUN_ID` (`child-pi-spawn.ts:382`) ⇒ `run-id`;
|
|
35
|
+
`PI_CREW_KIND=subagent` + `PI_CREW_TASK_ID` + `PI_CREW_PARENT_PID` in the recorded set ⇒ `task-id`;
|
|
36
|
+
a `PI_CREW_PARENT_PID` that is a recorded worker pid and is dead ⇒ `parent-pid`. `PI_CREW_RUN_ID` is deliberately
|
|
37
|
+
NOT matched (registry: `test-only`, no src writer).
|
|
38
|
+
- Guards: Linux `/proc` only (else `[]`, fail-open); never self, never an ancestor, same uid only; the candidate must
|
|
39
|
+
have started no earlier than the run (> 60 s of pre-run clock skew rejected); the candidate's group must not be ours;
|
|
40
|
+
one signal per group per leader incarnation (idempotent across reconciler ticks); and an extra rung — a group whose
|
|
41
|
+
LEADER is alive and carries no marker is skipped (`leader-alive-unmatched`), so a live session's group is never hit.
|
|
42
|
+
- Call sites: `stale-reconciler.ts` sweeps ONLY when the run is terminal, the process is not a worker
|
|
43
|
+
(`PI_CREW_KIND`/crew depth 0), and every recorded runner/worker pid is dead; `team-tool/doctor.ts` (`focus=zombies`)
|
|
44
|
+
reports the survivors as `runDescendants` — report-only, `dryRun: true`, nothing killed.
|
|
45
|
+
- Kill switches: `PI_CREW_REAP_GROUP` (11a) and `PI_CREW_REAP_RUN_DESCENDANTS` (11b), both boolean, default ON, both
|
|
46
|
+
forwarded to the detached background runner (never to a worker) so a disable actually takes effect where the reaping
|
|
47
|
+
runs.
|
|
48
|
+
|
|
49
|
+
**Known limitations** (unchanged by design, documented on the module and in the SPEC): a descendant that calls
|
|
50
|
+
`setsid()` or runs inside its own container/init (`docker`, `systemd-run`) has a group we cannot prove and is left
|
|
51
|
+
alone; pi's bash tool remains the primary source of detached grandchildren — the upstream fix (tracked, non-detached
|
|
52
|
+
children for tool commands, or a cgroup/subreaper for the worker tree) is what would make this sweep unnecessary;
|
|
53
|
+
and the sweep can only match processes that still carry the markers (a scrubbed env is invisible).
|
|
54
|
+
|
|
55
|
+
**11c — sync-run descent proof + test pin (leader-approved options (a) + F1 fix).**
|
|
56
|
+
|
|
57
|
+
- Record sync-run `manifest.sessionPid` so foreground-run leaks satisfy the descent proof (wave 11c): a sync run has no `async.pid`, so a leaked worker grandchild whose `PI_CREW_PARENT_PID` points at the session pi itself could never be proven and was skipped forever; `sessionPid` is recorded at creation and treated as a recorded parent (dead-only, like the others). Also pin `PI_CREW_REAP_RUN_DESCENDANTS` in the sweep suite's setup so the tests no longer depend on the ambient kill-switch state. Battery addendum: `docs/real-test/reports/real-test-2026-10-24-wave11-battery-addendum.md` (F1/F2 closed, verdict PASS).
|
|
58
|
+
|
|
59
|
+
## [0.11.38] — Wave 10: pi-subagents lessons (10 ports) + watchdog hung-alert hardening (WD-DEDUP / WD-STALE-RECORD / WD-DELIVER-GUARD)
|
|
60
|
+
|
|
61
|
+
### Fixed — watchdog alerts for FINISHED runs: delivery-time guard (WD-DELIVER-GUARD, root-caused 2026-10-23)
|
|
62
|
+
|
|
63
|
+
- **fix(watchdog): "appears hung" still reached the user for an already-completed run — the root cause is DELIVERY-time staleness, NOT a missing finalize.** `finalizeRun` was correct both incidents (`run.completed` written, non-force `cancel` answered "already completed; nothing to cancel" 15s after the 06:12 alert): the alert 06:12:47.778 landed AFTER `run.completed` 06:12:28 because it had been EMITTED while the run was still genuinely running and was then parked in the host's follow-up queue — `pi.sendUserMessage(..., {deliverAs:"followUp"})` on a STREAMING session goes to `agent.followUp(...)` (`agent-session.js`) and is delivered at the next idle boundary, 28 minutes later in the incident. `clearQueue()` is all-or-nothing, so a queued item is unretractable: the emit-time re-verify (WD-DEDUP/WD-STALE-RECORD) cannot close this class. **Fix in `src/runtime/foreground-watchdog.ts` (WD-DELIVER-GUARD):** a hung alert is no longer handed to the host eagerly — while the session streams it is held in a bounded module-scope pending map (cap 256, one in flight per runId, self-clearing at a 10-min deadline) and retried every 500ms; at the idle boundary (the instant the host DELIVERS instead of queueing) the run state is re-read one final time and the alert is **DROPPED** when the run is terminal (`completed`/`failed`/`cancelled`/`blocked`) or when every task is terminal (stale record), with a one-time `PI_CREW_DEBUG` log per runId. **FAIL-OPEN by design:** a throwing loader, an absent manifest, or a host without the idle capability (`ctx.isIdle`) still delivers — a broken state file or an older host must never swallow a genuine hung alert. Wired at the one real call site (`foreground-run-controller.ts` → `startForegroundWatchdog({ ..., isSessionIdle })`). The "going quiet" handoff notice is delivery-gated too; the terminal-completion notice and the stale-record notice stay eager (they are true statements, and a late one is still accurate).
|
|
64
|
+
- **tests**: watchdog suite 15 → 20 — (i) emit-while-running + run completed before delivery ⇒ 0 messages, exactly 1 drop debug log; (ii) still hung at delivery ⇒ exactly 1 message, nothing left pending; (iii) unreadable state (throw) and vanished manifest ⇒ fail-open delivery; (iv) drop once per runId across many cycles (no re-queue, no re-alert — WD-DEDUP not regressed); (v) legacy caller without an idle probe is still guarded at handoff.
|
|
65
|
+
- **cap-expiry path now re-verifies too (no unverified delivery)** — residual closed from the 2026-10-23 ship battery (`docs/real-test/reports/real-test-2026-10-23-wave10-watchdog-ship-battery.md`): the other way out of the pending map is the 10-minute `deliverCapMs` deadline, taken when the session never goes idle (one foreground tool call blocking past the cap). That exit runs the SAME `classifyAtDelivery` as the idle boundary — terminal manifest or all-tasks-terminal ⇒ DROP with the one-time debug log, and only a run that still reads as hung (or an unreadable/absent record, fail-open) is handed over. "Late beats never" now applies to genuinely hung runs only. Pinned by 4 new tests (watchdog suite 20 → 24): cap expiry + all-tasks-terminal ⇒ 0 messages + 1 drop log (real state write), cap expiry + terminal manifest ⇒ 0 messages + 1 drop log, cap expiry + still hung ⇒ exactly 1 message / nothing pending, cap expiry + throwing loader ⇒ 1 message (fail-open). Mutation-checked: skipping the verify on the cap path turns both drop tests red.
|
|
66
|
+
|
|
67
|
+
### Fixed — watchdog stale-record silence + poll-interval override (WD-STALE-RECORD / WD-INTERVAL, root-caused 2026-10-23)
|
|
68
|
+
|
|
69
|
+
- **fix(watchdog): "appears hung" alerts for runs that are actually DONE** — root cause of the two live incidents (team_20261023030033 @04:13, team_20261023050900 @06:12) is NOT a missing finalize: `finalizeRun` wrote `completed` both times (`src/runtime/finalize-run.ts:514` updateRunStatus → `run.completed`, `:559` locked `saveRunManifestAsync`) — proven by `team action='cancel'` answering "already completed; nothing to cancel" 15s after the 06:12 alert landed. The alert was a **stale emit-time snapshot**: the watchdog emitted at ~05:44 during a 480s progress gap (`agents.json` `progress.lastActivityAt` older than the 5-min `AGENT_ACTIVITY_LIVENESS_MS`, `src/runtime/process-status.ts:68,95-125`), and `sendUserMessage({deliverAs:"followUp"})` then QUEUED it behind the parent's blocking tool call for 28 minutes — delivered 19s AFTER `run.completed`. Two bounded fixes in `src/runtime/foreground-watchdog.ts`: **(a) WD-STALE-RECORD** — the pre-emit re-verify now reads `tasks.json` too (`loadRunManifestById` already returns `tasks`); manifest still active BUT every task terminal (completed/failed/cancelled/skipped/needs_attention, `TEAM_TERMINAL_TASK_STATUSES`) ⇒ treat the record as stale: no hung alert, one-time `PI_CREW_DEBUG` log, and exactly ONE user-facing notice per runId ("run X record stale (all tasks terminal) — skipped hung alert", module-scope bounded map). FAIL-OPEN by design: empty/unreadable/corrupt task evidence is never read as "all terminal", so a genuinely hung run still alerts. **(b) WD-INTERVAL** — new `PI_CREW_WATCHDOG_INTERVAL_MS` (registered in the env registry + forwarding decision table as `gap`, default 300000 = the old constant) overrides the 5-minute poll cadence; an explicit `checkIntervalMs` still wins. Documented residual (not fixed here, needs a delivery-time hook the extension API does not expose): a snapshot can still be delivered late by the followUp queue — the emit-time gate cannot re-verify at delivery.
|
|
70
|
+
- **tests**: watchdog suite 9 → 15 — all-tasks-terminal ⇒ 0 hung alerts + exactly 1 stale notice; one task still running ⇒ alert fires; missing `tasks.json` ⇒ never read as terminal (no stale claim, unchanged silent skip, no crash); corrupt `tasks.json` ⇒ legacy hung alert kept (fail-open); stale notice is per-runId across watchdog restarts; `PI_CREW_WATCHDOG_INTERVAL_MS` drives the cadence. Registry snapshot/metadata tests extended for the new env var.
|
|
71
|
+
|
|
72
|
+
### Fixed — watchdog hung-alert dedup (WD-DEDUP, live-caught 2026-10-23)
|
|
73
|
+
|
|
74
|
+
- **fix(watchdog): repeated "appears hung" alerts for a run already terminal** — live pain 2026-10-23 (run team_20261023030033…): the foreground watchdog re-alerted "appears hung" for a run that had already completed, because (1) nothing deduped the notice across cycles/instances (the ARCH-5 notice cap of 2 still allowed one repeat, and a re-armed watchdog instance started the counter fresh) and (2) a check armed by an older poll cycle emitted without re-verifying the run state at emit time. Fix in `src/runtime/foreground-watchdog.ts`: **(a) alert-once per runId** — a module-level bounded FIFO map (cap 256, oldest entry evicted — no unbounded growth) suppresses every later "appears hung" emit for the same runId, even from a fresh watchdog instance; **(b) pre-emit re-verify** — the run state is reloaded immediately before any emit; a terminal (or vanished) run skips the emit entirely (one-time `PI_CREW_DEBUG` debug log) and keeps monitoring, so the next cycle's terminal branch still delivers the single completion notice as before. The ARCH-5 going-quiet handoff stays one-shot. Pinned by 4 new tests: alert-once (incl. restart immunity), re-verify skip at emit time, natural terminal transition after first alert, bounded-cap eviction.
|
|
75
|
+
|
|
76
|
+
### Wave 10 — pi-subagents lessons port (verified 26-item matrix, 2026-10-22)
|
|
77
|
+
|
|
78
|
+
All ten actionable items from the pi-subagents deep research (5 tiers: architecture → perf → prompt/meta → docs/intercom/missions → live measurement). Two lessons were CLOSED by evidence instead of ported: spawn/parallel performance (live measurement §10-11: pi-crew process-per-worker 0.57–0.76s beats pisub in-process 0.83s solo / 3.27s fan-out per child — nothing to port) and declaration-pinning (not applicable: pi-crew is process-per-worker, the platform owns tool declarations).
|
|
79
|
+
|
|
80
|
+
- **W10-1 cache TTL split (lesson #10)**: headless single-run workers now spawn with `PI_CACHE_RETENTION=short` — long-retention prompt-cache writes carry a higher per-write surcharge that a one-shot worker never amortizes. Injected at the spawn-options layer (same slot as `PI_CODING_AGENT_DIR`); surface panes and RPC warm sessions keep the parent default. The built.env canary rejects the key anywhere else.
|
|
81
|
+
- **W10-2 memoized green verify-gate (lesson #3)**: a PASS verdict is recorded keyed by the workspace tree OID (`git write-tree`) + check list + node version; a re-gate on a bit-identical tree serves the memo without re-running typecheck/test:critical (tree equality subsumes the M1-7 manifest bookends). Only PASS-with-real-runs records; FAILED/INCONCLUSIVE/drift never memoize; non-git cwd fails open; kill-switch `PI_CREW_VERIFY_GATE_MEMO` (default ON).
|
|
82
|
+
- **W10-3 mailbox typed reason + strict replyTo routing (lesson #22)**: messages gain an optional `reason` enum {need_decision, progress_update, interview_request} and responses resolve ONLY the request their own requester opened (A→B→C rule) — parallel asks can no longer cross-resolve.
|
|
83
|
+
- **W10-4 LLM-slop lint rules (lesson #21)**: `npm run check:slop-rules` (in `ci`) — comment/string-aware source scanners: `no-json-parse-nonnull` + `no-any-deref` STRICT (clean today), `no-double-cast` WARN-only over a 41-site baseline (burn-down follow-up). `// slop-ok: <reason>` escape hatch.
|
|
84
|
+
- **W10-5 decision-drift guardian (lesson #20)**: critic/oracle reconstruct the run's inherited-decisions baseline and must flag contradictions with `DRIFT-BLOCK:`; `extractVerifierVerdict` degrades a pass carrying DRIFT-BLOCK to "none" (fail-closed: a later truncated source cannot launder the marker).
|
|
85
|
+
- **W10-6 budget-boundary policy (lesson #16)**: delegation-patterns skill — budgets block read-tools, NOT mutation-tools; an elapsed timeout is not a mutation-safe boundary (no auto-revert racing the timeout).
|
|
86
|
+
- **W10-7 usage reconcile (lesson #14)**: every model attempt carries its own usage (failed-fallback tokens were invisible — task.usage shows only the FINAL run); `usageReconcile` note on the task when the sum-of-attempts vs final cost gap exceeds 5% with ≥2 usage-carrying attempts. Observability only.
|
|
87
|
+
- **W10-8 read-lane same-tree guard (lesson #4)**: READ-ONLY lanes dirtying their worktree (modified OR untracked — `git status --porcelain`, diff alone misses untracked) stamp an `unexpectedMutation` note; fail-open, leader decides.
|
|
88
|
+
- **W10-9 typed JSON gates (lesson #2)**: checks accept `{output:"json", schema}` (minimal subset type/required/properties/items/enum, zero new deps) — invalid stdout or schema mismatch FAILS at exit 0; parsed `structuredOutput` lands on the check result; typed gates never memoize.
|
|
89
|
+
- **Deferred (PoC when token economics demands it)**: fork prompt-cache tier (lesson #9, #2879: 52k→1.7k written) — needs an agent-side extension in the child pi process; architecturally separate from this wave.
|
|
90
|
+
|
|
5
91
|
### Fixed (live battery 2026-10-18 — real-test v0.11.24)
|
|
6
92
|
|
|
7
93
|
- **fix(gate): RT24-F1 root-gate config/autonomy read over-block** — `rootGateBlockReason` counted the `args` subcommand carrier as a config patch key, so worker-origin `config {args:"show"}` READS were rejected as "writes user-scope config" (live-caught in the v0.11.24 real-test battery; docblock promised reads open). `args` added to the carrier-key sets for `config` and `autonomy`; unknown junk keys stay write-classified (fail-closed). Pinned: reads with carrier keys open, writes hidden behind the same key still gated (`unset`/patch keys), user config byte-untouched. Live-verified end-to-end (fresh `pi -p`, worker env): read allowed + dumps config; `unset` write gate-blocked verbatim "root-session required".
|
|
@@ -8729,3 +8815,6 @@ the repo. Two genuine wins, plus an informational CI job that never blocks.
|
|
|
8729
8815
|
pattern (`jiti` flagged unused but is used via runtime path-loading). A
|
|
8730
8816
|
blocking gate would create an unpaid maintenance backlog unsuitable for a
|
|
8731
8817
|
solo-maintained extension.
|
|
8818
|
+
|
|
8819
|
+
### Hardening (W10 batch A)
|
|
8820
|
+
- `check-slop-rules` no-double-cast baseline ratchet (41, 2026-10-22 — burn down only) + real-CLI e2e pin; W10-7 reconcileUsage missing-finalUsage guard; W10-8 READ-ONLY lane mutation one-line stderr visibility; W10-9 structuredOutput JSON round-trip pin; W10-2 verify-gate memo time-dependent-skip intent pin.
|
package/agents/critic.md
CHANGED
|
@@ -27,6 +27,19 @@ You are a critic. Your job is to CHALLENGE plans and designs BEFORE execution
|
|
|
27
27
|
- Ownership conflicts (two phases writing the same files)
|
|
28
28
|
- Verification gaps (criterion that can't be objectively checked)
|
|
29
29
|
|
|
30
|
+
## Decision-drift guardian (W10-5)
|
|
31
|
+
Before critiquing, reconstruct the run's INHERITED DECISIONS baseline: every
|
|
32
|
+
decision already locked in the task context (SPEC/GATES constraints, pinned
|
|
33
|
+
architecture choices, knowledge entries, leader instructions). Then, for EACH
|
|
34
|
+
of your recommendations, answer: does it contradict any inherited decision?
|
|
35
|
+
- Contradiction found ⇒ you MUST open your verdict with a line containing
|
|
36
|
+
`DRIFT-BLOCK: <decision-ref> — <the contradiction in one sentence>` and
|
|
37
|
+
treat the recommendation as BLOCKED until the leader explicitly re-decides.
|
|
38
|
+
- No contradiction ⇒ say nothing about drift (do not pad with empty checks).
|
|
39
|
+
A PASS verdict carrying a DRIFT-BLOCK line is treated as NOT VERIFIED by the
|
|
40
|
+
runtime (post-execution.ts W10-5) — flagging a contradiction and approving
|
|
41
|
+
anyway is self-contradictory and downgrades to leader review.
|
|
42
|
+
|
|
30
43
|
## Output format
|
|
31
44
|
|
|
32
45
|
End with exactly this block:
|
package/agents/oracle.md
CHANGED
|
@@ -33,6 +33,20 @@ If the question is not strategic, name the right role in ROUTING and stop.
|
|
|
33
33
|
- For debugging guidance: give a ranked hypothesis list with the cheapest discriminating experiment per hypothesis.
|
|
34
34
|
- For simplification: name the exact deletions/refactors, and what breaks if done — or state that nothing breaks.
|
|
35
35
|
|
|
36
|
+
## Decision-drift guardian (W10-5)
|
|
37
|
+
You are the consistency guardian for LONG runs: reconstruct the INHERITED
|
|
38
|
+
DECISIONS baseline (SPEC/GATES constraints, pinned architecture choices,
|
|
39
|
+
knowledge entries, earlier leader decisions in the task context) before
|
|
40
|
+
advising. For EACH recommendation, answer: does it contradict an inherited
|
|
41
|
+
decision?
|
|
42
|
+
- Contradiction found ⇒ prepend a line `DRIFT-BLOCK: <decision-ref> — <the
|
|
43
|
+
contradiction>` to ORACLE_ADVICE and set ROUTING: leader-decision — the
|
|
44
|
+
leader must explicitly re-decide, not silently absorb your advice.
|
|
45
|
+
- No contradiction ⇒ stay silent about drift.
|
|
46
|
+
A pass-shaped verdict carrying DRIFT-BLOCK is treated as NOT VERIFIED by the
|
|
47
|
+
runtime (post-execution.ts W10-5); flagging a contradiction while endorsing
|
|
48
|
+
the plan anyway is self-contradictory.
|
|
49
|
+
|
|
36
50
|
## Output format
|
|
37
51
|
|
|
38
52
|
End with exactly this block:
|