{"id":51337582,"url":"https://github.com/chiam-ck/bss-cli","last_synced_at":"2026-07-02T04:03:45.826Z","repository":{"id":353894014,"uuid":"1207483855","full_name":"chiam-ck/bss-cli","owner":"chiam-ck","description":"Lightweight, SID-aligned, TMF-compliant BSS for terminal-first operation. LLM-native. eSIM-first.","archived":false,"fork":false,"pushed_at":"2026-06-30T09:11:41.000Z","size":7240,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T11:14:50.878Z","etag":null,"topics":["bss","esim","fastapi","langgraph","llm-agent","mvno","telco","tmf"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chiam-ck.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-11T02:03:06.000Z","updated_at":"2026-06-30T09:11:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"a79da14b-6e64-488b-bfc1-2b5def47ceff","html_url":"https://github.com/chiam-ck/bss-cli","commit_stats":null,"previous_names":["samurai-bot/bss-cli","chiam-ck/bss-cli"],"tags_count":46,"template":false,"template_full_name":null,"purl":"pkg:github/chiam-ck/bss-cli","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chiam-ck%2Fbss-cli","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chiam-ck%2Fbss-cli/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chiam-ck%2Fbss-cli/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chiam-ck%2Fbss-cli/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chiam-ck","download_url":"https://codeload.github.com/chiam-ck/bss-cli/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chiam-ck%2Fbss-cli/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35032146,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bss","esim","fastapi","langgraph","llm-agent","mvno","telco","tmf"],"created_at":"2026-07-02T04:03:45.306Z","updated_at":"2026-07-02T04:03:45.819Z","avatar_url":"https://github.com/chiam-ck.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# BSS-CLI\n\n\u003e The entire BSS, in a terminal. SID-aligned. TMF-compliant. LLM-native. eSIM-first.\n\nA complete reference Business Support System for a small mobile prepaid MVNO that runs from a single terminal command. Nine TMF-compliant service containers (Catalog, CRM with Cases/Tickets/Port-requests, Payment, COM, SOM, Subscription, Mediation, Rating, Provisioning-sim) plus two web portals (self-serve customer + operator cockpit). Every operation is a tool the LLM can call; the primary UI is the `bss` CLI plus ASCII visualizations and a scoped chat surface in the customer portal.\n\nFor engineers learning telco BSS/OSS, for a small MVNO that wants a deployable MVP, and as a substrate for agentic experiments against realistic telco operations. **eSIM-only, bundled-prepaid, block-on-exhaust, card-on-file mandatory.** eKYC, real-customer UI, network elements, batch CDR, and OCS protocols are intentionally out of scope (channel-layer concerns).\n\n**Status (v1.6 — Cockpit CRM workbench):** the cockpit browser now carries full CRM screens around the chat — Customers (list + 360), Cases (queue + workbench), Orders (cross-customer queue + COM/SOM detail), Catalog (plans/VAS/promotions), Subscription detail — reads direct via `bss-clients`, routine case-workbench writes policy-gated, and every destructive or money-moving verb handing off to chat as a prefilled draft so propose-then-`/confirm` stays the single chokepoint (DECISIONS 2026-06-10). Built on v1.5: the operator cockpit's natural-language grammar is an agentic loop — one prompt can chain N tool calls, gated by `BSS_REPL_LLM_AUTONOMY` (`granular` default: each destructive step propose-then-`/confirm`s separately; `batched` opt-in: first destructive in a `/confirm`-resumed loop gates, the rest run autonomously). Unknown values fail-closed at boot (`AutonomyMisconfigured`); a 3-strike bail stops tool-failure thrash; a chrome filter keeps emit-only text out of LLM history. Default model is `deepseek/deepseek-v4-pro` (v1.5.1, swapped from Gemma 4 26B which needed too much tool-call scaffolding). v1.4 shipped a Playwright e2e suite (10 specs green, ~37s, visual-artefact galleries under `docs/e2e-reports/`). v1.3.0 brought customer↔offer pairing upfront. v1.3.1 closes the loop: new `bss promo unassign` reverses an assignment cleanly (BSS eligibility row delete + loyalty `offer.expire` on the issued offer; falls back to `offer.revoke` if the customer already claimed it). New synced seed (`make seed-demo` / `make seed-demo-reset`) produces a coherent demo dataset across BSS + loyalty in lockstep — idempotent, demo-prefix surgical, BSS-only mode supported when loyalty isn't configured. v1.3.0 substance carries: `bss promo assign` mints the customer↔offer pairing in loyalty at assign time (`offer.issue`), activation uses `offer.advance_to_claimed` for the targeted lane (public typed codes unchanged, still claim-by-code), reverses the v1.1.1 \"consume collapsed to one path\" amendment for the targeted lane only. Also bundles a v1.2.x schema fix — `UNIQUE (msisdn)` / `UNIQUE (iccid)` on `subscription.subscription` are now partial indices excluding terminated rows, so inventory can legitimately recycle phone numbers without bricking the next order at `subscription.create`. v1.2 stays intact: the COM ↔ SOM ↔ subscription order pipeline can no longer silently lose or strand a paid order. Transactional outbox (`bss_events.relay`) is the single publisher for the order path — staged events delivered at-least-once with `FOR UPDATE SKIP LOCKED`, no more publish-before-commit. Safe consumers (`bss_events.bind_consumer`) give every queue retry + a `\u003cqueue\u003e.parked` terminal — a poison message parks (operator-visible) instead of dropping; inbox dedup on `event_id` makes redelivery idempotent; `subscription.create` is idempotent on `commercial_order_id` so MQ redelivery can't double-charge the card-on-file. A reconciliation sweeper flags `order.stuck` once per order left `in_progress` past 15 min — operator backstop, never auto-resolves. The v1.1.3 stranded-order bug class is closed at the mechanism, not the symptom. Live-verified on real RabbitMQ + Postgres (rebuilt containers, end-to-end order + chaos cases). Promotions (v1.1) intact: non-targeted typed codes + targeted eligibility-gated codes via the optional `loyalty-cli` adapter. Built on v1.0 — all three real-provider integrations live (Resend, Didit, Stripe Checkout); telco hygiene closed (MNP, MSISDN replenishment, roaming); operator cockpit single-operator-by-design behind a secure perimeter (REPL canonical, browser veneer over the same `Conversation` store); cockpit knowledge tool (v0.20) cites from `docs/HANDBOOK.md` + runbooks + CLAUDE.md. The only mocked surface left for production is SM-DP+ (real eSIM provisioning is NDA-gated).\n\n## Screenshots\n\n### Operator cockpit — REPL (canonical surface)\n\nThe `bss` REPL is the cockpit. Type natural language; the agent calls tools; results render as ASCII cards. Slash commands (`/ports`, `/360`, `/focus`, `/confirm`) cover deterministic operator flows.\n\n![bss REPL banner](docs/screenshots/bss_repl_v1.jpg)\n\n### Operator cockpit — browser (chat + CRM screens)\n\nSame `Conversation` store as the REPL; exit `bss`, open `localhost:9002`, see the same turns. No login wall (single-operator-by-design behind a secure perimeter).\n\n- **Sessions index** — `localhost:9002/`. Recent conversations + customer search + new-conversation CTA.\n\n  ![cockpit sessions index](docs/screenshots/portal_csr_cockpit_sessions_v1.jpg)\n\n- **Live conversation** — agent renders ASCII tool cards inline (catalog VAS list, plan detail, etc.). Destructive actions propose first, wait for `/confirm`.\n\n  ![cockpit conversation](docs/screenshots/portal_csr_cockpit_session_v1.jpg)\n\n- **CRM screens (v1.6)** — Customers (search + 360 with balance bars, name/contact CRUD, card-on-file), Cases (queue + workbench: notes, transitions, priority, close, full ticket lifecycle), Orders (create/submit/cancel + cross-customer queue + COM/SOM decomposition detail), Catalog (plans/VAS/promotions + offering/price/window admin), Subscription detail (balances, usage tail, eSIM, plan change/renew/VAS/terminate). Destructive and money-moving verbs are direct CRUD behind a **two-step confirm panel**; every screen also carries **\"Ask the agent\"** handoffs that open a focused chat session with the message drafted — the operator reviews and sends.\n\n### Self-serve portal (customer-facing)\n\n- **Public landing** — `localhost:9001/welcome`.\n\n  ![self-serve welcome](docs/screenshots/portal_self_serve_welcome_v1.jpg)\n\n- **Plan picker** — `localhost:9001/plans`. Three plans, all four allowance rows aligned (Data / Voice / SMS / Roaming). PLAN_S has no roaming included; PLAN_M ships 500 mb, PLAN_L ships 2 GB.\n\n  ![self-serve plans](docs/screenshots/portal_self_serve_plans_v1.jpg)\n\n### Distributed trace (`bss trace`)\n\n- **Signup chain swimlane** — every span across COM → SOM → Inventory → Provisioning-sim → Subscription → Payment, ASCII rendered:\n\n  ![bss trace swimlane](docs/screenshots/bss_trace_swimlane_v0_2.png)\n\n\u003e Re-capture against your own stack: `uv run python docs/screenshots/capture_portals.py` for the web surfaces; see [`docs/screenshots/CAPTURE.md`](docs/screenshots/CAPTURE.md) for prereqs and the manual REPL capture.\n\n## How writes flow\n\nEvery BSS write goes through the per-service policy layer. Three trigger paths feed it:\n\n- **Direct via `bss-clients`** — every CLI/REPL command, the entire signup funnel, every post-login self-serve route, and every read. Sub-second, deterministic, no LLM.\n- **Orchestrator-mediated via `astream_once`** — the customer-portal chat surface (only orchestrator-mediated route post-v0.11). Wraps a LangGraph ReAct agent over the same tool registry. The same policy chokepoint enforces both paths, so audit + attribution stay coherent.\n- **In-process tick loops** — the v0.18 renewal worker fires automatic renewals and sends upcoming-renewal reminder emails on a 60-second tick. `FOR UPDATE SKIP LOCKED` makes it multi-replica safe by construction.\n\nThe audit log gets a coherent attribution on every write: `actor`, `channel` (`portal-self-serve` / `portal-csr` / `portal-chat` / `cli` / `system:renewal_worker`), and `service_identity` (`portal_self_serve` / `default` / etc. via the v0.9 named-token perimeter).\n\n## What's in the box (v0.7 → v1.5)\n\n| Release | What landed |\n|---|---|\n| **v0.7** | Catalog versioning + plan changes; subscription price snapshotted at order time; renewal reads the snapshot, not the catalog |\n| **v0.8** | Self-serve portal authentication — email + magic-link / OTP, server-side sessions, public-route allowlist, step-up scaffolding |\n| **v0.9** | Named tokens at the BSS perimeter; `service_identity` propagation through audit + structlog + OTel |\n| **v0.10** | Authenticated post-login customer self-serve writes go direct (chat stays orchestrator-mediated); per-resource ownership policies + step-up gating |\n| **v0.11** | Signup funnel goes direct (sub-second per step). Chat is the only orchestrator-mediated route |\n| **v0.12** | Chat scoping — `customer_self_serve` profile + `*.mine` wrappers + ownership trip-wire + per-customer caps + 5-category escalation. 14-day soak |\n| **v0.13** | Operator cockpit. CLI REPL canonical, browser veneer over a shared Postgres-backed `Conversation` store. v0.5 staff-auth retired |\n| **v0.14** | Real-provider integration arc begins: per-domain adapter Protocols, `integrations` schema for forensic external-call + webhook-event logging, ResendEmailAdapter for transactional auth mail |\n| **v0.15** | KYC (Didit) + the eSIM-provider seam. Channel-layer KYC; BSS only verifies signed attestations + corroboration |\n| **v0.16** | Payment (Stripe Checkout + webhook reconciliation). PCI scope guard refuses to boot in production-stripe mode if a card-number `\u003cinput\u003e` survives in any rendered template |\n| **v0.17** | 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_roaming` allowance type, `VAS_ROAMING_1GB` top-up) |\n| **v0.18** | Automated subscription-renewal worker. Three sweeps per tick: renew due / skip blocked-overdue / send upcoming-renewal email. Multi-replica safe via `FOR UPDATE SKIP LOCKED` from day one |\n| **v0.20** | Cockpit knowledge tool — Tier-0 FTS over `docs/HANDBOOK.md` + CLAUDE.md + runbooks; LLM cites anchors, citation guard catches un-cited claims. Catalog `--data-roaming-mb` flag closes the v0.17 admin gap. Postgres image moves to `pgvector/pgvector:pg16` |\n| **v1.0** | General Availability — 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.65 GiB all-in incl. infra; p99 internal API \u003c 50 ms), write-through policy. Real-provider integrations live; SM-DP+ remains NDA-gated mock. Single-Postgres / schema-per-domain holds; documented split path is ready when traffic demands it |\n| **v1.1** | Promo codes — *integrate, don't build*. The separate `chiam-ck/loyalty-cli` is the entitlement engine; BSS owns only the money terms (`catalog.promotion`) and composes over HTTP. Non-targeted typed codes (`SUMMER25`) + targeted codes gated by a BSS eligibility list (operator pre-assigns via `bss promo assign`; auto-applies for eligible customers, rejected for others). Discount composes on the lowest-active price; charged effective for 1 / N / perpetual periods, decremented at renewal, ended by a plan change. Claim-at-activation (a provisioning failure never burns a code; a payment decline revokes it). Operator-only tools (`bss promo`, `promo.*`) — customers type a code, never issue one. **loyalty-cli is an optional adapter — BSS-CLI runs fully without it (promos just off); set `BSS_LOYALTY_*` to enable.** Runbook: [`docs/runbooks/promo-codes.md`](docs/runbooks/promo-codes.md) |\n| **v1.2** | Resilient COM/SOM pipeline — the order path stops being able to silently lose or strand a paid order. **Transactional outbox** (`bss_events.relay`, per-service in-process tick, `FOR UPDATE SKIP LOCKED`) takes over publishing from the inline `exchange.publish` path that had a publish-before-commit hazard and lost failed publishes. **Safe consumers** (`bss_events.bind_consumer`) replace the bare `message.process()` that dropped on any exception — every queue now has retry-TTL + `\u003cqueue\u003e.parked`; a poison message parks (operator-visible incident) instead of stranding the order, fixing the v1.1.3 failure mode at the root. **Inbox dedup** (`\u003cschema\u003e.processed_event` keyed on `event_id`) makes consumers idempotent under at-least-once delivery; `subscription.create` is idempotent on `commercial_order_id` so MQ redelivery can't double-charge the card-on-file. **Reconciliation sweeper** flags `order.stuck` once per order left `in_progress` past 15 min — operator backstop, never auto-resolves. Live-verified end-to-end on real RabbitMQ + Postgres. Required one-time deploy step (queue redeclaration) in [`docs/runbooks/v1.2-pipeline-deploy.md`](docs/runbooks/v1.2-pipeline-deploy.md) |\n| **v1.3** | Pairing-at-assign + recovery ergonomics. `bss promo assign` mints the customer↔offer pairing in loyalty at assign time (`offer.issue`); activation uses `offer.advance_to_claimed` for the targeted lane (public typed codes stay claim-by-code) — partially reverses the v1.1.1 \"consume collapsed to one path\" amendment for the targeted lane only. `bss promo unassign` reverses an assignment cleanly (eligibility-row delete + loyalty `offer.expire`, falling back to `offer.revoke` if already claimed). New **synced demo seed** (`make seed-demo` / `make seed-demo-reset` / `make demo-restore`) builds a coherent BSS + loyalty dataset in lockstep — idempotent, demo-prefix surgical, BSS-only when loyalty is unwired. Bundles a v1.2.x schema fix: `UNIQUE (msisdn)` / `UNIQUE (iccid)` on `subscription.subscription` become partial indices excluding terminated rows, so recycled phone numbers can't brick the next `subscription.create` |\n| **v1.4** | Playwright-driven e2e suite over the self-serve portal + cockpit veneer. `docker-compose.e2e.yml` pins mock providers (payment/KYC/email/eSIM) + a deterministic LLM fixture; `make e2e` runs demo-restore → pytest → gallery → teardown with cleanup traps. 10 specs green at v1.4.1, ~37s wall-clock. Every run emits **visual artefacts** under `docs/e2e-reports/\u003cUTC-ts\u003e/` — a self-contained `index.html` gallery with per-spec screenshots + embedded video + Playwright trace |\n| **v1.5** | Multi-step cockpit autonomy. The REPL/cockpit grammar becomes an agentic loop — one operator prompt chains N tool calls. `BSS_REPL_LLM_AUTONOMY` (`granular` default / `batched` opt-in) controls `/confirm` granularity across destructive steps; unknown values fail-closed at boot (`AutonomyMisconfigured`). 3-strike bail caps tool-failure thrash (`MAX_CONSECUTIVE_TOOL_FAILURES`); a chrome filter keeps emit-only assistant text out of rehydrated LLM history. The `ITERATIVE FLOW` prompt block is operator-only — never on the `customer_self_serve` chat surface (doctrine guard). v1.5.1 swaps the default model to `deepseek/deepseek-v4-pro` |\n\nFull per-release narratives in [`phases/V0_X_Y.md`](phases/), [`phases/V1_1_0.md`](phases/V1_1_0.md), [`phases/V1_2_0.md`](phases/V1_2_0.md), [`phases/V1_3_0.md`](phases/V1_3_0.md), [`phases/V1_4_0.md`](phases/V1_4_0.md), and [`phases/V1_5_0.md`](phases/V1_5_0.md). Post-v1.0 work and non-goals live in [`ROADMAP.md`](ROADMAP.md).\n\n## Quick start\n\n### Prerequisites\n\n- Docker + Docker Compose\n- Python 3.12 + [uv](https://docs.astral.sh/uv/)\n- An OpenRouter API key (or any OpenAI-compatible endpoint) for the orchestrator-mediated chat + REPL\n\n### Bring-your-own-infra (BYOI — Postgres / RabbitMQ already running)\n\n\u003e **v0.20+ prereq.** The cockpit knowledge tool's table uses pgvector.\n\u003e Most managed Postgres tiers (RDS, Cloud SQL, Aiven, Neon) offer it\n\u003e as a switch-on extension. Run **`CREATE EXTENSION IF NOT EXISTS\n\u003e vector`** once on your BSS database before `make migrate`.\n\u003e See [`docs/runbooks/knowledge-indexer.md`](docs/runbooks/knowledge-indexer.md)\n\u003e for the full procedure.\n\n```bash\ngit clone \u003crepo\u003e\ncd bss-cli\ncp .env.example .env\n\n# Generate a real BSS_API_TOKEN; the sentinel value rejects on startup.\nsed -i \"s/^BSS_API_TOKEN=changeme$/BSS_API_TOKEN=$(openssl rand -hex 32)/\" .env\n\n# Edit .env: BSS_DB_URL, BSS_RABBITMQ_URL, BSS_LLM_API_KEY,\n# optionally BSS_OTEL_EXPORTER_OTLP_ENDPOINT (e.g. http://tech-vm:4318)\n\n# v0.20+ — install pgvector once on the target Postgres:\npsql \"$BSS_DB_URL\" -c \"CREATE EXTENSION IF NOT EXISTS vector;\"\n\ndocker compose up -d         # 9 services + 2 portals\nmake migrate                  # Alembic on the existing Postgres (currently at 0023)\nmake seed                     # 3 plans + 4 VAS offerings + 1000 MSISDNs + 1000 eSIM profiles\nmake knowledge-reindex        # populate the cockpit's doc corpus (372 chunks)\nbss                           # opens the cockpit REPL\n```\n\n### All-in-one (bundled infra)\n\nThe bundled `docker-compose.infra.yml` ships **`pgvector/pgvector:pg16`** as the\nPostgres image (v0.20+; drop-in same-major replacement for stock\n`postgres:16-alpine`). pgvector activates automatically — no manual `CREATE\nEXTENSION` needed.\n\n```bash\ndocker compose -f docker-compose.yml -f docker-compose.infra.yml up -d\nmake migrate\nmake seed\nmake knowledge-reindex              # populate the cockpit's doc corpus\nbss                                 # cockpit REPL\nopen http://localhost:9001/         # self-serve portal\nopen http://localhost:9002/         # operator cockpit (browser veneer)\nopen http://localhost:16686/        # Jaeger UI (traces)\n```\n\n### First commands worth running\n\n```bash\nmake scenarios                   # 19 hero scenarios — sanity-check the install (~100s)\nmake doctrine-check              # 16 grep guards (clock, OTel, channel, renewal, knowledge, ...)\nbss subscription show SUB-0001\nbss inventory msisdn list --prefix 9000 --limit 5\nbss trace for-order ORD-0001\nbss admin knowledge search \"rotate cockpit token\"   # v0.20+ doc-corpus search\n```\n\n### Demo data (`make demo-restore`)\n\n`make seed` (always available) populates **reference data** — agents, SLA\npolicies, plans, MSISDN + eSIM pools, provisioning-sim config. No customers,\nno promotions, no test users. It is BSS-only — never touches `loyalty-cli`\neven when one is wired.\n\nv1.3.1 added a **synced demo seed** for messy-environment recovery:\n\n```bash\nmake seed-demo          # 3 demo customers + 2 promos + 2 VIP assignments,\n                        # in lockstep across BSS and loyalty-cli\nmake seed-demo-reset    # surgical reverse: unassign (loyalty offer.expire +\n                        # BSS row delete), drop demo promotions, delete demo\n                        # customers from both systems. Demo-prefix only.\nmake loyalty-reset      # full wipe of loyalty.* + audit.* schemas (TRUNCATE\n                        # + re-stamp alembic head). Companion to reset-db.\nmake demo-restore       # the single-button golden state: reset-db (BSS) +\n                        # loyalty-reset + seed-demo. Use after any messy\n                        # session to return to a known-good clean state.\n```\n\n`seed-demo` is **idempotent** (re-runs skip-if-present on both sides) and\nrespects the optional-adapter rule: with `BSS_LOYALTY_API_TOKEN` unset, the\ncustomer half still runs and the promo lane logs a clean skip — BSS-only\ndemo dataset, no loyalty entries created.\n\nNaming convention: every demo row is keyed on `*.demo@bss-cli.local` emails\nor `PROMO_DEMO_*` / `DEMO_*` ids, so `seed-demo-reset` is surgical and never\ntouches operator data.\n\n### Automated end-to-end tests (`make e2e`)\n\nv1.4 ships a **Playwright-driven** suite covering the self-serve portal\n(`localhost:9001`) and the operator cockpit browser veneer (`localhost:9002`).\n10 specs green at v1.4.1, ~37 seconds wall-clock end-to-end.\n\nEvery run produces **visual artefacts** under `docs/e2e-reports/\u003cUTC-ts\u003e/`:\n\n```\ndocs/e2e-reports/20260525T173800Z/\n├── index.html                          ← open this in any browser\n├── junit.xml\n├── test-signup-golden-path-smoke/\n│   ├── 01-signed-in.png\n│   ├── 02-signup-form-blank.png\n│   ├── 03-signup-form-filled.png\n│   ├── 04-confirmation-with-esim-qr.png\n│   ├── 05-dashboard-active-line.png\n│   ├── trace.zip      (open with: playwright show-trace)\n│   └── video.webm\n└── … (9 more specs)\n```\n\n`index.html` is a self-contained gallery — one section per spec with\ninline screenshot grid + embedded video + trace download. The end of\n`make e2e` prints the `file://` URL.\n\n```bash\nmake e2e                # bring up override stack → demo-restore →\n                        # run pytest → generate gallery → tear down →\n                        # restore normal stack. Trap on EXIT/INT/TERM\n                        # so Ctrl-C still cleans up.\nmake e2e CLEAN=1        # same, but full `down -v` first (drops volumes).\nmake e2e-down           # manual escape-hatch if a previous run left the\n                        # override stack up.\n```\n\nHow it works:\n\n1. `docker-compose.e2e.yml` is layered over the normal compose to pin\n   `BSS_PAYMENT_PROVIDER=mock`, `BSS_KYC_ALLOW_PREBAKED=true`,\n   `BSS_PORTAL_EMAIL_PROVIDER=logging`, `BSS_ESIM_PROVIDER=sim`, plus\n   `BSS_LLM_FIXTURE_PATH` on `portal-csr` for deterministic cockpit\n   LLM responses. Your `.env` is not touched.\n2. `make demo-restore` runs first for clean seed.\n3. Tests run via `pytest` in `packages/bss-e2e/`. Each test fixture\n   wires per-spec Playwright tracing + video recording + named\n   `snap(\"label\")` screenshots. A `pytest_sessionfinish` hook generates\n   the gallery `index.html`.\n4. On exit, the override stack comes down and the normal stack comes\n   back up.\n\nPre-condition: your `.env` should not have a real `sk_live_*` Stripe key —\nthe v0.16 startup template-scan will refuse to boot with `mock` providers if\nit spots one. Use mock or unset for `BSS_PAYMENT_*`, `BSS_PORTAL_KYC_*`, and\n`BSS_PORTAL_EMAIL_*` creds before invoking `make e2e`.\n\nSee `phases/V1_4_0.md` for the suite design and\n`docs/e2e-reports/README.md` for the artefact-format details.\n\n### Multi-step cockpit actions (`BSS_REPL_LLM_AUTONOMY`, v1.5)\n\nThe cockpit's natural-language grammar is an **agentic loop** as of v1.5 — one operator prompt can chain N tool calls (\"show me CUST-001 then list their subscriptions\", \"investigate CASE-042\", \"register CUST-XYZ then create order for PLAN_M\"). One env var controls how chatty the loop is with `/confirm`:\n\n```bash\n# Default — each destructive step propose-then-/confirms separately.\nBSS_REPL_LLM_AUTONOMY=granular\n\n# Opt-in — first destructive in a loop gates; subsequent destructive\n# steps in the same /confirm-resumed loop execute autonomously.\nBSS_REPL_LLM_AUTONOMY=batched\n```\n\nUnknown values fail-closed at boot (`AutonomyMisconfigured`) — same shape as `BSS_API_TOKEN=changeme`. Per-process scope; a per-session slash-override is deferred to v1.5.1. See [HANDBOOK §8.20](docs/HANDBOOK.md#820-multi-step-cockpit-actions-v15) for worked examples + recovery semantics + the 3-strike bail rule + the chrome-filter contract.\n\n## Documentation map\n\n- [`CLAUDE.md`](CLAUDE.md) — project doctrine; read first\n- [`docs/HANDBOOK.md`](docs/HANDBOOK.md) — **v0.20+ single-file operator handbook** consolidating setup, env vars, providers, personas, domain features, and every runbook into one Obsidian-friendly reference. The cockpit knowledge tool indexes this\n- [`ARCHITECTURE.md`](ARCHITECTURE.md) — topology, call patterns, deployability matrix, AWS deployment path\n- [`DATA_MODEL.md`](DATA_MODEL.md) — schemas + tables + relationships\n- [`TOOL_SURFACE.md`](TOOL_SURFACE.md) — every LLM tool with arg shape and return shape\n- [`DECISIONS.md`](DECISIONS.md) — non-obvious architectural choices, append-only\n- [`CONTRIBUTING.md`](CONTRIBUTING.md) — phase discipline, DECISIONS pattern, test conventions\n- [`ROADMAP.md`](ROADMAP.md) — shipped + post-v1.0 + non-goals\n- [`phases/`](phases/) — per-release build plans (PHASE_01 → PHASE_10, V0_2_0 → V0_20_0, V1_1_0 → V1_5_0)\n- [`docs/runbooks/`](docs/runbooks/) — operational procedures (knowledge-indexer + pgvector prereq, Jaeger BYOI, API token rotation, snapshot regen, MNP port flows, Stripe cutover, payment idempotency, chat ownership trip, chat caps, chat-escalated case triage, chat transcript retention, three-provider sandbox soak, promo codes, v1.2 pipeline deploy)\n\n## Tracing with `bss trace`\n\nEvery service exports OpenTelemetry traces to Jaeger. Read them three ways:\n\n```bash\nbss trace for-order ORD-0014           # ASCII swimlane in the terminal — see screenshot\nbss trace for-subscription SUB-0007\nbss trace get 4a8f9e2c0123…            # by trace id\n\nopen http://localhost:16686/            # all-in-one — Jaeger UI\nopen http://tech-vm:16686/              # BYOI; see docs/runbooks/jaeger-byoi.md\n```\n\nFor BYOI installs, run a single-container Jaeger on a separate host and point `BSS_OTEL_EXPORTER_OTLP_ENDPOINT` at it. The full ASCII swimlane is taller than the cropped README screenshot — run the command in your terminal to see every span.\n\n## Hero scenarios\n\nLiving regression suite under `scenarios/*.yaml`. **17 scenarios tagged `hero`** as of v0.18 — every release adds one for the headline feature. Each scenario starts with `admin.reset_operational_data` + `clock.freeze_at` for determinism.\n\n```bash\nmake scenarios                                 # all 17 (~95s wall clock)\nbss scenario list scenarios                    # inventory\nbss scenario validate scenarios/*.yaml         # parse-check\nbss scenario run scenarios/\u003cname\u003e.yaml         # single run\nbss scenario run-all scenarios --tag hero      # tag-filtered\n```\n\nLLM-driven scenarios (the few that ask the agent to reason rather than dispatch deterministically) should pass three runs in a row before tagging — model variance is real and the gate exists to catch flakes.\n\n### v0.12 14-day soak\n\n`scenarios/soak/run_soak.py` provisions N synthetic customers and runs them in parallel for D simulated days under an accelerated frozen clock. Each customer fires events probabilistically (10% dashboard / 5% chat / 1% escalation / 0.5% top-up / 0.1% cross-customer probe). Soak gates: zero ownership-check trips, zero cross-customer leaks, chat-usage drift ≤ 5%, p99 chat latency under 5s (alarm at 15s), bounded transcript-table growth.\n\n```bash\n# Smoke (validates wiring; ~1 min wall clock)\nuv run python -m scenarios.soak.run_soak --customers 2 --days 1\n\n# Substantive run (default report path: soak/report-v0.12.md)\nuv run python -m scenarios.soak.run_soak --customers 30 --days 14\n```\n\nThe v0.12 baseline run is checked in at [`soak/report-v0.12.md`](soak/report-v0.12.md). A soak re-run on v1.0 was the gate that anchored the GA tag.\n\n## License\n\nApache-2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchiam-ck%2Fbss-cli","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchiam-ck%2Fbss-cli","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchiam-ck%2Fbss-cli/lists"}