Skip to content

product: rebuild as durable Sky Notify service - #1

Merged
skylerblue333 merged 17 commits into
mainfrom
product/sky-notify-20260823
Aug 23, 2026
Merged

product: rebuild as durable Sky Notify service#1
skylerblue333 merged 17 commits into
mainfrom
product/sky-notify-20260823

Conversation

@skylerblue333

Copy link
Copy Markdown
Owner

Product #10 of the SKYCOIN4444 standalone-product master plan.

This release candidate replaces the placeholder notification router with a durable single-node notification service using FastAPI + SQLite/WAL. It adds idempotent submission, explicit pending/sending/delivered/dead-letter states, bounded retry budgets, HTTPS webhook delivery with exact hostname allowlisting and no redirects, deterministic log delivery for local/CI verification, optional bearer protection, bounded JSON payloads, health/readiness/metrics, modern Python 3.12 dependencies, lifecycle tests, dependency audit, and a non-root container.

Truthful boundary: this PR does not claim distributed exactly-once delivery, multi-region HA, email/SMS/push providers, tenant isolation, encryption at rest, or compliance certification. Merge only after the exact PR head passes compile, Ruff, pytest, pip-audit, Docker build, and non-root image verification.

@chatgpt-codex-connectorchatgpt-codex-connectorBot 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:9471ed161b

ℹ️ 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".

- name: Compile
run: python -m compileall -q src tests
- name: Lint
run: ruff check src tests

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 Make the new Ruff gate pass

In the newly added verify lint step, running the exact documented command ruff check src tests exits with status 1 because tests/test_main.py:9 triggers E712 (== True). Consequently, every CI run stops at this step before tests, auditing, or the image build; fix the retained violation or explicitly configure the intended Ruff rules before making this a required gate.

Useful? React with 👍 / 👎.

Comment threadsrc/main.py
raise HTTPException(status_code=400, detail="credentials are not allowed in webhook URLs")


@app.get("/healthz")

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 Update retained tests for the replaced endpoints

Once lint passes, the checked-in tests/test_main.py still requests /health and /api/v1/process, but this change replaces those routes with /healthz and the notification API. Both retained tests therefore receive 404 responses under the newly added pytest -q CI step, so the new service cannot pass its advertised pipeline unless those tests are updated or compatibility routes are retained.

Useful? React with 👍 / 👎.

Comment threadsrc/main.py
def claim_due(self, now: float) -> sqlite3.Row | None:
with self.connect() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute("SELECT * FROM notifications WHERE status='pending' AND next_attempt<=? ORDER BY created_at LIMIT 1", (now,)).fetchone()

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 Reclaim interrupted sending records

When the process is killed after claim_due commits the sending state but before finish runs—for example during the webhook request—the record no longer matches this status='pending' query after restart. With no lease expiry or startup recovery for sending, that notification remains stuck forever and reaches neither retry nor dead-letter handling, defeating durability during routine restarts.

Useful? React with 👍 / 👎.

Comment threadsrc/main.py
return {"status": "idle"}
ok, error = await deliver(row)
store.finish(row["id"], ok, error, time.time())
return {"status": "delivered" if ok else "retry_scheduled", "id": row["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.

P2 Badge Report dead-letter exhaustion from run-once

When a provider failure occurs on the final allowed attempt, finish changes the record to dead_letter, but this response still unconditionally reports retry_scheduled for every failure. For example, a notification with maxAttempts=1 is already terminal after this call, so callers and schedulers receive an incorrect state and may wait for a retry that will never occur.

Useful? React with 👍 / 👎.

@skylerblue333
skylerblue333 merged commit df880aa into mainAug 23, 2026
1 check passed
Sign up for freeto 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.

1 participant

@skylerblue333