Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions emrg/server/ios_app_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ At low install volume, acquisition happens on the store page rather than inside

**At this stage installs are the only hard number.** Price and metadata are not the bottleneck while nobody sees the app, so a pricing experiment is judged by the installs it produces rather than by the argument for it — including a decision to make the app free.

**A price change is a distribution event, not a setting.** Installs move on the day the price moves, not on the day a build ships: a run of releases at an unchanged price produces nothing, while a cut produces a jump on its own day and a tail that is still producing installs a week later. So a cut is planned as a launch with one day to spend, never as an initial state with none — list at a small non-zero price first, choose the day for the drop, and work that day like a release. What has to be true the day before is the store's own list: the fields the platform freezes already live on a released version, the page complete, the territories open. A drop spends its one day on whatever the page is that morning.

**Read the install figure from the sales report, not from a chart.** The platform's daily sales report is the measurement; a number reconstructed from a web trend graph is an estimate that under-reports the same interval, and a day the report does not answer for is a day of zero rather than a missing reading.

#### 5.3 The platform locks a submission, so the order is the rule

Submitting is a sequence of API calls rather than a form filled in once, and two of its steps refuse work done in the wrong order. Both refusals belong to the platform, not to the client that receives them, and both are cheap to avoid and expensive to discover.
Expand Down
60 changes: 60 additions & 0 deletions tests/test_prompt_templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -3599,3 +3599,63 @@ def test_a_render_context_written_by_a_test_is_read_from_the_builder():
+ ", ".join(faults)
+ " — render through `_test_context`, whose keys are the real builder's"
)


#: The one day a price cut has, and what §5.2 must say about spending it.
IOS_PRICING_HEADING = "#### 5.2 The store page is where a small app is found"
IOS_PRICING_ENDS_AT = "#### 5.3 The platform locks a submission, so the order is the rule"

IOS_PRICING_PHRASES = (
"A price change is a distribution event, not a setting",
"planned as a launch",
"spends its one day",
"not from a chart",
)


def _ios_pricing_section(text: str) -> str:
return _bounded_section(text, IOS_PRICING_HEADING, IOS_PRICING_ENDS_AT)


def test_the_ios_app_template_plans_a_price_cut_as_a_distribution_event() -> None:
"""§5.2 must say that a price change is an event with one day to spend.

Not decoration: the section already judged a pricing experiment by the installs it
produces, and that sentence alone reads as permission to ship a price and wait. What the
install data says is the opposite — installs move on the day the price moves, and a run of
builds at an unchanged price moves nothing — so a task told only the first half makes the
app free from the start and spends the event on a store nobody is looking at. The
paragraph is what turns the permission into a plan, and the one after it says which number
the plan is judged by, because a reading reconstructed from a graph under-reports the
interval it claims to describe and a day the report omits is a zero.
"""
text = _IOS_TEMPLATE.read_text(encoding="utf-8")
section = _ios_pricing_section(text)
missing = _missing_phrases(section, IOS_PRICING_PHRASES)
assert not missing, (
f"ios_app_prompt.md §5.2 no longer plans a price cut as an event: {missing}. Without "
"these the section permits a pricing experiment and says nothing about when to run it, "
"so the app is made free as its initial state and no event is ever spent."
)

# One home: a rule stated twice leaves a reader holding two answers with no way to choose.
stated = text.count("A price change is a distribution event")
assert stated == 1, (
f"the price-as-an-event rule is stated {stated} times in ios_app_prompt.md; this "
"template's own rule is that a fact lives in exactly one place"
)


def test_the_ios_pricing_pin_answers_both_ways() -> None:
"""The control: a section that has lost the rule reads as missing, not as passing.

Without it, a §5.2 emptied of the rule and a reader pointed at the wrong region read the
same as a section that still carries it — which is the defect `_bounded_section` exists to
prevent, one level up.
"""
section = _ios_pricing_section(_IOS_TEMPLATE.read_text(encoding="utf-8"))
for phrase in IOS_PRICING_PHRASES:
stripped = section.replace(phrase, "")
assert phrase in _missing_phrases(stripped, IOS_PRICING_PHRASES), (
f"{phrase!r} is not pinned: a §5.2 stripped of it still reads as carrying the rule"
)
Loading