arctx 0.2.0b2__tar.gz
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.
- arctx-0.2.0b2/.gitignore +30 -0
- arctx-0.2.0b2/PKG-INFO +48 -0
- arctx-0.2.0b2/README.md +21 -0
- arctx-0.2.0b2/pyproject.toml +64 -0
- arctx-0.2.0b2/src/arctx/__init__.py +48 -0
- arctx-0.2.0b2/src/arctx/core/__init__.py +43 -0
- arctx-0.2.0b2/src/arctx/core/_json.py +28 -0
- arctx-0.2.0b2/src/arctx/core/append.py +46 -0
- arctx-0.2.0b2/src/arctx/core/cuts.py +61 -0
- arctx-0.2.0b2/src/arctx/core/graph_view.py +26 -0
- arctx-0.2.0b2/src/arctx/core/ids.py +32 -0
- arctx-0.2.0b2/src/arctx/core/run/__init__.py +5 -0
- arctx-0.2.0b2/src/arctx/core/run/anchor.py +40 -0
- arctx-0.2.0b2/src/arctx/core/run/attach.py +49 -0
- arctx-0.2.0b2/src/arctx/core/run/cut.py +50 -0
- arctx-0.2.0b2/src/arctx/core/run/dump.py +174 -0
- arctx-0.2.0b2/src/arctx/core/run/handle.py +163 -0
- arctx-0.2.0b2/src/arctx/core/run/outcomes.py +26 -0
- arctx-0.2.0b2/src/arctx/core/run/trace.py +63 -0
- arctx-0.2.0b2/src/arctx/core/run/transition.py +89 -0
- arctx-0.2.0b2/src/arctx/core/run/view.py +34 -0
- arctx-0.2.0b2/src/arctx/core/run_graph.py +241 -0
- arctx-0.2.0b2/src/arctx/core/schema/__init__.py +28 -0
- arctx-0.2.0b2/src/arctx/core/schema/graph.py +40 -0
- arctx-0.2.0b2/src/arctx/core/schema/payloads.py +288 -0
- arctx-0.2.0b2/src/arctx/core/schema/requirements.py +22 -0
- arctx-0.2.0b2/src/arctx/core/schema/snapshots.py +21 -0
- arctx-0.2.0b2/src/arctx/core/schema/work.py +79 -0
- arctx-0.2.0b2/src/arctx/core/schema/work_helpers.py +297 -0
- arctx-0.2.0b2/src/arctx/core/sync/__init__.py +18 -0
- arctx-0.2.0b2/src/arctx/core/sync/local.py +405 -0
- arctx-0.2.0b2/src/arctx/core/sync/records.py +88 -0
- arctx-0.2.0b2/src/arctx/core/sync/shared_store.py +65 -0
- arctx-0.2.0b2/src/arctx/core/types.py +30 -0
- arctx-0.2.0b2/src/arctx/ext/__init__.py +83 -0
- arctx-0.2.0b2/src/arctx/ext/base.py +108 -0
- arctx-0.2.0b2/src/arctx/ext/command/__init__.py +49 -0
- arctx-0.2.0b2/src/arctx/ext/command/payloads.py +77 -0
- arctx-0.2.0b2/src/arctx/ext/command/verbs/__init__.py +1 -0
- arctx-0.2.0b2/src/arctx/ext/command/verbs/run.py +135 -0
- arctx-0.2.0b2/src/arctx/ext/enabled.py +73 -0
- arctx-0.2.0b2/src/arctx/ext/git/__init__.py +158 -0
- arctx-0.2.0b2/src/arctx/ext/git/events.py +132 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/__init__.py +1 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/attach.py +93 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/finish.py +353 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/repo.py +299 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/session.py +180 -0
- arctx-0.2.0b2/src/arctx/ext/git/helpers/start.py +120 -0
- arctx-0.2.0b2/src/arctx/ext/git/payloads.py +288 -0
- arctx-0.2.0b2/src/arctx/ext/git/queries.py +44 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/__init__.py +1 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/_forward_transition.py +244 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/cherry_pick.py +168 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/commit.py +92 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/merge.py +178 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/reset.py +172 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/revert.py +205 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/rewrite.py +105 -0
- arctx-0.2.0b2/src/arctx/ext/git/verbs/verify.py +169 -0
- arctx-0.2.0b2/src/arctx/paths.py +149 -0
- arctx-0.2.0b2/src/arctx/payload_builder.py +153 -0
- arctx-0.2.0b2/src/arctx/session/__init__.py +157 -0
- arctx-0.2.0b2/src/arctx/storage/__init__.py +14 -0
- arctx-0.2.0b2/src/arctx/storage/_cache.py +103 -0
- arctx-0.2.0b2/src/arctx/storage/base.py +42 -0
- arctx-0.2.0b2/src/arctx/storage/jsonl.py +385 -0
- arctx-0.2.0b2/src/arctx/storage/sqlite.py +355 -0
- arctx-0.2.0b2/tests/__init__.py +1 -0
- arctx-0.2.0b2/tests/core/__init__.py +0 -0
- arctx-0.2.0b2/tests/core/run/__init__.py +0 -0
- arctx-0.2.0b2/tests/core/run/test_adopt_rewrite.py +236 -0
- arctx-0.2.0b2/tests/core/run/test_cherry_pick.py +199 -0
- arctx-0.2.0b2/tests/core/run/test_command_extension.py +42 -0
- arctx-0.2.0b2/tests/core/run/test_commit.py +201 -0
- arctx-0.2.0b2/tests/core/run/test_merge.py +315 -0
- arctx-0.2.0b2/tests/core/run/test_parallel_session_guard.py +377 -0
- arctx-0.2.0b2/tests/core/run/test_reset.py +422 -0
- arctx-0.2.0b2/tests/core/run/test_revert.py +268 -0
- arctx-0.2.0b2/tests/core/run/test_verify.py +372 -0
- arctx-0.2.0b2/tests/core/schema/__init__.py +0 -0
- arctx-0.2.0b2/tests/core/schema/test_branch_payload.py +75 -0
- arctx-0.2.0b2/tests/core/schema/test_cherry_pick_payload.py +104 -0
- arctx-0.2.0b2/tests/core/schema/test_command_payload.py +28 -0
- arctx-0.2.0b2/tests/core/schema/test_merge_payload.py +140 -0
- arctx-0.2.0b2/tests/core/schema/test_reset_event.py +76 -0
- arctx-0.2.0b2/tests/core/schema/test_revert_payload.py +80 -0
- arctx-0.2.0b2/tests/core/schema/test_work_helpers.py +197 -0
- arctx-0.2.0b2/tests/core/test_ancestors_of.py +89 -0
- arctx-0.2.0b2/tests/core/test_branch_members.py +126 -0
- arctx-0.2.0b2/tests/core/test_current_sha.py +117 -0
- arctx-0.2.0b2/tests/core/test_cuts.py +98 -0
- arctx-0.2.0b2/tests/core/test_dump.py +107 -0
- arctx-0.2.0b2/tests/core/test_run_api.py +190 -0
- arctx-0.2.0b2/tests/core/test_run_graph.py +157 -0
- arctx-0.2.0b2/tests/core/test_schema.py +228 -0
- arctx-0.2.0b2/tests/ext/__init__.py +0 -0
- arctx-0.2.0b2/tests/ext/test_enabled_persist.py +32 -0
- arctx-0.2.0b2/tests/ext/test_extension_base.py +30 -0
- arctx-0.2.0b2/tests/ext/test_registry.py +71 -0
- arctx-0.2.0b2/tests/fixtures/__init__.py +1 -0
- arctx-0.2.0b2/tests/fixtures/dummy_ext.py +43 -0
- arctx-0.2.0b2/tests/storage/test_cache.py +69 -0
- arctx-0.2.0b2/tests/storage/test_jsonl_store.py +175 -0
- arctx-0.2.0b2/tests/storage/test_payload_registry.py +81 -0
- arctx-0.2.0b2/tests/storage/test_sqlite_store.py +137 -0
arctx-0.2.0b2/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.coverage
|
|
9
|
+
htmlcov/
|
|
10
|
+
|
|
11
|
+
# ARCTX generated run/output files
|
|
12
|
+
runs/
|
|
13
|
+
arctx_output/
|
|
14
|
+
.arctx/
|
|
15
|
+
state_round_*.json
|
|
16
|
+
request_*.json
|
|
17
|
+
attempts.jsonl
|
|
18
|
+
decisions.jsonl
|
|
19
|
+
findings.jsonl
|
|
20
|
+
requirements.json
|
|
21
|
+
run.json
|
|
22
|
+
|
|
23
|
+
# Package build artifacts
|
|
24
|
+
dist/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
|
|
27
|
+
# IDE / Agent state directories
|
|
28
|
+
.claude/
|
|
29
|
+
.antigravitycli/
|
|
30
|
+
|
arctx-0.2.0b2/PKG-INFO
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arctx
|
|
3
|
+
Version: 0.2.0b2
|
|
4
|
+
Summary: ARCTX: append-only DAG for thought, work context, and parallel exploration
|
|
5
|
+
Project-URL: Homepage, https://github.com/takumiecd/stag
|
|
6
|
+
Project-URL: Repository, https://github.com/takumiecd/stag
|
|
7
|
+
Author: Takumi Ishida
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: typing-extensions>=4.0.0
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: black>=23.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
24
|
+
Provides-Extra: fast
|
|
25
|
+
Requires-Dist: orjson>=3.9; extra == 'fast'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# arctx
|
|
29
|
+
|
|
30
|
+
Python API for ARCTX (Arc + Context) — records the process of optimization and problem-solving.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install arctx
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import arctx as arctx
|
|
42
|
+
|
|
43
|
+
handle = arctx.init(arctx.Requirement(text="Solve the problem"))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Package layout
|
|
47
|
+
|
|
48
|
+
This package provides the core API, storage, and extension framework. The `arctx` command-line tool is in the separate `arctx-cli` package.
|
arctx-0.2.0b2/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# arctx
|
|
2
|
+
|
|
3
|
+
Python API for ARCTX (Arc + Context) — records the process of optimization and problem-solving.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install arctx
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import arctx as arctx
|
|
15
|
+
|
|
16
|
+
handle = arctx.init(arctx.Requirement(text="Solve the problem"))
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Package layout
|
|
20
|
+
|
|
21
|
+
This package provides the core API, storage, and extension framework. The `arctx` command-line tool is in the separate `arctx-cli` package.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "arctx"
|
|
7
|
+
version = "0.2.0b2"
|
|
8
|
+
description = "ARCTX: append-only DAG for thought, work context, and parallel exploration"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Takumi Ishida"},
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"typing-extensions>=4.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
fast = [
|
|
30
|
+
"orjson>=3.9",
|
|
31
|
+
]
|
|
32
|
+
dev = [
|
|
33
|
+
"pytest>=7.0",
|
|
34
|
+
"pytest-asyncio>=0.21",
|
|
35
|
+
"black>=23.0",
|
|
36
|
+
"ruff>=0.1.0",
|
|
37
|
+
"mypy>=1.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://github.com/takumiecd/stag"
|
|
42
|
+
Repository = "https://github.com/takumiecd/stag"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.build.targets.wheel]
|
|
45
|
+
packages = ["src/arctx"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
pythonpath = ["src"]
|
|
50
|
+
|
|
51
|
+
[tool.black]
|
|
52
|
+
line-length = 100
|
|
53
|
+
target-version = ["py310"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
line-length = 100
|
|
57
|
+
select = ["E", "F", "W", "I", "N", "D", "UP", "B", "C4", "SIM"]
|
|
58
|
+
ignore = ["D100", "D104", "D203", "D213"]
|
|
59
|
+
|
|
60
|
+
[tool.mypy]
|
|
61
|
+
python_version = "3.10"
|
|
62
|
+
warn_return_any = true
|
|
63
|
+
warn_unused_configs = true
|
|
64
|
+
disallow_untyped_defs = true
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""arctx: records the process of optimization and problem-solving."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from arctx.core.graph_view import GraphView
|
|
6
|
+
from arctx.core.run import RunHandle
|
|
7
|
+
from arctx.core.run import init as _core_init
|
|
8
|
+
from arctx.core.run_graph import RunGraph
|
|
9
|
+
from arctx.core.schema import (
|
|
10
|
+
CutPayload,
|
|
11
|
+
Node,
|
|
12
|
+
NodePayload,
|
|
13
|
+
Payload,
|
|
14
|
+
PayloadBase,
|
|
15
|
+
Requirement,
|
|
16
|
+
TraceContext,
|
|
17
|
+
Transition,
|
|
18
|
+
TransitionPayload,
|
|
19
|
+
register_payload_class,
|
|
20
|
+
)
|
|
21
|
+
from arctx.core.types import (
|
|
22
|
+
TargetKind,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__version__ = "0.2.0b2"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def init(requirement: Requirement, *, run_id: str | None = None) -> RunHandle:
|
|
29
|
+
"""Create a core run handle without enabling extensions."""
|
|
30
|
+
return _core_init(requirement, run_id=run_id)
|
|
31
|
+
|
|
32
|
+
__all__ = [
|
|
33
|
+
"CutPayload",
|
|
34
|
+
"GraphView",
|
|
35
|
+
"Node",
|
|
36
|
+
"NodePayload",
|
|
37
|
+
"Payload",
|
|
38
|
+
"PayloadBase",
|
|
39
|
+
"Requirement",
|
|
40
|
+
"RunGraph",
|
|
41
|
+
"RunHandle",
|
|
42
|
+
"TargetKind",
|
|
43
|
+
"TraceContext",
|
|
44
|
+
"Transition",
|
|
45
|
+
"TransitionPayload",
|
|
46
|
+
"init",
|
|
47
|
+
"register_payload_class",
|
|
48
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Core graph model."""
|
|
2
|
+
|
|
3
|
+
from arctx.core.graph_view import GraphView
|
|
4
|
+
from arctx.core.ids import opaque_id, sequential_id, slugify, timestamp_id
|
|
5
|
+
from arctx.core.run import RunHandle, init
|
|
6
|
+
from arctx.core.run_graph import RunGraph
|
|
7
|
+
from arctx.core.schema import (
|
|
8
|
+
CutPayload,
|
|
9
|
+
Node,
|
|
10
|
+
NodePayload,
|
|
11
|
+
Payload,
|
|
12
|
+
PayloadBase,
|
|
13
|
+
Requirement,
|
|
14
|
+
TraceContext,
|
|
15
|
+
Transition,
|
|
16
|
+
TransitionPayload,
|
|
17
|
+
register_payload_class,
|
|
18
|
+
)
|
|
19
|
+
from arctx.core.types import (
|
|
20
|
+
TargetKind,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"CutPayload",
|
|
25
|
+
"GraphView",
|
|
26
|
+
"Node",
|
|
27
|
+
"NodePayload",
|
|
28
|
+
"Payload",
|
|
29
|
+
"PayloadBase",
|
|
30
|
+
"Requirement",
|
|
31
|
+
"RunGraph",
|
|
32
|
+
"RunHandle",
|
|
33
|
+
"TargetKind",
|
|
34
|
+
"TraceContext",
|
|
35
|
+
"Transition",
|
|
36
|
+
"TransitionPayload",
|
|
37
|
+
"init",
|
|
38
|
+
"opaque_id",
|
|
39
|
+
"register_payload_class",
|
|
40
|
+
"sequential_id",
|
|
41
|
+
"slugify",
|
|
42
|
+
"timestamp_id",
|
|
43
|
+
]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Fast JSON helpers with orjson fallback.
|
|
2
|
+
|
|
3
|
+
Used by storage hot paths. CLI output formatting should keep using
|
|
4
|
+
the stdlib `json` module directly for ensure_ascii / indent control.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import orjson as _orjson
|
|
10
|
+
|
|
11
|
+
def loads(s: str | bytes) -> object:
|
|
12
|
+
return _orjson.loads(s)
|
|
13
|
+
|
|
14
|
+
def dumps(obj: object) -> str:
|
|
15
|
+
return _orjson.dumps(obj, option=_orjson.OPT_SORT_KEYS).decode("utf-8")
|
|
16
|
+
|
|
17
|
+
HAVE_ORJSON: bool = True
|
|
18
|
+
|
|
19
|
+
except ImportError:
|
|
20
|
+
import json as _json
|
|
21
|
+
|
|
22
|
+
def loads(s: str | bytes) -> object: # type: ignore[misc]
|
|
23
|
+
return _json.loads(s)
|
|
24
|
+
|
|
25
|
+
def dumps(obj: object) -> str: # type: ignore[misc]
|
|
26
|
+
return _json.dumps(obj, ensure_ascii=False, sort_keys=True)
|
|
27
|
+
|
|
28
|
+
HAVE_ORJSON = False
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Append-only storage batches for concurrent writers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Literal, Union
|
|
7
|
+
|
|
8
|
+
from arctx.core.graph_view import GraphView
|
|
9
|
+
from arctx.core.schema.graph import Node, Transition
|
|
10
|
+
from arctx.core.schema.payloads import PayloadBase
|
|
11
|
+
from arctx.core.schema.work import WorkEvent, WorkSession
|
|
12
|
+
|
|
13
|
+
GraphRecordKind = Literal["node", "transition", "payload", "view"]
|
|
14
|
+
GraphRecord = Union[Node, Transition, PayloadBase, GraphView]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class GraphRecordEnvelope:
|
|
19
|
+
"""A graph record plus the table/category it belongs to."""
|
|
20
|
+
|
|
21
|
+
record_kind: GraphRecordKind
|
|
22
|
+
record_id: str
|
|
23
|
+
record: GraphRecord
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class AppendBatch:
|
|
28
|
+
"""One atomic append unit for a run."""
|
|
29
|
+
|
|
30
|
+
run_id: str
|
|
31
|
+
user_id: str
|
|
32
|
+
work_session_id: str
|
|
33
|
+
records: tuple[GraphRecordEnvelope, ...]
|
|
34
|
+
work_session: WorkSession
|
|
35
|
+
events: tuple[WorkEvent, ...]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class AppendResult:
|
|
40
|
+
"""Result returned after an append batch is committed."""
|
|
41
|
+
|
|
42
|
+
event_id: str
|
|
43
|
+
event_seq: int
|
|
44
|
+
record_ids: tuple[str, ...]
|
|
45
|
+
event_ids: tuple[str, ...] = ()
|
|
46
|
+
event_seqs: tuple[int, ...] = ()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Read-time computation of inactive transitions and nodes."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from arctx.core.run_graph import RunGraph
|
|
6
|
+
from arctx.core.schema.payloads import CutPayload
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _cut_payloads(graph: RunGraph) -> list[CutPayload]:
|
|
10
|
+
return [p for p in graph.payloads.values() if isinstance(p, CutPayload)]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cut_transition_ids(graph: RunGraph) -> set[str]:
|
|
14
|
+
return {p.target_id for p in _cut_payloads(graph) if p.target_kind == "transition"}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cut_node_ids(graph: RunGraph) -> set[str]:
|
|
18
|
+
return {p.target_id for p in _cut_payloads(graph) if p.target_kind == "node"}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _compute_inactive(graph: RunGraph) -> tuple[set[str], set[str]]:
|
|
22
|
+
inactive_transitions: set[str] = set(cut_transition_ids(graph))
|
|
23
|
+
inactive_nodes: set[str] = set(cut_node_ids(graph))
|
|
24
|
+
|
|
25
|
+
frontier_nodes = list(inactive_nodes)
|
|
26
|
+
frontier_transitions = list(inactive_transitions)
|
|
27
|
+
|
|
28
|
+
while frontier_nodes or frontier_transitions:
|
|
29
|
+
while frontier_transitions:
|
|
30
|
+
transition_id = frontier_transitions.pop()
|
|
31
|
+
out = graph.transition_output(transition_id)
|
|
32
|
+
if out and out not in inactive_nodes:
|
|
33
|
+
inactive_nodes.add(out)
|
|
34
|
+
frontier_nodes.append(out)
|
|
35
|
+
|
|
36
|
+
while frontier_nodes:
|
|
37
|
+
node_id = frontier_nodes.pop()
|
|
38
|
+
for transition_id in graph.transitions_from_node(node_id):
|
|
39
|
+
if transition_id not in inactive_transitions:
|
|
40
|
+
inactive_transitions.add(transition_id)
|
|
41
|
+
frontier_transitions.append(transition_id)
|
|
42
|
+
|
|
43
|
+
return inactive_transitions, inactive_nodes
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def inactive_transition_ids(graph: RunGraph) -> set[str]:
|
|
47
|
+
inactive_transitions, _ = _compute_inactive(graph)
|
|
48
|
+
return inactive_transitions
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def inactive_node_ids(graph: RunGraph) -> set[str]:
|
|
52
|
+
_, inactive_nodes = _compute_inactive(graph)
|
|
53
|
+
return inactive_nodes
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def is_active_node(graph: RunGraph, node_id: str) -> bool:
|
|
57
|
+
return node_id not in inactive_node_ids(graph)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def is_inactive_transition(graph: RunGraph, transition_id: str) -> bool:
|
|
61
|
+
return transition_id in inactive_transition_ids(graph)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""GraphView: a named label anchored to a root node in RunGraph.
|
|
2
|
+
|
|
3
|
+
The contents of a view are determined at read time by reachability from
|
|
4
|
+
root_node_id via output transitions. No membership sets are stored.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
from arctx.core.types import JSONValue, to_jsonable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class GraphView:
|
|
16
|
+
"""A named label for a subgraph rooted at a single node."""
|
|
17
|
+
|
|
18
|
+
view_id: str
|
|
19
|
+
name: str
|
|
20
|
+
root_node_id: str
|
|
21
|
+
metadata: dict[str, JSONValue] = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
def to_dict(self) -> dict[str, JSONValue]:
|
|
24
|
+
d = to_jsonable(self)
|
|
25
|
+
assert isinstance(d, dict)
|
|
26
|
+
return d # type: ignore[return-value]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Identifier helpers for run and transition records."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import uuid
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
_SAFE_ID = re.compile(r"[^a-zA-Z0-9_.-]+")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def slugify(value: str, fallback: str = "item") -> str:
|
|
14
|
+
"""Return a stable filesystem-safe slug."""
|
|
15
|
+
slug = _SAFE_ID.sub("_", value.strip()).strip("._-").lower()
|
|
16
|
+
return slug or fallback
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def timestamp_id(prefix: str, now: datetime | None = None) -> str:
|
|
20
|
+
"""Return a timestamp-based id."""
|
|
21
|
+
current = now or datetime.now(timezone.utc)
|
|
22
|
+
return f"{slugify(prefix)}_{current.strftime('%Y%m%d_%H%M%S')}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def sequential_id(prefix: str, index: int, width: int = 4) -> str:
|
|
26
|
+
"""Return ids such as ``state_0001``."""
|
|
27
|
+
return f"{slugify(prefix)}_{index:0{width}d}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def opaque_id(prefix: str) -> str:
|
|
31
|
+
"""Return a collision-resistant opaque id with a readable kind prefix."""
|
|
32
|
+
return f"{slugify(prefix)}_{uuid.uuid4().hex}"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""RunHandle.anchor implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from arctx.core.schema.graph import Node
|
|
6
|
+
from arctx.core.schema.payloads import TransitionPayload
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def anchor_impl(
|
|
10
|
+
self,
|
|
11
|
+
from_node_id: str,
|
|
12
|
+
label: str,
|
|
13
|
+
*,
|
|
14
|
+
metadata: dict | None = None,
|
|
15
|
+
user_id: str | None = None,
|
|
16
|
+
work_session_id: str | None = None,
|
|
17
|
+
) -> Node:
|
|
18
|
+
"""Create a lightweight scope anchor node from an existing node.
|
|
19
|
+
|
|
20
|
+
An anchor is a Transition with type="anchor" and a generated output node.
|
|
21
|
+
The output node can then be used as a shared branching point for experiments.
|
|
22
|
+
"""
|
|
23
|
+
meta = dict(metadata or {})
|
|
24
|
+
meta.setdefault("kind", "anchor")
|
|
25
|
+
meta.setdefault("label", label)
|
|
26
|
+
|
|
27
|
+
payload = TransitionPayload(
|
|
28
|
+
payload_id="pending",
|
|
29
|
+
target_id="pending",
|
|
30
|
+
type="anchor",
|
|
31
|
+
content={"label": label},
|
|
32
|
+
metadata=meta,
|
|
33
|
+
)
|
|
34
|
+
transition = self.transition(
|
|
35
|
+
[from_node_id],
|
|
36
|
+
payload,
|
|
37
|
+
user_id=user_id,
|
|
38
|
+
work_session_id=work_session_id,
|
|
39
|
+
)
|
|
40
|
+
return self.run_graph.nodes[transition.output_node_id]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""RunHandle.attach implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from arctx.core.schema.payloads import PayloadBase
|
|
6
|
+
from arctx.core.run.transition import _clone_payload
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def attach_impl(
|
|
10
|
+
self,
|
|
11
|
+
node_id: str,
|
|
12
|
+
payload: PayloadBase,
|
|
13
|
+
*,
|
|
14
|
+
user_id: str | None = None,
|
|
15
|
+
work_session_id: str | None = None,
|
|
16
|
+
) -> PayloadBase:
|
|
17
|
+
"""Attach a node-targeting payload to a node.
|
|
18
|
+
|
|
19
|
+
*payload* must be a node-targeting payload (target_kind="node").
|
|
20
|
+
Returns the attached payload (with a freshly minted payload_id).
|
|
21
|
+
"""
|
|
22
|
+
if payload.target_kind != "node":
|
|
23
|
+
raise ValueError(
|
|
24
|
+
f"attach() requires a node-targeting payload "
|
|
25
|
+
f"(target_kind='node'), got {payload.target_kind!r}"
|
|
26
|
+
)
|
|
27
|
+
if node_id not in self.run_graph.nodes:
|
|
28
|
+
raise KeyError(f"unknown node_id: {node_id}")
|
|
29
|
+
|
|
30
|
+
cloned = _clone_payload(payload, self._next_id("pl"), node_id)
|
|
31
|
+
self.run_graph.attach_payload(cloned)
|
|
32
|
+
self.record_work_event(
|
|
33
|
+
user_id=user_id,
|
|
34
|
+
work_session_id=work_session_id,
|
|
35
|
+
event_type="payload_attached",
|
|
36
|
+
target_kind="node",
|
|
37
|
+
target_id=node_id,
|
|
38
|
+
created_records=(cloned.payload_id,),
|
|
39
|
+
summary=_node_payload_summary(cloned),
|
|
40
|
+
)
|
|
41
|
+
return cloned
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _node_payload_summary(payload: PayloadBase) -> str | None:
|
|
45
|
+
for attr in ("type", "text"):
|
|
46
|
+
val = getattr(payload, attr, None)
|
|
47
|
+
if isinstance(val, str) and val:
|
|
48
|
+
return val
|
|
49
|
+
return None
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""RunHandle.cut implementation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
from arctx.core.cuts import cut_node_ids, cut_transition_ids
|
|
8
|
+
from arctx.core.schema.payloads import CutPayload
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cut_impl(
|
|
12
|
+
self,
|
|
13
|
+
target_id: str,
|
|
14
|
+
*,
|
|
15
|
+
target_kind: Literal["node", "transition"],
|
|
16
|
+
reason: str | None = None,
|
|
17
|
+
user_id: str | None = None,
|
|
18
|
+
work_session_id: str | None = None,
|
|
19
|
+
) -> CutPayload:
|
|
20
|
+
"""Append a CutPayload to mark a Node or Transition inactive."""
|
|
21
|
+
if target_kind == "node":
|
|
22
|
+
if target_id not in self.run_graph.nodes:
|
|
23
|
+
raise KeyError(f"unknown node_id: {target_id}")
|
|
24
|
+
if target_id in cut_node_ids(self.run_graph):
|
|
25
|
+
raise ValueError(f"node already cut: {target_id}")
|
|
26
|
+
elif target_kind == "transition":
|
|
27
|
+
if target_id not in self.run_graph.transitions:
|
|
28
|
+
raise KeyError(f"unknown transition_id: {target_id}")
|
|
29
|
+
if target_id in cut_transition_ids(self.run_graph):
|
|
30
|
+
raise ValueError(f"transition already cut: {target_id}")
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(f"invalid target_kind: {target_kind!r}")
|
|
33
|
+
|
|
34
|
+
cut = CutPayload(
|
|
35
|
+
payload_id=self._next_id("pl"),
|
|
36
|
+
target_id=target_id,
|
|
37
|
+
target_kind=target_kind,
|
|
38
|
+
reason=reason,
|
|
39
|
+
)
|
|
40
|
+
self.run_graph.attach_payload(cut)
|
|
41
|
+
self.record_work_event(
|
|
42
|
+
user_id=user_id,
|
|
43
|
+
work_session_id=work_session_id,
|
|
44
|
+
event_type="cut_added",
|
|
45
|
+
target_kind=target_kind,
|
|
46
|
+
target_id=target_id,
|
|
47
|
+
created_records=(cut.payload_id,),
|
|
48
|
+
summary=reason,
|
|
49
|
+
)
|
|
50
|
+
return cut
|