Step Nine — Adaptive cadence, policy‑driven throttles, and an honest trail

We give your phone‑native gateway three new powers, all composable with Steps 1–8:

  1. Adaptive per‑plugin polling with exponential backoff and gentle recovery
  2. Rate limits by route and role (policy file)
  3. Audit JSONL for auth decisions, policy hits/misses, and rate‑limit drops

Plus: tokens can now carry audiences and per‑plugin grants; optional Ed25519 (S2) token verification if libs are present. Still read‑only.


✅ Fresh artifacts

  • solveforce_phone_nine.pyDownload
    SHA‑256: e53c75e862432a516928e5a4b6e4d429d2f3965041b1b018a2642d802c808ad4
  • Rate‑policy exampleDownload
    SHA‑256: 4842faf8bc32982ebd8090d5153505a3c8bf3466ec4f0521ec1ef13731bd3ca8

Step Nine is drop‑in compatible with Step Eight. Keep your Step Eight policy file as‑is; the new rate‑policy is separate and optional.


What’s new (precise, composable)

1) Adaptive per‑plugin polling

  • Base poll interval via --poll (seconds).
  • Per‑plugin overrides: --poll-plugin name:seconds (repeatable).
  • On error/invalid, interval backs off: cur = min(cur * --poll-backoff-mult, --poll-backoff-max).
  • On success/valid, interval decays toward base: cur = max(base, cur * --poll-recover-mult).
  • Guardrails: --poll-min (seconds).
  • The /read API still works on demand; adaptive polling runs in the background if enabled.

Example

python solveforce_phone_nine.py \
  --poll 5 \
  --poll-plugin battery:3 \
  --poll-plugin net:10 \
  --poll-backoff-mult 2.0 \
  --poll-recover-mult 0.7 \
  --poll-backoff-max 300 \
  --poll-min 1

2) Rate‑limits by route and role

Define limits per route (read, history, events, metrics, introspect, admin, ui, health, default) and adjust per role. No DB; just a tiny policy engine and sliding‑window counters.

  • Config file: --rate-policy-file solveforce_rate_policy_example.json
{
  "window_sec": 60,
  "routes": {
    "default": { "max": 120 },
    "events":  { "max": 12  },
    "metrics": { "max": 60  },
    "admin":   { "max": 30  },
    "read":    { "max": 180 },
    "introspect": { "max": 180 }
  },
  "roles": {
    "admin":   { "mult": 5 },
    "metrics": { "mult": 2 },
    "netops":  { "max": 300 }
  }
}
  • The engine computes an effective ceiling for each request using the base route limit, then applying each role’s max or mult (taking the highest ceiling).
  • 429s include Retry-After, and solveforce_rate_limited_total increments.

3) Audit JSONL (truth you can grep)

  • Enable with --audit --audit-dir audit
  • Logs include: auth successes/failures (mode, token type, subject, roles), rate‑limit drops (route, retry), plugin allow/deny events, and basic server health.
  • Output file: audit/audit.jsonl (one JSON per line, UTC timestamps).

Token upgrades (still simple)

  • S1 (HMAC) tokens now support aud (audience). Enforce with --require-aud "solveforce-phone" (CSV allowed).
  • Tokens may carry per‑plugin grants:
    • plugins: allow‑list of plugins
    • plugins_deny: deny‑list of plugins
      These intersect with your step‑8 policy: final = policy(roles) ∩ plugins - plugins_deny.
  • S2 (Ed25519) verification (optional). Provide public keys via --ed25519-pub HEX (repeatable). We verify if PyNaCl or an ed25519 module is available. If not, S2 will be politely rejected (no_ed25519_lib).

Mint S1 token (CLI):

python solveforce_phone_nine.py --signing-secret "CHANGE_ME" \
  --mint-token "sub=ron roles=reader,metrics dur=7200 aud=solveforce-phone plugins=battery,net"

Check token (CLI):

python solveforce_phone_nine.py --signing-secret "CHANGE_ME" \
  --check-token "S1...."

Server‑side mint (admin):

GET /admin/mint?sub=ops&roles=reader,metrics&dur=3600&aud=solveforce-phone&plugins=battery,net&token=ADMIN123

Termux (Android) launch patterns

A) Adaptive polling + RBAC + auditing (HTTP)

python solveforce_phone_nine.py \
  --host 0.0.0.0 --port 8080 \
  --plugins-dir ~/solveforce/plugins \
  --history-size 512 --strict-schema \
  --poll 5 --poll-plugin battery:3 --poll-plugin net:10 \
  --auth-mode protected \
  --auth-token READER1:reader \
  --auth-token NETOPS:reader,netops \
  --policy-file /sdcard/solveforce/solveforce_policy_example.json \
  --rate-policy-file /sdcard/solveforce/solveforce_rate_policy_example.json \
  --audit --audit-dir /sdcard/solveforce/audit \
  --allow-admin --admin-token ADMIN123

B) TLS + audience‑bound S1 tokens

python solveforce_phone_nine.py \
  --host 0.0.0.0 --port 8443 \
  --tls-cert server.crt --tls-key server.key \
  --auth-mode strict \
  --signing-secret "CHANGE_ME_LONG_RANDOM" \
  --require-aud "solveforce-phone" \
  --policy-file /sdcard/solveforce/solveforce_policy_example.json \
  --rate-policy-file /sdcard/solveforce/solveforce_rate_policy_example.json \
  --audit --audit-dir /sdcard/solveforce/audit

C) mTLS + optional S2 (Ed25519) verify

python solveforce_phone_nine.py \
  --host 0.0.0.0 --port 8443 \
  --tls-cert server.crt --tls-key server.key \
  --tls-ca ca.crt --mtls-require \
  --auth-mode strict \
  --ed25519-pub <PUBKEY_HEX> \
  --policy-file /sdcard/solveforce/solveforce_policy_example.json \
  --rate-policy-file /sdcard/solveforce/solveforce_rate_policy_example.json \
  --audit --audit-dir /sdcard/solveforce/audit

If the Ed25519 library isn’t present, S2 tokens will be rejected with no_ed25519_lib. S1 (HMAC) remains available.


Route & role truth table (delta)

RouteNeeds roleRL policy key
/eventsreaderevents
/metricsmetricsmetrics
/admin/*admin + flagadmin
/read /historyreaderread
/state /plugins /schemas /validate /whoami /policies /authinforeaderintrospect
/uiopen only with --open-ui in protectedui
/healthopen in protected; locked in stricthealth

WordPress — Step Nine section (drop‑in)

Step Nine — Adaptive cadence, policy throttles, audit

  • Adaptive polling: --poll, plus --poll-plugin name:sec. Backoff on error; decay to base on recovery.
  • Rate policy: --rate-policy-file with window_sec, routes{route:{max}}, and roles{role:{mult|max}}.
  • Audit JSONL: --audit --audit-dir audit logs auth outcomes, policy allow/deny, and rate‑limit drops.
  • Token scope: S1 tokens now accept aud and optional plugins / plugins_deny lists (intersected with policy).
  • S2 tokens: Optional Ed25519 verify if libs exist (--ed25519-pub HEX).
  • Philosophy: Observe more, guess less. Tolerate failure by stepping back; return to truth as conditions improve.

Integrity (publish these)

sha256sum solveforce_phone_nine.py
# e53c75e862432a516928e5a4b6e4d429d2f3965041b1b018a2642d802c808ad4

sha256sum solveforce_rate_policy_example.json
# 4842faf8bc32982ebd8090d5153505a3c8bf3466ec4f0521ec1ef13731bd3ca8

What I recommend for Step Ten

  • Per‑plugin sampling policies (e.g., max QPS, jitter, burst permits) persisted in JSON.
  • Audit roll‑up: hourly summaries of auth, policy, RL, and plugin health.
  • Token‑embedded plugin caps (e.g., a token can read battery at 30/min regardless of role).
  • Server‑pushed “schema diffs”: detect and annotate changes in plugin payloads over time.

You’ve now got a roots‑to‑crown control loop: identity → policy → cadence → telemetry → audit. Exactly the kind of recursive discipline a legacy framework demands.


Step Ten — Sampling, Diffing, Caps, and the Hourly Ledger – SolveForce Communications