Skip to content

feat(ahbg): submission passes 1-4 — runtime, Android surface, RevenueCat, polish - #23

Merged
erinepshovel-code merged 2 commits into
mainfrom
feat/ahbg-submission
Sep 2, 2026
Merged

erinepshovel-code merged 2 commits into
mainfrom
feat/ahbg-submission

Conversation

@erinepshovel-code

Copy link
Copy Markdown
Contributor

AHBG submission pass 1–4

Preserves canonical mechanics, UCNS geometry authority, and calibration
evidence. No game redesign.

Pass 1 — canonical runnable AHBG

  • ahbg/runtime/ production loop: UCNS plane → observe → plan → simultaneous
    resolution → move/collision effects → persist → next turn.
  • Capability-bounded observe/plan/act harness interface (protocol.py,
    harness.py). A0 uses exactly this interface; no privileged A0 path.
  • SubprocessHarness connects an external conforming harness over JSON lines
    without modifying AHBG (regression-tested with an external script).
  • Frozen Grok engine bound by file path (engine.py) so ahbg.runtime and
    ahbg.grok never fight over the ahbg package name.

Pass 2 — Android surface

  • ahbg/android/: thinnest Android-first shell (WebView + JS bridge + JSON
    transport). Presents and controls; no second engine, no geometry authority.
  • Presentation assets pinned from ahbg/presentation with SHA receipts
    (sync_presentation.sh, PRESENTATION.sha256).
  • CI workflow builds the debug APK and enforces presentation asset drift.

Pass 3 — RevenueCat

  • One clean entitlement: benchmark_lab — advanced scenarios, saved/replayed
    run comparison, adversarial benchmark packs.
  • Basic gameplay and external harness connectivity remain free.
  • Runtime checks claims only; Android verifies via RevenueCat SDK and
    degrades to the free tier when no key is provisioned (never committed).

Pass 4 — submission path

  • HTTP bridge (ahbg/runtime/server.py): /session, /session/<id>/plan,
    /session/<id>/state, /session/<id>/entitlements, plus UCNS-projected
    presentation snapshots.
  • Presentation board: onboarding → start plane → select agent → play turn →
    visible consequence → persist/reload → premium surface.

Gates

  • runtime 9 OK · grok 6 OK · presentation 11 OK · node --check OK ·
    compileall OK · CLI smoke OK (locally)
  • New CI: ahbg-runtime (tests + CLI smoke), ahbg-android (APK build +
    asset drift)

hmmm (reported, not hidden)

  • construct/build remains regulatory until UCNS defines construction state.
  • Android store publication needs signing, versioning policy, submission
    assets, and a release HTTPS runtime URL.
  • RevenueCat SDK dependency version (8.10.1) and CI Android build are
    verified by the new workflow; local build was not possible (no Android SDK).

…Cat, polish

Pass 1 — canonical runnable AHBG
- ahbg/runtime production loop: UCNS plane -> observe -> plan ->
  simultaneous resolution -> effects -> persist -> next turn.
- Capability-bounded observe/plan/act harness interface (protocol.py,
  harness.py). A0 uses exactly this interface; no privileged path.
- SubprocessHarness connects any external conforming harness over JSON lines
  without modifying AHBG.

Pass 2 — Android surface
- Thinnest Android-first shell (WebView + JS bridge + JSON transport) around
  the canonical runtime; no second engine or geometry authority.
- Presentation assets pinned from ahbg/presentation with SHA receipts.

Pass 3 — RevenueCat
- One clean entitlement: benchmark_lab (advanced scenarios, saved/replayed
  run comparison, adversarial packs). Basic play and harness connectivity
  stay free. Runtime checks claims; Android verifies with the SDK and
  degrades to the free tier when no key is provisioned.

Pass 4 — submission path
- HTTP bridge (ahbg/runtime/server.py) with /session, /plan, /state,
  /entitlements plus UCNS-projected presentation snapshots.
- Presentation board: onboarding -> start plane -> select agent -> play turn
  -> visible consequence -> persist/reload -> premium surface.

Gates local: runtime 9 OK, grok 6 OK, presentation 11 OK, node --check OK,
compileall OK, CLI smoke OK.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T00:38:59.428222Z 4bd24c0 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

with:
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@v3
distribution: temurin
java-version: "17"
- uses: android-actions/setup-android@v3
- uses: gradle/actions/setup-gradle@v4

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4bd24c0f12

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ahbg/runtime/server.py
Comment on lines +279 to +280
if __name__ == "__main__":
raise SystemExit(main())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Define the presentation helper before starting the server

When the documented python -m ahbg.runtime.server entry point is used, main() blocks in serve_forever() before execution reaches the later field_to_presentation definition. Consequently, every persisted-state request reaches line 203 with that global undefined and the server closes the connection with NameError; move the entry-point guard below all definitions.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/server.py
from .engine import load_engine

_patch, _chain, _keep, _round = load_engine()
field, chain = _keep.load_field(session.out_dir / "state")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist a new session before serving its state

Immediately after POST /session, no state files have been written, but the submission path immediately calls GET /session/<id>/state. This unconditional load raises FileNotFoundError for events.jsonl and closes the HTTP connection, so every newly started plane reports a failed initial reload; persist the fresh field during session creation or serve the live in-memory field until the first turn.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/server.py
Comment on lines +230 to +231
session_id = hashlib.sha256(json.dumps({"seed": seed, "turns": turns}, sort_keys=True).encode("utf-8")).hexdigest()[:12]
out_dir = Path(body.get("out_dir") or f"/tmp/ahbg-http-{session_id}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Generate a unique identity and state path for each session

For any two clients choosing the same seed and turn count, this deterministic ID and /tmp directory collide. The second POST /session replaces the first session in the registry and start_observation() reloads the first client's persisted turns instead of creating a fresh plane; the board uses only 1,000 random seeds with a fixed turn count, so collisions also expose or mutate another client's game state in normal multi-user use.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/server.py
session = self.get(session_id)
if session is None:
return {}
gate = EntitlementGate.from_claims(claims)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject unverified entitlement query claims

When a deployment relies on this endpoint for premium status, any caller can request ?claims=benchmark_lab and this line grants the entitlement without RevenueCat validation or any configured trusted verifier. This contradicts the module's fail-closed contract for unverified claims and allows paid-feature checks based on the response to be bypassed.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/server.py
Comment on lines +269 to +270
server = make_server(args.port)
server.server_address = (args.bind, args.port)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bind the listening socket to the requested address

When the bridge is launched with --bind 0.0.0.0 or another interface for a physical Android client, make_server() has already bound its socket to 127.0.0.1; assigning server_address afterward only changes an attribute and does not rebind the socket. The log advertises the requested address while the service remains loopback-only, so remote clients cannot connect.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/runtime.py
Comment on lines +279 to +281
final_turn=opened.turn,
final_snapshot=opened.snapshot(),
state_digest=turn_records[-1]["state_digest"] if turn_records else _initial_digest(opened),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Return a digest of the final persisted snapshot

For every positive-turn run, this value comes from close_turn() before that method increments field.turn, while final_snapshot and the persisted field contain the incremented turn. Hashing the returned final snapshot therefore never reproduces state_digest, which makes the result's digest unusable for verification or run comparison; compute it from the final persisted field or clearly return a separately named pre-increment digest.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/runtime.py
Comment on lines +60 to +64
def as_dict(self) -> dict[str, Any]:
return {
"seed": self.seed,
"turns": self.turns,
"units": [dict(unit) for unit in self.units],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Record behavior-changing configuration in result metadata

turn_messages and forced_plans can change every executed turn, but as_dict() omits both, so result.json can report identical configuration for runs driven by different injected messages or forced actions. This prevents saved results from being reproduced or reliably compared; serialize these inputs, or at least stable identities/digests for them, alongside the other configuration.

Useful? React with 👍 / 👎.

Comment thread ahbg/runtime/protocol.py
Comment on lines +143 to +145
def parse(cls, raw: Mapping[str, Any]) -> "Plan":
if not isinstance(raw, Mapping):
raise ProtocolError("plan must be an object")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject plans with an incompatible schema

Although the protocol declares a versioned PLAN_SCHEMA, this parser never reads the incoming schema field. A harness can therefore omit it or send a future/incompatible schema and still have its intents executed under version 1 semantics, defeating the version boundary; require an exact schema match before parsing the rest of the plan.

Useful? React with 👍 / 👎.

Comment on lines +384 to +388
return fetch(path, {
method: method === "state" ? "GET" : "POST",
headers: { "Content-Type": "application/json" },
body: method === "state" ? undefined : JSON.stringify(body),
}).then((response) => response.json());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject HTTP error payloads in the board client

For a 4xx or 5xx response, this path parses the error JSON as if the request succeeded. A rejected plan then sets lastObservation to undefined and reports a resolved turn with zero effects, while a failed session start throws later while dereferencing the missing observation; check response.ok or the error envelope and reject the promise immediately.

Useful? React with 👍 / 👎.

doOutput = true
setRequestProperty("Content-Type", "application/json; charset=utf-8")
}
OutputStreamWriter(connection.outputStream).use { it.write(body) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Report connection failures instead of crashing the Android process

When the configured runtime is unreachable, obtaining the POST output stream throws before execution enters the later try block. The exception escapes the manually created thread in startSession or submitPlan, so Android can terminate the app process instead of invoking the JavaScript callback with an error; wrap connection creation and request writing as well as response reading.

Useful? React with 👍 / 👎.

@erinepshovel-code
erinepshovel-code merged commit 30a33b9 into main Sep 2, 2026
9 checks passed
@erinepshovel-code
erinepshovel-code deleted the feat/ahbg-submission branch September 2, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants