shadowbox 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
shadowbox/store.py ADDED
@@ -0,0 +1,85 @@
1
+ """SQLite persistence for models, simulations, and reports (M4a local server).
2
+
3
+ Cloud deploy (M4b) swaps this module for a D1-backed store with the same
4
+ function signatures; the API layer does not change.
5
+ """
6
+
7
+ import json
8
+ import sqlite3
9
+ import uuid
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ _SCHEMA = """
14
+ CREATE TABLE IF NOT EXISTS models (
15
+ id TEXT PRIMARY KEY,
16
+ body TEXT NOT NULL
17
+ );
18
+ CREATE TABLE IF NOT EXISTS simulations (
19
+ id TEXT PRIMARY KEY,
20
+ model_id TEXT NOT NULL REFERENCES models(id),
21
+ scenario TEXT NOT NULL,
22
+ seed INTEGER NOT NULL,
23
+ status TEXT NOT NULL,
24
+ report TEXT NOT NULL
25
+ );
26
+ """
27
+
28
+
29
+ def _connect(path: Path) -> sqlite3.Connection:
30
+ conn = sqlite3.connect(path)
31
+ conn.execute("PRAGMA foreign_keys = ON")
32
+ conn.executescript(_SCHEMA)
33
+ return conn
34
+
35
+
36
+ class Store:
37
+ """File-backed store; one connection per call keeps the API thread-safe."""
38
+
39
+ def __init__(self, path: Path) -> None:
40
+ self._path = path # file created lazily on first use, never on import
41
+
42
+ def _new_id(self) -> str:
43
+ return uuid.uuid4().hex[:16]
44
+
45
+ def save_model(self, body: dict[str, Any]) -> str:
46
+ model_id = self._new_id()
47
+ with _connect(self._path) as conn:
48
+ conn.execute(
49
+ "INSERT INTO models (id, body) VALUES (?, ?)", (model_id, json.dumps(body))
50
+ )
51
+ return model_id
52
+
53
+ def get_model(self, model_id: str) -> dict[str, Any] | None:
54
+ with _connect(self._path) as conn:
55
+ row = conn.execute("SELECT body FROM models WHERE id = ?", (model_id,)).fetchone()
56
+ return json.loads(row[0]) if row else None
57
+
58
+ def save_simulation(
59
+ self, model_id: str, scenario: dict[str, Any], seed: int, report: dict[str, Any]
60
+ ) -> str:
61
+ sim_id = self._new_id()
62
+ with _connect(self._path) as conn:
63
+ conn.execute(
64
+ "INSERT INTO simulations (id, model_id, scenario, seed, status, report)"
65
+ " VALUES (?, ?, ?, ?, 'completed', ?)",
66
+ (sim_id, model_id, json.dumps(scenario), seed, json.dumps(report)),
67
+ )
68
+ return sim_id
69
+
70
+ def get_simulation(self, sim_id: str) -> dict[str, Any] | None:
71
+ with _connect(self._path) as conn:
72
+ row = conn.execute(
73
+ "SELECT model_id, scenario, seed, status, report FROM simulations WHERE id = ?",
74
+ (sim_id,),
75
+ ).fetchone()
76
+ if row is None:
77
+ return None
78
+ return {
79
+ "id": sim_id,
80
+ "model_id": row[0],
81
+ "scenario": json.loads(row[1]),
82
+ "seed": row[2],
83
+ "status": row[3],
84
+ "report": json.loads(row[4]),
85
+ }
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.5
2
+ Name: shadowbox
3
+ Version: 0.3.0
4
+ Summary: Executable architectural model for safe what-if experimentation (M0: contracts + validate)
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: fastapi>=0.115
8
+ Requires-Dist: jsonschema>=4.0
9
+ Requires-Dist: pydantic>=2.0
10
+ Requires-Dist: pyyaml>=6.0
11
+ Requires-Dist: rich>=13.0
12
+ Requires-Dist: typer>=0.9
13
+ Requires-Dist: uvicorn>=0.30
14
+ Description-Content-Type: text/markdown
15
+
16
+ # ShadowBox
17
+
18
+ Executable architectural model for safe what-if experimentation. Model the system. Experiment safely.
19
+
20
+ > Scope: headless CLI (validate, simulate, compare, report, import, init, serve) plus local API, static demo, and React Studio. Simulation output is always labeled with assumptions, confidence, and seed — never presented as production measurement.
21
+
22
+ ## Requirements
23
+
24
+ - Python `>=3.13` (pinned via `.python-version`)
25
+ - `uv` for env and runs (no Docker needed)
26
+
27
+ ## Install
28
+
29
+ With a clone (development):
30
+
31
+ ```powershell
32
+ uv python pin 3.13
33
+ uv venv
34
+ uv sync --group dev
35
+ ```
36
+
37
+ Without a clone (use only):
38
+
39
+ ```powershell
40
+ uvx --from "shadowbox @ git+https://github.com/Pa004/shadowBox.git@v0.2.0" shadowbox init --out demo
41
+ ```
42
+
43
+ ## Run in development
44
+
45
+ ```powershell
46
+ uv run shadowbox init --out demo
47
+ uv run shadowbox import --from demo/docker-compose.yaml --out demo/model2.yaml
48
+ uv run shadowbox validate demo/model.yaml --scenario demo/scenarios/db-failure.yaml
49
+ uv run shadowbox simulate demo/model.yaml --scenario demo/scenarios/db-failure.yaml --seed 42 --out report.json
50
+ uv run shadowbox report report.json --format text
51
+ uv run shadowbox compare --a base.json --b report.json
52
+ ```
53
+
54
+ Exit codes: `0` valid/pass, `2` scenario regression (compare), `3` invalid input or existing files without `--force` (prints `E_*` code).
55
+
56
+ Import notes: every performance field is an estimated default (see warnings). Calibrate before trusting output.
57
+
58
+ Chaos cards ship in the package (`init` writes them to `cards/`): `db-down`, `cache-poison`, `latency-500ms`, `traffic-10x`, `zone-loss`, `slow-dependency`, `queue-overflow`.
59
+
60
+ ## API server (local)
61
+
62
+ ```powershell
63
+ uv run uvicorn shadowbox.api:app --port 8000
64
+ # or: uv run shadowbox serve --port 8000
65
+ ```
66
+
67
+ Endpoints: `POST /api/v1/models`, `GET /api/v1/models/{id}`, `POST /api/v1/simulations?model_id=...`, `GET /api/v1/simulations/{id}[/events|/metrics|/report]`. Events are paginated (`limit` 1..1000, `cursor` offset over the stored 500-request sample). State lives in `shadowbox.db` (git-ignored, created on first use). The API has no authentication: bind to localhost (`serve` defaults to `127.0.0.1`) and never expose it directly to the internet.
68
+
69
+ ## Deploy (Cloudflare free tier, no card)
70
+
71
+ Scaffold ready in `wrangler.jsonc` + `schema.sql` + `apps/api/worker.py` + `apps/web/` (static demo, no build step). Remaining steps need your Cloudflare account:
72
+
73
+ ```powershell
74
+ !npm install -g wrangler
75
+ !wrangler login
76
+ !wrangler d1 create shadowbox # paste database_id into wrangler.jsonc
77
+ !wrangler d1 execute shadowbox --file schema.sql
78
+ !uvx --from workers-py pywrangler dev # local Worker emulation, no account needed
79
+ !wrangler deploy
80
+ ```
81
+
82
+ Production note: the Worker serves the same FastAPI app; swapping the SQLite file store for the D1 binding is a follow-up task verified against a real account (M4b-full). The static demo deploys to Pages as-is and talks to any API base URL.
83
+
84
+ Open `apps/web/index.html` after `Run` to scrub virtual time: the SVG graph colors failed components red and shows active faults per second (first 500 sampled requests).
85
+
86
+ ## Studio (React + Cytoscape)
87
+
88
+ Full UI in `apps/studio/` (Vite, strict TS). Needs Node deps (run yourself):
89
+
90
+ ```powershell
91
+ cd apps/studio
92
+ npm install
93
+ npm run build # tsc plus vite
94
+ npm run dev # /api proxies to 127.0.0.1:8000
95
+ ```
96
+
97
+ Run any scenario vs baseline, inspect p99/error verdict, scrub the failure cascade on the Cytoscape graph.
98
+
99
+ ## Environment variables
100
+
101
+ None required. Server mode reads no env vars yet; Cloudflare D1 bindings arrive with the production Worker swap.
102
+
103
+ ## Project structure
104
+
105
+ ```text
106
+ src/shadowbox/ # model, dsl, cli, errors, engine, metrics, report, cards, api, store
107
+ src/shadowbox/data/ # canonical example + chaos cards (shipped in the wheel)
108
+ schemas/ # model-v1.json, scenario-v1.json
109
+ tests/ # unit, deterministic (golden seed 42), property, integration
110
+ tests/fixtures/ # broken models (E_CYCLE, E_REF)
111
+ apps/web/ # static demo with replay (no build)
112
+ apps/studio/ # React + Cytoscape UI (Vite, strict TS)
113
+ ```
114
+
115
+ ## Run tests
116
+
117
+ ```powershell
118
+ uv run ruff check .
119
+ uv run mypy src
120
+ uv run pytest
121
+ ```
122
+
123
+ Benchmarks (reference: i7-1255U, 16GB, Python 3.13; SLO: 50k events < 2s):
124
+
125
+ - checkout db-failure (6k reqs, 85k events): ~0.05s
126
+ - 60k reqs, 840k events: ~0.6s
127
+
128
+ ## Deploy notes
129
+
130
+ Local-first: CLI and `serve` need nothing but Python. Demo deploy: Cloudflare Pages (web) + Python Worker (FastAPI via `workers.asgi`) + D1 — see `Deploy (Cloudflare...)` above. No paid service, no credit card at any tier. See `ShadowBox.md` (local spec, git-ignored) for the full contract.
@@ -0,0 +1,29 @@
1
+ shadowbox/__init__.py,sha256=o_QtY1_AvIUnqpsVyGITrFt4bdOlGJEo86lcTCcgOuQ,159
2
+ shadowbox/api.py,sha256=g32KlSsMq6pU5uyviHt4gJwAkdbJEtaNbPoKYgm4Mjc,4858
3
+ shadowbox/cards.py,sha256=Ds_T2Og1okx6InFbGumkpzpapVb5uZkrK8KA3fQSycA,2327
4
+ shadowbox/cli.py,sha256=aRw6S0Ypx4peWbDw4Q6FSkPz779Qyic--tZIL6gI5JI,8414
5
+ shadowbox/compare.py,sha256=8dnzO9cqJIIwi3f0-OIxi3Ztq1ZElltz86zi135OmSU,3563
6
+ shadowbox/dsl.py,sha256=nTZSu1StPpudHBdjxDKnylfHPAo8Ym1OxQYvzCt9844,3114
7
+ shadowbox/engine.py,sha256=AJfSJeG9XlzKw26T33B_ohf6ByelXK660U9jRWar1lI,7905
8
+ shadowbox/errors.py,sha256=ZqfRuI2O9RsCJRZQVf9-Q-9XXu4GfY0TaTSk-JqNTfE,707
9
+ shadowbox/metrics.py,sha256=Bb1aAL_kNyjusWfh5mPg_rc6wW89e-SqvUI66D4ti24,1828
10
+ shadowbox/model.py,sha256=eoRNQ_k5hw2MkAxThoBjagQDEwYH217Bew3JCh3Y29o,1926
11
+ shadowbox/report.py,sha256=NU47bqVbDl_FoHcQjVH6VF_X3Ka7YIugPrDtUj8TRJM,1538
12
+ shadowbox/store.py,sha256=zNU6_b9gIPqRrZYKLpBbJLdaNEVIHlEH3wwxDkC-gfY,2684
13
+ shadowbox/data/__init__.py,sha256=jUhnEvdba9MKVpBiy43hbNqZH-H0Vyspnl_23_XjSok,76
14
+ shadowbox/data/cards/cache-poison.yaml,sha256=L7mS51H1Z_V3Jc2J77tJ1LUyyVQYk-LDnaVMOddwXzo,290
15
+ shadowbox/data/cards/db-down.yaml,sha256=ndaEcepXSk4pqnrZSRO5XCd2lAKJv_xVSjON0LTq-HE,267
16
+ shadowbox/data/cards/latency-500ms.yaml,sha256=KRtMmJLin6ztVrLgATIYALtupxcKGu6fMUsO3CWWCr4,356
17
+ shadowbox/data/cards/queue-overflow.yaml,sha256=743eZqtrMjM1n-c9Sd-JyYlZvXh6OxbriCEAybND6wk,212
18
+ shadowbox/data/cards/slow-dependency.yaml,sha256=E2YZDirycQWZabLGXqRv7H1ohWbj_uVes8VX1-D1vDg,323
19
+ shadowbox/data/cards/traffic-10x.yaml,sha256=K2r4w-_u0IIPWZmGVen99TfURUlVwfMuQJr35lxXqY0,214
20
+ shadowbox/data/cards/zone-loss.yaml,sha256=3Sq_f9i_WtPVvfko3Pg7TEkFpmm_HD_spixPyDn6pSo,355
21
+ shadowbox/data/example/docker-compose.yaml,sha256=QtQ2NLVb41So98upHPd8xD7YUYs8TVY7lK-fdtOpBZA,264
22
+ shadowbox/data/example/model.yaml,sha256=Z7rGZMtnc16kXAFBujKVWUbQb4gxhehBLevlbY-sGZg,648
23
+ shadowbox/data/example/scenarios/db-failure.yaml,sha256=h9nXmjMAvG29OPp2W9SkTcx_RQA01IrAAeiE846LX28,270
24
+ shadowbox/importers/__init__.py,sha256=hwFgnRof5uJCK9ea2j-gZqKrv9cHAjliyRQ9qJfwlt0,97
25
+ shadowbox/importers/compose.py,sha256=pjNLtc_oJmjU7nrfiKZHYBsSBzkxGLYnXOU__xCw95k,3834
26
+ shadowbox-0.3.0.dist-info/METADATA,sha256=gLj9g7tIW3m4-6NWjd067uqBG5qdAc7dfeEet6u5h_U,5097
27
+ shadowbox-0.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
28
+ shadowbox-0.3.0.dist-info/entry_points.txt,sha256=VYWQuwhvc9Aek2hm5AdJt8sPDC4OVtBk7ZYHFbL2068,48
29
+ shadowbox-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ shadowbox = shadowbox.cli:app