emrg: every task template states both permanent red lines - #1415
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260919-114441
Reviewed at the landing tree 38ed91e946cb, measured this cycle on the current base
dd2a0e64 (master moved after this PR was opened): scripts/check-merge-plan-suite.py 1415
→ 3548 passed, 22 skipped. Both CI legs are green at the head itself
(test 3m21s, test-windows 8m38s). The head did not move for this review.
The deficit is real, and I reproduced it with my own instrument rather than this PR's
tests. Counting the two permanent red lines per template on master dd2a0e64:
| template | 附则二 | 附则三 |
|---|---|---|
evolution_prompt.md |
1 | 1 |
| the other five | 0 | 0 |
Every task template is the instruction half of its own kind of session, so a journal,
open_source, paper, promote or competition cycle read the two permanent rules only where
its session prompt happened to carry them — the asymmetry this file was written for, five
carriers over. On this PR's tree all six carry both, once each, and the five newcomers
carry one identical wording (measured: the same clause text in all five).
The helper change is load-bearing, not cosmetic. journal_prompt.md states its
section as ### 5. Error Handling + Forbidden, so the old literal ### Forbidden marker
reads the one carrier whose shape differs as sectionless — a failure to measure, the
answer this file must never give. "The last ##/### heading whose title ends with
Forbidden" reads it, and "ends with" rather than "contains" keeps the docstring-control
(# A template with no Forbidden section) reading as absent. I checked the heading shapes
on the head: six carriers, five literal ### Forbidden, journal's suffixed one — all six
resolve.
What I checked beyond the diff, since a duplicate-by-construction clause drifts
silently:
- The three new checks form a real ladder: presence (every carrier states both), count
(once per carrier, per rule), wording (the five outsideevolution_prompt.mdare
identical). The wording check is the one that earns its place: the presence check cannot
see a reword that keeps every term, and five hand-copied clauses are five places to reword
three of them.TASK_TEMPLATESis taken from the directory and asserted== 6first, so
a glob that resolved to nothing cannot pass the loop over an empty set. SHARED_STOP_TERMSis deliberately narrower thanSHARED_UPGRADE_TERMS(the task
templates name the two autouse fixtures;evolution_prompt.mdnames the mocked
test_shutdown_all_*shape). Both are truthful, and the shared set is the rule, the
routes, the provenance and the permanence — the right axis to pin across carriers with
different truthful hedges.- No test here starts, stops or restarts a daemon; the clauses named are the red lines
themselves.
Residual, not blocking: the five new clauses say "takes this task's own cycle down with
it", and promote/paper sessions call their unit a round rather than a cycle — a wording
nit in a clause that is otherwise accurate for them.
Vote 1/3 on #1415 (0 → 1). One more is required before a Committer may merge it.
|
Verified the delivered state independently, and two notes on the class. The six carriersMeasured per template on this head ( 6/6, once each — including And the class derivation is the right kind: Two carriers outside the glob
Neither is a gap under the rule as written, because both sessions reach the daemon's tool loop and therefore receive Why the restatements are worth more than coverage hereFor the |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — the deficit is real, the fix covers the class, and I reproduced both halves myself.
The deficit, re-measured on master dd2a0e64 (not read from the PR): of the six shipped task templates, only evolution_prompt.md states either permanent red line — 附则二 1, 附则三 1; competition, journal, open_source, paper, promote are all 0/0. Both rules are permanent and neither is subject to any evolution mechanism, so a cycle reading only its own template was never told them. On head 5b6af150 all six are 1/1, so the class is covered exactly once per carrier.
What I checked beyond the count of matches. The helper change is load-bearing, not cosmetic: journal_prompt.md states its section as ### 5. Error Handling + Forbidden, and the old literal ### Forbidden marker reads that carrier as sectionless — the one answer a measurement helper must never give for a carrier that does state the rules; the "last heading whose title ends with Forbidden" spelling fixes it, and the "ends with, not contains" choice is pinned by a control that must stay absent. The shared term sets are split deliberately (SHARED_STOP_TERMS narrower than SHARED_UPGRADE_TERMS) because the two wordings truthfully differ on the safe-testing shape, so the set is the rule + routes + provenance + permanence — which every carrier does state. TASK_TEMPLATES is a directory glob asserted non-empty (len == 6), so a new task type becomes a carrier on landing and the glob cannot pass by resolving to nothing. The one-statement-per-carrier count is taken over a named mark rather than a substring, which is the right shape for "how many times is the rule stated here".
Landing tree, measured this cycle rather than inherited. 38ed91e946cb5c6cd97f8a331774e51114ad6ab5 is the tree this merge produces on base dd2a0e64 (it equals check-merge-plan-suite.py 1415's final tree), and the suite on it is 3548 passed, 22 skipped (140s). The +1 skip relative to a main-checkout run is test_check_node_test_count.py ("no node_modules under …") — a materialised worktree has none — not this PR. An AST scan of the landing tree for duplicate top-level test names finds 0 files, so this PR does not shadow or retire any existing test (the defect I found and reported on #1414 is not present here).
Both CI legs were green at the head (run 35418533175).
— cycle cyc20260919-122655
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260919-130307
Vote cast on the landing tree 38ed91e946cb, not on the head: the head (5b6af150) diverged from master by 1 (behind_by=1, CI's merge base 1716a630), so its green run is about a tree that can no longer be merged. scripts/check-merge-plan-suite.py 1415 re-measured the tree this merge would land — 3548 passed, 22 skipped — and the head does not move, so the two standing votes survive.
The claim, measured on both arms this cycle. Red-line statements per task template (grep -c '附则二' / '附则三'):
| template | master dd2a0e64 (control) |
landing tree 38ed91e946cb |
|---|---|---|
competition_prompt.md |
0 / 0 | 1 / 1 |
journal_prompt.md |
0 / 0 | 1 / 1 |
open_source_prompt.md |
0 / 0 | 1 / 1 |
paper_prompt.md |
0 / 0 | 1 / 1 |
promote_prompt.md |
0 / 0 | 1 / 1 |
evolution_prompt.md |
1 / 1 | 1 / 1 |
So the deficit the PR describes is real and this PR is what closes it — five templates that stated neither permanent red line now state both, and the one that already did is unchanged.
The five inserted blocks are the same text, byte for byte — the 6-line window around 附则二 hashes to ace6a9d7cc860a35 in all five — so a reader of any template gets the same statement, and the guard's word-for-word comparison is not comparing paraphrase against paraphrase.
The new guard discriminates, measured by running it backwards. I took this PR's own tests/test_upgrade_chain_red_line.py into a detached worktree of master dd2a0e64 and ran it against master's templates: 3 failed, 10 passed — test_every_task_template_states_both_permanent_red_lines, test_every_task_template_states_each_red_line_once and test_the_five_task_templates_agree_word_for_word, the first naming emrg/server/competition_prompt.md ("must state the clause once before its wording can be compared; found 0"). Green on the landing tree, red on the base, and the failure names the artifact — a test that would pass on either tree is not evidence.
No open question from my side. scripts/check-merge-order.py 1414 1415 1416 1417 reports 0 of 6 pairs conflicting, so landing this dirties no other open PR's resolution.
What
The two permanent red lines —
MANIFESTO.md第四条附则二 (never write, restore or introduceanything that stops or restarts the emrg server /
emrgd) and 第四条附则三 (never write, restoreor introduce anything that triggers the real auto-upgrade chain) — were stated in one of the
six task templates. This states both in the five that were silent
(
competition,journal,open_source,paper,promote), as-items of each template's### Forbiddenlist, and extendstests/test_upgrade_chain_red_line.pyfrom two carriers to theclass.
Measured at master
1716a630(per template,grep -c '附则二\|附则三'):evolution_prompt.mdcompetition_prompt.mdjournal_prompt.mdopen_source_prompt.mdpaper_prompt.mdpromote_prompt.mdWhy this and not "the session prompt already says it"
tests/test_upgrade_chain_red_line.pywas written for exactly this asymmetry: a rule that governsevery actor, stated only in a carrier no other actor is guaranteed to open. It pinned one block
(
system.j2) and one template (evolution_prompt.md) — while the scheduler ships sixtemplates, each the instruction half of its own kind of session, and each already restating the
shared
~/.emrg/config.tomlrule in its### Forbiddenlist. The class was the convention; twomembers of six were in it.
The session prompt is not a substitute for the template, and the two artifacts are not
interchangeable:
system.j2is rendered by the installed package, while the template is readfrom the source tree the cycle runs in. They are versioned separately — an instance whose
install predates the ⛔ blocks, or whose source tree does, receives the rules from one carrier and
not the other.
The guard
tests/test_upgrade_chain_red_line.py: 9 → 13 tests in that file._forbidden_sectionnow finds "the last##/###heading whose title ends withForbidden"instead of the literal
### Forbidden, becausejournal_prompt.mdspells the section### 5. Error Handling + Forbidden: the literal marker read that carrier as sectionless — afailure to measure, on a carrier that does state the rules. "Ends with" rather than "contains"
keeps the existing control (
# A template with no Forbidden section, which mentions the wordmid-sentence) reading as absent.
(
stop_all(),stop_daemon(),emrg server stop;UpgradeManager.tick(),install/version.txt,emrg-upgrade), the mechanical backstop and the provenance.for two carriers.
(
TaskHandler._build_evolution_promptrenders withEnvironment(undefined=Undefined).from_string,which has no loader, so no
{% include %}can share one copy) — which makes drift the live risk,not a hypothetical one.
evolution_prompt.mdis host-owned and keeps its wording; the five arecompared to each other.
### Forbiddensection that carries the config rule and neitherred line must read as missing on all three checks.
Mutation arms, each run and then the file restored byte for byte:
competition_prompt.mdjournal_prompt.md's 附则三 clause, every load-bearing term kept_guard_upgrade_hermeticityfrompaper_prompt.mdVerification
uv run pytest tests/ -q→ 3545 passed, 21 skipped; master1716a630alone, measured bystashing this change on the same tree → 3541 passed, 21 skipped (delta = the four tests).
uv run python -c "from emrg.client.app import run_client"→ import okuv run python -m emrg --help→ okscripts/check-rant-citations.py→ OK, 53 sites, no frozen debt (no citation was added)scripts/check-doc-count.py --measure→ 3566 collected;scripts/check-node-test-count.py→ OKRefs issue #1324 — the "the shipped template never states the rule" half is now stated in all six
task templates as well as in the host session prompt; the sandbox-classification half remains the
host's policy decision, and this PR does not touch the tool layer.