sequentialist-core 0.1.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.
@@ -0,0 +1,150 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (C) 2026 Novikov Laboratories LLC
3
+ # Commercial licenses for use outside the AGPL: see COMMERCIAL.md
4
+ """Storage in one SQLite database file (the sqlite3 module of the standard library).
5
+
6
+ The state and the clearing of the journal are one transaction. The
7
+ database runs in WAL mode; `sync="fsync"` sets ``synchronous=FULL``
8
+ (survives a power failure), ``"flush"`` sets ``NORMAL`` (survives a crash of
9
+ the process). Writers lock tasks with files next to the database
10
+ (``<database>.locks/<task>.lock``); ``":memory:"`` locks within the process.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import sqlite3
16
+ import threading
17
+ from pathlib import Path
18
+ from typing import Any, Sequence
19
+
20
+ from ..canonical import loads
21
+ from ..errors import ConflictError, StorageError
22
+ from ._lock import FileLock, Lock, MemoryLock
23
+ from .base import Storage, check_document_name, check_task_id
24
+
25
+ FORMAT = "sequentialist.sqlite/1"
26
+
27
+ _SCHEMA = """
28
+ create table if not exists meta (key text primary key, value text not null);
29
+ create table if not exists tasks (task_id text primary key, version integer not null, state text not null);
30
+ create table if not exists journal (task_id text not null, pos integer not null, entry text not null,
31
+ primary key (task_id, pos));
32
+ create table if not exists audit (task_id text not null, n integer not null, entry text not null,
33
+ primary key (task_id, n));
34
+ create table if not exists documents (task_id text not null, name text not null, document text not null,
35
+ primary key (task_id, name));
36
+ """
37
+
38
+
39
+ def _dump(value: Any) -> str:
40
+ return json.dumps(value, sort_keys=True, ensure_ascii=False)
41
+
42
+
43
+ class SQLiteStorage(Storage):
44
+ def __init__(self, path: str | Path, *, sync: str = "fsync"):
45
+ if sync not in ("fsync", "flush"):
46
+ raise ValueError("sync must be 'fsync' or 'flush'")
47
+ self.path = str(path)
48
+ self._memory = self.path == ":memory:"
49
+ self._guard = threading.Lock()
50
+ self._held: set[str] = set()
51
+ self._db = sqlite3.connect(self.path, check_same_thread=False, isolation_level=None)
52
+ with self._guard:
53
+ if not self._memory:
54
+ self._db.execute("pragma journal_mode=wal")
55
+ self._db.execute(f"pragma synchronous={'full' if sync == 'fsync' else 'normal'}")
56
+ self._db.executescript(_SCHEMA)
57
+ row = self._db.execute("select value from meta where key = 'format'").fetchone()
58
+ if row is None:
59
+ self._db.execute("insert into meta values ('format', ?)", (FORMAT,))
60
+ elif row[0] != FORMAT:
61
+ raise StorageError(f"{self.path}: format {row[0]!r}, this version reads {FORMAT!r}")
62
+
63
+ def lock(self, task_id: str) -> Lock:
64
+ check_task_id(task_id)
65
+ if self._memory:
66
+ return MemoryLock(self._held, self._guard, task_id, f"task {task_id!r}")
67
+ locks = Path(self.path + ".locks")
68
+ locks.mkdir(exist_ok=True)
69
+ return FileLock(str(locks / f"{task_id}.lock"), f"task {task_id!r} in {self.path}")
70
+
71
+ def _query(self, sql: str, args: tuple[Any, ...] = ()) -> list[tuple[Any, ...]]:
72
+ with self._guard:
73
+ return self._db.execute(sql, args).fetchall()
74
+
75
+ def tasks(self) -> list[str]:
76
+ return [r[0] for r in self._query("select task_id from tasks order by task_id")]
77
+
78
+ def load(self, task_id: str) -> tuple[int, dict[str, Any]] | None:
79
+ rows = self._query("select version, state from tasks where task_id = ?", (check_task_id(task_id),))
80
+ return None if not rows else (rows[0][0], loads(rows[0][1]))
81
+
82
+ def save(self, task_id: str, state: dict[str, Any], *, expected: int, clear_journal: bool = False) -> int:
83
+ check_task_id(task_id)
84
+ with self._guard:
85
+ db = self._db
86
+ db.execute("begin immediate")
87
+ try:
88
+ row = db.execute("select version from tasks where task_id = ?", (task_id,)).fetchone()
89
+ current = 0 if row is None else row[0]
90
+ if current != expected:
91
+ raise ConflictError(f"task {task_id!r}: stored version {current}, expected {expected}")
92
+ db.execute("insert or replace into tasks values (?, ?, ?)", (task_id, current + 1, _dump(state)))
93
+ if clear_journal:
94
+ db.execute("delete from journal where task_id = ?", (task_id,))
95
+ db.execute("commit")
96
+ except BaseException:
97
+ db.execute("rollback")
98
+ raise
99
+ return current + 1
100
+
101
+ def _append(self, table: str, task_id: str, entries: Sequence[dict[str, Any]]) -> None:
102
+ column = "pos" if table == "journal" else "n"
103
+ with self._guard:
104
+ db = self._db
105
+ db.execute("begin immediate")
106
+ try:
107
+ row = db.execute(f"select max({column}) from {table} where task_id = ?", (task_id,)).fetchone()
108
+ start = 0 if row[0] is None else row[0] + 1
109
+ db.executemany(f"insert into {table} values (?, ?, ?)",
110
+ [(task_id, start + i, _dump(e)) for i, e in enumerate(entries)])
111
+ db.execute("commit")
112
+ except BaseException:
113
+ db.execute("rollback")
114
+ raise
115
+
116
+ def journal_append(self, task_id: str, entries: Sequence[dict[str, Any]]) -> None:
117
+ if entries:
118
+ self._append("journal", check_task_id(task_id), entries)
119
+
120
+ def journal_read(self, task_id: str) -> list[dict[str, Any]]:
121
+ rows = self._query("select entry from journal where task_id = ? order by pos", (check_task_id(task_id),))
122
+ return [loads(r[0]) for r in rows]
123
+
124
+ def audit_append(self, task_id: str, entry: dict[str, Any]) -> None:
125
+ self._append("audit", check_task_id(task_id), [entry])
126
+
127
+ def audit_read(self, task_id: str) -> list[dict[str, Any]]:
128
+ rows = self._query("select entry from audit where task_id = ? order by n", (check_task_id(task_id),))
129
+ return [loads(r[0]) for r in rows]
130
+
131
+ def put_document(self, task_id: str, name: str, document: dict[str, Any]) -> None:
132
+ with self._guard:
133
+ self._db.execute("insert or replace into documents values (?, ?, ?)",
134
+ (check_task_id(task_id), check_document_name(name), _dump(document)))
135
+
136
+ def get_document(self, task_id: str, name: str) -> dict[str, Any] | None:
137
+ rows = self._query("select document from documents where task_id = ? and name = ?",
138
+ (check_task_id(task_id), check_document_name(name)))
139
+ if not rows:
140
+ return None
141
+ found: dict[str, Any] = loads(rows[0][0])
142
+ return found
143
+
144
+ def documents(self, task_id: str) -> list[str]:
145
+ return [r[0] for r in self._query("select name from documents where task_id = ? order by name",
146
+ (check_task_id(task_id),))]
147
+
148
+ def close(self) -> None:
149
+ with self._guard:
150
+ self._db.close()
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (C) 2026 Novikov Laboratories LLC
3
+ # Commercial licenses for use outside the AGPL: see COMMERCIAL.md
4
+ __version__ = "0.1.0"
@@ -0,0 +1,129 @@
1
+ Metadata-Version: 2.4
2
+ Name: sequentialist-core
3
+ Version: 0.1.0
4
+ Summary: Common layer of the sequentialist libraries: specs, a checkpointed engine over sequential-analysis runs, storage, audit log, reports
5
+ Author: Andrej Novikov
6
+ Maintainer-email: Novikov Laboratories LLC <andrei@hobukob.ru>
7
+ License-Expression: AGPL-3.0-only OR LicenseRef-Commercial
8
+ Project-URL: Source, https://github.com/andre-a-no/sequentialist-core
9
+ Project-URL: Issues, https://github.com/andre-a-no/sequentialist-core/issues
10
+ Project-URL: Commercial license, https://github.com/andre-a-no/sequentialist-core/blob/main/COMMERCIAL.md
11
+ Keywords: sequential analysis,anytime-valid inference,audit log,checkpoint,reproducibility
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ License-File: COMMERCIAL.md
26
+ Requires-Dist: sequential-analysis<0.5,>=0.4
27
+ Provides-Extra: yaml
28
+ Requires-Dist: PyYAML>=5.1; extra == "yaml"
29
+ Dynamic: license-file
30
+
31
+ # sequentialist-core
32
+
33
+ The common layer of the sequentialist libraries: applications of sequential
34
+ statistics built on
35
+ [sequential-analysis](https://github.com/andre-a-no/sequential-analysis).
36
+ The statistics live in sequential-analysis; this package turns its
37
+ procedures into applications that run for months, survive crashes and can
38
+ be audited, and every conclusion they report names its guarantee
39
+ (`exact`, `asymptotic`, `model`, `approximate` or `none`), as the procedure
40
+ behind it states it.
41
+
42
+ | Library | Repository | Status |
43
+ |---|---|---|
44
+ | `sequentialist-core` | this one | 0.1.2: specs, the checkpointed engine, storage, audit log, reports, simulations |
45
+ | `sequentialist-ab`, `@sequentialist/ab-client` (npm) | [sequentialist-ab](https://github.com/andre-a-no/sequentialist-ab) | 0.1.0: the engine of an A/B testing platform |
46
+ | `sequentialist-quality` | [sequentialist-quality](https://github.com/andre-a-no/sequentialist-quality) | 0.1.0: sequential acceptance sampling of lots |
47
+ | `sequentialist-monitor` | [sequentialist-monitor](https://github.com/andre-a-no/sequentialist-monitor) | planned: equipment, processes and energy |
48
+ | `sequentialist-markets` | [sequentialist-markets](https://github.com/andre-a-no/sequentialist-markets) | planned: risk, backtests, strategy decay |
49
+ | `sequentialist-clinical` | [sequentialist-clinical](https://github.com/andre-a-no/sequentialist-clinical) | planned: trials, drug safety, quality of care |
50
+ | `sequentialist-release`, `sequentialist-mlops` | | planned: canary releases, models in production |
51
+
52
+ The libraries are installed separately and share the `sequentialist`
53
+ namespace. None is on PyPI yet.
54
+
55
+ ```
56
+ pip install sequentialist-core # Python 3.8+, depends only on sequential-analysis
57
+ pip install "sequentialist-core[yaml]" # specs in YAML as well as JSON
58
+ ```
59
+
60
+ | Module | What it does |
61
+ |---|---|
62
+ | `spec` | Specs with declarative schemas: every error reported at once with its JSON pointer; a normal form (durations in seconds, timestamps in epoch seconds) and a spec id (SHA-256 of its canonical JSON); JSON Schema for other tools |
63
+ | `engine` | Tasks of several streams, each a `Run` of sequential-analysis: idempotent intake by (source, seq), a journal written before processing, checkpoints, replay after a crash, segments (a new run after an alarm or a window), commands of people, one writer per task, shards |
64
+ | `storage` | `MemoryStorage`, `FileStorage` (a directory of JSON files), `SQLiteStorage`; `open_storage("files:...")` |
65
+ | `audit` | The hash-chained log of decisions and actions, with the evidence behind each: procedure, guarantee, step, history digest; `verify_audit` |
66
+ | `report` | Reports in JSON and Markdown; a `Finding` cannot be made without its guarantee |
67
+ | `simulate` | Error rates of a whole pipeline by simulation, with Monte Carlo intervals |
68
+ | `replay` | Recorded data through the live code, on the data's own clock: the same audit log every time |
69
+ | `sinks` | Notifications of decisions: log, JSON lines, webhooks with retries |
70
+ | `privacy` | Pseudonyms for identifiers of people (HMAC-SHA-256 under the data owner's key) |
71
+
72
+ A library implements a `Handler`: it declares its streams and processes
73
+ records, the passing of time and commands. The engine does the rest:
74
+
75
+ ```python
76
+ from sequentialist.core import Engine, Handler, Record, SQLiteStorage, Stream
77
+ from sequential_analysis.procedures import MeanEDetector
78
+
79
+ class Watch(Handler):
80
+ def __init__(self, spec):
81
+ self.spec = spec
82
+
83
+ def streams(self):
84
+ return {"mean": Stream(MeanEDetector(m=self.spec["m"], arl=10000, direction="increase"))}
85
+
86
+ def on_record(self, record, ctx):
87
+ event = ctx.step("mean", {"x": record.data["x"]})
88
+ if event is not None and event.terminal:
89
+ ctx.record("decision", "alarm", basis={"t": event.t}, evidence=["mean"])
90
+ ctx.restart("mean", "alarm")
91
+
92
+ engine = Engine(SQLiteStorage("tasks.db"))
93
+ task = engine.create("line-3", "example.watch/1", {"m": 0.2}, Watch({"m": 0.2}))
94
+ task.ingest([Record("sensor-7", 0, {"x": 0.18}, time=1793491200.0)])
95
+ task.close()
96
+ task = engine.open("line-3", Watch) # restores the checkpoint, replays the journal
97
+ ```
98
+
99
+ `sequentialist tasks STORAGE` lists stored tasks; `sequentialist audit
100
+ STORAGE TASK` verifies and prints a task's audit log. Installed libraries
101
+ add their own subcommands.
102
+
103
+ **Limits of this version.** The whole state of a task is written at every
104
+ checkpoint, so its size (and the cost of a checkpoint) grows with what the
105
+ handler keeps; the A/B handler keeps one entry per unit. Locks protect a
106
+ task within one host; across hosts the storage must be shared and locked
107
+ by other means (Postgres storage is planned).
108
+
109
+ ## What the engine guarantees
110
+
111
+ * **Idempotent intake**: a record is identified by its source and sequence
112
+ number; sending it again changes nothing.
113
+ * **Nothing accepted is lost**: records are journaled before they are
114
+ processed; after a crash the task is restored from its checkpoint and the
115
+ journal is replayed, and the result is the uninterrupted run bit for bit
116
+ (tested on every storage, with crashes at every point that matters).
117
+ * **An audit log of every decision**, chained by SHA-256, with the
118
+ procedure, its guarantee, the step and the history digest of
119
+ sequential-analysis that each decision rests on (`sequentialist audit`
120
+ verifies it).
121
+ * **One writer per task**, enforced by locks and version checks.
122
+
123
+ ## License
124
+
125
+ GNU AGPL v3 or a commercial license ([COMMERCIAL.md](COMMERCIAL.md)).
126
+ Contributions need the [Contributor License Agreement](CLA.md); see
127
+ [CONTRIBUTING.md](CONTRIBUTING.md).
128
+
129
+ Contact: andrei@hobukob.ru, a.hobukob@gmail.com.
@@ -0,0 +1,28 @@
1
+ sequentialist/core/__init__.py,sha256=A3CD2zwo9pMsOv9qtTK9leGhPuMRbHbU-_x0Ao_7OtY,2483
2
+ sequentialist/core/audit.py,sha256=38_KnPApnamLQNowgPPpy9vgvFWbJIXVIFmvpcUFJeo,5931
3
+ sequentialist/core/canonical.py,sha256=8GAf8PeoNKZeMV8vxaPmkN5BMaNccbFwtybz6j7uNho,1601
4
+ sequentialist/core/cli.py,sha256=BKrQE_6uPgGX09gpJ39FPpkrGZPh_N2yAV4IleRjzUU,3758
5
+ sequentialist/core/engine.py,sha256=ApGeS5iEu8q19ia69K-yilYi7PjUBo_P5SjOjnboNE4,27189
6
+ sequentialist/core/errors.py,sha256=lsz2K3hcL7SkUv5thlXdXIZ7UxxSNN-XYWkrrlaKRZ4,1487
7
+ sequentialist/core/privacy.py,sha256=aOI9eMKLzCPlq4YCSjIxVQ9AUmWq1Y-8byFnHuMfOK0,1404
8
+ sequentialist/core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ sequentialist/core/replay.py,sha256=njeREhx2aU9UyFBevlEHO9mKppCp2vyGzFnfKJKD824,1641
10
+ sequentialist/core/report.py,sha256=jKuI7Z9Qbq2ihgbpGy039kQJUSExMG739rmollcGQIk,5318
11
+ sequentialist/core/schema.py,sha256=2kDz8xcI_Ri5yEedgCCFkvJUPLZaz_gvAuRVyH7dgJI,17277
12
+ sequentialist/core/simulate.py,sha256=MMY3EybPbUciB08Hrgm4AQkg5owM9x_Uqnpm_xUl2hA,2243
13
+ sequentialist/core/sinks.py,sha256=Ns9QNf2_ZJTxaKJcCLM9V_rO3hkO4Cn4wEHVeNDtpko,3872
14
+ sequentialist/core/spec.py,sha256=7gHsKzKSMX8vhmWYCeAY_dFpAc7hB2heSqtejFNWd7g,4472
15
+ sequentialist/core/version.py,sha256=CsfruV3uKgWYX83KIO66KnP0vrJNWBI15uYWd-5cGEw,175
16
+ sequentialist/core/storage/__init__.py,sha256=0Jn2OfLg2TVq40jZtD0KUJ63bDvaNJ6Ar7gu-_skuhE,1081
17
+ sequentialist/core/storage/_lock.py,sha256=Yk2Md-DBnLLtIxL2KHTEp2-cMViAkkJnZr__l2EtiV0,2577
18
+ sequentialist/core/storage/base.py,sha256=30BEXjMR9xHmMA919sScyKFQwqYt2FU5ylu3RMVo5hE,4050
19
+ sequentialist/core/storage/files.py,sha256=-mI8zpUqcjxPW27ja_CupRPdFGwiRPGcq_Zk-ma1VCw,7118
20
+ sequentialist/core/storage/memory.py,sha256=NfHr9QFwvkqo-tggmwEShBGDdIrRd478GaQwkonKWQE,2966
21
+ sequentialist/core/storage/sqlite.py,sha256=o0Jz9A4SaoaG3hiXQaGGsxZhYbyCi46SXl5GrKvHjh8,7118
22
+ sequentialist_core-0.1.0.dist-info/licenses/COMMERCIAL.md,sha256=4cW3QcGfbCjxNYZq5bNHdsWCh8wwJjHKWk4dXJNa7O4,5819
23
+ sequentialist_core-0.1.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
24
+ sequentialist_core-0.1.0.dist-info/METADATA,sha256=Y5oz1XC3MeE-0Zm__QbUm10iQydgfbj_sZK-qzX6f4s,7090
25
+ sequentialist_core-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
26
+ sequentialist_core-0.1.0.dist-info/entry_points.txt,sha256=kvVM3CuWVivOcJm37VxT8fATBftSaOxs11x5j46OQDU,178
27
+ sequentialist_core-0.1.0.dist-info/top_level.txt,sha256=xPS1PnoauDjS39ioTd_o3S_P227_qgXDCp-ND0IIaSs,14
28
+ sequentialist_core-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ sequentialist = sequentialist.core.cli:main
3
+
4
+ [sequentialist.commands]
5
+ audit = sequentialist.core.cli:audit_command
6
+ tasks = sequentialist.core.cli:tasks_command
@@ -0,0 +1,89 @@
1
+ # Licensing
2
+
3
+ The sequentialist libraries are dual-licensed: open source under the GNU
4
+ AGPL v3, and under a commercial license for products whose source code
5
+ stays closed. They are built on
6
+ [sequential-analysis](https://github.com/andre-a-no/sequential-analysis),
7
+ which is licensed the same way.
8
+
9
+
10
+ ## Do I need a commercial license?
11
+
12
+ | Your use | License |
13
+ |---|---|
14
+ | Research, teaching, study, personal projects | AGPL v3 — free |
15
+ | An open-source project released under the AGPL v3 (or the GPL v3, which can be combined with it) | AGPL v3 — free |
16
+ | Internal use in an organisation, with no copies given to others and no access over a network for people outside it | AGPL v3 — free |
17
+ | A product or service you distribute or offer over a network, with its complete source code published under the AGPL v3 | AGPL v3 — free |
18
+ | A closed-source product that includes a sequentialist library (application, library, device, firmware) | **commercial license** |
19
+ | A network service (SaaS, API, web application, an experimentation platform) built with a sequentialist library whose source code you do not publish | **commercial license** |
20
+ | Your organisation's policy does not allow AGPL software, or you need warranty, support or indemnification | **commercial license** |
21
+
22
+ Every sequentialist library depends on sequential-analysis. A commercial
23
+ license for a sequentialist library therefore covers sequential-analysis as
24
+ used by it; both are licensed by the same company.
25
+
26
+ ## Open source: GNU AGPL v3
27
+
28
+ The code is available under the [GNU Affero General Public License,
29
+ version 3](LICENSE) (`AGPL-3.0-only`). You may use, study, modify and
30
+ redistribute it free of charge, provided that you comply with the AGPL. In
31
+ short: if you distribute software that includes a sequentialist library, or
32
+ let users interact with it over a network, you must make the complete
33
+ corresponding source code of that software available under the AGPL as
34
+ well. This text is a summary; the license itself decides.
35
+
36
+ ## Commercial license
37
+
38
+ A commercial license releases the licensed product from the obligations of
39
+ the AGPL: you may keep its source code closed. Terms (per product, per
40
+ organisation, with or without support) are agreed individually.
41
+
42
+ Copyright holder and licensor: **Novikov Laboratories LLC**
43
+ (ООО «Лаборатории Новиковых»; OGRN 1231600027548, INN 1685011563).
44
+
45
+ - Email: andrei@hobukob.ru, a.hobukob@gmail.com
46
+
47
+ Please describe your product and how you intend to use the libraries.
48
+
49
+ ## Contributions
50
+
51
+ Contributions are accepted under a [Contributor License Agreement](CLA.md),
52
+ so that the project can continue to be offered under both licenses. See
53
+ [CONTRIBUTING.md](CONTRIBUTING.md).
54
+
55
+ ---
56
+
57
+ # Лицензирование
58
+
59
+ Библиотеки sequentialist распространяются по двойной лицензии: как открытое
60
+ ПО по GNU AGPL v3 и по коммерческой лицензии для продуктов с закрытым
61
+ исходным кодом. Они построены на
62
+ [sequential-analysis](https://github.com/andre-a-no/sequential-analysis),
63
+ которая лицензируется так же.
64
+
65
+
66
+ ## Нужна ли вам коммерческая лицензия?
67
+
68
+ | Как вы используете | Лицензия |
69
+ |---|---|
70
+ | Исследования, преподавание, обучение, личные проекты | AGPL v3 — бесплатно |
71
+ | Открытый проект под AGPL v3 (или GPL v3, совместимой с ней) | AGPL v3 — бесплатно |
72
+ | Внутреннее использование в организации без передачи копий и без сетевого доступа для посторонних | AGPL v3 — бесплатно |
73
+ | Продукт или сервис, который вы распространяете или предоставляете по сети, с полным исходным кодом, открытым по AGPL v3 | AGPL v3 — бесплатно |
74
+ | Закрытый продукт, включающий библиотеку sequentialist (приложение, библиотека, устройство, прошивка) | **коммерческая лицензия** |
75
+ | Сетевой сервис (SaaS, API, веб-приложение, платформа экспериментов) на основе библиотеки sequentialist, исходный код которого вы не публикуете | **коммерческая лицензия** |
76
+ | Политика вашей организации не допускает ПО под AGPL, или вам нужны гарантии, поддержка, защита от претензий | **коммерческая лицензия** |
77
+
78
+ Каждая библиотека sequentialist зависит от sequential-analysis.
79
+ Коммерческая лицензия на библиотеку sequentialist поэтому покрывает и
80
+ sequential-analysis в том объёме, в каком библиотека её использует;
81
+ правообладатель у обеих один.
82
+
83
+ Правообладатель и лицензиар — ООО «Лаборатории Новиковых» (Novikov
84
+ Laboratories LLC; ОГРН 1231600027548, ИНН 1685011563).
85
+ Контакты: andrei@hobukob.ru, a.hobukob@gmail.com. Опишите, пожалуйста, ваш
86
+ продукт и то, как вы собираетесь использовать библиотеки.
87
+
88
+ Вклад в проект принимается по [лицензионному соглашению участника](CLA.md).
89
+ Краткое изложение выше не заменяет текст лицензии AGPL v3.