M1 unbundled + deploy pipeline + M2 PR A (foundation) - #1
Open
ArchDLayer wants to merge 3 commits into
Open
Conversation
…ent pipeline M1 (data layer and streak engine) was shipped as a single-file bundle for transport onto a phone-only workflow. This expands it back into the proper repo layout described in HANDOFF.md: - streaks.py, db.py, models.py, views.py, render_text.py, tools/demo.py, tests/*, migrations/001_init.sql, extracted programmatically from the bundle's _SOURCES/_TESTS/_SCHEMA_SQL dicts - tools/build_single_file.py reconstructed as the script that produces challenge_keeper.py; regenerating it reproduces the original bundle byte-for-byte - requirements.txt, .env.example, .gitignore, README.md - CLAUDE.md with standing instructions for future sessions Also builds the deployment pipeline ahead of there being a bot to deploy: - deploy/challengebot.service — systemd unit, non-root user, Restart=always - deploy/cloud-init.yaml — first-boot setup for Oracle ARM free tier (Ubuntu/aarch64), notes on Hetzner differences - .github/workflows/deploy.yml — SSH deploy on push to main, restarts the service, reinstalls requirements only if they changed - deploy/README.md — phone click-path: which GitHub secrets to add, what to paste into the provider's form, how to check the service afterwards Verified: 76 tests pass, python3 -m tools.demo and python3 challenge_keeper.py both print the same sample board. challengebot.service will crash-loop harmlessly until M2 adds bot.py — the goal here is a proven pipeline, not a running bot. M2 is not started. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WTxQxm8bFaQDEFVdJEafnt
…default Pre-existing in the M1 bundle: create_challenge() fell back to date.today() when start_date wasn't given. CLAUDE.md's own rule is that nothing in this codebase calls date.today() directly -- every instant becomes a day through streaks.resolve_local_date() so the user's timezone and grace window are always applied. This was never exercised because every existing caller (tools/demo.py, tests/test_models.py) already passed start_date explicitly, but the upcoming /newchallenge handler is the first real caller that could have hit it. start_date is now a required keyword argument. The three test call sites that relied on the default now pass one explicitly. Bundle regenerated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WTxQxm8bFaQDEFVdJEafnt
Foundation milestone from challenge-bot-plan.md section 5, M2, steps 1-4: - config.py: TELEGRAM_BOT_TOKEN / DATABASE_PATH from the environment - strings.py: every user-facing string, keyed - challenge_setup.py: name/target/timezone validation and the quick-pick zone list (Vilnius/Warsaw/London/Lisbon/Athens/Madrid). No Telegram import, same isolation principle as streaks.py/views.py -- this is the only new module that's actually testable in an environment without a live `telegram` install. - handlers/admin.py: is_group_admin(), checked live via getChatMember, never a hardcoded username list - handlers/commands.py: the /newchallenge conversation (name, target, unit, mode, miss policy, confirm), ending by creating the challenge and posting + pinning the text board with a Join button - handlers/callbacks.py: the Join button (enrols, then deep-links to DM) and the timezone conversation (/start tz, /settz, quick-pick + free-text "Other" validated against zoneinfo) - bot.py: wires everything into a polling Application Product decisions from discussion: freeze tokens are not wired up this milestone (freezes=0, matching models.enrol()'s default) -- longest_streak stays the mitigation for reset_progress dropout instead. Grace window stays at the 3-hour default. The admin who runs /newchallenge is not auto-enrolled. Testing note: this environment cannot install python-telegram-bot (no outbound PyPI access), so nothing importing `telegram` could be executed here. What's covered by tests/test_challenge_setup.py (16 tests) and tests/test_m2_setup_flow.py (6 tests) is every non-Telegram call the handlers make -- validation and the exact models.py/views.py sequence each flow uses -- run directly rather than through Telegram objects. The bot.py/handlers wiring itself is unverified by execution; first real test is the VPS. 98 tests total, all passing. The M1 bundle is untouched -- challenge_keeper.py stays standard-library-only by design. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WTxQxm8bFaQDEFVdJEafnt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Status (updated as work continues — read this section first)
This PR is being built across multiple sessions/conversations. This section
is the continuity log so a fresh session (or you, on your phone) can pick up
without re-reading the whole chat history. Commit messages also carry
per-commit rationale.
Done and pushed:
challenge_keeper.py(M1) into the real module layout, rebuilttools/build_single_file.py, verified byte-identical regeneration.phone click-path README.
models.create_challenge()defaultedstart_datetodate.today(), violatingCLAUDE.md's own rule. Now arequired argument; callers resolve "today" via
resolve_local_date()./newchallengeguided setup, post+pin the textboard, Join button → enrollment → DM deep-link → timezone picker
(quick-pick zones + free-text "Other"). See
bot.py,config.py,strings.py,challenge_setup.py,handlers/.Decisions made along the way, and why:
reset_progress(onemissed day wipes all 100 days) likely causes dropout around week six.
Decided to surface
longest_streakprominently instead of implementingfreeze tokens —
reset_progressstays fully strict,freezes_leftstaysunused (
models.enrol()still defaults it to 0).into four: A (this one, foundation) → B (core loop: check-in,
repeat-tap handling, full board / my stats buttons — the plan's own M2
"done when" checkpoint) → C (image board: avatars, Pillow rendering)
→ D (refresh job).
both
api.telegram.organd PyPI installs, sopython-telegram-botcan'tbe installed or imported here. Everything not touching
telegramistested directly (98 tests passing: the original 76 +
challenge_setup.pyvalidation + a call-sequence integration test mirroring what the handlers
do). The
bot.py/handlers/Telegram wiring itself is unverified byexecution — first real test happens once this is running somewhere with
real network access (the VPS, or a Termux stopgap).
Madrid — matches the zones already used in the M1 demo data.
Telegram-side state (as of the session that built PR A): a throwaway dev
bot (
@Challange2536_bot) exists with privacy mode enabled, added as adminto a throwaway group ("Challange group"). Its token was shared directly in
that chat session, not stored anywhere — if it's needed again and lost,
regenerate via BotFather →
/mybots→ the bot → API Token. This is allthrowaway/dev-only; the real bot and group get created separately later.
Not started: PR B onward. M2's Telegram-facing wiring has not been
tested against live Telegram at all yet.
Original summary (M1 unbundle + deploy pipeline)
challenge_keeper.pywas a single-file transport bundle for M1 (data layerand streak engine). This PR expands it into the real project, per
HANDOFF.md, and builds the deployment pipeline ahead of there being a botto deploy.
Unbundled:
streaks.py,db.py,models.py,views.py,render_text.py,tools/demo.py,tests/*,migrations/001_init.sql— extractedprogrammatically from the bundle, not retyped
tools/build_single_file.pyreconstructed — this is what generated thebundle originally. Running it regenerates
challenge_keeper.pybyte-for-byte identical to the file you uploaded.
challenge_keeper.pyisa build artifact now — edit the modules and rerun the build script, per
CLAUDE.md.requirements.txt,.env.example,.gitignore,README.md,CLAUDE.mdDeployment pipeline (
deploy/,.github/workflows/deploy.yml):deploy/challengebot.service— systemd unit, non-root user,Restart=alwaysdeploy/cloud-init.yaml— first-boot setup targeting Oracle Cloud ARM freetier (Ubuntu/aarch64), with Hetzner fallback notes
.github/workflows/deploy.yml— on push tomain: SSH in,git reset --hardto the new commit, reinstall requirements only if
requirements.txtchanged, restart the service
deploy/README.md— the phone click-path: generating the two SSH keypairsinvolved, which GitHub secrets to add and where, what to paste into the
cloud provider's instance-creation form, how to confirm it worked
Until
bot.pyexists,challengebot.servicecrash-loops harmlessly underRestart=always. Now that PR A addsbot.py, that's no longer true oncethis merges and deploys — worth re-checking service status after that.
What you need to do by hand
to be merged now and continue on a follow-up PR/branch).
deploy/README.mdhas the click-path.VPS_HOST,VPS_USER,VPS_SSH_KEY).stopgap (flagged in the plan) to run the bot directly on your phone.
One open decision, flagged not resolved (from the original M1 PR)
The freeze-token question— resolved above: skipped in favor ofsurfacing
longest_streak.