bss-cli
v1.7.0Every release — minors and patches — from v0.1.0 to v1.7.0.
Versions
-
v1.7.0
Retire product offering — the first write path that didn’t exist: operators can now retire an offering (
bss admin catalog retire-offering --id PLAN_X) or set a pastvalid_tovia the existing window form — both fliplifecycle_status → "retired"+is_sellable → False. Before this releasePATCH /admin/catalog/offering/{id}/windowonly touchedvalid_from/valid_towithout changing lifecycle status, so an offering with a year-old expiry still showed active in the cockpit. Two paths, same policy gate: the explicitPOST /admin/catalog/offering/{id}/retire(exposed throughbss-clients, the CLI, and a cockpit “Retire…” danger panel with two-step confirm) stampsvalid_to=now()when the window is still open and raisesPOLICY_VIOLATIONon double-retire; the auto-transition path catches the “set expiry to a past date” workflow operators were already using (futurevalid_todoes not auto-retire; clearingvalid_toon a retired offering does not un-retire). Existing subscriptions are unaffected (v0.7 price-snapshot doctrine). 7 new catalog tests + full cockpit confirm-gate coverage. -
v1.6.2
Email lookup + degeneration guards — born from a live cockpit incident: asked to troubleshoot an escalation identified only by an email address, the agent had no email resolver (
customer.list’sname_containsis a display-name LIKE) and Gemma at temperature 0 with no completion cap ground out a single 53k-character repetition loop over ~10 minutes — and the v1.5 3-strike bail never fired, because every replayedcustomer.list → []was a success. Three fixes, one per layer:customer.find_by_email(TMF629GET /customer/by-email, query param so plus-addressing survives;operator_cockpitprofile only — customers keep identifying via session; the CSR search box gains the same email lane);BSS_LLM_MAX_TOKENS(default 2048) caps every completion so a degenerate loop costs one bounded call, not ten unbounded minutes; and a stuck-loop bail — three consecutive identical (tool, args, result) triples end the turn with the same “couldn’t recover” panel, while progressing polls never trip (the result is part of the key). The lint adoption that followed exposed a since-inception bug:clock.now’s tool def shadowed its ownbss_clock.nowimport, so the tool called itself and every invocation raised — fixed with a regression test that actually invokes it. Also: CRM inventory + catalog suites decoupled from shared dev-DB state (rollback-isolation fixtures; FK-referenced scenario leftovers neutralised in-txn via UPDATE instead of DELETE), andmake lintis real for the first time — ruff declared and 503 accumulated findings swept to zero, now enforced; mypy split to a known-redlint-typestarget pending a typing project.make testfully green (24 suites) against a lived-in dev DB. Verified live: the incident prompt now resolvesfind_by_email → customer.get → case.show_transcript_forand answers in ~2 minutes. -
v1.6.0
Operator-cockpit CRM — the browser on
:9002becomes a full CRM around the chat (the conversational UX stays the centrepiece; the UI supplements it — operator-directed Phase 0 amendment, DECISIONS 2026-06-10 ×2). Five screen families: Customers (search / state filter / pagination → 360 with KYC, contact-medium CRUD, name edit, subscriptions with live balance bars, orders, cases, card-on-file, interactions), Cases (queue → workbench: notes, take / await / resume / resolve, priority, close, full ticket lifecycle with agent assign), Orders (create / submit / cancel + cross-customer queue via a new COM list filter + COM→SOM decomposition detail), Catalog (plans / VAS / promotions + add-offering / add-price / set-window admin), Subscriptions (balances, SOM services, usage tail, eSIM re-display, plan-change / renew / VAS / terminate). Write doctrine: direct CRUD, one route → one policy-gatedbss-clientscall,PolicyViolationflashed back verbatim; destructive + money verbs require a two-step confirm panel (confirm=yes; routes refuse bare POSTs — test-pinned both directions); "Ask the agent" handoffs open a focused session with the verb drafted, never auto-sent; promo assignment stays conversational (loyalty pairing). UX overhaul: design system (accent links, panel cards, three button weights + danger, dot badges, dense mono-id tables), fixed-viewport app shell driven byvisualViewport(compose box always on screen on iPad — keyboard pan, accessory pill, focus-zoom all handled), and disconnect-proof turns (agent runs in a detached server task; SSE heartbeats; 45 s client watchdog re-attaches and renders the persisted reply — no more lost first turns). Six drift bugs fixed en route: case PATCH was trigger-only (case.update_priority422'd since v0.13), the entire ticket client surface was broken (open_ticket422 always,assign_ticketsilent no-op,transition_ticket422 always),list_casesagent filter sent the wrong query param, and the case page silently blanked snake_case DTO fields. Ops:docs/runbooks/litellm-proxy.md(LiteLLM model-route rules — thedeepseek/prefix hits DeepSeek's native API which rejects dotted tool names;openrouter/…routes don't — plus the Docker network re-attach after a proxy recreate); deployment default flips to Gemma 4 31B via OpenRouter. Verified: 19/19 hero scenarios, Playwright across five viewports incl. iPad + keyboard-height proxy, operator-verified on a physical iPad. -
v1.5.0
Multi-step orchestration in the cockpit — compound questions ("investigate CASE-…", "register CUST + create order", "show customer and their subscriptions") now chain in one operator turn. New
BSS_REPL_LLM_AUTONOMY={granular,batched}env var controls how many/confirms a compound destructive action needs (granular default re-gates after each destructive; batched authorises the loop on the first/confirm). Five pieces, no new container / schema / migration: the env var (fail-closed at boot, same shape asBSS_API_TOKEN=changeme); autonomy-aware destructive gating via shared per-graphLoopState; anITERATIVE FLOWprompt block + softened v0.19 "Done." rule (the agent may chain another tool_call instead of always terminating); a 3-strike loop bail catching Gemma thrash on repeated tool failures; and a cockpit chrome filter that strips banner-shape AND narrated-call ("I propose to terminate … `subscription.terminate(…)`") mimicry from both history rehydration and live emits. Symmetric bubble overrides so post-propose says"Proposed X(args). Type /confirm to authorise."and post-execute says"Executed X(args)."— pre-v1.5 both shapes rendered as"Done."and one of them was lying. Design lifted from loyalty-cli's v0.11 pattern; adapted for bss-cli surfaces (REPL + browser veneer). Doctrine: CLAUDE.md gains a v1.5 cockpit anti-pattern block (autonomy module ownership,ITERATIVE FLOWoperator-only, 3-strike constant, chrome-prefix inventory lock); HANDBOOK gains §8.20 with worked examples + when-to-flip-to-batched guidance. Verified: orchestrator 403→420, bss-cockpit 66→95, all green;make e2e+make e2e-batchedboth 12 passed / 1 skipped with three new specs (compound granular, compound batched, case investigation). Per-session/autonomy {granular,batched}slash override deferred to v1.5.1. -
v1.4.1
All four v1.4.0 xfails resolved — 10/10 e2e specs green (was 6/10 + 4 xfail). Two new architectural pieces:
bss promo exhaustadmin FSM verb (active → exhausted) via migration 0030, so the exhausted-code spec exercises a real state transition; and aBSS_LLM_FIXTURE_PATHorchestrator mock-LLM seam (MockChatModelsubclassingBaseChatModel) for deterministic cockpit specs — tools still execute against real services. Every run drops a self-contained visual gallery underdocs/e2e-reports/<UTC-ts>/— per-spec screenshots, Playwrighttrace.zip, embeddedvideo.webm, and a hero/summary parsed from the JUnit XML. -
v1.4.0
End-to-end test suite via Playwright. Ten specs across the self-serve portal (signup golden path, public typed promo, targeted promo on dashboard, exhausted-code degrade, step-up auth) and the cockpit browser veneer (sessions index, tool roundtrip, propose-then-
/confirm, knowledge citation fallback, slash-command parity) — 6/10 green + 4 xfail at v1.4.0 (closed in v1.4.1), ~37 s wall-clock. Tests are doctrine-compliant callers: setup goes throughbss-clientsand Typer verbs, never direct DB inserts; the suite authenticates over the real magic-link / OTP path.docker-compose.e2e.ymlpins mock payment / KYC / email / eSIM so no live providers are touched. -
v1.3.1
bss promo unassign+ synced demo seed. New verb reverses an assignment cleanly (BSS row delete + loyaltyoffer.expire/revokefallback).make seed-demo/make seed-demo-resetproduces a coherent demo dataset across both systems; BSS-only mode (loyalty unconfigured) still works as a first-class path. -
v1.3.0
Customer↔offer pairing in loyalty at assign time.
bss promo assignnow also callsoffer.issueupfront so loyalty’s per-customer views and campaign rosters reflect the assignment the moment the operator runs it — closing the v1.1.1 audit/visibility gap where BSS held eligibility but loyalty was empty until the customer ordered. Activation switches tooffer.advance_to_claimedfor the targeted lane (the path retired in v1.1.1 is back, only for this lane); public typed codes continue mint-and-claim by code, unchanged. Saga semantics: a loyalty refusal at assign time degrades toloyalty_offer_id=NULLwith an operator-visible warning, and COM transparently falls back to claim-by-code at activation. Bundles a v1.2.x schema fix — partial unique indices onsubscription.subscriptionmsisdn/iccid that exclude terminated rows, so inventory can legitimately recycle phone numbers without bricking the next order atsubscription.create. -
v1.2.0
Resilient COM ↔ SOM ↔ subscription order pipeline. Transactional outbox (
bss_events.relay, per-service in-process tick,FOR UPDATE SKIP LOCKED) replaces the inline publish that had a publish-before-commit hazard and silently lost failed publishes — staged events delivered at-least-once. Safe consumers (bss_events.bind_consumer) replace the baremessage.process()that dropped on any exception: every queue now has a retry-TTL sibling and a<queue>.parkedterminal — a poison message parks (operator-visible incident) instead of stranding the order, closing the v1.1.3 failure class at the mechanism. Inbox dedup (<schema>.processed_eventkeyed onevent_id) makes consumers idempotent under at-least-once delivery;subscription.createis idempotent oncommercial_order_idso MQ redelivery can’t double-charge the card-on-file. A reconciliation sweeper flagsorder.stuckonce per order leftin_progresspast 15 min — operator backstop, never auto-resolves. Live-verified on real RabbitMQ + Postgres. -
v1.1.3
Promo claim resilience. A promo claim failure at activation (exhausted code, loyalty refusal/outage) no longer bricks the order: it degrades to full price, emits
order.promo_not_applied, and completes activation. Previously the order was strandedin_progress(no subscription) after KYC + payment had already succeeded. -
v1.1.2
Promo preview fix. The self-serve signup live-discount endpoint read
codewhile the HTMX form sentpromo_code, so typed-code previews were always empty. Endpoint now accepts both; regression test added. Ships the v1.1 promo self-serve screenshots + capture tooling. -
v1.1.1
Promo codes via an optional loyalty-cli entitlement adapter. Public typed codes + targeted eligibility-gated codes; discounts compose on the v0.7 price snapshot and charge the effective amount for one / N / perpetual periods. The adapter is optional — no loyalty token configured means the promo subsystem switches off and every core flow runs unchanged (graceful degradation). Operator surface is CLI-native (
bss promo create / assign / show); customers redeem but never mint. -
v1.0.1
Bot-driven CLI hygiene patch. Six fixes:
bss customer createprinted the wrong name field (TMF629 nesting);bss payment add-card404'd on a non-existent/dev/tokenize;PolicyViolationFromServer.messageAttributeError across 13 files;cardSummarynesting bug incustomer create --card; and six surfaces (portal, REPL cockpit, LLM tooling, CLI help, renewal-reminder emails) had hardcoded the v0.1 PLAN_S/M/L whitelist, so new offerings (PLAN_XL, PLAN_CNY) now appear automatically. - v1.0.0 General Availability — the wrap of the v0.x arc. All seven principles intact under load: bundled-prepaid, card-on-file, block-on-exhaust, CLI-first / LLM-native, TMF-compliant, lightweight (~2.05 GiB all-in including Postgres / RabbitMQ / Jaeger; p99 internal API under 50 ms), write-through policy. Real-provider integrations live (Resend, Didit, Stripe Checkout); SM-DP+ remains NDA-gated mock. Cockpit knowledge tool answers operator how-to questions from the indexed handbook with cited anchors. Single-Postgres / schema-per-domain holds; the documented split path is ready when traffic demands it.
-
v0.20.2
RAG discovery + REPL/browser parity + mobile cockpit. Fixes a one-line order-of-operations bug where the REPL pulled the conversation transcript after appending the new user turn, so Gemma saw the same
HumanMessagetwice with no assistant turn between — it deflected, and the RAG / knowledge-search miss rate spiked on the REPL only. Transcript is now pulled before the append, matching the browser cockpit. Also: cockpit responsive on phones + tablets, widerknowledge.searchdefaults, thought-prefix stripped. -
v0.20.1
Cockpit prose rendering + UX polish. Markdown-aware cockpit prose rendering; wider thread canvas; tighter chat-bubble typography (drop to 12.5px); empty-bubble guard after
knowledge.search;astream_oncefinally-blockContextVar.resetguarded; renamed a local that shadowed theknowledge_calledimport. -
v0.20.0
Cockpit knowledge tool (handbook RAG).
knowledge.search/knowledge.getover an indexed doc corpus (HANDBOOK + CLAUDE + runbooks); citation guard catches un-cited handbook claims; Tier-0 Postgres FTS default, Tier-1 pgvector hybrid behind a backend toggle. Newknowledgeschema,--data-roaming-mbflag closes the v0.17 catalog admin gap. Postgres image moves topgvector/pgvector:pg16. New single-filedocs/HANDBOOK.mdconsolidates setup, env vars, providers, personas, and every runbook into one Obsidian-friendly reference (the same corpus the cockpit indexes). -
v0.19.1
Cockpit rendering polish + customer-chat truthfulness. Single source of truth for tool-result rendering across REPL + browser veneer — ASCII renderers move into
bss-cockpit; both surfaces emit the same<details open><pre>…</pre></details>block. Verbatim escalation reply rewritten — drops the false 24-hour-email promise (no platform-side email automation exists). NewBSS_OPERATOR_SUPPORT_EMAIL/BSS_OPERATOR_NAMEenv vars fix the bug where the bot directed customers to email themselves. Newinventory.msisdn.counttool +GET /msisdn/countendpoint as source of truth for "how many numbers do we have?" prompts. -
v0.19.0
Soak baseline before v1.0. UX polish: deterministic intent intercept on list-shaped REPL queries (kills LLM fabrication), Rich/box-drawing ASCII panels rendered as
<pre>in the cockpit browser, consolidated docs sweep, README rewrite to 1.0-baseline, KYC-provider rename (myinfo→prebaked), retired stale SHIP_CRITERIA + Phase-12 framing. -
v0.18.1
Source service version from
BSS_RELEASEon all surfaces. v0.18.0 drift fix: every service's/healthhad been reportingversion='0.1.0'instead of the current product release, and 4 of the 9 services (com, som, subscription, provisioning-sim) didn't include a version field in/healthat all. All 9 serviceconfig.pyfiles now importBSS_RELEASEfrombss_models; one bump inbss_models/__init__.pypropagates to every surface. Newmake doctrine-checkgrep guard rejects any literalversion: str = "X.Y.Z"in anyconfig.py— 14 guards total. -
v0.18.0
Automated subscription-renewal worker. In-process tick loop in the subscription lifespan: three sweeps per tick (renew due / skip blocked-overdue / send upcoming-renewal email).
FOR UPDATE SKIP LOCKEDfrom day one — multi-replica safe by construction. -
v0.17.0
Telco hygiene release. MNP (port-in / port-out via
crm.port_request), MSISDN replenishment (bss inventory msisdn add-range+ low-watermark event), roaming as a product (data_roamingallowance type,VAS_ROAMING_1GBtop-up). -
v0.16.0
Real Stripe Checkout + webhook reconciliation (PCI scope guard refuses to boot in production-stripe mode if a card-number
<input>survives in any rendered template). Idempotency keys, drift detection, refund-record-only. - v0.15.0 Real Didit KYC + the eSIM-provider seam. Channel-layer KYC: hosted UI + signed-webhook corroboration. BSS only verifies signed attestations + corroboration.
-
v0.14.0
Real-provider integration arc begins. Per-domain adapter Protocols,
integrationsschema for forensic external-call + webhook-event logging, ResendEmailAdapter for transactional auth mail. -
v0.13.0
Operator cockpit. CLI REPL canonical, browser veneer over a shared Postgres-backed
Conversationstore. v0.5 staff-auth retired in favour of single-operator-by-design behind a secure perimeter;OPERATOR.mdas operator-editable persona;/confirmfor destructive ops. -
v0.12.1
Step-up replay preserves POST body. Sensitive-action POSTs (name / phone / address / email-change / VAS / COF / plan-change) bouncing through
/auth/step-upno longer discard the customer's typed input.requires_step_upstashes the filtered POST body inportal_auth.step_up_pending_actionkeyed by(session_id, action_label); afterverify_step_upsucceeds, an auto-submit replay page POSTs the stashed fields to the original URL with the fresh grant cookie. Generic across everyrequires_step_up-gated route; no-JS fallback button preserves the flow without scripts. -
v0.12.0
Chat scoping + escalation + 14-day soak.
customer_self_servetool profile, output ownership trip-wire, per-customer rate + cost caps, five non-negotiable escalation categories, popup chat widget on every post-login page, pre-signup browse mode. - v0.11.0 Signup funnel migrates to direct API; the v0.4 LLM-mediated signup demo retires. Each step is one direct write from a route handler. Wall time: ~85s → <5s. Chat is the only orchestrator-mediated route that remains.
-
v0.10.0
Post-login self-serve goes direct — top-up, plan-change scheduler, COF management, eSIM redownload, line cancel, contact updates, billing history. One route, one
bss-clientswrite, no orchestrator hop. Step-up auth gates every sensitive label. -
v0.9.0
Named-token perimeter. Each external surface gets its own
BSS_*_API_TOKEN;service_identityflows intoaudit.domain_eventso audit answers "which surface initiated this write?" not just "which actor". -
v0.8.0
Self-serve portal login wall — email + magic-link OTP + step-up auth for sensitive writes. New
portal_authschema; tokens HMAC-SHA-256 with a server pepper;PortalSessionMiddlewarebindsrequest.state.customer_idfrom the verified session. -
v0.7.0
Catalog versioning + plan-change snapshot doctrine. No proration; price snapshotted at order time so renewal locks in the price even when the catalog re-prices. Operator-initiated
migrate_to_new_pricewith regulatory notice. - v0.6.0 Docs sweep, REPL renderer dispatch (rendered ASCII cards in the interactive REPL), tech-debt sweep, snapshot test framework.
- v0.5.0 CSR portal (customer 360 + ask-the-agent), case threading, scenario engine extensions.
- v0.4.0 Self-serve portal (HTMX + SSE agent log).
-
v0.3.0
BSS_API_TOKENmiddleware — the smallest possible auth story, behind theauth_context.pyseam that has been in every service since day one. -
v0.2.0
OpenTelemetry + Jaeger +
bss traceASCII swimlane. - v0.1.1 Defer billing to v0.2 and clean up v0.1.0 drift.
- v0.1.0 Nine services, full TMF surface, write-through-policy, hero scenarios.