activegraph 1.0.3__tar.gz → 1.0.5__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.
- {activegraph-1.0.3 → activegraph-1.0.5}/PKG-INFO +9 -1
- {activegraph-1.0.3 → activegraph-1.0.5}/README.md +7 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/__init__.py +1 -1
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/graph.py +27 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/PKG-INFO +9 -1
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/SOURCES.txt +1 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/requires.txt +1 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/pyproject.toml +7 -1
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_doc_links.py +20 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_graph.py +98 -0
- activegraph-1.0.5/tests/test_llms_txt.py +184 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_requeue_unfired.py +64 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/__main__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/behaviors/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/behaviors/base.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/behaviors/decorators.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/cli/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/cli/main.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/cli/quickstart.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/clock.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/event.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/ids.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/patch.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/core/view.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/frame.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/anthropic.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/cache.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/openai.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/parsing.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/prompt.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/provider.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/recorded.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/llm/types.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/logging.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/metrics.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/migration.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/prometheus.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/observability/status.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/behaviors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/fixtures/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/fixtures/companies.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/object_types.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/document_researcher.md +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/memo_synthesizer.md +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/question_generator.md +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/risk_identifier.md +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/settings.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/tools.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/loader.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/scaffold.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/policy.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/_live.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/behavior_graph.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/budget.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/config_errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/diff.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/exec_errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/patterns.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/queue.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/registration_errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/registry.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/runtime.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/scheduler.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/runtime/view_builder.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/base.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/conformance.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/memory.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/postgres.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/serde.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/sqlite.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/store/url.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/base.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/cache.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/context.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/decorators.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/errors.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/graph_query.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/recorded.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/tools/web_fetch.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/trace/__init__.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/trace/causal.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph/trace/printer.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/dependency_links.txt +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/entry_points.txt +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/activegraph.egg-info/top_level.txt +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/setup.cfg +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_activate_after.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_causal_cross_tool.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_cli.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_clock.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_diff.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_diligence_pack.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_diligence_with_tools.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_doc_site_reachable.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_errors_format.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_event.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_fork.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_ids.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_anthropic.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_behavior.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_budget.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_causal.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_claim_extraction.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_default_model.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_determinism.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_failure.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_openai.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_prompt.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_provider_fixtures.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_replay.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_tool_loop.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_trace.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_trace_snapshot.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_llm_types.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_migration.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_observability_logging.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_observability_metrics.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_operate_example.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_pack_scaffold.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_packs.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_patch.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_pattern_matcher.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_pattern_parser.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_pattern_subscriptions.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_persistence.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_postgres_store.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_quickstart.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_quickstart_snapshot.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_replay.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_replay_trace_snapshot.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_resume_example.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_runtime.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_runtime_status.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_serde.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_store_conformance.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_store_url.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_tool_replay.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_tool_trace_snapshot.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_tools.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_trace.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_tutorial_snippets.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_v1_0_1_patches.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_v1_0_3_behavior_failed_ux.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_v1_0_3_tool_multiturn.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_version_sync.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_view.py +0 -0
- {activegraph-1.0.3 → activegraph-1.0.5}/tests/test_wheel_completeness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: activegraph
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: An event-sourced reactive graph runtime for long-running, auditable, agentic systems.
|
|
5
5
|
Author: Active Graph contributors
|
|
6
6
|
License: MIT
|
|
@@ -49,6 +49,7 @@ Provides-Extra: docs
|
|
|
49
49
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
50
50
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
51
51
|
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocs-llmstxt>=0.2; extra == "docs"
|
|
52
53
|
|
|
53
54
|
# Active Graph
|
|
54
55
|
|
|
@@ -219,6 +220,13 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
219
220
|
per-error catalog.
|
|
220
221
|
- **[10-minute tutorial](https://docs.activegraph.ai/quickstart/)** — install
|
|
221
222
|
to a working custom behavior, including fork-and-diff.
|
|
223
|
+
- **AI coding assistants** — the docs are machine-readable at
|
|
224
|
+
[docs.activegraph.ai/llms.txt](https://docs.activegraph.ai/llms.txt)
|
|
225
|
+
(structured index) and
|
|
226
|
+
[docs.activegraph.ai/llms-full.txt](https://docs.activegraph.ai/llms-full.txt)
|
|
227
|
+
(concatenated full content), generated from the same source markdown
|
|
228
|
+
as the rendered site. Built for AI agents evaluating the framework
|
|
229
|
+
via Claude Code, Cursor, Replit, and similar tooling.
|
|
222
230
|
- **[CHANGELOG.md](CHANGELOG.md)** — every release, with per-version
|
|
223
231
|
migration notes.
|
|
224
232
|
- **[CONTRACT.md](CONTRACT.md)** — locked design decisions, version
|
|
@@ -167,6 +167,13 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
167
167
|
per-error catalog.
|
|
168
168
|
- **[10-minute tutorial](https://docs.activegraph.ai/quickstart/)** — install
|
|
169
169
|
to a working custom behavior, including fork-and-diff.
|
|
170
|
+
- **AI coding assistants** — the docs are machine-readable at
|
|
171
|
+
[docs.activegraph.ai/llms.txt](https://docs.activegraph.ai/llms.txt)
|
|
172
|
+
(structured index) and
|
|
173
|
+
[docs.activegraph.ai/llms-full.txt](https://docs.activegraph.ai/llms-full.txt)
|
|
174
|
+
(concatenated full content), generated from the same source markdown
|
|
175
|
+
as the rendered site. Built for AI agents evaluating the framework
|
|
176
|
+
via Claude Code, Cursor, Replit, and similar tooling.
|
|
170
177
|
- **[CHANGELOG.md](CHANGELOG.md)** — every release, with per-version
|
|
171
178
|
migration notes.
|
|
172
179
|
- **[CONTRACT.md](CONTRACT.md)** — locked design decisions, version
|
|
@@ -181,6 +181,33 @@ class Graph:
|
|
|
181
181
|
out.append(r)
|
|
182
182
|
return out
|
|
183
183
|
|
|
184
|
+
def relations(
|
|
185
|
+
self,
|
|
186
|
+
source: Optional[str] = None,
|
|
187
|
+
target: Optional[str] = None,
|
|
188
|
+
type: Optional[str] = None,
|
|
189
|
+
) -> list[Relation]:
|
|
190
|
+
"""Return relations filtered by ``source``, ``target``, and/or ``type``.
|
|
191
|
+
|
|
192
|
+
v1.0.4 #1: the canonical filter API on ``Graph``. Decomposes the
|
|
193
|
+
v0 ``get_relations(object_id=, direction=)`` axis into separate
|
|
194
|
+
``source`` and ``target`` slots so the call reads the way users
|
|
195
|
+
already write it (matches ``docs/concepts/graph.md``). Filter
|
|
196
|
+
kwargs compose by AND; calling with no kwargs returns every
|
|
197
|
+
relation. ``Graph.get_relations(object_id=, type=, direction=)``
|
|
198
|
+
stays as a backward-compatible alias.
|
|
199
|
+
"""
|
|
200
|
+
out: list[Relation] = []
|
|
201
|
+
for r in self._relations.values():
|
|
202
|
+
if source is not None and r.source != source:
|
|
203
|
+
continue
|
|
204
|
+
if target is not None and r.target != target:
|
|
205
|
+
continue
|
|
206
|
+
if type is not None and r.type != type:
|
|
207
|
+
continue
|
|
208
|
+
out.append(r)
|
|
209
|
+
return out
|
|
210
|
+
|
|
184
211
|
def neighborhood(self, object_id: str, depth: int = 1) -> tuple[list[Object], list[Relation]]:
|
|
185
212
|
if object_id not in self._objects:
|
|
186
213
|
return ([], [])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: activegraph
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: An event-sourced reactive graph runtime for long-running, auditable, agentic systems.
|
|
5
5
|
Author: Active Graph contributors
|
|
6
6
|
License: MIT
|
|
@@ -49,6 +49,7 @@ Provides-Extra: docs
|
|
|
49
49
|
Requires-Dist: mkdocs>=1.6; extra == "docs"
|
|
50
50
|
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
51
51
|
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
|
|
52
|
+
Requires-Dist: mkdocs-llmstxt>=0.2; extra == "docs"
|
|
52
53
|
|
|
53
54
|
# Active Graph
|
|
54
55
|
|
|
@@ -219,6 +220,13 @@ in [`docs/concepts/relations.md`](https://docs.activegraph.ai/concepts/relations
|
|
|
219
220
|
per-error catalog.
|
|
220
221
|
- **[10-minute tutorial](https://docs.activegraph.ai/quickstart/)** — install
|
|
221
222
|
to a working custom behavior, including fork-and-diff.
|
|
223
|
+
- **AI coding assistants** — the docs are machine-readable at
|
|
224
|
+
[docs.activegraph.ai/llms.txt](https://docs.activegraph.ai/llms.txt)
|
|
225
|
+
(structured index) and
|
|
226
|
+
[docs.activegraph.ai/llms-full.txt](https://docs.activegraph.ai/llms-full.txt)
|
|
227
|
+
(concatenated full content), generated from the same source markdown
|
|
228
|
+
as the rendered site. Built for AI agents evaluating the framework
|
|
229
|
+
via Claude Code, Cursor, Replit, and similar tooling.
|
|
222
230
|
- **[CHANGELOG.md](CHANGELOG.md)** — every release, with per-version
|
|
223
231
|
migration notes.
|
|
224
232
|
- **[CONTRACT.md](CONTRACT.md)** — locked design decisions, version
|
|
@@ -120,6 +120,7 @@ tests/test_llm_tool_loop.py
|
|
|
120
120
|
tests/test_llm_trace.py
|
|
121
121
|
tests/test_llm_trace_snapshot.py
|
|
122
122
|
tests/test_llm_types.py
|
|
123
|
+
tests/test_llms_txt.py
|
|
123
124
|
tests/test_migration.py
|
|
124
125
|
tests/test_observability_logging.py
|
|
125
126
|
tests/test_observability_metrics.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "activegraph"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.5"
|
|
8
8
|
description = "An event-sourced reactive graph runtime for long-running, auditable, agentic systems."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -65,6 +65,12 @@ docs = [
|
|
|
65
65
|
"mkdocs>=1.6",
|
|
66
66
|
"mkdocs-material>=9.5",
|
|
67
67
|
"mkdocstrings[python]>=0.24",
|
|
68
|
+
# v1.0.5 #1: generates /llms.txt and /llms-full.txt at the site
|
|
69
|
+
# root so AI coding assistants fetching docs.activegraph.ai get
|
|
70
|
+
# clean markdown instead of mkdocs-rendered HTML. Same maintainer
|
|
71
|
+
# as mkdocstrings; native dual-output via the `full_output:` config
|
|
72
|
+
# key. Surfaced by the v1.0.4 external user-test.
|
|
73
|
+
"mkdocs-llmstxt>=0.2",
|
|
68
74
|
]
|
|
69
75
|
|
|
70
76
|
[project.scripts]
|
|
@@ -66,6 +66,22 @@ _DOCS_URL_RE = re.compile(
|
|
|
66
66
|
)
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
# URLs that are generated at build time and do NOT correspond to a
|
|
70
|
+
# `docs/*.md` source file. These bypass the source-presence check
|
|
71
|
+
# because their existence is verified by a dedicated build-time gate
|
|
72
|
+
# rather than by source-tree presence.
|
|
73
|
+
#
|
|
74
|
+
# `/llms.txt` and `/llms-full.txt` (CONTRACT v1.0.5 #1) are emitted by
|
|
75
|
+
# the `mkdocs-llmstxt` plugin during `mkdocs build`; their existence
|
|
76
|
+
# and well-formedness are verified by `tests/test_llms_txt.py`. Any
|
|
77
|
+
# URL added here must have a paired build-time test asserting the
|
|
78
|
+
# file lands at the site root.
|
|
79
|
+
_BUILD_GENERATED_URLS = frozenset({
|
|
80
|
+
"/llms.txt",
|
|
81
|
+
"/llms-full.txt",
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
|
|
69
85
|
def _strip_trailing_punct(s: str) -> str:
|
|
70
86
|
"""URLs in prose often end at sentence boundaries; trim trailing
|
|
71
87
|
punctuation that isn't part of the URL."""
|
|
@@ -181,6 +197,10 @@ def test_every_referenced_docs_url_has_a_source_page() -> None:
|
|
|
181
197
|
missing: dict[Path, list[tuple[str, list[Path]]]] = {}
|
|
182
198
|
unmapped: list[tuple[str, list[Path]]] = []
|
|
183
199
|
for url_path, sources in referenced.items():
|
|
200
|
+
if url_path in _BUILD_GENERATED_URLS:
|
|
201
|
+
# Existence guaranteed by tests/test_llms_txt.py at build
|
|
202
|
+
# time; no docs/*.md source to check.
|
|
203
|
+
continue
|
|
184
204
|
expected = _url_to_source_path(url_path)
|
|
185
205
|
if expected is None:
|
|
186
206
|
unmapped.append((url_path, sources))
|
|
@@ -129,6 +129,104 @@ def test_query_alias_still_works_with_positional_arg():
|
|
|
129
129
|
assert {o.type for o in g.query("claim")} == {"claim"}
|
|
130
130
|
|
|
131
131
|
|
|
132
|
+
# v1.0.4 #1: graph.relations(source=, target=, type=) as the canonical
|
|
133
|
+
# filter API, decomposing the v0 get_relations(object_id=, direction=)
|
|
134
|
+
# axis into separate source/target slots. The contract claim is the
|
|
135
|
+
# eight filter combinations enumerated in CONTRACT v1.0.4 #1; the tests
|
|
136
|
+
# anchor on each combination directly, not on equivalence to the alias.
|
|
137
|
+
# The alias-still-works check is its own test.
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _three_node_graph():
|
|
141
|
+
g = _g()
|
|
142
|
+
a = g.add_object("task", {"k": "a"})
|
|
143
|
+
b = g.add_object("task", {"k": "b"})
|
|
144
|
+
c = g.add_object("task", {"k": "c"})
|
|
145
|
+
rel_ab_dep = g.add_relation(a.id, b.id, "depends_on")
|
|
146
|
+
rel_ac_dep = g.add_relation(a.id, c.id, "depends_on")
|
|
147
|
+
rel_bc_blk = g.add_relation(b.id, c.id, "blocks")
|
|
148
|
+
rel_ba_dep = g.add_relation(b.id, a.id, "depends_on")
|
|
149
|
+
return g, a, b, c, rel_ab_dep, rel_ac_dep, rel_bc_blk, rel_ba_dep
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_relations_no_kwargs_returns_every_relation():
|
|
153
|
+
# Contract row 1: (None, None, None) -> every relation.
|
|
154
|
+
g, _, _, _, *rels = _three_node_graph()
|
|
155
|
+
out = g.relations()
|
|
156
|
+
assert {r.id for r in out} == {r.id for r in rels}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def test_relations_source_only_returns_outgoing_from_source():
|
|
160
|
+
# Contract row 2: (A, None, None) -> relations where source == A.
|
|
161
|
+
g, a, b, c, rel_ab, rel_ac, _, _ = _three_node_graph()
|
|
162
|
+
out = g.relations(source=a.id)
|
|
163
|
+
assert {r.id for r in out} == {rel_ab.id, rel_ac.id}
|
|
164
|
+
assert all(r.source == a.id for r in out)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_relations_target_only_returns_incoming_to_target():
|
|
168
|
+
# Contract row 3: (None, B, None) -> relations where target == B.
|
|
169
|
+
g, a, b, c, rel_ab, _, _, _ = _three_node_graph()
|
|
170
|
+
out = g.relations(target=b.id)
|
|
171
|
+
assert {r.id for r in out} == {rel_ab.id}
|
|
172
|
+
assert all(r.target == b.id for r in out)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def test_relations_source_and_target_returns_intersection():
|
|
176
|
+
# Contract row 4: (A, B, None) -> relations from A to B.
|
|
177
|
+
g, a, b, c, rel_ab, _, _, rel_ba = _three_node_graph()
|
|
178
|
+
out = g.relations(source=a.id, target=b.id)
|
|
179
|
+
assert {r.id for r in out} == {rel_ab.id}
|
|
180
|
+
# Not the reverse direction.
|
|
181
|
+
assert rel_ba.id not in {r.id for r in out}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_relations_type_only_returns_every_relation_of_type():
|
|
185
|
+
# Contract row 5: (None, None, T) -> every relation of type T.
|
|
186
|
+
g, *_, rel_bc_blk, _ = _three_node_graph()
|
|
187
|
+
out = g.relations(type="blocks")
|
|
188
|
+
assert {r.id for r in out} == {rel_bc_blk.id}
|
|
189
|
+
assert all(r.type == "blocks" for r in out)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_relations_source_and_type_returns_outgoing_of_type():
|
|
193
|
+
# Contract row 6: (A, None, T) -> relations from A of type T.
|
|
194
|
+
g, a, _, _, rel_ab, rel_ac, _, _ = _three_node_graph()
|
|
195
|
+
out = g.relations(source=a.id, type="depends_on")
|
|
196
|
+
assert {r.id for r in out} == {rel_ab.id, rel_ac.id}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_relations_target_and_type_returns_incoming_of_type():
|
|
200
|
+
# Contract row 7: (None, B, T) -> relations to B of type T.
|
|
201
|
+
g, a, b, _, rel_ab, _, _, _ = _three_node_graph()
|
|
202
|
+
out = g.relations(target=b.id, type="depends_on")
|
|
203
|
+
assert {r.id for r in out} == {rel_ab.id}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_relations_source_target_and_type_most_specific():
|
|
207
|
+
# Contract row 8: (A, B, T) -> relations from A to B of type T.
|
|
208
|
+
g, a, b, _, rel_ab, _, _, rel_ba = _three_node_graph()
|
|
209
|
+
out = g.relations(source=a.id, target=b.id, type="depends_on")
|
|
210
|
+
assert {r.id for r in out} == {rel_ab.id}
|
|
211
|
+
# Different-direction same-type relation is excluded.
|
|
212
|
+
assert rel_ba.id not in {r.id for r in out}
|
|
213
|
+
# Wrong-type from A->B does not exist; sanity check empty for a wrong type.
|
|
214
|
+
assert g.relations(source=a.id, target=b.id, type="blocks") == []
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_get_relations_alias_still_works():
|
|
218
|
+
# Backward-compat: the v0 shape continues to work byte-identically.
|
|
219
|
+
# This is the alias relationship, not the contract claim — kept as
|
|
220
|
+
# its own test per CONTRACT v1.0.4 #1's Standing Rule §2 carve-out.
|
|
221
|
+
g, a, b, c, rel_ab, rel_ac, _, rel_ba = _three_node_graph()
|
|
222
|
+
out = g.get_relations(object_id=a.id, direction="outgoing")
|
|
223
|
+
assert {r.id for r in out} == {rel_ab.id, rel_ac.id}
|
|
224
|
+
incoming = g.get_relations(object_id=a.id, direction="incoming")
|
|
225
|
+
assert {r.id for r in incoming} == {rel_ba.id}
|
|
226
|
+
both = g.get_relations(object_id=a.id, direction="both")
|
|
227
|
+
assert {r.id for r in both} == {rel_ab.id, rel_ac.id, rel_ba.id}
|
|
228
|
+
|
|
229
|
+
|
|
132
230
|
def test_neighborhood_walks_to_depth():
|
|
133
231
|
g = _g()
|
|
134
232
|
a = g.add_object("task", {})
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""llms.txt / llms-full.txt generation gate. CONTRACT v1.0.5 #1.
|
|
2
|
+
|
|
3
|
+
Builds the mkdocs site into a temp directory and asserts that
|
|
4
|
+
``site/llms.txt`` and ``site/llms-full.txt`` exist after the build,
|
|
5
|
+
are well-formed (H1, blockquote summary, at least one H2 section),
|
|
6
|
+
and reference the nav-anchor pages named in CONTRACT v1.0.5 #1's
|
|
7
|
+
contract claim. The "content coverage" assertion is the boundary
|
|
8
|
+
guard against the failure mode where the build succeeds but emits
|
|
9
|
+
an llms.txt that contains only the H1 — file existence alone is
|
|
10
|
+
not sufficient.
|
|
11
|
+
|
|
12
|
+
Standing Rule §2 shape: the test anchors on the contract boundary
|
|
13
|
+
the v1.0.5 #1 amendment names ("both files exist after build and
|
|
14
|
+
are well-formed"), not on the implementation path of the
|
|
15
|
+
`mkdocs-llmstxt` plugin. If a future release swaps the plugin for
|
|
16
|
+
a custom build script or a different plugin, this test stays
|
|
17
|
+
correct without modification.
|
|
18
|
+
|
|
19
|
+
Marked ``slow`` because ``mkdocs build`` takes several seconds.
|
|
20
|
+
Local ``pytest`` skips it by default; the ``.github/workflows/docs.yml``
|
|
21
|
+
workflow runs it explicitly via ``pytest -m slow tests/test_llms_txt.py``.
|
|
22
|
+
The precedent is ``tests/test_doc_site_reachable.py`` (CONTRACT v1.1
|
|
23
|
+
#9), which uses the same marker for the same reason.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import shutil
|
|
29
|
+
import subprocess
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
|
|
32
|
+
import pytest
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
36
|
+
MKDOCS_CONFIG = REPO_ROOT / "mkdocs.yml"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.fixture(scope="module")
|
|
40
|
+
def built_site(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
|
41
|
+
"""Run ``mkdocs build`` into a temp dir; return the site/ path.
|
|
42
|
+
|
|
43
|
+
Module-scoped: the build is shared across every assertion in
|
|
44
|
+
this file. The fixture skips the entire module if ``mkdocs`` is
|
|
45
|
+
not installed — keeps the gate honest about its hard dependency
|
|
46
|
+
on the docs-build environment without polluting unrelated test
|
|
47
|
+
runs.
|
|
48
|
+
"""
|
|
49
|
+
if shutil.which("mkdocs") is None:
|
|
50
|
+
pytest.skip("mkdocs binary not available; install '.[docs]' to run")
|
|
51
|
+
|
|
52
|
+
site_dir = tmp_path_factory.mktemp("llms_txt_gate") / "site"
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
[
|
|
55
|
+
"mkdocs",
|
|
56
|
+
"build",
|
|
57
|
+
"--config-file",
|
|
58
|
+
str(MKDOCS_CONFIG),
|
|
59
|
+
"--site-dir",
|
|
60
|
+
str(site_dir),
|
|
61
|
+
"--clean",
|
|
62
|
+
],
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
cwd=REPO_ROOT,
|
|
66
|
+
)
|
|
67
|
+
if result.returncode != 0:
|
|
68
|
+
# Surface the actual mkdocs error so a failing gate is
|
|
69
|
+
# diagnosable from the CI log — not just "build failed."
|
|
70
|
+
pytest.fail(
|
|
71
|
+
"mkdocs build failed:\n"
|
|
72
|
+
f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}"
|
|
73
|
+
)
|
|
74
|
+
return site_dir
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.slow
|
|
78
|
+
def test_llms_txt_exists(built_site: Path) -> None:
|
|
79
|
+
"""Contract: ``/llms.txt`` is generated at site root after build."""
|
|
80
|
+
llms = built_site / "llms.txt"
|
|
81
|
+
assert llms.is_file(), (
|
|
82
|
+
f"site/llms.txt missing after mkdocs build (expected at {llms}). "
|
|
83
|
+
"Check the `llmstxt` plugin block in mkdocs.yml."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.slow
|
|
88
|
+
def test_llms_full_txt_exists(built_site: Path) -> None:
|
|
89
|
+
"""Contract: ``/llms-full.txt`` is generated at site root after build."""
|
|
90
|
+
llms_full = built_site / "llms-full.txt"
|
|
91
|
+
assert llms_full.is_file(), (
|
|
92
|
+
f"site/llms-full.txt missing after mkdocs build (expected at {llms_full}). "
|
|
93
|
+
"Check the `full_output:` key in the `llmstxt` plugin block."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.slow
|
|
98
|
+
def test_llms_txt_h1_and_blockquote(built_site: Path) -> None:
|
|
99
|
+
"""Contract: ``llms.txt`` starts with the H1 and a blockquote summary.
|
|
100
|
+
|
|
101
|
+
The plugin derives the H1 from ``site_name`` and the blockquote
|
|
102
|
+
from ``site_description``. The assertion shape catches both the
|
|
103
|
+
plugin-not-running case and the case where ``mkdocs.yml`` loses
|
|
104
|
+
one of those keys (which would break the llms.txt convention
|
|
105
|
+
described at llmstxt.org).
|
|
106
|
+
"""
|
|
107
|
+
content = (built_site / "llms.txt").read_text(encoding="utf-8")
|
|
108
|
+
lines = content.splitlines()
|
|
109
|
+
|
|
110
|
+
assert lines[0] == "# Active Graph", (
|
|
111
|
+
f"llms.txt does not start with the expected H1; got: {lines[0]!r}"
|
|
112
|
+
)
|
|
113
|
+
# The blockquote summary line — first non-empty line after the H1.
|
|
114
|
+
blockquote = next((ln for ln in lines[1:] if ln.strip()), "")
|
|
115
|
+
assert blockquote.startswith("> "), (
|
|
116
|
+
f"llms.txt does not carry a blockquote summary; got: {blockquote!r}"
|
|
117
|
+
)
|
|
118
|
+
assert len(blockquote) > len("> "), (
|
|
119
|
+
"llms.txt blockquote summary is empty after the '> ' marker"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@pytest.mark.slow
|
|
124
|
+
def test_llms_txt_has_section_headers(built_site: Path) -> None:
|
|
125
|
+
"""Contract: ``llms.txt`` carries H2 sections (navigation aid shape)."""
|
|
126
|
+
content = (built_site / "llms.txt").read_text(encoding="utf-8")
|
|
127
|
+
h2_count = sum(1 for ln in content.splitlines() if ln.startswith("## "))
|
|
128
|
+
assert h2_count >= 4, (
|
|
129
|
+
f"llms.txt has {h2_count} H2 section headers; expected at least 4 "
|
|
130
|
+
"(Quickstart / Concepts / Cookbook / Reference at minimum). "
|
|
131
|
+
"A file with the H1 but no sections is not useful as a nav aid."
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@pytest.mark.slow
|
|
136
|
+
def test_llms_txt_references_nav_anchor_pages(built_site: Path) -> None:
|
|
137
|
+
"""Contract: ``llms.txt`` references the nav-anchor pages by name.
|
|
138
|
+
|
|
139
|
+
The v1.0.5 #1 amendment names three anchors for the coverage
|
|
140
|
+
check: ``concepts/graph.md``, ``quickstart.md``, and at least
|
|
141
|
+
one cookbook page. These are the load-bearing entry points for
|
|
142
|
+
a first-time AI reader walking the index; if any one is
|
|
143
|
+
missing, the file fails to deliver on its navigation-aid
|
|
144
|
+
purpose.
|
|
145
|
+
"""
|
|
146
|
+
content = (built_site / "llms.txt").read_text(encoding="utf-8")
|
|
147
|
+
|
|
148
|
+
assert "concepts/graph/" in content, (
|
|
149
|
+
"llms.txt does not reference docs/concepts/graph.md — the "
|
|
150
|
+
"primary entry point for the framework's primitive model."
|
|
151
|
+
)
|
|
152
|
+
assert "quickstart/" in content, (
|
|
153
|
+
"llms.txt does not reference docs/quickstart.md — the "
|
|
154
|
+
"ten-minute tutorial that the README points new users at."
|
|
155
|
+
)
|
|
156
|
+
assert "cookbook/" in content, (
|
|
157
|
+
"llms.txt does not reference any cookbook page — at least "
|
|
158
|
+
"one of common-patterns.md / debugging.md / multi-run-scripts.md "
|
|
159
|
+
"is expected."
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@pytest.mark.slow
|
|
164
|
+
def test_llms_full_txt_well_formed(built_site: Path) -> None:
|
|
165
|
+
"""Contract: ``llms-full.txt`` carries the H1 + actual page bodies.
|
|
166
|
+
|
|
167
|
+
Asserts on a distinctive prose marker from ``docs/quickstart.md``
|
|
168
|
+
so a file that contains the H1 and blockquote but no concatenated
|
|
169
|
+
bodies fails. The marker is the tutorial's opening sentence,
|
|
170
|
+
voice-locked since v1.0-rc1 and unlikely to churn.
|
|
171
|
+
"""
|
|
172
|
+
content = (built_site / "llms-full.txt").read_text(encoding="utf-8")
|
|
173
|
+
|
|
174
|
+
assert content.startswith("# Active Graph"), (
|
|
175
|
+
"llms-full.txt does not start with the expected H1"
|
|
176
|
+
)
|
|
177
|
+
# Distinctive phrase from docs/quickstart.md's first paragraph —
|
|
178
|
+
# if llms-full.txt contains it, the quickstart body made it
|
|
179
|
+
# into the concatenated output.
|
|
180
|
+
assert "Ten minutes from install to a working custom behavior" in content, (
|
|
181
|
+
"llms-full.txt does not contain the quickstart body marker; "
|
|
182
|
+
"the file may be the index-only output instead of the "
|
|
183
|
+
"concatenated full content."
|
|
184
|
+
)
|
|
@@ -128,6 +128,70 @@ class TestRequeueUnfiredDoesNotFalselyRequeue:
|
|
|
128
128
|
except FileNotFoundError:
|
|
129
129
|
pass
|
|
130
130
|
|
|
131
|
+
def test_zero_subscriber_event_ids_are_absent_from_requeue_set_on_load(self):
|
|
132
|
+
"""Boundary-anchored sibling to the queue_depth assertion above.
|
|
133
|
+
|
|
134
|
+
CONTRACT v0.5 #8 names the boundary as `Runtime.load` and the
|
|
135
|
+
subject as the requeue set itself: "events with NO
|
|
136
|
+
`behavior.started` referencing them are re-queued; events that
|
|
137
|
+
any behavior started on are NOT re-queued." A zero-subscriber
|
|
138
|
+
event has no `behavior.started` referencing it but is
|
|
139
|
+
popped-and-discarded by the runtime loop — so the contract
|
|
140
|
+
carve-out is that zero-subscriber events are NOT in the
|
|
141
|
+
requeue set on load.
|
|
142
|
+
|
|
143
|
+
The sibling test above asserts `queue_depth == 0` (the
|
|
144
|
+
implementation's symptom). This test asserts directly on the
|
|
145
|
+
boundary the contract names: it identifies the zero-subscriber
|
|
146
|
+
event IDs from the saved log, reloads, and verifies those IDs
|
|
147
|
+
are absent from `rt._queue` after `_requeue_unfired` ran on
|
|
148
|
+
load. Anchors on what the contract says, not on whether the
|
|
149
|
+
post-load queue depth happens to be zero (CONTRACT v1.0.4 #4,
|
|
150
|
+
Standing Rule §2).
|
|
151
|
+
"""
|
|
152
|
+
_register_subscriber_for_goal_only()
|
|
153
|
+
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
|
|
154
|
+
path = f.name
|
|
155
|
+
os.remove(path)
|
|
156
|
+
try:
|
|
157
|
+
g = Graph()
|
|
158
|
+
rt = Runtime(g, persist_to=path)
|
|
159
|
+
rt.run_goal("test_goal")
|
|
160
|
+
rt.save_state()
|
|
161
|
+
|
|
162
|
+
zero_subscriber_event_ids = {
|
|
163
|
+
e.id for e in rt.graph.events
|
|
164
|
+
if e.type == "downstream.no_subscribers"
|
|
165
|
+
}
|
|
166
|
+
assert zero_subscriber_event_ids, (
|
|
167
|
+
"Fixture is supposed to produce at least one "
|
|
168
|
+
"downstream.no_subscribers event. Without it, the "
|
|
169
|
+
"contract claim's specific subject (the zero-"
|
|
170
|
+
"subscriber carve-out) isn't being exercised."
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
_register_subscriber_for_goal_only()
|
|
174
|
+
rt2 = Runtime.load(path, run_id=rt.run_id)
|
|
175
|
+
|
|
176
|
+
requeued_event_ids = {e.id for e in rt2._queue._q} # noqa: SLF001
|
|
177
|
+
falsely_requeued = zero_subscriber_event_ids & requeued_event_ids
|
|
178
|
+
assert not falsely_requeued, (
|
|
179
|
+
f"CONTRACT v0.5 #8 carve-out: zero-subscriber events are "
|
|
180
|
+
f"popped-and-discarded with no behavior.started emitted, "
|
|
181
|
+
f"so on Runtime.load they must NOT appear in the requeue "
|
|
182
|
+
f"set. The following zero-subscriber event IDs were "
|
|
183
|
+
f"falsely requeued: {sorted(falsely_requeued)}. This is "
|
|
184
|
+
f"the v0.5 #8 boundary the contract names; failure here "
|
|
185
|
+
f"means the requeue set itself violates the contract "
|
|
186
|
+
f"regardless of what queue_depth summarizes."
|
|
187
|
+
)
|
|
188
|
+
finally:
|
|
189
|
+
for suffix in ("", "-wal", "-shm"):
|
|
190
|
+
try:
|
|
191
|
+
os.remove(path + suffix)
|
|
192
|
+
except FileNotFoundError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
131
195
|
|
|
132
196
|
class TestRequeueUnfiredPreservesCrashRecovery:
|
|
133
197
|
"""The fix narrows the requeue scope to events after the last drain.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/document_researcher.md
RENAMED
|
File without changes
|
{activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/memo_synthesizer.md
RENAMED
|
File without changes
|
{activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/question_generator.md
RENAMED
|
File without changes
|
{activegraph-1.0.3 → activegraph-1.0.5}/activegraph/packs/diligence/prompts/risk_identifier.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|