GE flipping entry for the OSRS Codex (#1) #7

Merged
pbwraith merged 9 commits from issue-1 into main 2026-08-29 10:16:47 +02:00
Owner

Part of #1 (EPIC: OSRS Codex) — implements the epic's uncovered P0 economy backlog item "flip candidates". This PR intentionally does NOT say "Closes #1": the epic is a tracking umbrella and its backlog items #2-#6 are still open.

What

New entry codex/economy/flip-candidates.yaml (id: flip-candidates, status: verified) describing GE margin flipping as a bot-able activity: state machine, action list (universal verbs only), run control, risk, and a best_practice block where every line carries a wiki source.

Data / sourcing (all fetched and cross-checked 2026-08-27)

  • prices.runescape.wiki mapping + latest snapshot; 15-item shortlist limits cross-checked against Module:Exchange/ — all agree.
  • GE tax verified from the wiki at 2% (raised from 1% on 2025-05-29, capped 5M/item, sub-50gp effectively untaxed) — the entry's net model is 0.98*sell − buy at band edges.
  • Mechanics cited: OSRS wiki Grand Exchange + Grand Exchange/Buying limits (4h window, connected limits), Merchanting (spread exploitation, volume = safety, avoid static-price items, bulk-flip capital ~20M+).
  • Key dated finding: at the 2026-08-27 snapshot, 11 of 21 classic staples are net-positive at band edges after the 2% fee; sub-2%-spread items (law/chaos/blood runes, dragon bones, bars) need sub-low buys or above-high sells. Top-8 candidate table embedded with buy/sell/limit per item.

Tests

  • python3 tools/validate.py → RESULT: 2 entries, 0 errors, 1 warning (the warning is the pre-existing researched flag on the golden low-alch-f2p.yaml — this entry adds zero researched flags).
  • Every embedded figure (anchor, supplies limits, gp/h model, top-8 table) re-computed from the fetched dataset in CI-style assertions — all match.
  • git diff --check clean.
Part of #1 (EPIC: OSRS Codex) — implements the epic's uncovered P0 economy backlog item "flip candidates". This PR intentionally does NOT say "Closes #1": the epic is a tracking umbrella and its backlog items #2-#6 are still open. ## What New entry `codex/economy/flip-candidates.yaml` (id: flip-candidates, status: verified) describing GE margin flipping as a bot-able activity: state machine, action list (universal verbs only), run control, risk, and a best_practice block where every line carries a wiki source. ## Data / sourcing (all fetched and cross-checked 2026-08-27) - prices.runescape.wiki `mapping` + `latest` snapshot; 15-item shortlist limits cross-checked against Module:Exchange/<Item> — all agree. - GE tax verified from the wiki at **2%** (raised from 1% on 2025-05-29, capped 5M/item, sub-50gp effectively untaxed) — the entry's net model is `0.98*sell − buy` at band edges. - Mechanics cited: OSRS wiki Grand Exchange + Grand Exchange/Buying limits (4h window, connected limits), Merchanting (spread exploitation, volume = safety, avoid static-price items, bulk-flip capital ~20M+). - Key dated finding: at the 2026-08-27 snapshot, 11 of 21 classic staples are net-positive at band edges after the 2% fee; sub-2%-spread items (law/chaos/blood runes, dragon bones, bars) need sub-low buys or above-high sells. Top-8 candidate table embedded with buy/sell/limit per item. ## Tests - `python3 tools/validate.py` → RESULT: 2 entries, 0 errors, 1 warning (the warning is the pre-existing researched flag on the golden `low-alch-f2p.yaml` — this entry adds zero researched flags). - Every embedded figure (anchor, supplies limits, gp/h model, top-8 table) re-computed from the fetched dataset in CI-style assertions — all match. - `git diff --check` clean.
pbwraith added 1 commit 2026-08-27 10:17:44 +02:00
Implements the epic's uncovered P0 economy backlog item (flip candidates).
Verified source chain 2026-08-27: prices.runescape.wiki mapping + latest,
all 15 shortlist limits cross-checked against Module:Exchange (agree);
GE tax verified at 2% since 2025-05-29 (1% at 2021 intro), 5M/item cap,
sub-50gp effectively untaxed; Merchanting + Grand Exchange/Buying limits
pages cited for mechanics. Net model: 0.98*sell - buy at band edges.
Gate: python3 tools/validate.py exit 0 (2 entries, 0 errors, 1 pre-existing
warning on the golden entry's researched flag — none added by this entry).
Author
Owner

[governor-review] BOUNCED

Reviewed the actual merge result: main 986fc1a + head 04a4af0 (synthetic merge 0ea12ae).

Blocking findings:

  1. The GE tax calculation is fractional, but the game floors the 2% tax to whole coins per item. At the committed Rune arrowtips prices, tax is floor(244 * 0.02) = 4, so net is 244 - 4 - 223 = 17 GP, not 16.12. Applying the same integer rule to the committed top-eight table gives 598,000 GP per four-hour window, or 149,500 GP/hour, not 138,000. Update net_per_cycle and every derived table/rate; add a deterministic calculation check so fractional-tax arithmetic cannot return.

  2. The state machine cannot complete a sell cycle. sell transitions to wait_fill, but wait_fill only defines all pending buys filled -> sell. There is no pending sells filled -> collect transition, so a literal translator can loop back through sell instead of collecting. Split buy-fill and sell-fill states or add phase-aware transitions, and pin the full buy -> sell -> collect path.

  3. The advertised high rate assumes eight GE slots and includes members-only candidates, while accounts.membership is hybrid. F2P accounts have three slots and cannot trade the members-only rows. Qualify the existing economics as members-only and provide a separate F2P candidate/rate path, or change the account classification.

Evidence: python3 tools/validate.py passes with 2 entries, 0 errors, and the one pre-existing warning; git diff --check is clean. These are semantic translation/economy defects rather than schema failures. Repair this same issue-1 branch and retain PR #7.

[governor-review] BOUNCED Reviewed the actual merge result: main 986fc1a + head 04a4af0 (synthetic merge 0ea12ae). Blocking findings: 1. The GE tax calculation is fractional, but the game floors the 2% tax to whole coins per item. At the committed Rune arrowtips prices, tax is floor(244 * 0.02) = 4, so net is 244 - 4 - 223 = 17 GP, not 16.12. Applying the same integer rule to the committed top-eight table gives 598,000 GP per four-hour window, or 149,500 GP/hour, not 138,000. Update net_per_cycle and every derived table/rate; add a deterministic calculation check so fractional-tax arithmetic cannot return. 2. The state machine cannot complete a sell cycle. sell transitions to wait_fill, but wait_fill only defines all pending buys filled -> sell. There is no pending sells filled -> collect transition, so a literal translator can loop back through sell instead of collecting. Split buy-fill and sell-fill states or add phase-aware transitions, and pin the full buy -> sell -> collect path. 3. The advertised high rate assumes eight GE slots and includes members-only candidates, while accounts.membership is hybrid. F2P accounts have three slots and cannot trade the members-only rows. Qualify the existing economics as members-only and provide a separate F2P candidate/rate path, or change the account classification. Evidence: python3 tools/validate.py passes with 2 entries, 0 errors, and the one pre-existing warning; git diff --check is clean. These are semantic translation/economy defects rather than schema failures. Repair this same issue-1 branch and retain PR #7.
pbwraith added the
bounced
label 2026-08-27 12:10:00 +02:00
pbwraith added 1 commit 2026-08-27 12:27:20 +02:00
Governor-review bounce (PR #7) fixes on issue-1:
1. GE 2% convenience fee rounds DOWN per item: net = sell - floor(0.02*sell) - buy.
   Anchor net 16.12 -> 17 (244-4-223); top-8 members table recomputed with
   floored tax -> 598,000/4h = 149,500 gp/h (was 138,000); gp_per_hour_low/high
   updated. Validator now rejects fractional-tax arithmetic for GE-margin entries.
2. State machine split wait_fill into wait_buy_fill / wait_sell_fill so the full
   buy -> buy-fill -> sell -> sell-fill -> collect path is explicit; sell cycle
   can no longer loop back into sell without collecting.
3. Economy qualified as the members path (8 slots); F2P path added (3 slots,
   F2P-tradeable rows only): Iron bar + Cosmic rune + Silver ore = 227,000/4h
   = 56,750 gp/h. Members/F2P flags cross-checked against wiki item pages.
pbwraith removed the
bounced
label 2026-08-27 12:27:55 +02:00
Author
Owner

[worker-bounce-fix attempt 1]

Repaired the same issue-1 branch; no new branch/PR. Commit 864f2c9 on top of 04a4af0 (head now 864f2c9, base main unchanged). Fixes finding-by-finding:

1. GE tax is floored per item — whole-coin net.
Wiki (fetched today): "The 2% tax rounds down to the nearest whole number, so items sold for below 50 coins have a tax of less than one coin". Model changed from 0.98*sell - buy to sell - floor(0.02*sell) - buy:

  • Anchor: Rune arrowtips 244/223 → tax floor(244*0.02)=4 → net 17 (was 16.12).
  • Top-8 members table recomputed with floored tax: 170,000 (arrowtips), 90,000 (iron bar), 72,000 (cosmic), 72,000 (magic logs), 65,000 (silver ore), 54,000 (mud rune), 45,000 (swordfish), 30,000 (lobster) = 598,000 per 4h = 149,500 gp/h (was 138,000). gp_per_hour_low/high → 74,750 / 149,500.
  • Deterministic guard added to tools/validate.py: GE-margin entries (tags flipping/merchanting/ge) must satisfy net_per_cycle == income - floor(0.02*income) - cost; fractional arithmetic is now an ERROR (verified: the old 16.12 fails the gate with exit 1).

2. Sell cycle can now complete.
Split wait_fill into phase-aware wait_buy_fill / wait_sell_fill. Pinned path is now explicit: buy -> wait_buy_fill -> sell -> wait_sell_fill -> collect — wait_sell_fill -> collect fires on "all pending sells filled", and window-lapse sell re-quotes loop wait_sell_fill -> sell. A literal translator cannot loop sell→wait_fill→sell anymore.

3. Members economics qualified; separate F2P path added.
accounts.membership stays hybrid but the modeled economics are now explicitly the members path (8 slots). New F2P path (3 slots, F2P-tradeable rows only — members flags cross-checked against wiki item pages): Iron bar 90,000 + Cosmic rune 72,000 + Silver ore 65,000 = 227,000 per 4h = 56,750 gp/h (alternates swordfish/lobster). Summary, requires_other, best_practice, profit_drivers all carry the split.

Tests: python3 tools/validate.py → RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched flag on low-alch-f2p). git diff --check clean. Negative test: the old fractional net_per_cycle fails the new gate check. Wiki sources for flooring and slot counts fetched and cross-checked 2026-08-27.

[worker-bounce-fix attempt 1] Repaired the same `issue-1` branch; no new branch/PR. Commit `864f2c9` on top of `04a4af0` (head now 864f2c9, base main unchanged). Fixes finding-by-finding: **1. GE tax is floored per item — whole-coin net.** Wiki (fetched today): *"The 2% tax rounds down to the nearest whole number, so items sold for below 50 coins have a tax of less than one coin"*. Model changed from `0.98*sell - buy` to `sell - floor(0.02*sell) - buy`: - Anchor: Rune arrowtips 244/223 → tax floor(244*0.02)=4 → net **17** (was 16.12). - Top-8 members table recomputed with floored tax: 170,000 (arrowtips), 90,000 (iron bar), 72,000 (cosmic), 72,000 (magic logs), 65,000 (silver ore), 54,000 (mud rune), 45,000 (swordfish), 30,000 (lobster) = **598,000 per 4h = 149,500 gp/h** (was 138,000). gp_per_hour_low/high → 74,750 / 149,500. - Deterministic guard added to `tools/validate.py`: GE-margin entries (tags flipping/merchanting/ge) must satisfy `net_per_cycle == income - floor(0.02*income) - cost`; fractional arithmetic is now an ERROR (verified: the old 16.12 fails the gate with exit 1). **2. Sell cycle can now complete.** Split `wait_fill` into phase-aware `wait_buy_fill` / `wait_sell_fill`. Pinned path is now explicit: `buy -> wait_buy_fill -> sell -> wait_sell_fill -> collect` — `wait_sell_fill -> collect` fires on "all pending sells filled", and window-lapse sell re-quotes loop `wait_sell_fill -> sell`. A literal translator cannot loop sell→wait_fill→sell anymore. **3. Members economics qualified; separate F2P path added.** `accounts.membership` stays `hybrid` but the modeled economics are now explicitly the members path (8 slots). New F2P path (3 slots, F2P-tradeable rows only — members flags cross-checked against wiki item pages): Iron bar 90,000 + Cosmic rune 72,000 + Silver ore 65,000 = **227,000 per 4h = 56,750 gp/h** (alternates swordfish/lobster). Summary, requires_other, best_practice, profit_drivers all carry the split. **Tests:** `python3 tools/validate.py` → RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched flag on low-alch-f2p). `git diff --check` clean. Negative test: the old fractional net_per_cycle fails the new gate check. Wiki sources for flooring and slot counts fetched and cross-checked 2026-08-27.
Author
Owner

[governor-review] BOUNCED — attempt 2

Re-reviewed repaired head 864f2c9 against main 986fc1a (synthetic merge a93c82a). The original three findings are fixed correctly and the merge-result gate passes.

New blocking finding:

tools/validate.py documents and comments this as a GE-margin rule scoped to flipping/merchanting, but the implementation checks tags & {flipping, merchanting, ge}. The generic ge tag is not proof that income_per_cycle is a taxed resale: a training, processing, or procurement entry may legitimately carry ge while its income/cost model is unrelated to flip tax. Such an entry would now fail the global gate unless its economics accidentally satisfy the flip equation.

Scope the rule to unambiguous margin tags (flipping/merchanting), or add an explicit economy model discriminator. Add two regression fixtures/checks: a fractional-tax flipping entry must fail, while a non-margin entry carrying only ge plus a non-flip tag must pass.

Evidence: python3 tools/validate.py on the synthetic merge returns 2 entries, 0 errors, 1 pre-existing warning; git diff --check is clean. The blocker is the newly broadened global validator behavior. Repair the same issue-1 branch and retain PR #7.

[governor-review] BOUNCED — attempt 2 Re-reviewed repaired head 864f2c9 against main 986fc1a (synthetic merge a93c82a). The original three findings are fixed correctly and the merge-result gate passes. New blocking finding: tools/validate.py documents and comments this as a GE-margin rule scoped to flipping/merchanting, but the implementation checks tags & {flipping, merchanting, ge}. The generic ge tag is not proof that income_per_cycle is a taxed resale: a training, processing, or procurement entry may legitimately carry ge while its income/cost model is unrelated to flip tax. Such an entry would now fail the global gate unless its economics accidentally satisfy the flip equation. Scope the rule to unambiguous margin tags (flipping/merchanting), or add an explicit economy model discriminator. Add two regression fixtures/checks: a fractional-tax flipping entry must fail, while a non-margin entry carrying only ge plus a non-flip tag must pass. Evidence: python3 tools/validate.py on the synthetic merge returns 2 entries, 0 errors, 1 pre-existing warning; git diff --check is clean. The blocker is the newly broadened global validator behavior. Repair the same issue-1 branch and retain PR #7.
pbwraith added the
bounced
label 2026-08-27 19:27:48 +02:00
pbwraith added 1 commit 2026-08-27 19:34:16 +02:00
Governor-review bounce (PR #7, attempt 2) fix on issue-1:
- The floor-tax rule previously triggered on any entry carrying the
  generic 'ge' tag (tags & {flipping, merchanting, ge}). A training,
  processing, or procurement entry may legitimately carry 'ge' while
  its income/cost model is unrelated to flip tax, so it could fail the
  global gate unless its economics accidentally satisfied the flip
  equation.
- Rule now gates ONLY the unambiguous margin tags (flipping and/or
  merchanting); the bare 'ge' tag no longer triggers it.
- Rule extracted into ge_floor_tax_error() and a --selftest mode added
  with three regression fixtures, run on every gate invocation:
    * fractional-tax flipping entry  -> must FAIL
    * non-margin entry with only 'ge' + 'processing' (economics
      deliberately violate the flip equation) -> must PASS
    * correctly-floored flipping entry -> must PASS
- Demo (old scope): ge+processing fixture with net 2000 vs floored 1900
  errored under the old tag set; low-alch-f2p's -112 vs -115 model
  would also false-positive if it carried 'ge'.

Tests: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing
warning (researched flag on low-alch-f2p); --selftest -> 3 PASS, 0 errors;
git diff --check clean.
pbwraith removed the
bounced
label 2026-08-27 19:34:38 +02:00
Author
Owner

[worker-bounce-fix attempt 2]

Repaired the same issue-1 branch; no new branch/PR. Commit f6675ba on top of 864f2c9 (head now f6675ba, base main unchanged). Fixes the attempt-2 review finding on tools/validate.py:

Finding (validator over-scoped): tools/validate.py documented the floor-tax rule as GE-margin scoped to flipping/merchanting, but the implementation gated on tags & {flipping, merchanting, ge}. The generic ge tag is not proof that income_per_cycle is a taxed resale — a training/processing/procurement entry may legitimately carry ge while its income/cost model is unrelated to flip tax, and would fail the global gate unless its economics accidentally satisfied the flip equation.

Fix: scoped the rule to the unambiguous margin tags — MARGIN_TAGS = {flipping, merchanting}. The bare ge tag no longer triggers the floor-tax gate. The rule itself is unchanged (extracted into ge_floor_tax_error()), so flip entries are still gated exactly as before.

Regression fixtures added (--selftest, wired to run on every gate invocation):

  1. fractional-tax flipping entry (income 244 / cost 223 / net 16.12) -> MUST FAIL: SELFTEST PASS
  2. non-margin entry with only ge + processing, economics deliberately violating the flip equation (income 5000 / cost 3000 / net 2000 vs floored model 1900) -> MUST PASS: SELFTEST PASS
  3. correctly-floored flipping entry (244/223/17) -> MUST PASS: SELFTEST PASS

False-positive demonstration (old scope): under the pre-fix tag set, fixture 2 errored (2000 != 1900); low-alch-f2p's economics (income 180 / cost 292 / net -112 vs floored model -115) would likewise false-positive the old gate if it carried the ge tag. Both pass under the margin-only scope.

Tests: python3 tools/validate.py -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched flag on low-alch-f2p); python3 tools/validate.py --selftest -> 3 PASS, 0 errors; git diff --check clean. Head f6675ba verified post-push: PR state=open, merged=false, head.ref=issue-1, 2 changed files with non-empty diff.

[worker-bounce-fix attempt 2] Repaired the same `issue-1` branch; no new branch/PR. Commit `f6675ba` on top of `864f2c9` (head now f6675ba, base main unchanged). Fixes the attempt-2 review finding on tools/validate.py: **Finding (validator over-scoped):** `tools/validate.py` documented the floor-tax rule as GE-margin scoped to flipping/merchanting, but the implementation gated on `tags & {flipping, merchanting, ge}`. The generic `ge` tag is not proof that income_per_cycle is a taxed resale — a training/processing/procurement entry may legitimately carry `ge` while its income/cost model is unrelated to flip tax, and would fail the global gate unless its economics accidentally satisfied the flip equation. **Fix:** scoped the rule to the unambiguous margin tags — `MARGIN_TAGS = {flipping, merchanting}`. The bare `ge` tag no longer triggers the floor-tax gate. The rule itself is unchanged (extracted into `ge_floor_tax_error()`), so flip entries are still gated exactly as before. **Regression fixtures added (--selftest, wired to run on every gate invocation):** 1. fractional-tax flipping entry (income 244 / cost 223 / net 16.12) -> MUST FAIL: `SELFTEST PASS` 2. non-margin entry with only `ge` + `processing`, economics deliberately violating the flip equation (income 5000 / cost 3000 / net 2000 vs floored model 1900) -> MUST PASS: `SELFTEST PASS` 3. correctly-floored flipping entry (244/223/17) -> MUST PASS: `SELFTEST PASS` **False-positive demonstration (old scope):** under the pre-fix tag set, fixture 2 errored (`2000 != 1900`); `low-alch-f2p`'s economics (income 180 / cost 292 / net -112 vs floored model -115) would likewise false-positive the old gate if it carried the `ge` tag. Both pass under the margin-only scope. **Tests:** `python3 tools/validate.py` -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing `researched` flag on low-alch-f2p); `python3 tools/validate.py --selftest` -> 3 PASS, 0 errors; `git diff --check` clean. Head f6675ba verified post-push: PR state=open, merged=false, head.ref=issue-1, 2 changed files with non-empty diff.
Author
Owner

[governor-review] BOUNCED — attempt 3

Re-reviewed repaired head f6675ba against current main 79cab78. The attempt-2 generic ge scoping finding is fixed, the exact tree passes (2 entries, 0 errors, 1 pre-existing warning), and all three current selftests pass.

One blocking false-positive remains in the new global validator:

ge_floor_tax_error() now applies to every flipping/merchanting entry, but it always uses floor(sell * 0.02) and never applies the 5,000,000 GP per-item cap. The new Codex entry itself documents that cap in both best_practice and risk. For a valid per-item high-value flip with sell=300,000,000, buy=294,000,000, the real capped tax is 5,000,000 and correct net is 1,000,000; the validator instead computes 6,000,000 tax and demands net=0, rejecting the correct entry.

Please either implement the documented cap in this global rule (min(floor(0.02 * sell), 5_000_000)) or scope the rule behind an explicit economy/tax-model discriminator rather than inferring a universal formula from tags. Add a regression fixture for a sale above the cap that accepts the correct capped-tax net. Keep the existing fractional-tax failure and generic-ge non-margin pass fixtures.

This is a validator-scope blocker, not a problem with the Rune arrowtips arithmetic itself.

[governor-review] BOUNCED — attempt 3 Re-reviewed repaired head f6675ba against current main 79cab78. The attempt-2 generic `ge` scoping finding is fixed, the exact tree passes (`2 entries, 0 errors, 1 pre-existing warning`), and all three current selftests pass. One blocking false-positive remains in the new global validator: `ge_floor_tax_error()` now applies to every `flipping`/`merchanting` entry, but it always uses `floor(sell * 0.02)` and never applies the 5,000,000 GP per-item cap. The new Codex entry itself documents that cap in both `best_practice` and `risk`. For a valid per-item high-value flip with sell=300,000,000, buy=294,000,000, the real capped tax is 5,000,000 and correct net is 1,000,000; the validator instead computes 6,000,000 tax and demands net=0, rejecting the correct entry. Please either implement the documented cap in this global rule (`min(floor(0.02 * sell), 5_000_000)`) or scope the rule behind an explicit economy/tax-model discriminator rather than inferring a universal formula from tags. Add a regression fixture for a sale above the cap that accepts the correct capped-tax net. Keep the existing fractional-tax failure and generic-`ge` non-margin pass fixtures. This is a validator-scope blocker, not a problem with the Rune arrowtips arithmetic itself.
pbwraith added the
bounced
label 2026-08-27 21:27:52 +02:00
Author
Owner

[worker-guidance-request]

PR !7 (osrs-codex, head issue-1 @ f6675ba, base main) has been bounced three times; two [worker-bounce-fix] attempts are already on the branch, so per lane policy the third review's finding goes to guidance instead of a third repair.

Review request (latest governor review, 2026-08-27):
ge_floor_tax_error() in tools/validate.py applies floor(sell * 0.02) to every flipping/merchanting entry and never applies the 5,000,000 GP per-item cap that the flip-candidates entry itself documents (best_practice + risk). For a valid high-value flip with sell=300,000,000 / buy=294,000,000, the real capped tax is 5,000,000 and the correct net is 1,000,000; the validator instead computes 6,000,000 tax and demands net=0, rejecting the correct entry. The reviewer requires either the documented cap in the rule (min(floor(0.02 * sell), 5_000_000)) or an explicit economy/tax-model discriminator, plus a regression fixture for a sale above the cap (keeping the existing fractional-tax failure and generic-ge non-margin pass fixtures).

Attempted commits/fixes on the same issue-1 branch (PR !7 retained throughout):

  • 864f2c9 (attempt 1): floored GE tax to whole coins per item (sell - floor(0.02*sell) - buy), split wait_fill into wait_buy_fill/wait_sell_fill pinning buy -> sell -> collect, qualified the 8-slot members economics and added a separate F2P path (3 slots). Fixed review 1.
  • f6675ba (attempt 2): scoped the floor-tax gate from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; added --selftest regression fixtures (fractional-tax flip must fail, generic-ge non-margin must pass, correctly-floored flip must pass). Fixed review 2.

Exact remaining failure: the still-broad tag-scoped rule computes uncapped floor tax for all margin entries. Verifiable now: a flip entry with sell=300,000,000 / buy=294,000,000 / net=1,000,000 fails the current gate (6,000,000 tax demanded -> net 0), though the wiki-capped tax makes it a correct entry.

One direct question: for the next repair, may the implementer add the documented per-item cap to the rule (min(floor(0.02 * sell), 5_000_000) in ge_floor_tax_error()) plus the above-cap regression fixture — or do you want the rule moved behind an explicit economy/tax-model discriminator in the schema so the global validator never infers a universal tax formula from tags?

[worker-guidance-request] PR !7 (osrs-codex, head `issue-1` @ f6675ba, base main) has been bounced three times; two `[worker-bounce-fix]` attempts are already on the branch, so per lane policy the third review's finding goes to guidance instead of a third repair. Review request (latest governor review, 2026-08-27): `ge_floor_tax_error()` in tools/validate.py applies `floor(sell * 0.02)` to every `flipping`/`merchanting` entry and never applies the 5,000,000 GP per-item cap that the flip-candidates entry itself documents (best_practice + risk). For a valid high-value flip with sell=300,000,000 / buy=294,000,000, the real capped tax is 5,000,000 and the correct net is 1,000,000; the validator instead computes 6,000,000 tax and demands net=0, rejecting the correct entry. The reviewer requires either the documented cap in the rule (`min(floor(0.02 * sell), 5_000_000)`) or an explicit economy/tax-model discriminator, plus a regression fixture for a sale above the cap (keeping the existing fractional-tax failure and generic-`ge` non-margin pass fixtures). Attempted commits/fixes on the same `issue-1` branch (PR !7 retained throughout): - 864f2c9 (attempt 1): floored GE tax to whole coins per item (`sell - floor(0.02*sell) - buy`), split wait_fill into wait_buy_fill/wait_sell_fill pinning buy -> sell -> collect, qualified the 8-slot members economics and added a separate F2P path (3 slots). Fixed review 1. - f6675ba (attempt 2): scoped the floor-tax gate from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; added --selftest regression fixtures (fractional-tax flip must fail, generic-ge non-margin must pass, correctly-floored flip must pass). Fixed review 2. Exact remaining failure: the still-broad tag-scoped rule computes uncapped floor tax for all margin entries. Verifiable now: a flip entry with sell=300,000,000 / buy=294,000,000 / net=1,000,000 fails the current gate (6,000,000 tax demanded -> net 0), though the wiki-capped tax makes it a correct entry. One direct question: for the next repair, may the implementer add the documented per-item cap to the rule (`min(floor(0.02 * sell), 5_000_000)` in ge_floor_tax_error()) plus the above-cap regression fixture — or do you want the rule moved behind an explicit economy/tax-model discriminator in the schema so the global validator never infers a universal tax formula from tags?
pbwraith added
needs-guidance
and removed
bounced
labels 2026-08-27 21:31:34 +02:00
Author
Owner

[governor-guidance-request]

Decision needed: What tax-validation contract should the Codex use?

A. Apply the documented capped OSRS tax formula globally (Recommended) — use min(floor(0.02 * sell), 5_000_000) and add an above-cap regression.
B. Require an explicit tax-model field per entry — avoids assuming one universal formula but expands the schema and every applicable entry.
C. Remove the global margin/tax gate — keeps entry-local arithmetic but gives up cross-entry validation.

Mandatory regardless of choice:

  • Retain the fractional-tax failure fixture.
  • Retain the generic-ge non-margin pass fixture.
  • Repair this existing PR and issue-1 branch; do not open a replacement PR.

Owner: reply with A, B, or C. The governor will translate the choice into worker-ready guidance and then transition this same PR back to bounced for repair.

[governor-guidance-request] Decision needed: What tax-validation contract should the Codex use? A. Apply the documented capped OSRS tax formula globally (Recommended) — use `min(floor(0.02 * sell), 5_000_000)` and add an above-cap regression. B. Require an explicit tax-model field per entry — avoids assuming one universal formula but expands the schema and every applicable entry. C. Remove the global margin/tax gate — keeps entry-local arithmetic but gives up cross-entry validation. Mandatory regardless of choice: - Retain the fractional-tax failure fixture. - Retain the generic-`ge` non-margin pass fixture. - Repair this existing PR and `issue-1` branch; do not open a replacement PR. Owner: reply with A, B, or C. The governor will translate the choice into worker-ready guidance and then transition this same PR back to `bounced` for repair.
Author
Owner

[owner-guidance]

Owner decision: use a single global GE tax formula for all entries:

min(floor(0.02 * sell_price), 5_000_000)

Please update this same PR branch accordingly. Preserve the validator coverage that proves fractional tax is rejected, the 5,000,000-coin cap is enforced, and a generic GE entry that is not a margin-flipping entry still passes validation.

This PR is being returned to the worker as bounced for repair.

[owner-guidance] Owner decision: use a single global GE tax formula for all entries: `min(floor(0.02 * sell_price), 5_000_000)` Please update this same PR branch accordingly. Preserve the validator coverage that proves fractional tax is rejected, the 5,000,000-coin cap is enforced, and a generic GE entry that is not a margin-flipping entry still passes validation. This PR is being returned to the worker as `bounced` for repair.
pbwraith added
bounced
and removed
needs-guidance
labels 2026-08-28 09:31:03 +02:00
pbwraith added 1 commit 2026-08-28 09:36:06 +02:00
Owner decision 2026-08-28: single global GE tax formula for all entries:
min(floor(0.02 * sell_price), 5_000_000). Apply the per-item cap inside
ge_floor_tax_error(); the margin-tag scope (flipping/merchanting) and the
generic-ge non-margin exemption are unchanged.

Regression fixtures now cover:
- fractional-tax flipping entry must FAIL (kept)
- non-margin entry with only ge + processing must PASS (kept)
- correctly-floored flipping entry must PASS (kept)
- high-value flip above the 5,000,000 GP cap (sell 300,000,000 / buy
  294,000,000 / net 1,000,000) must PASS — the old uncapped rule computed
  6,000,000 tax, demanded net 0, and rejected this valid entry (new)

Closes the governor's attempt-3 blocker on PR !7.
pbwraith removed the
bounced
label 2026-08-28 09:36:42 +02:00
Author
Owner

[worker-bounce-fix attempt 3]

Repaired the same issue-1 branch; no new branch/PR. Commit 00bfc35 on top of f6675ba (head now 00bfc35, base main unchanged). Implements the owner decision (reply to the governor's guidance request, 2026-08-28): option A — a single global GE tax formula.

Finding (attempt-3 review / owner decision): ge_floor_tax_error() applied floor(sell * 0.02) to every flipping/merchanting entry without the 5,000,000 GP per-item cap that the flip-candidates entry itself documents (best_practice + risk). For a valid high-value flip sell=300,000,000 / buy=294,000,000 the real capped tax is 5,000,000 and the correct net is 1,000,000; the old rule computed 6,000,000 tax and demanded net=0, rejecting the correct entry.

Fix: the rule now uses the owner-approved single global formula:
tax = min(floor(0.02 * sell), 5_000_000); required net_per_cycle == sell - tax - buy. The margin-tag scope (MARGIN_TAGS = {flipping, merchanting}) and the generic-ge non-margin exemption are unchanged — the "global" part is one universal formula, no per-entry tax-model discriminator (option B rejected).

Regression coverage (all wired into --selftest, which runs on every gate invocation):

  1. fractional-tax flipping entry (244/223/16.12) -> MUST FAIL — kept: SELFTEST PASS
  2. non-margin entry with only ge + processing, economics violating the flip equation (5000/3000/2000) -> MUST PASS — kept: SELFTEST PASS
  3. correctly-floored flipping entry (244/223/17) -> MUST PASS — kept: SELFTEST PASS
  4. high-value flip above the 5,000,000 GP cap (sell=300,000,000 / buy=294,000,000 / net=1,000,000) -> MUST PASS — NEW: SELFTEST PASS (old uncapped rule rejected it: demanded net 0)

Tests: python3 tools/validate.py -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched on low-alch-f2p), exit 0; python3 tools/validate.py --selftest -> 4 PASS, 0 errors, exit 0; git diff --check clean. Negative demo (raw arithmetic): old tax floor(300M*0.02)=6,000,000 -> demanded net 0 vs fixture net 1,000,000 (old rule REJECTS); capped tax min(6M, 5M)=5,000,000 -> net 1,000,000. Existing entry economics unaffected (all values below the cap; 244/223/17 still passes).

Head 00bfc35 verified post-push: PR !7 state=open, merged=false, head.ref=issue-1, 2 changed files (flip-candidates.yaml, tools/validate.py) with non-empty diff. Left in review for manual review.

[worker-bounce-fix attempt 3] Repaired the same `issue-1` branch; no new branch/PR. Commit `00bfc35` on top of `f6675ba` (head now 00bfc35, base main unchanged). Implements the owner decision (reply to the governor's guidance request, 2026-08-28): option A — a single global GE tax formula. **Finding (attempt-3 review / owner decision):** `ge_floor_tax_error()` applied `floor(sell * 0.02)` to every flipping/merchanting entry without the 5,000,000 GP per-item cap that the flip-candidates entry itself documents (best_practice + risk). For a valid high-value flip sell=300,000,000 / buy=294,000,000 the real capped tax is 5,000,000 and the correct net is 1,000,000; the old rule computed 6,000,000 tax and demanded net=0, rejecting the correct entry. **Fix:** the rule now uses the owner-approved single global formula: `tax = min(floor(0.02 * sell), 5_000_000)`; required `net_per_cycle == sell - tax - buy`. The margin-tag scope (MARGIN_TAGS = {flipping, merchanting}) and the generic-`ge` non-margin exemption are unchanged — the "global" part is one universal formula, no per-entry tax-model discriminator (option B rejected). **Regression coverage (all wired into `--selftest`, which runs on every gate invocation):** 1. fractional-tax flipping entry (244/223/16.12) -> MUST FAIL — kept: SELFTEST PASS 2. non-margin entry with only `ge` + `processing`, economics violating the flip equation (5000/3000/2000) -> MUST PASS — kept: SELFTEST PASS 3. correctly-floored flipping entry (244/223/17) -> MUST PASS — kept: SELFTEST PASS 4. high-value flip above the 5,000,000 GP cap (sell=300,000,000 / buy=294,000,000 / net=1,000,000) -> MUST PASS — NEW: SELFTEST PASS (old uncapped rule rejected it: demanded net 0) **Tests:** `python3 tools/validate.py` -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing `researched` on low-alch-f2p), exit 0; `python3 tools/validate.py --selftest` -> 4 PASS, 0 errors, exit 0; `git diff --check` clean. Negative demo (raw arithmetic): old tax floor(300M*0.02)=6,000,000 -> demanded net 0 vs fixture net 1,000,000 (old rule REJECTS); capped tax min(6M, 5M)=5,000,000 -> net 1,000,000. Existing entry economics unaffected (all values below the cap; 244/223/17 still passes). Head 00bfc35 verified post-push: PR !7 state=open, merged=false, head.ref=issue-1, 2 changed files (flip-candidates.yaml, tools/validate.py) with non-empty diff. Left in review for manual review.
Author
Owner

[owner-guidance-addendum]

This repair must also follow the owner's global activity-lifecycle contract. The GE flipping entry must cover the complete runnable flow rather than only its margin arithmetic: required starting capital and prerequisites; selecting and buying the item; offer placement and monitoring; partial fills and cancellations; tax-aware sale handling; inventory and GE-slot state; collecting proceeds; repeating or switching items; breaks; recovery from insufficient funds, no fill, or stale prices; and explicit stop conditions.

Add a worked example that begins with preparation and capital allocation, completes a buy/sell cycle, crosses the first relevant recovery or break boundary, and shows how the activity resumes. Validators and rates must describe the complete lifecycle, not an idealized inner loop.

[owner-guidance-addendum] This repair must also follow the owner's global activity-lifecycle contract. The GE flipping entry must cover the complete runnable flow rather than only its margin arithmetic: required starting capital and prerequisites; selecting and buying the item; offer placement and monitoring; partial fills and cancellations; tax-aware sale handling; inventory and GE-slot state; collecting proceeds; repeating or switching items; breaks; recovery from insufficient funds, no fill, or stale prices; and explicit stop conditions. Add a worked example that begins with preparation and capital allocation, completes a buy/sell cycle, crosses the first relevant recovery or break boundary, and shows how the activity resumes. Validators and rates must describe the complete lifecycle, not an idealized inner loop.
Author
Owner

[governor-review] BOUNCED — lifecycle contract

Reviewed the actual merge result of head 00bfc358cbd2f89ab51e7ccb7f9c0a541f5bfac7 against current base 79cab78786a1e90516439b7f4ec6101f37587824 in a clean synthetic merge.

The owner-approved tax repair is correct. The global margin rule now uses min(floor(0.02 * sell), 5_000_000), retains margin-only tag scoping, and preserves the required regressions.

Passing evidence:

  • git diff --check base...head — exit 0.
  • python3 tools/validate.py — exit 0; 2 entries, 0 errors, 1 pre-existing citation warning.
  • python3 tools/validate.py --selftest — exit 0; all 4 fixtures pass, including fractional-tax failure, generic-ge non-margin pass, and above-cap pass.

New blocking requirement: this head was pushed before owner-guidance addendum 10271, so it does not yet implement the required complete activity lifecycle.

  1. There is no worked start-to-finish example. Add a deterministic example that begins with capital allocation and candidate selection, places buys, handles at least one partial fill or stale/no-fill offer, cancels/requotes or otherwise recovers using an explicitly supported action, sells, collects and reconciles inventory/coins/GE slots, reaches the first scheduled break, resumes, and then reaches a stop condition.
  2. The state machine only handles all pending buys filled. On a 4-hour lapse it goes from wait_buy_fill directly to collect, after which the remaining purchased quantity has no explicit sell/recovery path. Model partial quantities, cancellation/requote, collection, and sale of acquired units so no items, coins, or occupied GE slots are stranded. If the current action grammar lacks the required cancellation mutation, extend the grammar/schema once and use it explicitly rather than describing an unavailable action.
  3. Make preparation and runtime state concrete: minimum starting capital, members/F2P slot count, selected candidates, per-slot offer phase and quantities, inventory/collection state, buy-limit window, and reconciliation after collection. Failure paths must cover insufficient funds, no fill, stale prices, and unavailable slots.
  4. Connect the configured break cadence to an explicit break/resume path. A run_control.break tuple alone does not show when offers are safely left, how state is preserved, or where execution resumes.
  5. Qualify rate claims against the complete lifecycle. Keep the 4-hour buy-limit calculation as a clearly named economic ceiling, but show how the claimed operating range accounts for partial fills, cancellations/requotes, collection/re-entry, and breaks; do not present the ideal full-fill inner-loop ceiling as an achieved runtime rate.

Repair this existing issue-1 branch and PR. Do not reopen the already-fixed tax findings.

[governor-review] BOUNCED — lifecycle contract Reviewed the actual merge result of head `00bfc358cbd2f89ab51e7ccb7f9c0a541f5bfac7` against current base `79cab78786a1e90516439b7f4ec6101f37587824` in a clean synthetic merge. The owner-approved tax repair is correct. The global margin rule now uses `min(floor(0.02 * sell), 5_000_000)`, retains margin-only tag scoping, and preserves the required regressions. Passing evidence: - `git diff --check base...head` — exit 0. - `python3 tools/validate.py` — exit 0; 2 entries, 0 errors, 1 pre-existing citation warning. - `python3 tools/validate.py --selftest` — exit 0; all 4 fixtures pass, including fractional-tax failure, generic-`ge` non-margin pass, and above-cap pass. New blocking requirement: this head was pushed before owner-guidance addendum `10271`, so it does not yet implement the required complete activity lifecycle. 1. There is no worked start-to-finish example. Add a deterministic example that begins with capital allocation and candidate selection, places buys, handles at least one partial fill or stale/no-fill offer, cancels/requotes or otherwise recovers using an explicitly supported action, sells, collects and reconciles inventory/coins/GE slots, reaches the first scheduled break, resumes, and then reaches a stop condition. 2. The state machine only handles `all pending buys filled`. On a 4-hour lapse it goes from `wait_buy_fill` directly to `collect`, after which the remaining purchased quantity has no explicit sell/recovery path. Model partial quantities, cancellation/requote, collection, and sale of acquired units so no items, coins, or occupied GE slots are stranded. If the current action grammar lacks the required cancellation mutation, extend the grammar/schema once and use it explicitly rather than describing an unavailable action. 3. Make preparation and runtime state concrete: minimum starting capital, members/F2P slot count, selected candidates, per-slot offer phase and quantities, inventory/collection state, buy-limit window, and reconciliation after collection. Failure paths must cover insufficient funds, no fill, stale prices, and unavailable slots. 4. Connect the configured break cadence to an explicit break/resume path. A `run_control.break` tuple alone does not show when offers are safely left, how state is preserved, or where execution resumes. 5. Qualify rate claims against the complete lifecycle. Keep the 4-hour buy-limit calculation as a clearly named economic ceiling, but show how the claimed operating range accounts for partial fills, cancellations/requotes, collection/re-entry, and breaks; do not present the ideal full-fill inner-loop ceiling as an achieved runtime rate. Repair this existing `issue-1` branch and PR. Do not reopen the already-fixed tax findings.
pbwraith added the
bounced
label 2026-08-28 10:10:42 +02:00
pbwraith added 1 commit 2026-08-28 10:19:24 +02:00
Owner lifecycle addendum (2026-08-28) + governor bounce 4 (lifecycle contract):
- state machine now covers the ENTIRE buy -> sell -> collect flow with no
  stranded state: partial fills tracked per slot (filled_qty ledger), 4h-lapse
  buys go to a new cancel_requote state (ge_cancel: units return to inventory,
  cash refunds, slot freed) instead of collect, cancelled units still sell at
  the high edge, lapse sells re-quote or lower rather than strand
- action grammar + schema extended ONCE with the cancellation mutation
  ge_cancel (was missing; ge_clear/ge_offers_done are predicates only) and
  used explicitly in the new a8 cancel-requote action
- concrete runtime state: minimum per-slot capital table (buy x 4h limit),
  members 8-slot vs F2P 3-slot paths, per-slot offer phase + quantities,
  buy-limit window, inventory/collection reconciliation in a9
- breaks connected to an explicit safe/resume path: resting offers persist
  server-side + per-slot ledger survive the pause; resume = re-open GE,
  re-check offers, continue current phase; never break mid-collect/mid-place
- rates qualified against the full lifecycle: 149,500 gp/h is named the 4h
  buy-limit CEILING; realized range = ceiling x fill_factor (~0.5-0.9) x
  duty_cycle (~0.90-0.95) ~ 96,300 gp/h midpoint; gp_per_hour_low 74,750
- pinned worked example: capital 22.5M -> 8 slot quotes -> arrowtips partial
  fill (4,000/10,000 tracked) -> iron bar full fill sell (90,000 net) ->
  cosmic no-fill ge_cancel + requote -> arrowtips partial matures at lapse
  (cancel, sell 4,000, 68,000 net) -> first break after 6-10 rotations ->
  resume -> stop conditions
- version 2 -> 3; evidence: validate 2 entries 0 errors 1 pre-existing WARN,
  selftest 4 PASS (fractional-tax fail, ge non-margin pass, floored pass,
  above-cap pass), synthetic merge main 79cab78 + head clean + gate green,
  git diff --check clean
pbwraith removed the
bounced
label 2026-08-28 10:20:08 +02:00
Author
Owner

[worker-bounce-fix attempt 4]

Commit: 3572255 on branch issue-1 (PR #7 retained, head 00bfc35 -> 3572255). 3 files: codex/economy/flip-candidates.yaml, docs/action-grammar.md, schemas/entry.schema.json. Implements the owner lifecycle addendum (owner-guidance-addendum, comment 10271) — the head previously pushed (00bfc35) predated it; the tax repair itself is untouched (per the governor: do not reopen the already-fixed tax findings).

Governor bounce 4 (lifecycle contract) answered finding by finding:

  1. Complete worked example — pinned under run_control. Deterministic, start-to-finish: capital 22,500,000 gp allocated per slot (buy edge x 4h limit table: arrowtips 2.23M, iron 1.45M, cosmic 2.196M, silver 0.949M, swordfish 3.09M, lobster 1.59M, magic logs 9.264M, mud 1.728M) -> 8 slot quotes at the low edges -> PARTIAL FILL (arrowtips 4,000/10,000 tracked in the per-slot ledger, offer keeps resting) -> FULL FILL (iron bar: sell 10,000 @ 157, tax floor(157x0.02)=3, net 90,000, collect + reconcile) -> NO-FILL on cosmic (ge_cancel at window lapse: 0 units, 2,196,000 refunded, slot freed; re-scan -> requote fresh window) -> PARTIAL MATURES at lapse (arrowtips 4,000 cancel -> units return to inventory, 6,000x223 refunded -> sell 4,000 @ 244, tax 4, net 68,000) -> FIRST SCHEDULED BREAK after 6-10 rotations -> resume -> stop condition (session target / capital floor / 3 aborted offers).

  2. State machine can no longer strand anything. wait_buy_fill -> collect on lapse is GONE; lapse/stale with filled_qty < quoted_qty now goes to a new cancel_requote state (a8, verb ge_cancel): acquired units return to inventory, unspent cash refunds, slot freed, then re-quote at the fresh edge (buy while margin positive) or free the slot with any acquired units still sold at the high edge. Sell-side lapse also cancels + re-quotes (or lowers to the current band) — units, coins, and slots are fully accounted for through collect + reconciliation (a9: inventory empty, slots free, ledger closed).
    The grammar LACKED a cancellation mutation — ge_clear/ge_offers_done are documented predicates only — so per the governor's instruction it was extended ONCE: ge_cancel added to docs/action-grammar.md (Exchange section) and to the schema verb enum, and used explicitly by a8. No unavailable action is referenced.

  3. Preparation and runtime state are concrete. Minimum starting capital per slot + per path (members ~22.5M / F2P 3-slot ~5.5M) in requires_other; members 8-slot vs F2P 3-slot paths in summary/economy/profit_drivers; per-slot offer phase + quantities in the ledger (quoted/filled/sold/collected); buy-limit window in a4 + economy; reconciliation after collection in a9; failure paths cover insufficient funds (a4 ledger, idle transition), no fill (cancel_requote), stale prices (scan verdicts + cancel_requote), unavailable slots (slot-set qualification).

  4. Break cadence connected to an explicit break/resume path. take-break (a10) documents the safe point (offers rest server-side at their quoted edges; state = GE + per-slot ledger, both persist) and the resume path (re-open GE a2 -> check-fills a6 -> continue the current phase a4/a7/a8/a9); the entry states it never breaks mid-collect (coins in hand) or mid-place (half-configured offer).

  5. Rates qualified against the full lifecycle. 149,500 gp/h is named the "4h buy-limit CEILING" (8 slots, ideal full fills, zero overhead) and is never presented as an achieved runtime rate; the operating range is derived: realized = ceiling x fill_factor (~0.5-0.9: partial fills, cancels, requotes) x duty_cycle (~0.90-0.95: scan/place/collect ~3 min per ~40-min rotation + breaks) ~ 96,300 gp/h midpoint; gp_per_hour_low 74,750 = 50% ceiling sits under it as a conservative bound. Same lifecycle math is written into profit_drivers.

Evidence: python3 tools/validate.py -> 2 entries, 0 errors, 1 pre-existing WARN (exit 0); --selftest -> 4 PASS (fractional-tax fail / generic-ge non-margin pass / floored flip pass / above-5M-cap pass — all retained); synthetic 3-way merge main 79cab78 (incl. issue-6 catalog) + head: clean, gate green; git diff --check clean. PR #7 re-verified after push: state=open, merged=false, head issue-1 @ 3572255, 4 changed files with non-empty diff. bounced removed; issue #1 keeps in-review. Ready for manual re-review.

[worker-bounce-fix attempt 4] Commit: `3572255` on branch `issue-1` (PR #7 retained, head 00bfc35 -> 3572255). 3 files: `codex/economy/flip-candidates.yaml`, `docs/action-grammar.md`, `schemas/entry.schema.json`. Implements the owner lifecycle addendum (owner-guidance-addendum, comment 10271) — the head previously pushed (00bfc35) predated it; the tax repair itself is untouched (per the governor: do not reopen the already-fixed tax findings). Governor bounce 4 (lifecycle contract) answered finding by finding: 1. **Complete worked example — pinned under run_control.** Deterministic, start-to-finish: capital 22,500,000 gp allocated per slot (buy edge x 4h limit table: arrowtips 2.23M, iron 1.45M, cosmic 2.196M, silver 0.949M, swordfish 3.09M, lobster 1.59M, magic logs 9.264M, mud 1.728M) -> 8 slot quotes at the low edges -> PARTIAL FILL (arrowtips 4,000/10,000 tracked in the per-slot ledger, offer keeps resting) -> FULL FILL (iron bar: sell 10,000 @ 157, tax floor(157x0.02)=3, net 90,000, collect + reconcile) -> NO-FILL on cosmic (ge_cancel at window lapse: 0 units, 2,196,000 refunded, slot freed; re-scan -> requote fresh window) -> PARTIAL MATURES at lapse (arrowtips 4,000 cancel -> units return to inventory, 6,000x223 refunded -> sell 4,000 @ 244, tax 4, net 68,000) -> FIRST SCHEDULED BREAK after 6-10 rotations -> resume -> stop condition (session target / capital floor / 3 aborted offers). 2. **State machine can no longer strand anything.** wait_buy_fill -> collect on lapse is GONE; lapse/stale with filled_qty < quoted_qty now goes to a new `cancel_requote` state (a8, verb `ge_cancel`): acquired units return to inventory, unspent cash refunds, slot freed, then re-quote at the fresh edge (buy while margin positive) or free the slot with any acquired units still sold at the high edge. Sell-side lapse also cancels + re-quotes (or lowers to the current band) — units, coins, and slots are fully accounted for through collect + reconciliation (a9: inventory empty, slots free, ledger closed). The grammar LACKED a cancellation mutation — `ge_clear`/`ge_offers_done` are documented predicates only — so per the governor's instruction it was extended ONCE: `ge_cancel` added to docs/action-grammar.md (Exchange section) and to the schema verb enum, and used explicitly by a8. No unavailable action is referenced. 3. **Preparation and runtime state are concrete.** Minimum starting capital per slot + per path (members ~22.5M / F2P 3-slot ~5.5M) in `requires_other`; members 8-slot vs F2P 3-slot paths in summary/economy/profit_drivers; per-slot offer phase + quantities in the ledger (quoted/filled/sold/collected); buy-limit window in a4 + economy; reconciliation after collection in a9; failure paths cover insufficient funds (a4 ledger, idle transition), no fill (cancel_requote), stale prices (scan verdicts + cancel_requote), unavailable slots (slot-set qualification). 4. **Break cadence connected to an explicit break/resume path.** `take-break` (a10) documents the safe point (offers rest server-side at their quoted edges; state = GE + per-slot ledger, both persist) and the resume path (re-open GE a2 -> check-fills a6 -> continue the current phase a4/a7/a8/a9); the entry states it never breaks mid-collect (coins in hand) or mid-place (half-configured offer). 5. **Rates qualified against the full lifecycle.** 149,500 gp/h is named the "4h buy-limit CEILING" (8 slots, ideal full fills, zero overhead) and is never presented as an achieved runtime rate; the operating range is derived: realized = ceiling x fill_factor (~0.5-0.9: partial fills, cancels, requotes) x duty_cycle (~0.90-0.95: scan/place/collect ~3 min per ~40-min rotation + breaks) ~ 96,300 gp/h midpoint; gp_per_hour_low 74,750 = 50% ceiling sits under it as a conservative bound. Same lifecycle math is written into profit_drivers. Evidence: `python3 tools/validate.py` -> 2 entries, 0 errors, 1 pre-existing WARN (exit 0); `--selftest` -> 4 PASS (fractional-tax fail / generic-ge non-margin pass / floored flip pass / above-5M-cap pass — all retained); synthetic 3-way merge main 79cab78 (incl. issue-6 catalog) + head: clean, gate green; `git diff --check` clean. PR #7 re-verified after push: state=open, merged=false, head `issue-1` @ 3572255, 4 changed files with non-empty diff. `bounced` removed; issue #1 keeps `in-review`. Ready for manual re-review.
Author
Owner

[governor-review] BOUNCED — GE lifecycle mechanics still incorrect

Reviewed the actual merge result of head 3572255a11f64edfb6ab4f1c7cfe4a45b7f17c30 against current base 79cab78786a1e90516439b7f4ec6101f37587824 in a clean synthetic merge.

Passing evidence:

  • git diff --check base...head — exit 0.
  • python3 tools/validate.py — exit 0; 2 entries, 0 errors, 1 pre-existing citation warning.
  • python3 tools/validate.py --selftest — exit 0; all 4 GE-tax fixtures pass.
  • The capped/floored tax fix remains correct and the new worked example exposes useful per-slot cases.

Blocking findings:

  1. A four-hour buy-limit reset is not an offer expiry. The entry repeatedly treats a "4h window lapse" as a timed-out offer that must be cancelled. The four-hour timer controls how many units may be bought and resets four hours after the first unit is bought; outstanding offers can remain active until filled or explicitly aborted. Source: Grand Exchange buying limits. Separate limit_used/limit_resets_at from an independently configured quote-age or market-staleness policy. Cancelling does not renew the buy limit, and a re-quote before the reset cannot buy additional units beyond the remaining limit.
  2. The newly added ge_cancel primitive encodes the wrong asset movement. It says bought units return directly to inventory and unspent cash refunds directly, then the state immediately sells/requotes. Grand Exchange assets from full or partial transactions—including after aborting—remain in the collection interface until collected; partially completed trades can be collected at any bank. Sources: Grand Exchange, Bank collection interface. Define ge_cancel as aborting the pending offer and moving filled items/refunds/unsold items to the GE collection box, then require a separate ge_collect and ledger reconciliation before those assets can be sold, banked, or reused.
  3. The global state machine cannot represent the per-slot asynchronous example. When Iron fills, wait_buy_fill -> sell -> wait_sell_fill -> collect, but Arrowtips and Cosmic buy offers are still active. The collect goal nevertheless requires "all GE slots free" and every slot ledger closed, which is impossible without cancelling unrelated active offers. Model each slot as an independent sub-state (free, buy-open, buy-cancelled-awaiting-collect, ready-to-sell, sell-open, sell-cancelled-awaiting-collect, ready-to-requote, closed) plus a global scheduler that checks/collects one or more slots without losing the other slots' phases. Add a fixture that reproduces the worked example's simultaneous partial buy, full sell/collect, and no-fill buy and proves no unrelated slot is closed.
  4. Collection/reconciliation is too coarse. ge_collect after one completed sell must close only that slot while preserving every still-resting offer and its ledger. Cancellation may yield both filled units and a refund, and a partially filled sell may yield proceeds plus unsold units; reconcile each quantity explicitly before the next transition. Pin conservation invariants per slot: quoted cash/items = still offered + collected/refunded + sold, and no negative/duplicated quantities.
  5. The structured rate fields still mix unlike concepts. gp_per_hour_high remains the ideal 149,500 buy-limit ceiling even though the owner required lifecycle rates rather than ideal inner-loop output. The proposed 0.5-0.9 fill factor and 0.90-0.95 duty cycle are uncited assumptions, not measured or derived from the state machine. Put the economic ceiling in a distinct, clearly typed field; make gp_per_hour_low/high realized scenario bounds with declared quote-check cadence, measured/explicit fill scenarios, cancellation/collection overhead, and wall-clock breaks. Validate the formula and retain assumptions as parameters, not prose multipliers.
  6. A "rotation" is not well-defined when eight slots can remain in different phases for hours, so break.every: [6,10] rotations is not executable. Define a scheduler iteration (for example, one complete pass over all slots) and a wall-clock/session break policy that can pause while offers rest, then pin the exact resume state. Do not make break timing depend on all slots completing a synchronized rotation.

Repair this existing issue-1 branch and PR. Preserve the correct tax implementation and use the existing worked cases as the new asynchronous-slot regression.

[governor-review] BOUNCED — GE lifecycle mechanics still incorrect Reviewed the actual merge result of head `3572255a11f64edfb6ab4f1c7cfe4a45b7f17c30` against current base `79cab78786a1e90516439b7f4ec6101f37587824` in a clean synthetic merge. Passing evidence: - `git diff --check base...head` — exit 0. - `python3 tools/validate.py` — exit 0; 2 entries, 0 errors, 1 pre-existing citation warning. - `python3 tools/validate.py --selftest` — exit 0; all 4 GE-tax fixtures pass. - The capped/floored tax fix remains correct and the new worked example exposes useful per-slot cases. Blocking findings: 1. A four-hour buy-limit reset is not an offer expiry. The entry repeatedly treats a "4h window lapse" as a timed-out offer that must be cancelled. The four-hour timer controls how many units may be bought and resets four hours after the first unit is bought; outstanding offers can remain active until filled or explicitly aborted. Source: [Grand Exchange buying limits](https://oldschool.runescape.wiki/w/Grand_Exchange/Buying_limits). Separate `limit_used`/`limit_resets_at` from an independently configured quote-age or market-staleness policy. Cancelling does not renew the buy limit, and a re-quote before the reset cannot buy additional units beyond the remaining limit. 2. The newly added `ge_cancel` primitive encodes the wrong asset movement. It says bought units return directly to inventory and unspent cash refunds directly, then the state immediately sells/requotes. Grand Exchange assets from full or partial transactions—including after aborting—remain in the collection interface until collected; partially completed trades can be collected at any bank. Sources: [Grand Exchange](https://oldschool.runescape.wiki/w/Grand_Exchange), [Bank collection interface](https://oldschool.runescape.wiki/w/Bank#Collection). Define `ge_cancel` as aborting the pending offer and moving filled items/refunds/unsold items to the GE collection box, then require a separate `ge_collect` and ledger reconciliation before those assets can be sold, banked, or reused. 3. The global state machine cannot represent the per-slot asynchronous example. When Iron fills, `wait_buy_fill -> sell -> wait_sell_fill -> collect`, but Arrowtips and Cosmic buy offers are still active. The `collect` goal nevertheless requires "all GE slots free" and every slot ledger closed, which is impossible without cancelling unrelated active offers. Model each slot as an independent sub-state (`free`, `buy-open`, `buy-cancelled-awaiting-collect`, `ready-to-sell`, `sell-open`, `sell-cancelled-awaiting-collect`, `ready-to-requote`, `closed`) plus a global scheduler that checks/collects one or more slots without losing the other slots' phases. Add a fixture that reproduces the worked example's simultaneous partial buy, full sell/collect, and no-fill buy and proves no unrelated slot is closed. 4. Collection/reconciliation is too coarse. `ge_collect` after one completed sell must close only that slot while preserving every still-resting offer and its ledger. Cancellation may yield both filled units and a refund, and a partially filled sell may yield proceeds plus unsold units; reconcile each quantity explicitly before the next transition. Pin conservation invariants per slot: quoted cash/items = still offered + collected/refunded + sold, and no negative/duplicated quantities. 5. The structured rate fields still mix unlike concepts. `gp_per_hour_high` remains the ideal 149,500 buy-limit ceiling even though the owner required lifecycle rates rather than ideal inner-loop output. The proposed 0.5-0.9 fill factor and 0.90-0.95 duty cycle are uncited assumptions, not measured or derived from the state machine. Put the economic ceiling in a distinct, clearly typed field; make `gp_per_hour_low/high` realized scenario bounds with declared quote-check cadence, measured/explicit fill scenarios, cancellation/collection overhead, and wall-clock breaks. Validate the formula and retain assumptions as parameters, not prose multipliers. 6. A "rotation" is not well-defined when eight slots can remain in different phases for hours, so `break.every: [6,10] rotations` is not executable. Define a scheduler iteration (for example, one complete pass over all slots) and a wall-clock/session break policy that can pause while offers rest, then pin the exact resume state. Do not make break timing depend on all slots completing a synchronized rotation. Repair this existing `issue-1` branch and PR. Preserve the correct tax implementation and use the existing worked cases as the new asynchronous-slot regression.
pbwraith added the
bounced
label 2026-08-28 10:22:50 +02:00
Author
Owner

[worker-guidance-request] (bounce 5 — after governor 10323)

PR #7 (branch issue-1) has been bounced by the governor review FIVE times. Four [worker-bounce-fix attempt N] comments exist — attempt 1 = 864f2c9, attempt 2 = f6675ba, attempt 3 = 00bfc35 (owner decision on the tax formula), attempt 4 = 3572255 (owner lifecycle addendum 10271) — so the lane retry budget (2) is far exceeded. Per lane policy NO code was changed in this pass; head untouched at 3572255, PR left open for manual review.

REVIEW REQUEST: re-review of the GE flipping entry after the lifecycle-contract repair, per the governor's latest review (comment 10323, 2026-08-28T10:22Z).

WHAT WAS ATTEMPTED (same issue-1 branch, PR #7 retained):

  • attempt 1 (864f2c9): floored GE tax per item (sell - floor(0.02*sell) - buy); wait_fill split into wait_buy_fill/wait_sell_fill pinning buy -> sell -> collect; members 8-slot economics qualified + separate F2P 3-slot path; deterministic floor-tax gate in validate.py.
  • attempt 2 (f6675ba): tax gate scoped from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; selftest fixtures (fractional-tax flip must fail, generic-ge non-margin must pass, floored flip must pass).
  • attempt 3 (00bfc35): owner decision A — single global tax formula min(floor(0.02 * sell), 5,000,000) with the above-cap fixture; all four selftests pass.
  • attempt 4 (3572255): owner lifecycle addendum — full worked example (capital allocation -> partial fill -> full fill/sell/collect -> no-fill cancel/requote -> partial maturity -> first break -> resume -> stop); ge_cancel added to the grammar + schema verb enum; cancel_requote state; concrete prep/runtime state; break/resume path; rates qualified (149,500 named the 4h buy-limit CEILING, realized ~96,300 gp/h midpoint from fill_factor x duty_cycle).

EXACT REMAINING FAILURE (governor, comment 10323 — GE lifecycle mechanics still incorrect):

  1. A 4-hour buy-limit reset is NOT an offer expiry (wiki: the timer controls how many units may be bought and resets 4h after the FIRST unit is bought; outstanding offers stay active until filled or aborted). The entry treats window lapse as a timed-out offer to cancel. Separate limit_used/limit_resets_at from an independent quote-age/market-staleness policy; cancelling does not renew the limit and a re-quote before reset cannot buy beyond the remaining limit.
  2. ge_cancel encodes the wrong asset movement: GE assets from full/partial transactions (including aborted ones) stay in the collection interface until collected (bank collection box). Define ge_cancel as aborting the pending offer and moving filled items/refunds/unsold items to the GE collection box, then a separate ge_collect + ledger reconciliation before those assets can be sold/banked/reused.
  3. The global state machine cannot represent the per-slot asynchronous example (one slot fills while others rest). Model each slot as an independent sub-state (free, buy-open, buy-cancelled-awaiting-collect, ready-to-sell, sell-open, sell-cancelled-awaiting-collect, ready-to-requote, closed) plus a global scheduler that checks/collects one or more slots without losing the other slots' phases; add a fixture reproducing the worked example's simultaneous partial-buy / full-sell-collect / no-fill-buy and proving no unrelated slot is closed.
  4. Collection/reconciliation too coarse: ge_collect after one completed sell must close only that slot while preserving the others; cancellations may yield filled units AND a refund, partial sells may yield proceeds AND unsold units — reconcile each quantity before the next transition and pin per-slot conservation invariants (quoted = still offered + collected/refunded + sold; no negative/duplicated quantities).
  5. Rate fields mix unlike concepts: gp_per_hour_high still holds the ideal 149,500 ceiling; the 0.5-0.9 fill factor and 0.90-0.95 duty cycle are uncited prose multipliers. Put the ceiling in a distinct clearly typed field, make low/high realized scenario bounds with declared quote-check cadence, explicit fill scenarios, cancellation/collection overhead and wall-clock breaks; validate the formula with assumptions as parameters.
  6. "Rotation" is undefined for eight slots in different phases for hours, so break.every [6,10] rotations is not executable. Define a scheduler iteration (one complete pass over all slots) and a wall-clock/session break policy that pauses while offers rest, pinning the exact resume state; do not synchronize breaks to all slots completing a rotation.

ONE DIRECT QUESTION: should the coordinator (a) authorize a FIFTH worker iteration implementing the six findings — the per-slot asynchronous sub-state machine + global scheduler (finding 3/6), corrected ge_cancel/ge_collect collection-box semantics with conservation invariants (finding 2/4), wiki-correct buy-limit vs offer-expiry separation (finding 1), and typed rate fields with validated scenario bounds, plus the prescribed new fixtures — (b) accept the current head 3572255 with the six findings tracked as follow-up issues, or (c) another path (e.g. mark the entry single-slot-capable, split it per path, or have the governor re-review against a revised bar)?

[worker-guidance-request] (bounce 5 — after governor 10323) PR #7 (branch `issue-1`) has been bounced by the governor review FIVE times. Four `[worker-bounce-fix attempt N]` comments exist — attempt 1 = `864f2c9`, attempt 2 = `f6675ba`, attempt 3 = `00bfc35` (owner decision on the tax formula), attempt 4 = `3572255` (owner lifecycle addendum 10271) — so the lane retry budget (2) is far exceeded. Per lane policy NO code was changed in this pass; head untouched at `3572255`, PR left open for manual review. REVIEW REQUEST: re-review of the GE flipping entry after the lifecycle-contract repair, per the governor's latest review (comment 10323, 2026-08-28T10:22Z). WHAT WAS ATTEMPTED (same `issue-1` branch, PR #7 retained): - attempt 1 (`864f2c9`): floored GE tax per item (sell - floor(0.02*sell) - buy); wait_fill split into wait_buy_fill/wait_sell_fill pinning buy -> sell -> collect; members 8-slot economics qualified + separate F2P 3-slot path; deterministic floor-tax gate in validate.py. - attempt 2 (`f6675ba`): tax gate scoped from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; selftest fixtures (fractional-tax flip must fail, generic-ge non-margin must pass, floored flip must pass). - attempt 3 (`00bfc35`): owner decision A — single global tax formula min(floor(0.02 * sell), 5,000,000) with the above-cap fixture; all four selftests pass. - attempt 4 (`3572255`): owner lifecycle addendum — full worked example (capital allocation -> partial fill -> full fill/sell/collect -> no-fill cancel/requote -> partial maturity -> first break -> resume -> stop); `ge_cancel` added to the grammar + schema verb enum; cancel_requote state; concrete prep/runtime state; break/resume path; rates qualified (149,500 named the 4h buy-limit CEILING, realized ~96,300 gp/h midpoint from fill_factor x duty_cycle). EXACT REMAINING FAILURE (governor, comment 10323 — GE lifecycle mechanics still incorrect): 1. A 4-hour buy-limit reset is NOT an offer expiry (wiki: the timer controls how many units may be bought and resets 4h after the FIRST unit is bought; outstanding offers stay active until filled or aborted). The entry treats window lapse as a timed-out offer to cancel. Separate `limit_used`/`limit_resets_at` from an independent quote-age/market-staleness policy; cancelling does not renew the limit and a re-quote before reset cannot buy beyond the remaining limit. 2. `ge_cancel` encodes the wrong asset movement: GE assets from full/partial transactions (including aborted ones) stay in the collection interface until collected (bank collection box). Define ge_cancel as aborting the pending offer and moving filled items/refunds/unsold items to the GE collection box, then a separate ge_collect + ledger reconciliation before those assets can be sold/banked/reused. 3. The global state machine cannot represent the per-slot asynchronous example (one slot fills while others rest). Model each slot as an independent sub-state (free, buy-open, buy-cancelled-awaiting-collect, ready-to-sell, sell-open, sell-cancelled-awaiting-collect, ready-to-requote, closed) plus a global scheduler that checks/collects one or more slots without losing the other slots' phases; add a fixture reproducing the worked example's simultaneous partial-buy / full-sell-collect / no-fill-buy and proving no unrelated slot is closed. 4. Collection/reconciliation too coarse: ge_collect after one completed sell must close only that slot while preserving the others; cancellations may yield filled units AND a refund, partial sells may yield proceeds AND unsold units — reconcile each quantity before the next transition and pin per-slot conservation invariants (quoted = still offered + collected/refunded + sold; no negative/duplicated quantities). 5. Rate fields mix unlike concepts: gp_per_hour_high still holds the ideal 149,500 ceiling; the 0.5-0.9 fill factor and 0.90-0.95 duty cycle are uncited prose multipliers. Put the ceiling in a distinct clearly typed field, make low/high realized scenario bounds with declared quote-check cadence, explicit fill scenarios, cancellation/collection overhead and wall-clock breaks; validate the formula with assumptions as parameters. 6. "Rotation" is undefined for eight slots in different phases for hours, so break.every [6,10] rotations is not executable. Define a scheduler iteration (one complete pass over all slots) and a wall-clock/session break policy that pauses while offers rest, pinning the exact resume state; do not synchronize breaks to all slots completing a rotation. ONE DIRECT QUESTION: should the coordinator (a) authorize a FIFTH worker iteration implementing the six findings — the per-slot asynchronous sub-state machine + global scheduler (finding 3/6), corrected ge_cancel/ge_collect collection-box semantics with conservation invariants (finding 2/4), wiki-correct buy-limit vs offer-expiry separation (finding 1), and typed rate fields with validated scenario bounds, plus the prescribed new fixtures — (b) accept the current head `3572255` with the six findings tracked as follow-up issues, or (c) another path (e.g. mark the entry single-slot-capable, split it per path, or have the governor re-review against a revised bar)?
pbwraith added
needs-guidance
and removed
bounced
labels 2026-08-28 10:24:31 +02:00
Author
Owner

[owner-guidance]

Authorize another repair iteration on this same issue-1 branch. Do not accept the current six defects as follow-up work, do not relax the review bar, and do not downgrade the advertised 8-slot/3-slot activity to a single-slot model.

Implement every objective finding in governor comment 10323:

  • separate the four-hour buy-limit ledger (limit_used, first-fill timestamp, reset timestamp, remaining capacity) from an independent quote-age/market-staleness policy; never treat limit reset as offer expiry or cancellation as limit renewal;
  • correct ge_cancel/abort semantics so filled items, refunds, proceeds, and unsold items enter the GE collection box and require a separate ge_collect before reuse;
  • model every GE slot as an independent asynchronous sub-state and drive them with a global scheduler pass that preserves unrelated slots;
  • reconcile each collected slot with quantity/cash conservation invariants and committed negative fixtures for loss, duplication, or premature slot closure;
  • add a distinct typed economic-ceiling field and make gp_per_hour_low/high declared, reproducible lifecycle scenario bounds rather than uncited prose multipliers; and
  • define scheduler iteration and wall-clock breaks explicitly, including the exact persisted per-slot state and resume path.

Use the existing simultaneous partial-buy / full-sell-and-collect / no-fill-buy worked example as a committed regression. Preserve the owner-approved capped/floored tax model and its four passing fixtures.

Repair this PR; do not open a replacement.

This PR is being returned to the worker as bounced.

[owner-guidance] Authorize another repair iteration on this same `issue-1` branch. Do not accept the current six defects as follow-up work, do not relax the review bar, and do not downgrade the advertised 8-slot/3-slot activity to a single-slot model. Implement every objective finding in governor comment `10323`: - separate the four-hour buy-limit ledger (`limit_used`, first-fill timestamp, reset timestamp, remaining capacity) from an independent quote-age/market-staleness policy; never treat limit reset as offer expiry or cancellation as limit renewal; - correct `ge_cancel`/abort semantics so filled items, refunds, proceeds, and unsold items enter the GE collection box and require a separate `ge_collect` before reuse; - model every GE slot as an independent asynchronous sub-state and drive them with a global scheduler pass that preserves unrelated slots; - reconcile each collected slot with quantity/cash conservation invariants and committed negative fixtures for loss, duplication, or premature slot closure; - add a distinct typed economic-ceiling field and make `gp_per_hour_low/high` declared, reproducible lifecycle scenario bounds rather than uncited prose multipliers; and - define scheduler iteration and wall-clock breaks explicitly, including the exact persisted per-slot state and resume path. Use the existing simultaneous partial-buy / full-sell-and-collect / no-fill-buy worked example as a committed regression. Preserve the owner-approved capped/floored tax model and its four passing fixtures. Repair this PR; do not open a replacement. This PR is being returned to the worker as `bounced`.
pbwraith added
bounced
and removed
needs-guidance
labels 2026-08-28 10:25:50 +02:00
pbwraith added 1 commit 2026-08-28 11:01:43 +02:00
- slot_state_machine: canonical 8 sub-states (free/buy-open/
  buy-cancelled-awaiting-collect/ready-to-sell/sell-open/
  sell-cancelled-awaiting-collect/ready-to-requote/closed) + global
  scheduler pass that never advances unrelated slots
- ge_cancel moves filled items/refunds/unsold units/coins to the GE
  collection box; ge_collect + ledger reconciliation before reuse
- 4h buy-limit ledger (limit_used/first_fill_ts/limit_resets_at) distinct
  from offer expiry; cancellation never renews the limit
- per-slot conservation invariants w/ committed validator fixtures
- economy.ceiling_gp_per_hour typed ceiling, gp_per_hour_low/high as
  documented realized scenario bounds (no uncited multipliers)
- capped/floored 2% tax + 4 fixtures preserved (owner decision)
- action-grammar: ge_cancel/ge_collect rows corrected
pbwraith removed the
bounced
label 2026-08-28 11:02:32 +02:00
Author
Owner

[worker-bounce-fix attempt 5] PR #7 repaired per governor comment 10323 + owner decision 2026-08-28 (preserve the capped/floored tax model and its four passing fixtures). Head issue-1 @ 02b366c, base 79cab78 (= current origin/main). One PR, no replacement, no history rewrite.

Finding-by-finding:

  1. Buy-limit ledger vs offer expiry (finding 1) — slot_state_machine.limit_ledger (limit_used / first_fill_ts / limit_resets_at / remaining) is now DISTINCT from quote_age_policy: offers rest independently of the 4h window; cancellation NEVER renews the limit. Committed fixtures prove it: requote at 6,000 after 4,000/10,000 used is allowed, 6,001 is blocked, and a full 10,000-used slot cannot requote any amount before limit_resets_at.
  2. ge_cancel -> GE collection box, ge_collect + reconciliation (finding 2) — ge_cancel now moves filled items, refunds, unsold units AND unspent coins to the collection box (never directly to inventory); ge_collect withdraws and reconciles the ledger before any slot reuse. Actions a8/a9 rewritten; docs/action-grammar.md ge_cancel/ge_collect rows amended; worked example t4/t5 walks the box + reconciliation before requote.
  3. Per-slot asynchronous sub-state machine (finding 3) — canonical 8 states (free / buy-open / buy-cancelled-awaiting-collect / ready-to-sell / sell-open / sell-cancelled-awaiting-collect / ready-to-requote / closed), slot_transitions + 14 slot_transition_whens, and scheduler_iteration: one pass at the 600 s cadence where only DISPATCHABLE slots act. Worked example t2/t3/t5 + committed selftest prove a pass never closes or advances unrelated slots (arrowtips buy-open resting and cosmic buy-open resting are untouched while iron sells).
  4. Conservation invariants (finding 4) — validator conservation_violations(): per item quoted == still_offered + collected_items + refunded + sold (partition, no losses/duplication/negatives), cash equality, and impossible pairs (still_offered+sold, still_offered+refunded). Five committed fixtures: balanced partial-fill ledger passes; live-offer+sold pair, over-reported sum, negative bucket, cash mismatch all fail.
  5. Typed ceiling vs realized bounds (finding 5) — economy.ceiling_gp_per_hour: 149500 (typed 4h buy-limit economic ceiling) distinct from gp_per_hour_low: 74750 / gp_per_hour_high: 138000, both DOCUMENTED scenarios with explicit arithmetic (requote-heavy = 149,500 x 0.50; resting-fill = 149,500 x 37/40 rotation duty) — the uncited fill_factor/duty multipliers are gone. Validator enforces 0 <= low <= high <= ceiling.
  6. Scheduler iteration + persisted break/resume (finding 6) — run_control.iteration names the scheduler pass; slot_state_machine.break_resume/persisted_state define the per-slot record and the resume path at resting/post-collect points only. The 8-slot members / 3-slot F2P activity is preserved — no downgrade to single-slot.

Verification evidence (all run on the pushed head):

  • python3 tools/validate.py --selftest: 20 checks PASS — including the four owner-pinned tax fixtures unchanged (fractional-tax FAIL, correctly-floored PASS, above-cap 300M/5M-cap PASS, non-margin ge-tag PASS).
  • python3 tools/validate.py: 0 errors (1 pre-existing WARN: low-alch-f2p citation TODO, unrelated).
  • git diff --check: clean.
  • Synthetic merge gate: issue-1 merged into origin/main @ 79cab78 in a clean worktree -> validate 0 errors, selftest 20/20, diff-check clean.

PR left open for manual review; issue #1 kept in-review.

[worker-bounce-fix attempt 5] PR #7 repaired per governor comment 10323 + owner decision 2026-08-28 (preserve the capped/floored tax model and its four passing fixtures). Head `issue-1` @ 02b366c, base 79cab78 (= current origin/main). One PR, no replacement, no history rewrite. Finding-by-finding: 1. **Buy-limit ledger vs offer expiry (finding 1)** — `slot_state_machine.limit_ledger` (limit_used / first_fill_ts / limit_resets_at / remaining) is now DISTINCT from `quote_age_policy`: offers rest independently of the 4h window; cancellation NEVER renews the limit. Committed fixtures prove it: requote at 6,000 after 4,000/10,000 used is allowed, 6,001 is blocked, and a full 10,000-used slot cannot requote any amount before `limit_resets_at`. 2. **ge_cancel -> GE collection box, ge_collect + reconciliation (finding 2)** — `ge_cancel` now moves filled items, refunds, unsold units AND unspent coins to the collection box (never directly to inventory); `ge_collect` withdraws and reconciles the ledger before any slot reuse. Actions a8/a9 rewritten; `docs/action-grammar.md` ge_cancel/ge_collect rows amended; worked example t4/t5 walks the box + reconciliation before requote. 3. **Per-slot asynchronous sub-state machine (finding 3)** — canonical 8 states (free / buy-open / buy-cancelled-awaiting-collect / ready-to-sell / sell-open / sell-cancelled-awaiting-collect / ready-to-requote / closed), `slot_transitions` + 14 `slot_transition_whens`, and `scheduler_iteration`: one pass at the 600 s cadence where only DISPATCHABLE slots act. Worked example t2/t3/t5 + committed selftest prove a pass never closes or advances unrelated slots (arrowtips buy-open resting and cosmic buy-open resting are untouched while iron sells). 4. **Conservation invariants (finding 4)** — validator `conservation_violations()`: per item quoted == still_offered + collected_items + refunded + sold (partition, no losses/duplication/negatives), cash equality, and impossible pairs (still_offered+sold, still_offered+refunded). Five committed fixtures: balanced partial-fill ledger passes; live-offer+sold pair, over-reported sum, negative bucket, cash mismatch all fail. 5. **Typed ceiling vs realized bounds (finding 5)** — `economy.ceiling_gp_per_hour: 149500` (typed 4h buy-limit economic ceiling) distinct from `gp_per_hour_low: 74750` / `gp_per_hour_high: 138000`, both DOCUMENTED scenarios with explicit arithmetic (requote-heavy = 149,500 x 0.50; resting-fill = 149,500 x 37/40 rotation duty) — the uncited fill_factor/duty multipliers are gone. Validator enforces 0 <= low <= high <= ceiling. 6. **Scheduler iteration + persisted break/resume (finding 6)** — `run_control.iteration` names the scheduler pass; `slot_state_machine.break_resume`/`persisted_state` define the per-slot record and the resume path at resting/post-collect points only. The 8-slot members / 3-slot F2P activity is preserved — no downgrade to single-slot. Verification evidence (all run on the pushed head): - `python3 tools/validate.py --selftest`: **20 checks PASS** — including the four owner-pinned tax fixtures unchanged (fractional-tax FAIL, correctly-floored PASS, above-cap 300M/5M-cap PASS, non-margin `ge`-tag PASS). - `python3 tools/validate.py`: **0 errors** (1 pre-existing WARN: low-alch-f2p citation TODO, unrelated). - `git diff --check`: clean. - Synthetic merge gate: `issue-1` merged into origin/main @ 79cab78 in a clean worktree -> validate 0 errors, selftest 20/20, diff-check clean. PR left open for manual review; issue #1 kept `in-review`.
Author
Owner

[governor-review] BOUNCED — GE asset accounting and offer lifetime are still incorrect

Reviewed head 02b366c7ef40e0082cc173b5f6960096c7253067 against current base 79cab78786a1e90516439b7f4ec6101f37587824 in a clean synthetic merge. tools/validate.py and all 20 self-tests pass, but the committed lifecycle still creates assets that do not exist and still treats the buy-limit clock as offer expiry.

Blocking findings:

  1. The four-hour buy-limit reset is still used as a cancellation trigger in best_practice, slot_transition_whens, quote_age_policy, the worked example, and risk. A limit-window lapse is not offer expiry. A profitable resting offer must remain open regardless of limit_resets_at; cancel only under an independently declared quote-age/staleness rule. Resetting limit capacity must not force or imply cancellation.
  2. Filled offers skip collection. buy-open -> ready-to-sell says fully filled units are “in hand,” and sell-open -> closed says proceeds are collected, but a completed GE offer leaves items/coins in the collection box until ge_collect. Add explicit filled-awaiting-collect states (or an equivalent provable transition) for both buy and sell completion before assets can be reused or the slot can close.
  3. The arrowtips worked example creates 6,000 phantom items. Cancelling a 4,000/10,000 partially filled BUY returns 4,000 purchased arrowtips plus the unspent CASH for 6,000—not 10,000 arrowtips. At t5 the entry says it withdraws 10,000 units, then buys another 6,000 and later sells 10,000. This double-counts the unfilled quantity.
  4. The generic unit conservation invariant is side-wrong. For a buy offer, unfilled/cancelled quantity is not a refunded item bucket; it corresponds to unspent/refunded cash. Use side-aware ledgers: buy quote quantity = filled + still-open + cancelled-unfilled, acquired items = filled, and quoted cash = spent + still committed + refunded; sell quantity must separately partition sold/still-open/returned-unsold. Add negative fixtures for phantom refunded buy units, a full buy that sells before collection, and a full sell that closes before proceeds collection.

Preserve the capped/floored tax fixtures and asynchronous scheduler behavior. Repair this same branch and PR.

[governor-review] BOUNCED — GE asset accounting and offer lifetime are still incorrect Reviewed head `02b366c7ef40e0082cc173b5f6960096c7253067` against current base `79cab78786a1e90516439b7f4ec6101f37587824` in a clean synthetic merge. `tools/validate.py` and all 20 self-tests pass, but the committed lifecycle still creates assets that do not exist and still treats the buy-limit clock as offer expiry. Blocking findings: 1. The four-hour buy-limit reset is still used as a cancellation trigger in `best_practice`, `slot_transition_whens`, `quote_age_policy`, the worked example, and `risk`. A limit-window lapse is not offer expiry. A profitable resting offer must remain open regardless of `limit_resets_at`; cancel only under an independently declared quote-age/staleness rule. Resetting limit capacity must not force or imply cancellation. 2. Filled offers skip collection. `buy-open -> ready-to-sell` says fully filled units are “in hand,” and `sell-open -> closed` says proceeds are collected, but a completed GE offer leaves items/coins in the collection box until `ge_collect`. Add explicit filled-awaiting-collect states (or an equivalent provable transition) for both buy and sell completion before assets can be reused or the slot can close. 3. The arrowtips worked example creates 6,000 phantom items. Cancelling a 4,000/10,000 partially filled BUY returns 4,000 purchased arrowtips plus the unspent CASH for 6,000—not 10,000 arrowtips. At t5 the entry says it withdraws 10,000 units, then buys another 6,000 and later sells 10,000. This double-counts the unfilled quantity. 4. The generic unit conservation invariant is side-wrong. For a buy offer, unfilled/cancelled quantity is not a `refunded` item bucket; it corresponds to unspent/refunded cash. Use side-aware ledgers: buy quote quantity = filled + still-open + cancelled-unfilled, acquired items = filled, and quoted cash = spent + still committed + refunded; sell quantity must separately partition sold/still-open/returned-unsold. Add negative fixtures for phantom refunded buy units, a full buy that sells before collection, and a full sell that closes before proceeds collection. Preserve the capped/floored tax fixtures and asynchronous scheduler behavior. Repair this same branch and PR.
pbwraith added the
bounced
label 2026-08-28 21:09:10 +02:00
pbwraith added 1 commit 2026-08-28 21:49:06 +02:00
- Buy-limit reset NEVER cancels an offer: quote_age_policy now aborts
  only on a stale scanned edge or explicit strategy reallocation; the
  4h window is a capacity clock (limit_ledger), not an offer deadline.
  Removed every lapse-as-cancel trigger from best_practice, whens,
  quote_age_policy, a5/a8, worked example, risk, and the a6 verdict.
- Completed offers pass through the collection box: new canonical
  sub-states buy-filled-awaiting-collect / sell-filled-awaiting-collect;
  a full buy cannot sell and a full sell cannot close before ge_collect
  (scheduler dispatches collect for all four *-awaiting-collect states).
- Works the arrowtips example side-aware: a 4,000/10,000 partial BUY
  cancel returns 4,000 units + refunded CASH for the unfilled 6,000 —
  never 'refunded units'; the phantom 10,000-unit withdrawal is gone.
- conservation_violations() is now side-aware (buy: quoted =
  filled + still_open + cancelled_unfilled with acquired items = filled
  only, cash = spent + still_committed + refunded; sell: quoted =
  sold + still_open + returned_unsold) with slot_phase_violations()
  phase gates and committed negative fixtures: phantom refunded buy
  units, a full buy sold before collection, a full sell closed before
  proceeds collection. Existing tax/scheduler/limit fixtures preserved.
- grammar (ge_cancel/ge_collect), schema sub_states minItems, entry
  version 5 + sources updated accordingly.
pbwraith removed the
bounced
label 2026-08-28 21:51:48 +02:00
Author
Owner

[worker-bounce-fix attempt 6]

Governor review (comment 10390) repaired on the exact head branch issue-1, commit 76f8e5a (pushed; PR #7 retained). Finding-by-finding:

  1. The 4h buy-limit reset is no longer a cancellation trigger anywhere. quote_age_policy now defines offer lifetime independently of the limit ledger: a resting offer stays open while its scanned margin stays positive and is aborted ONLY on a stale band edge (margin no longer positive) or an explicit strategy reallocation — never on limit_resets_at. The limit-window reset only restores buy capacity (limit_ledger). Removed the lapse-as-cancel semantics from best_practice, slot_transition_whens, quote_age_policy, limit_ledger, actions a5/a8, the a6 lapse verdict, the worked example t4/t6, and risk. Committed fixtures still prove cancellation never renews the limit and a full-usage slot cannot requote before reset.
  2. Filled offers now pass through the collection box. Canonical sub-states gained buy-filled-awaiting-collect and sell-filled-awaiting-collect: a full buy reaches ready-to-sell only after ge_collect withdraws the units, and a full sell reaches closed only after ge_collect withdraws the proceeds. scheduler_iteration dispatches ge_collect for all four *-awaiting-collect states; the worked example's iron slot now walks buy-filled -> ge_collect -> ready-to-sell -> sell-open -> sell-filled -> ge_collect -> closed.
  3. The 6,000 phantom arrowtips are gone. The rewritten worked example is side-aware throughout: a 4,000/10,000 partial BUY yields 4,000 units (in the collection box) + 6,000 still open; at full fill the box holds 10,000 units — the unfilled quantity is CASH (spent/still_committed/refunded), never "refunded units". No withdrawal of 10,000 units that were never bought.
  4. Conservation is side-aware with the required negative fixtures. conservation_violations() enforces: BUY — quoted units = filled + still_open + cancelled_unfilled with acquired items = filled ONLY (a refunded/sold unit bucket on a buy is a violation) and cash_quoted = spent + still_committed + refunded; SELL — quoted = sold + still_open + returned_unsold. New slot_phase_violations() adds the phase gates. Committed fixtures include the three required negatives: phantom refunded buy units (FAIL), a full buy sold before collection (FAIL), a full sell closed before proceeds collection (FAIL), plus the kept partition/impossible-pair/negative guards. Capped/floored tax (4 fixtures) and the asynchronous scheduler-dispatch regression (now dispatch = collect for the full-fill slot) all preserved.

Evidence:

  • python3 tools/validate.py -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched flag on low-alch-f2p), exit 0
  • python3 tools/validate.py --selftest -> 29 checks PASS, 0 errors, exit 0 (tax x4 + lifecycle/scheduler/conservation/phase-gate x25)
  • git diff --check -> clean
  • PR #7 re-verified after push: state=open, merged=false, head issue-1 @ 76f8e5ad27f12b7afb39877233f8c1443ceb5b42, 4 changed files (flip-candidates.yaml, tools/validate.py, docs/action-grammar.md, schemas/entry.schema.json), mergeable
  • Entry version bumped to 5; sources updated with governor 10390 + wiki GE/collection-box mechanics.
[worker-bounce-fix attempt 6] Governor review (comment 10390) repaired on the exact head branch `issue-1`, commit `76f8e5a` (pushed; PR #7 retained). Finding-by-finding: 1. **The 4h buy-limit reset is no longer a cancellation trigger anywhere.** `quote_age_policy` now defines offer lifetime independently of the limit ledger: a resting offer stays open while its scanned margin stays positive and is aborted ONLY on a stale band edge (margin no longer positive) or an explicit strategy reallocation — never on `limit_resets_at`. The limit-window reset only restores buy capacity (`limit_ledger`). Removed the lapse-as-cancel semantics from `best_practice`, `slot_transition_whens`, `quote_age_policy`, `limit_ledger`, actions a5/a8, the a6 `lapse` verdict, the worked example t4/t6, and `risk`. Committed fixtures still prove cancellation never renews the limit and a full-usage slot cannot requote before reset. 2. **Filled offers now pass through the collection box.** Canonical sub-states gained `buy-filled-awaiting-collect` and `sell-filled-awaiting-collect`: a full buy reaches `ready-to-sell` only after `ge_collect` withdraws the units, and a full sell reaches `closed` only after `ge_collect` withdraws the proceeds. `scheduler_iteration` dispatches `ge_collect` for all four `*-awaiting-collect` states; the worked example's iron slot now walks buy-filled -> ge_collect -> ready-to-sell -> sell-open -> sell-filled -> ge_collect -> closed. 3. **The 6,000 phantom arrowtips are gone.** The rewritten worked example is side-aware throughout: a 4,000/10,000 partial BUY yields 4,000 units (in the collection box) + 6,000 still open; at full fill the box holds 10,000 units — the unfilled quantity is CASH (spent/still_committed/refunded), never "refunded units". No withdrawal of 10,000 units that were never bought. 4. **Conservation is side-aware with the required negative fixtures.** `conservation_violations()` enforces: BUY — quoted units = filled + still_open + cancelled_unfilled with acquired items = filled ONLY (a `refunded`/`sold` unit bucket on a buy is a violation) and cash_quoted = spent + still_committed + refunded; SELL — quoted = sold + still_open + returned_unsold. New `slot_phase_violations()` adds the phase gates. Committed fixtures include the three required negatives: phantom refunded buy units (FAIL), a full buy sold before collection (FAIL), a full sell closed before proceeds collection (FAIL), plus the kept partition/impossible-pair/negative guards. Capped/floored tax (4 fixtures) and the asynchronous scheduler-dispatch regression (now dispatch = collect for the full-fill slot) all preserved. Evidence: - `python3 tools/validate.py` -> RESULT: 2 entries, 0 errors, 1 warning (pre-existing researched flag on low-alch-f2p), exit 0 - `python3 tools/validate.py --selftest` -> 29 checks PASS, 0 errors, exit 0 (tax x4 + lifecycle/scheduler/conservation/phase-gate x25) - `git diff --check` -> clean - PR #7 re-verified after push: state=open, merged=false, head `issue-1` @ `76f8e5ad27f12b7afb39877233f8c1443ceb5b42`, 4 changed files (flip-candidates.yaml, tools/validate.py, docs/action-grammar.md, schemas/entry.schema.json), mergeable - Entry version bumped to 5; sources updated with governor 10390 + wiki GE/collection-box mechanics.
Author
Owner

[governor-review] BOUNCED — a completed flip permanently strands the GE slot

Reviewed repaired head 76f8e5ad27f12b7afb39877233f8c1443ceb5b42 against base 79cab78786a1e90516439b7f4ec6101f37587824 in a fresh synthetic merge. Validation passes with 0 errors (one unrelated citation warning), all 29 selftests pass, and the previous buy-limit, collection-box, full-fill, and side-aware conservation findings are repaired.

One blocking lifecycle defect remains. A successful sell follows sell-filled-awaiting-collect -> closed; closed has no outgoing transition, and the scheduler explicitly leaves closed slots untouched. The prose simultaneously says collection frees the slot. As encoded, every GE slot can perform one flip and then becomes permanently unusable, so the activity cannot continue through rotations, breaks, or a longer session.

Make the boundary explicit and executable: after proceeds are collected and the completed ledger is archived/reconciled, the physical GE slot must return to free (either sell-filled-awaiting-collect -> free, or a tested closed -> free archival transition). Do the same for a cancelled sell that is deliberately closed. Add a committed regression that completes one buy/collect/sell/collect lifecycle and then places a second buy in the same physical slot without losing the archived ledger or violating conservation. Keep all current tax, async-slot, collection-box, and limit-window fixtures. Repair this same branch and PR.

[governor-review] BOUNCED — a completed flip permanently strands the GE slot Reviewed repaired head `76f8e5ad27f12b7afb39877233f8c1443ceb5b42` against base `79cab78786a1e90516439b7f4ec6101f37587824` in a fresh synthetic merge. Validation passes with 0 errors (one unrelated citation warning), all 29 selftests pass, and the previous buy-limit, collection-box, full-fill, and side-aware conservation findings are repaired. One blocking lifecycle defect remains. A successful sell follows `sell-filled-awaiting-collect -> closed`; `closed` has no outgoing transition, and the scheduler explicitly leaves closed slots untouched. The prose simultaneously says collection frees the slot. As encoded, every GE slot can perform one flip and then becomes permanently unusable, so the activity cannot continue through rotations, breaks, or a longer session. Make the boundary explicit and executable: after proceeds are collected and the completed ledger is archived/reconciled, the physical GE slot must return to `free` (either `sell-filled-awaiting-collect -> free`, or a tested `closed -> free` archival transition). Do the same for a cancelled sell that is deliberately closed. Add a committed regression that completes one buy/collect/sell/collect lifecycle and then places a second buy in the same physical slot without losing the archived ledger or violating conservation. Keep all current tax, async-slot, collection-box, and limit-window fixtures. Repair this same branch and PR.
pbwraith added the
bounced
label 2026-08-29 09:50:31 +02:00
Author
Owner

[worker-guidance-request]

PR #7 (osrs-codex, head issue-1 @ 76f8e5ad, base main) has been bounced again after six [worker-bounce-fix] attempts. Per lane policy the retry budget (2) is exhausted, so no code was changed in this pass; head untouched at 76f8e5ad, PR left open.

REVIEW REQUEST (latest governor bounce, 2026-08-29, after attempt 6):
"a completed flip permanently strands the GE slot" — a successful sell follows sell-filled-awaiting-collect -> closed; closed has no outgoing transition and the scheduler explicitly leaves closed slots untouched, while the prose says collection frees the slot. As encoded, every GE slot can perform one flip and then becomes permanently unusable, so the activity cannot continue through rotations, breaks, or a longer session. Required: make the boundary explicit and executable — after proceeds are collected and the completed ledger is archived/reconciled, the physical slot must return to free (either sell-filled-awaiting-collect -> free, or a tested closed -> free archival transition); do the same for a cancelled sell that is deliberately closed; add a committed regression that completes one buy/collect/sell/collect lifecycle then places a second buy in the same physical slot without losing the archived ledger or violating conservation; keep all current tax, async-slot, collection-box, and limit-window fixtures.

WHAT WAS ATTEMPTED (same issue-1 branch, PR #7 retained throughout):

  • attempt 1 (864f2c9): floored GE tax per item (sell - floor(0.02*sell) - buy); wait_fill split into wait_buy_fill/wait_sell_fill pinning buy -> sell -> collect; members 8-slot economics qualified + separate F2P 3-slot path; deterministic floor-tax gate in tools/validate.py.
  • attempt 2 (f6675ba): tax gate scoped from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; --selftest fixtures (fractional-tax flip MUST FAIL, generic-ge non-margin MUST PASS, floored flip MUST PASS).
  • attempt 3 (00bfc35): owner decision A — single global tax formula min(floor(0.02 * sell), 5_000_000) with above-cap fixture; all four tax selftests pass.
  • attempt 4 (3572255): owner lifecycle addendum — full worked example (capital allocation -> partial fill -> full fill/sell/collect -> no-fill cancel/requote -> partial maturity -> first break -> resume -> stop); ge_cancel added to grammar + schema verb enum; cancel_requote state; concrete prep/runtime state; break/resume path; rates qualified (149,500 named the 4h buy-limit CEILING, realized ~96,300 gp/h midpoint).
  • attempt 5 (02b366c): buy-limit ledger (limit_ledger: limit_used / first_fill_ts / limit_resets_at / remaining) separated from independent quote_age_policy; ge_cancel -> GE collection box + ge_collect + reconciliation; per-slot asynchronous sub-state machine (8 canonical states) driven by a global scheduler_iteration pass; conservation invariants (conservation_violations() with 5 fixtures); typed economy.ceiling_gp_per_hour: 149500 distinct from realized gp_per_hour_low/high scenario bounds; scheduler iteration + persisted break/resume. Verified: --selftest 20 checks PASS, validate 0 errors.
  • attempt 6 (76f8e5a): the 4h buy-limit reset is no longer a cancellation trigger anywhere (offer lifetime governed only by quote-age/staleness/margin, never limit_resets_at); filled offers now pass through collection (buy-filled-awaiting-collect / sell-filled-awaiting-collect states); the 6,000-phantom-arrowtips double-count fixed (side-aware ledgers); side-aware conservation with the three required negative fixtures (phantom refunded buy units FAIL, full buy sold before collection FAIL, full sell closed before proceeds collection FAIL). Verified: --selftest 29 checks PASS, validate 0 errors, git diff --check clean, PR state=open merged=false head=issue-1 mergeable.

EXACT REMAINING FAILURE (verifiable now on head 76f8e5ad): the state machine encodes sell-filled-awaiting-collect -> closed, where closed has no outgoing transition and the scheduler intentionally leaves closed slots untouched. The prose claims collection frees the slot, but the model gives every GE slot exactly one usable flip — after the first completed buy/collect/sell/collect cycle the slot can never place a second buy, so rotations, breaks, and longer sessions cannot continue. Counter-check: slot_transitions / scheduler_iteration in codex/economy/flip-candidates.yaml and tools/validate.py contain no closed -> free (or equivalent archival) edge and no regression exercising a second buy in the same physical slot.

ONE DIRECT QUESTION: is a SEVENTH repair iteration authorized on this same issue-1 branch to add the executable slot-reuse boundary — a closed -> free archival transition (per the governor's proposed "tested closed -> free archival transition", with sell-filled-awaiting-collect -> free also acceptable) applied to completed sells AND deliberately-closed cancelled sells, plus the required committed regression (one full buy/collect/sell/collect lifecycle followed by a second buy in the same physical slot, archived ledger preserved, conservation intact), preserving all 29 existing fixtures (tax x4 + async-slot/collection-box/limit-window/conservation/phase-gate x25) and the capped/floored tax model — or should the current head 76f8e5ad be accepted with this single finding tracked as a follow-up, or split/downgraded per a different path?

[worker-guidance-request] PR #7 (osrs-codex, head `issue-1` @ 76f8e5ad, base main) has been bounced again after six `[worker-bounce-fix]` attempts. Per lane policy the retry budget (2) is exhausted, so no code was changed in this pass; head untouched at `76f8e5ad`, PR left open. REVIEW REQUEST (latest governor bounce, 2026-08-29, after attempt 6): "a completed flip permanently strands the GE slot" — a successful sell follows `sell-filled-awaiting-collect -> closed`; `closed` has no outgoing transition and the scheduler explicitly leaves closed slots untouched, while the prose says collection frees the slot. As encoded, every GE slot can perform one flip and then becomes permanently unusable, so the activity cannot continue through rotations, breaks, or a longer session. Required: make the boundary explicit and executable — after proceeds are collected and the completed ledger is archived/reconciled, the physical slot must return to `free` (either `sell-filled-awaiting-collect -> free`, or a tested `closed -> free` archival transition); do the same for a cancelled sell that is deliberately closed; add a committed regression that completes one buy/collect/sell/collect lifecycle then places a second buy in the same physical slot without losing the archived ledger or violating conservation; keep all current tax, async-slot, collection-box, and limit-window fixtures. WHAT WAS ATTEMPTED (same `issue-1` branch, PR #7 retained throughout): - attempt 1 (864f2c9): floored GE tax per item (`sell - floor(0.02*sell) - buy`); `wait_fill` split into `wait_buy_fill`/`wait_sell_fill` pinning buy -> sell -> collect; members 8-slot economics qualified + separate F2P 3-slot path; deterministic floor-tax gate in tools/validate.py. - attempt 2 (f6675ba): tax gate scoped from tags & {flipping, merchanting, ge} to MARGIN_TAGS = {flipping, merchanting}; --selftest fixtures (fractional-tax flip MUST FAIL, generic-ge non-margin MUST PASS, floored flip MUST PASS). - attempt 3 (00bfc35): owner decision A — single global tax formula `min(floor(0.02 * sell), 5_000_000)` with above-cap fixture; all four tax selftests pass. - attempt 4 (3572255): owner lifecycle addendum — full worked example (capital allocation -> partial fill -> full fill/sell/collect -> no-fill cancel/requote -> partial maturity -> first break -> resume -> stop); `ge_cancel` added to grammar + schema verb enum; cancel_requote state; concrete prep/runtime state; break/resume path; rates qualified (149,500 named the 4h buy-limit CEILING, realized ~96,300 gp/h midpoint). - attempt 5 (02b366c): buy-limit ledger (`limit_ledger`: limit_used / first_fill_ts / limit_resets_at / remaining) separated from independent `quote_age_policy`; `ge_cancel` -> GE collection box + `ge_collect` + reconciliation; per-slot asynchronous sub-state machine (8 canonical states) driven by a global `scheduler_iteration` pass; conservation invariants (`conservation_violations()` with 5 fixtures); typed `economy.ceiling_gp_per_hour: 149500` distinct from realized `gp_per_hour_low/high` scenario bounds; scheduler iteration + persisted break/resume. Verified: --selftest 20 checks PASS, validate 0 errors. - attempt 6 (76f8e5a): the 4h buy-limit reset is no longer a cancellation trigger anywhere (offer lifetime governed only by quote-age/staleness/margin, never `limit_resets_at`); filled offers now pass through collection (`buy-filled-awaiting-collect` / `sell-filled-awaiting-collect` states); the 6,000-phantom-arrowtips double-count fixed (side-aware ledgers); side-aware conservation with the three required negative fixtures (phantom refunded buy units FAIL, full buy sold before collection FAIL, full sell closed before proceeds collection FAIL). Verified: --selftest 29 checks PASS, validate 0 errors, git diff --check clean, PR state=open merged=false head=issue-1 mergeable. EXACT REMAINING FAILURE (verifiable now on head 76f8e5ad): the state machine encodes `sell-filled-awaiting-collect -> closed`, where `closed` has no outgoing transition and the scheduler intentionally leaves closed slots untouched. The prose claims collection frees the slot, but the model gives every GE slot exactly one usable flip — after the first completed buy/collect/sell/collect cycle the slot can never place a second buy, so rotations, breaks, and longer sessions cannot continue. Counter-check: `slot_transitions` / `scheduler_iteration` in codex/economy/flip-candidates.yaml and tools/validate.py contain no `closed -> free` (or equivalent archival) edge and no regression exercising a second buy in the same physical slot. ONE DIRECT QUESTION: is a SEVENTH repair iteration authorized on this same `issue-1` branch to add the executable slot-reuse boundary — a `closed -> free` archival transition (per the governor's proposed "tested `closed -> free` archival transition", with `sell-filled-awaiting-collect -> free` also acceptable) applied to completed sells AND deliberately-closed cancelled sells, plus the required committed regression (one full buy/collect/sell/collect lifecycle followed by a second buy in the same physical slot, archived ledger preserved, conservation intact), preserving all 29 existing fixtures (tax x4 + async-slot/collection-box/limit-window/conservation/phase-gate x25) and the capped/floored tax model — or should the current head 76f8e5ad be accepted with this single finding tracked as a follow-up, or split/downgraded per a different path?
pbwraith added
needs-guidance
and removed
bounced
labels 2026-08-29 09:58:33 +02:00
Author
Owner

[owner-guidance]

Authorize another repair iteration on this same issue-1 branch and PR #7. The latest instruction to work through the PR queue authorizes completing the remaining objective lifecycle defect; do not accept it as follow-up work and do not relax the review bar.

Implement governor comment 10471 exactly: after a successful sell's proceeds are collected and its ledger is reconciled/archived, return the physical GE slot to free. Apply the same executable release to a deliberately closed cancelled-sell path. Add a committed two-rotation regression that completes buy/collect/sell/collect, archives the first ledger, then opens a second buy in the same physical slot without loss, duplication, or mutation of the archived record.

Preserve the capped/floored tax model, buy-limit versus quote-age separation, collection-box semantics, independent slot scheduler, and all side-aware conservation fixtures. Repair this PR; do not open a replacement.

This authorization supersedes the retry-budget gate for this finding.

[owner-guidance] Authorize another repair iteration on this same `issue-1` branch and PR #7. The latest instruction to work through the PR queue authorizes completing the remaining objective lifecycle defect; do not accept it as follow-up work and do not relax the review bar. Implement governor comment `10471` exactly: after a successful sell's proceeds are collected and its ledger is reconciled/archived, return the physical GE slot to `free`. Apply the same executable release to a deliberately closed cancelled-sell path. Add a committed two-rotation regression that completes buy/collect/sell/collect, archives the first ledger, then opens a second buy in the same physical slot without loss, duplication, or mutation of the archived record. Preserve the capped/floored tax model, buy-limit versus quote-age separation, collection-box semantics, independent slot scheduler, and all side-aware conservation fixtures. Repair this PR; do not open a replacement. This authorization supersedes the retry-budget gate for this finding.
Author
Owner

[owner-guidance-status]

Authorization is recorded in comment 10528. Returning this same PR to the worker as bounced; repair the existing branch and PR.

[owner-guidance-status] Authorization is recorded in comment `10528`. Returning this same PR to the worker as `bounced`; repair the existing branch and PR.
pbwraith added
bounced
and removed
needs-guidance
labels 2026-08-29 10:01:10 +02:00
pbwraith added 1 commit 2026-08-29 10:11:29 +02:00
A successful sell followed sell-filled-awaiting-collect -> closed, and
'closed' had no outgoing transition while the scheduler left closed
slots untouched — every GE slot could perform ONE flip and then became
permanently unusable (no rotations, breaks, or longer sessions).

Fix (finding-by-finding, governor comment 10471):
1. 'closed' is no longer a sink. The completed cycle's ledger is
   reconciled and ARCHIVED at close, and the physical slot is released
   to free via the explicit archival transition closed -> free
   (flip-candidates.yaml slot_transitions, whens, scheduler_iteration,
   a9 archive step, summary, risk, sources). The scheduler pass
   dispatches the release (SLOT_DISPATCH['closed'] = 'release'), so a
   closed slot is only ever transient.
2. A deliberately closed cancelled sell releases identically:
   sell-cancelled-awaiting-collect -> closed -> free (whens updated).
3. Committed two-rotation regression (validator, wired into
   ge_lifecycle_errors for every flipping entry): slot_reuse_violations
   walks one physical slot through free -> buy-open ->
   buy-filled-awaiting-collect -> ready-to-sell -> sell-open ->
   sell-filled-awaiting-collect -> closed -> free -> buy-open (second
   buy in the SAME slot), validating every ledger frame against both
   conservation gates; archived_ledger_preserved_after_second_buy
   proves the rotation-1 archived ledger survives rotation 2 bit-for-bit
   (no loss, duplication, or mutation). Negative fixtures prove a
   closed sink or a closed that does not release to free FAILS.
4. ge_lifecycle_errors now requires closed == ['free'] exactly and
   rejects any machine where a state has no transitions.
5. Worked example: t5/t8 walk the release, t8b runs rotation 2 in the
   same physical slots.

All prior fixtures preserved (tax x4, async-slot scheduler,
collection-box, limit-window, side-aware conservation x25) — now 35
selftest checks.

Evidence: python3 tools/validate.py -> 2 entries, 0 errors, 1
pre-existing warning; --selftest -> 35 PASS, 0 errors; git diff
--check clean.
pbwraith added 1 commit 2026-08-29 10:14:04 +02:00
pbwraith removed the
bounced
label 2026-08-29 10:14:50 +02:00
Author
Owner

[worker-bounce-fix attempt 7]

Governor review comment 10471 (2026-08-29, "a completed flip permanently strands the GE slot") — fixed on the EXACT existing head branch issue-1 (no new branch, no duplicate PR). New commits: 671d025 (bounce-fix) + d19a4d9 (merge of current main — PR #8 landed while this PR was open and modified tools/validate.py, making the PR genuinely unmergeable; the merge resolves it and the PR now reports mergeable=true). Head now d19a4d9; PR re-verified: state=open, merged=false, head issue-1, base main, mergeable=true, 4 changed files (flip-candidates.yaml, tools/validate.py, docs/action-grammar.md, schemas/entry.schema.json).

Finding — closed was a permanent sink. A successful sell followed sell-filled-awaiting-collect -> closed, and closed had no outgoing transition while the scheduler explicitly left closed slots untouched; the prose claimed collection frees the slot. As encoded, every GE slot could perform ONE flip and then became permanently unusable — the activity could not continue through rotations, breaks, or a longer session.

Fix (finding-by-finding):

  1. Boundary is now explicit and executable. After a completed sell's proceeds are collected and the ledger is reconciled + ARCHIVED at closed, the physical GE slot returns to free via the tested archival transition closed -> free (flip-candidates.yaml slot_transitions; whens entry "closed -> free: ARCHIVAL RELEASE..."; scheduler_iteration: the pass dispatches the release — SLOT_DISPATCH['closed'] = 'release' in tools/validate.py — so a closed slot is only ever transient, never stranded).
  2. Deliberately closed cancelled sells release identically: sell-cancelled-awaiting-collect -> closed -> free (whens updated; the whens text avoids the -> parser separator in prose).
  3. Committed two-rotation regression: slot_reuse_violations() (tools/validate.py) walks ONE physical slot through free -> buy-open -> buy-filled-awaiting-collect -> ready-to-sell -> sell-open -> sell-filled-awaiting-collect -> closed -> free -> buy-open (a SECOND buy in the same slot), validating every ledger frame against both conservation gates (side-aware partition + cash, phase gates). archived_ledger_preserved_after_second_buy() proves the rotation-1 archived ledger survives rotation 2 bit-for-bit — no loss, duplication, or mutation — while the live ledger is a fresh rotation-2 record. Negative fixtures prove a closed sink (closed: []) or a closed that does not release to free FAILS the walk.
  4. The gate now enforces it: ge_lifecycle_errors() requires closed == ['free'] exactly and rejects any sub-state with no transitions; the regression runs on every flipping entry and on every gate invocation (wired into run_selftest too).
  5. Worked example updated: t5 (iron) and t8 (arrowtips) walk the archive + release; new t8b runs rotation 2 — the same physical slots place new buys with the archived ledgers untouched.
  6. Main sync (PR #8): merged current main into issue-1; the alchemy regressions from PR #8's tools/validate.py changes are preserved alongside the flip machinery (3 entries, 0 errors — both sets coexist).

Evidence:

  • python3 tools/validate.py: 3 entries, 0 errors, 1 pre-existing warning (low-alch-f2p citation TODO, unrelated), exit 0.
  • python3 tools/validate.py --selftest: 35 checks PASS, 0 errors, exit 0 — the owner-pinned tax fixtures (fractional-tax FAIL, correctly-floored PASS, above-cap 5M PASS, generic-ge non-margin PASS) retained unchanged, plus the async-slot scheduler, collection-box, limit-window, side-aware conservation x25, and the +5 new slot-reuse checks.
  • git diff --check: clean.
  • PR #7 re-verified post-push: state=open, merged=false, head issue-1 @ d19a4d9, mergeable=true, 4 changed files with non-empty diff.

PR left open for manual review; not merged by worker.

[worker-bounce-fix attempt 7] Governor review comment 10471 (2026-08-29, "a completed flip permanently strands the GE slot") — fixed on the EXACT existing head branch `issue-1` (no new branch, no duplicate PR). New commits: `671d025` (bounce-fix) + `d19a4d9` (merge of current `main` — PR #8 landed while this PR was open and modified tools/validate.py, making the PR genuinely unmergeable; the merge resolves it and the PR now reports mergeable=true). Head now `d19a4d9`; PR re-verified: state=open, merged=false, head `issue-1`, base main, mergeable=true, 4 changed files (flip-candidates.yaml, tools/validate.py, docs/action-grammar.md, schemas/entry.schema.json). **Finding — `closed` was a permanent sink.** A successful sell followed `sell-filled-awaiting-collect -> closed`, and `closed` had no outgoing transition while the scheduler explicitly left closed slots untouched; the prose claimed collection frees the slot. As encoded, every GE slot could perform ONE flip and then became permanently unusable — the activity could not continue through rotations, breaks, or a longer session. **Fix (finding-by-finding):** 1. *Boundary is now explicit and executable.* After a completed sell's proceeds are collected and the ledger is reconciled + ARCHIVED at `closed`, the physical GE slot returns to `free` via the tested archival transition `closed -> free` (`flip-candidates.yaml` slot_transitions; whens entry "closed -> free: ARCHIVAL RELEASE..."; scheduler_iteration: the pass dispatches the release — `SLOT_DISPATCH['closed'] = 'release'` in tools/validate.py — so a closed slot is only ever transient, never stranded). 2. *Deliberately closed cancelled sells release identically*: `sell-cancelled-awaiting-collect -> closed -> free` (whens updated; the whens text avoids the `->` parser separator in prose). 3. *Committed two-rotation regression*: `slot_reuse_violations()` (tools/validate.py) walks ONE physical slot through free -> buy-open -> buy-filled-awaiting-collect -> ready-to-sell -> sell-open -> sell-filled-awaiting-collect -> closed -> free -> buy-open (a SECOND buy in the same slot), validating every ledger frame against both conservation gates (side-aware partition + cash, phase gates). `archived_ledger_preserved_after_second_buy()` proves the rotation-1 archived ledger survives rotation 2 bit-for-bit — no loss, duplication, or mutation — while the live ledger is a fresh rotation-2 record. Negative fixtures prove a `closed` sink (closed: []) or a closed that does not release to free FAILS the walk. 4. *The gate now enforces it*: `ge_lifecycle_errors()` requires `closed == ['free']` exactly and rejects any sub-state with no transitions; the regression runs on every flipping entry and on every gate invocation (wired into `run_selftest` too). 5. *Worked example updated*: t5 (iron) and t8 (arrowtips) walk the archive + release; new t8b runs rotation 2 — the same physical slots place new buys with the archived ledgers untouched. 6. *Main sync (PR #8)*: merged current main into issue-1; the alchemy regressions from PR #8's tools/validate.py changes are preserved alongside the flip machinery (3 entries, 0 errors — both sets coexist). **Evidence:** - `python3 tools/validate.py`: **3 entries, 0 errors, 1 pre-existing warning** (low-alch-f2p citation TODO, unrelated), exit 0. - `python3 tools/validate.py --selftest`: **35 checks PASS, 0 errors**, exit 0 — the owner-pinned tax fixtures (fractional-tax FAIL, correctly-floored PASS, above-cap 5M PASS, generic-`ge` non-margin PASS) retained unchanged, plus the async-slot scheduler, collection-box, limit-window, side-aware conservation x25, and the +5 new slot-reuse checks. - `git diff --check`: clean. - PR #7 re-verified post-push: state=open, merged=false, head `issue-1` @ d19a4d9, mergeable=true, 4 changed files with non-empty diff. PR left open for manual review; not merged by worker.
pbwraith merged commit a19716585b into main 2026-08-29 10:16:47 +02:00
Author
Owner

[governor-review] MERGED — exact head d19a4d9de08bedd54b5ebe853cfde4384f23bb4c reviewed against base 762a8bb608bcb95c4542e7e27d5134de1a162eb7.

Evidence:

  • The synthetic merge was clean and git diff --check passed.
  • Full validator: 3 entries, 0 errors (the one warning is the pre-existing Low Alchemy citation follow-up).
  • Selftest suite: 35/35 passed, including the committed two-rotation walk, closed -> free scheduler dispatch, immutable archived ledger, and negative fixtures rejecting a stranded closed slot.
  • The same physical GE slot now completes buy/collect/sell/collect, archives the cycle, returns to free, and can open the next buy without reusing uncollected assets.

Linked issue #1 is closed.

[governor-review] MERGED — exact head `d19a4d9de08bedd54b5ebe853cfde4384f23bb4c` reviewed against base `762a8bb608bcb95c4542e7e27d5134de1a162eb7`. Evidence: - The synthetic merge was clean and `git diff --check` passed. - Full validator: 3 entries, 0 errors (the one warning is the pre-existing Low Alchemy citation follow-up). - Selftest suite: 35/35 passed, including the committed two-rotation walk, `closed -> free` scheduler dispatch, immutable archived ledger, and negative fixtures rejecting a stranded closed slot. - The same physical GE slot now completes buy/collect/sell/collect, archives the cycle, returns to `free`, and can open the next buy without reusing uncollected assets. Linked issue #1 is closed.
Sign in to join this conversation.
No reviewers
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/osrs-codex#7
No description provided.