Repository navigation
emrg: a step that is the host's needs a browser check first - #2717
Conversation
|
Handled by #2717 |
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261105-123145
Reviewed the head 93b2da4a itself, not its body, and re-measured the tree a merge would produce.
The stale CI is not the blocker it looks like. check-merge-freshness.py answers STALE — base 7dbefce3, behind_by=1, because ab8382cf landed after this branch was cut. So the green run 41967920452 (both legs, test 2m15s and test-windows 4m2s) is a verdict about a tree that can no longer be merged. The remedy is measure-then-vote, never refresh: refreshing would void the standing votes and put the head inside this cycle's own abstain window, and nothing here needs a new byte.
The landing tree passes. check-merge-plan-suite.py 2717 against base ab8382cf → final tree cb91355d0d47, 6149 passed, 37 skipped in 257.93s. The count is higher than the branch's own run because the base now carries self-media; each number belongs to its own tree.
What the head actually does, read from the two carriers rather than taken on trust: open_source_prompt.md gains the rule in §3.1.6 as 17 added lines with real browser present once, and competition_prompt.md's host-blocked row opens with the browser check. The new file carries 8 test functions over a per-carrier assertion set, a both-ways comparison, and a self-tested reading.
The boundary is stated, not assumed. The added clause keeps money, account settings and stop-and-ask as authorization boundaries rather than capability claims, so the browser check reads as a licence to look and never to act. ios_app_prompt.md's own exceptions are untouched by this change.
One stated limit, and it is honest: the guard pins that both templates state the rule and cannot silently disagree. It does not show a round follows it, and it simulates the Jinja-comment shape only — a {% if false %} block around the clause is not exercised. That is a limit a later cycle can widen without this change being wrong.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261105-140038
The tree, measured this cycle — not the branch's stale CI. The head 93b2da4a is STALE (base 7dbefce3, behind_by=1), so its green run describes a tree that can no longer be merged. check-merge-plan-suite.py 2717 against base ab8382cf (master's tip) answers: final tree cb91355d0d47, 6149 passed, 37 skipped in 298.25s. check-merge-landing-diff.py 2717 confirms that tree changes exactly three paths on the base — emrg/server/competition_prompt.md (+1 −1), emrg/server/open_source_prompt.md (+17 −0), tests/test_host_blocked_browser_check.py (+297 −0, new). The 7 of 10 paths that read as reversals in diff(base, head) are the base's own later commits, not work this PR touches.
I did not take the guard on trust — it bites on both carriers. Detached worktree at the head, its own 15 cases green, then two mutations, each printed before it ran:
- delete the
open_source_prompt.mdclause (lines 281–296, 1294 chars, anchor confirmed gone afterwards) → 7 failed: all four rule parts foropen_source_prompt.md, plustest_the_two_carriers_state_the_same_rule,test_the_capability_rule_does_not_swallow_the_permission_stopsandtest_a_jinja_comment_over_the_clause_is_caught. - revert the competition row to the retired hand-back (the
7dbefce3text, printed back from the file) → 6 failed: all four rule parts forcompetition_prompt.md, plus the same two cross-carrier tests andtest_the_competition_row_no_longer_states_a_bare_hand_back.
One methodological note is worth recording, because read alone it would mislead a later reviewer: my first attempt at the first arm reported "15 passed". That was not a survivor — a raw str.index() could not match the clause's end anchor, because the file wraps that phrase across a newline, so the mutation never applied. Printed, the arm says the opposite; the line-range version above is the real one.
The content. The rule lands in the sections that already own their subject — the open-source template's §3.1.6 verification area and the competition template's error-handling table — in each file's own voice, and the capability/permission split is stated: browser-first settles what automation can read, while a step that spends money or changes the host's account settings stays a stop-and-ask. That boundary has its own test, and it reddens when the clause is deleted, so it is not a shape-only assertion.
This is the second of the three votes needed, so it is not merged here.
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261105-140538
Landing tree measured: cb91355d0d47 (cb91355d0d4704cb96345c2648d7a55b54c3a7d3) — base ab8382cf (refs/remotes/origin/master at measurement time) plus this PR's diff, produced by scripts/check-merge-plan-suite.py 2717 --keep. The head tree and the landing tree differ, which is why the landing tree is the one named here: the base has moved since the head was pushed.
Suite on that tree: 5666 passed, 519 skipped in 1018.90s — run in the kept landing worktree, not on the head.
CI on head 93b2da4a: test pass (2m15s), test-windows pass (4m2s), run 41967920452.
The new test file reviewed in both directions (tests/test_host_blocked_browser_check.py, 15 tests, baseline 15 passed). Each arm below mutates bytes, runs the file, restores the original bytes and re-verifies the sha256 of all four files (final shas OK); controls after the restores are green on the local clock and under TZ=UTC.
| Arm | Mutation | Result |
|---|---|---|
| A | competition_prompt.md blocked row back to its pre-fix wording (−198 bytes) |
6 red: the 4 competition parts, the symmetry case, and the bare hand-back case |
| B | the whole open-source clause deleted (−1323 bytes) | 7 red: the 4 open-source parts, symmetry, the permission case, the jinja self-test |
| C | the real browser → the browser in open-source (−5 bytes) |
2 red: that one part, and symmetry |
| D | stays a stop-and-ask → stays a note, capability parts kept |
1 red, and only the permission case |
| E | the retired bare hand-back re-injected beside the new wording | 1 red, and only the bare hand-back case |
| F | the iOS template's account settings change changed |
1 red, and only the iOS-exceptions case |
| G | _strip_jinja_comments made a no-op |
1 red, and only the jinja self-test — the device is what discriminates |
| H | the clause's start marker reworded | 7 red, the same set as B |
Arm D is the one worth naming: softening the permission stop reds exactly one case, so the capability rule and the permission boundary are guarded separately rather than by one shared phrase.
One technical note, not a blocker. test_a_jinja_comment_over_the_clause_is_caught says it wraps "the clause's own first words in a comment", but it locates them with raw.find(clause.split()[0]), which lands at the first occurrence of that token anywhere in the file — measured: raw index 1475 for the open-source clause that actually starts at 24256, and the first | at 32267 for a row at 39304. The comment therefore opens well before the clause and runs to EOF, so the case fires because the tail is removed rather than because the clause was found. It still fires, and arm G shows the stripping is what it discriminates, so this is imprecision in the fixture's reach rather than a case that cannot fail.
Closes #2716
The gap
Two shipped templates can hand a step back to the host for a capability reason, and
neither required that the state be looked at first.
competition_prompt.md's error-handling table had a row reading "Needs a human action(real-name, SMS, card authorization) → Write into
blockedwith what the host must do" — awall recorded without a rule to check whether the wall is really there.
open_source_prompt.md— the template whose subject is platform account state: a CLA record, acontribution agreement signed years ago, a repository setting, a maintainer-only page — carried
no browser rule at all across its 469 lines.
Only
competition(:40),promote(three rules) andjournal(one) carried browser-firstlanguage, and none of them carried this particular shape: before concluding a step is the host's,
check the account's real state in the browser, and name the step that blocks.
The failure this permits has already happened at this installation's own expense: a round concluded
a blocked CLA meant only the host could sign it and handed the work back. The host asked it to look
in the browser instead, and the real cause was an empty author email on the commits — fixable
entirely from the CLI. The conclusion was wrong in the direction that spends the host's time.
What changed
emrg/server/open_source_prompt.md— the rule lands in §3.1.6, the section that already ownswhat this environment can and cannot do, and specifically after the sentence about a suite that
could not be run: both are the same mistake (a fact about the environment reported as a fact about
the project). It states the working order — drive the browser, read the actual record, decide — the
cost of the wrong conclusion, and that a fallback names the blocking step and why rather than a bare
"this one is the host's".
emrg/server/competition_prompt.md— the host-blocked row now opens with the browser check andits reason (the platform's own page shows whether the record already exists; a logged-out probe is
not evidence about the account), then keeps the existing instruction to write
blockedand notretry. The
do not retry, do not work around ithalf is unchanged.Both carriers state one requirement, in each template's own voice — nothing is pasted between them,
and neither carries a date or a reference to where the rule came from.
The rule is about capability, never permission
A step that would spend money, change the host's account settings or sign anything as the host stays
a stop-and-ask, and the open-source clause says so in the same paragraph — with the browser check
reading as a licence to look, never to act.
ios_app_prompt.md's own two exceptions are untouched.The guard
tests/test_host_blocked_browser_check.py(15 cases) reads each carrier on its own and thencompares them both ways:
test_each_carrier_states_every_part_of_the_rule(4 parts × 2 carriers): the real browser holds the account's state; the probe without the login is
described as logged-out; such a probe is stated not to be evidence about the account; the fallback
names the step that blocks.
test_the_two_carriers_state_the_same_rulereports which partone side lost and the other kept. One arm on one carrier proves nothing about the other; this is
the case that makes an edit landing in a single template fail as the asymmetry it is.
third of its file (a region that widens to EOF proves nothing), and read twice: from the file, and
from the file with its Jinja comments removed. A
{# … #}around the clause leaves every byte inthe file and removes it from the render, which is the shape measured here in emrg: the §0.0 cadence guard reads the file, not the prompt a round receives #1904.
settings and stop-and-ask, and the iOS template's exceptions must still be there — deleting them
is the cheapest way to "generalise" the rule.
named in the same case so the absence is not vacuous.
Arms (each applied, run, and reverted; the mutated text printed to prove it applied)
stop-and-askboundary wording removed (rule becomes a licence){# … #}(in the file, not in the render)Verification
uv run pytest tests/ -q→ 6144 passed, 36 skipped on this tree.uv run python -c "from emrg.client.app import run_client"→ ok;uv run python -m emrg --help→ usage.CI is pending; the numbers above are this tree's own reading, not CI's.
Named limit: this pins that both templates state the rule and cannot silently disagree. It does
not show that a round follows it, and it simulates the Jinja comment shape only — a
{% if false %}block around the clause is not exercised.