diff --git a/emrg/server/competition_prompt.md b/emrg/server/competition_prompt.md index 16b0f72b..80e00d99 100644 --- a/emrg/server/competition_prompt.md +++ b/emrg/server/competition_prompt.md @@ -24,6 +24,8 @@ You are EMRG's competition participation module. **Every cycle you MUST fully ex {% if task.extra_prompt %} ## Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/feishu_prompt.md b/emrg/server/feishu_prompt.md index 17e7eea9..338bac78 100644 --- a/emrg/server/feishu_prompt.md +++ b/emrg/server/feishu_prompt.md @@ -15,6 +15,8 @@ The account, the host's own `open_id`, and any preference about how to answer ar from here. A value this section does not give you is a `blocked` item for the host — never invented, never defaulted. +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/ios_app_prompt.md b/emrg/server/ios_app_prompt.md index 3bbcbd1b..5f8b9011 100644 --- a/emrg/server/ios_app_prompt.md +++ b/emrg/server/ios_app_prompt.md @@ -19,6 +19,8 @@ You are EMRG as the **product owner** of iOS applications: you choose what to bu {% if task.extra_prompt %} ## Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/journal_prompt.md b/emrg/server/journal_prompt.md index a284ead5..6e73eabc 100644 --- a/emrg/server/journal_prompt.md +++ b/emrg/server/journal_prompt.md @@ -31,6 +31,8 @@ do. The host unblocks it by adding `author_id` to this task's `config` in tasks. {% if task.extra_prompt %} ## Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/open_source_prompt.md b/emrg/server/open_source_prompt.md index 6a8c34d2..cda22fa2 100644 --- a/emrg/server/open_source_prompt.md +++ b/emrg/server/open_source_prompt.md @@ -14,6 +14,8 @@ You are EMRG's open-source participation module: this task contributes to a repo {% if task.extra_prompt %} ### Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/paper_prompt.md b/emrg/server/paper_prompt.md index 28aeead9..b4557c6a 100644 --- a/emrg/server/paper_prompt.md +++ b/emrg/server/paper_prompt.md @@ -9,6 +9,8 @@ You are EMRG's paper writing module. **Every writing session MUST fully execute {% if task.extra_prompt %} ## Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/promote_prompt.md b/emrg/server/promote_prompt.md index 94ab886b..925c988f 100644 --- a/emrg/server/promote_prompt.md +++ b/emrg/server/promote_prompt.md @@ -5,6 +5,8 @@ You are EMRG's community promotion module. **Every cycle you MUST fully execute {% if task.extra_prompt %} ## Task-specific Instructions (extra_prompt from tasks.yml) +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/emrg/server/self_media_prompt.md b/emrg/server/self_media_prompt.md index 44301030..d13fba35 100644 --- a/emrg/server/self_media_prompt.md +++ b/emrg/server/self_media_prompt.md @@ -19,6 +19,8 @@ Voice, audience, the account's positioning, standing subject preferences and any be written about are read from here. A value this section does not give you is a `blocked` item for the host — never invented, never defaulted. +These instructions amend this task for one installation; they are not a second task. Where they disagree with the rest of this prompt about the same subject, the narrower instruction governs — and where the disagreement is with a rule this prompt forbids something by, this prompt governs and the disagreement is a `blocked` item for the host rather than something to settle in silence. A subject these instructions leave unmentioned is not a disagreement. + {{ task.extra_prompt }} {% endif %} diff --git a/tests/test_prompt_templates.py b/tests/test_prompt_templates.py index 6229274f..c353a42f 100644 --- a/tests/test_prompt_templates.py +++ b/tests/test_prompt_templates.py @@ -3391,3 +3391,120 @@ def test_the_ios_feasibility_pin_answers_both_ways() -> None: f"deleting {phrase!r} from §3.4 still reads as present, so the pin above would " "stay green on a section that had lost the rule" ) + + +# ── the supplement block states which side wins, in every template that renders it ──────── +# +# Every task prompt renders an installation's `extra_prompt` under one heading. Two things can +# be said there, and for a long time only one of them was: what to do when the block is +# *silent* (two templates said it), and which side wins when the block *contradicts* the prompt +# (none did). A round that reads both and finds them disagreeing picks one, and which one it +# picks is then an accident of reading order — that is the failure this pin exists to prevent. +# +# The pin is deliberately **not** a blessed sentence. What it measures is that every carrier +# states the *same* statement, that it appears once, and that it sits inside the block: a +# rewording applied to every carrier stays green, while a rewording (or a deletion) in one +# carrier reddens and names that file, which is exactly the drift worth catching. The statement +# is read as the block's last paragraph, so a carrier that also explains how to read a *silent* +# block keeps that paragraph beside this one instead of losing it. + +_EXTRA_PROMPT_HEADING = "Task-specific Instructions (extra_prompt from tasks.yml)" +_EXTRA_PROMPT_INTERPOLATION = "{{ task.extra_prompt }}" + +#: The one template deliberately outside the pin, with the reason it is: editing it is a separate +#: class of work (a prompt-specific change), and it already settles this subject for its own +#: readers in §1.7 — the supplement's contradictions are adjudicated there from the source. +_EXTRA_PROMPT_EXEMPT = {"evolution_prompt.md"} + + +def _extra_prompt_block(text: str) -> str: + """The prose between the block's heading and the interpolation; "" when either is absent.""" + if _EXTRA_PROMPT_HEADING not in text or _EXTRA_PROMPT_INTERPOLATION not in text: + return "" + start = text.index(_EXTRA_PROMPT_HEADING) + len(_EXTRA_PROMPT_HEADING) + end = text.index(_EXTRA_PROMPT_INTERPOLATION) + return text[start:end] if end > start else "" + + +def _extra_prompt_statement(text: str) -> str: + """The block's last paragraph — the precedence statement, when one is there.""" + headed = text.split("\n", 1)[-1] if text.startswith("\n") else text + paragraphs = [p.strip() for p in headed.split("\n\n") if p.strip()] + return paragraphs[-1] if paragraphs else "" + + +def _extra_prompt_carriers() -> dict[str, str]: + """Template name -> its text, for every template that renders a supplement.""" + found: dict[str, str] = {} + for path in sorted(PROMPTS_DIR.glob("*_prompt.md")): + text = path.read_text(encoding="utf-8") + if _EXTRA_PROMPT_INTERPOLATION in text: + found[path.name] = text + return found + + +def test_every_template_that_renders_a_supplement_says_which_side_wins() -> None: + """One statement, the same in every carrier, once each, inside the block.""" + carriers = _extra_prompt_carriers() + assert len(carriers) > 5, ( + f"only {len(carriers)} template(s) render a supplement, so this walk is not reading the " + "family it claims to measure" + ) + assert set(carriers) - _EXTRA_PROMPT_EXEMPT, "every carrier is exempt, so nothing is measured" + + statements: dict[str, str] = {} + for name, text in carriers.items(): + if name in _EXTRA_PROMPT_EXEMPT: + continue + statement = _extra_prompt_statement(_extra_prompt_block(text)) + assert statement, ( + f"{name} renders a supplement but states nothing about which side wins when the " + "block contradicts the prompt, so a round reading both has to pick one" + ) + assert text.count(statement) == 1, ( + f"{name} states its supplement rule {text.count(statement)} times; this template's " + "own rule is that a fact lives in exactly one place" + ) + statements[name] = statement + + distinct = set(statements.values()) + assert len(distinct) == 1, ( + "the carriers state different rules, so the same round reads a different answer " + "depending on which template it was given: " + + "; ".join(f"{n} differs" for n, s in statements.items() if s != max(distinct, key=list(statements.values()).count)) + ) + + +def test_the_supplement_precedence_pin_answers_both_ways() -> None: + """The failure and the false failure, both supplied on synthetic text. + + Deletion must read as a carrier that lost the rule rather than as agreement, and a rewording + carried into every copy must stay green — otherwise the pin would be requiring a sentence + rather than the rule. + """ + text = (PROMPTS_DIR / "promote_prompt.md").read_text(encoding="utf-8") + statement = _extra_prompt_statement(_extra_prompt_block(text)) + assert statement, "the live template carries no statement for these arms to remove" + + # Arm 1 — the statement deleted: the block now has no paragraph, which is a carrier that + # lost the rule, not one that agrees with the others. + deleted = text.replace(statement, "") + assert _extra_prompt_statement(_extra_prompt_block(deleted)) == "", ( + "deleting the statement still reads as a statement, so this walk would stay green on a " + "template that had lost the rule" + ) + + # Arm 2 — a rewording carried into every copy: still one rule, and still one value across + # carriers, so the pin must not be reading a sentence. + reworded = text.replace(statement, "A different wording of the same rule.") + new_statement = _extra_prompt_statement(_extra_prompt_block(reworded)) + assert new_statement == "A different wording of the same rule.", "the arm did not land in the block" + assert new_statement != statement, "the arm changed nothing, so it measured nothing" + assert reworded.count(new_statement) == 1 + + # Arm 3 — the statement moved out of the block: it must no longer be read as the block's own + # rule, or a template could keep the words beside the block and still leave it unsettled. + displaced = text.replace(statement, "").rstrip() + "\n\n" + statement + "\n" + assert _extra_prompt_statement(_extra_prompt_block(displaced)) == "", ( + "a statement pasted outside the block is still read as the block's rule" + )