graphrefly 0.12.0__tar.gz → 0.13.0__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.
- {graphrefly-0.12.0 → graphrefly-0.13.0}/CHANGELOG.md +8 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/PKG-INFO +1 -1
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/optimizations.md +2 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/roadmap.md +2 -2
- {graphrefly-0.12.0 → graphrefly-0.13.0}/pyproject.toml +1 -1
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/__init__.py +1 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/node.py +18 -6
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reduction.py +79 -29
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_reduction.py +98 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.claude/skills/dev-dispatch/SKILL.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.claude/skills/parity/SKILL.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.claude/skills/qa/SKILL.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.gemini/skills/dev-dispatch/SKILL.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.gemini/skills/parity/SKILL.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.github/workflows/pages.yml +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.github/workflows/release.yml +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.gitignore +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/.mise.toml +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/CLAUDE.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/CONTRIBUTING.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/GEMINI.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/LICENSE +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/README.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/DESIGN-ARCHIVE-INDEX.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-access-control-actor-guard.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-cross-repo-implementation-audit.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-demo-test-strategy.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-graphrefly-spec-design.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-serialization-memory-footprint.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-tier2-parity-nonlocal-forward-inner.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-universal-reduction-layer.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/benchmarks/py-baseline.json +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/ADAPTER-CONTRACT.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/benchmark.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/docs-guidance.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/docs/test-guidance.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/examples/README.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/examples/basic_counter.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/llms.txt +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/compat/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/compat/async_utils.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/compat/asyncio_runner.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/compat/trio_runner.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/bridge.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/cancellation.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/clock.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/dynamic_node.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/guard.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/meta.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/protocol.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/runner.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/subgraph_locks.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/sugar.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/timer.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/core/versioning.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/adapters.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/backoff.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/backpressure.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/cascading_cache.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/checkpoint.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/composite.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/cron.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/data_structures.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/resilience.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/sources.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/tier1.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/extra/tier2.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/graph/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/graph/graph.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/integrations/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/integrations/django.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/integrations/fastapi.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/ai.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/cqrs.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/domain_templates.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/graphspec.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/memory.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/messaging.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/orchestration.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reactive_layout/__init__.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reactive_layout/measurement_adapters.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reactive_layout/reactive_block_layout.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reactive_layout/reactive_layout.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/py.typed +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/bench_core.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/conftest.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_adapter_contract.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_adapters_ingest.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_adapters_storage.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_backpressure.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_bridge.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_cascading_cache.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_concurrency.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_core.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_django.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_domain_templates.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_dynamic_node.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_edge_cases.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_composite.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_data_structures.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_resilience.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_sources.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_sources_http.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_tier1.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_extra_tier2.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_fastapi.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_graph.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_graphspec.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_guard.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_measurement_adapters.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_ai.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_cqrs.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_memory.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_messaging.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_orchestration.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_patterns_reduction.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_perf_smoke.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_protocol.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_reactive_block_layout.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_reactive_layout.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_regressions.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_runner.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_smoke.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_sugar.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/tests/test_versioning.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/.gitignore +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/README.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/astro.config.mjs +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/content.config.ts +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/package.json +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/pnpm-lock.yaml +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/public/llms.txt +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/py-api-sidebar.mjs +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/scripts/gen_api_docs.py +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/scripts/sync-docs.mjs +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/GraphreflyHero.astro +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/Header.astro +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/MobileMenuFooter.astro +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/PyodidePlayground.tsx +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/Sidebar.astro +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/components/SiteTitle.astro +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/BackoffPreset.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/BackoffStrategy.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/CheckpointAdapter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/CircuitBreaker.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/CircuitOpenError.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/CompactEntry.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/DeferWhen.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/DictCheckpointAdapter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/DistillBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/EmitStrategy.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/Extraction.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/FileCheckpointAdapter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/HttpBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/JitterMode.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/MemoryCheckpointAdapter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/Message.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/MessageType.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/Messages.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/NodeActions.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/NodeFn.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/NodeImpl.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/NodeStatus.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/PipeOperator.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/PubSubHub.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/ReactiveIndexBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/ReactiveListBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/ReactiveLogBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/ReactiveMapBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/SqliteCheckpointAdapter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/SubscribeHints.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/TimeoutError.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/TokenBucket.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/VerifiableBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/Versioned.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/WithBreakerBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/WithStatusBundle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/audit.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/batch.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/buffer.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/buffer_count.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/buffer_time.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/cache.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/cached.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/checkpoint_node_value.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/circuit_breaker.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/combine.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/concat.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/concat_map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/constant.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/debounce.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/decorrelated_jitter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/delay.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/derived.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/dispatch_messages.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/distill.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/distinct_until_changed.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/effect.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/element_at.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/emit_with_batch.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/empty.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/exhaust_map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/exponential.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/fallback.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/fibonacci.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/filter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/find.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/first.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/first_value_from.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/flat_map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/for_each.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_any.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_async_iter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_awaitable.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_cron.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_event_emitter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_fs_watch.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_git_hook.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_http.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_iter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_mcp.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_timer.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_webhook.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/from_websocket.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/gate.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/index.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/interval.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/is_batching.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/is_phase2_message.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/is_terminal_message.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/last.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/linear.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/log_slice.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/merge.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/message_tier.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/never.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/node.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/of.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/operator.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/pairwise.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/partition_for_batch.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/pausable.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/pipe.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/producer.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/propagates_to_meta.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/pubsub.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/race.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/rate_limiter.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/reactive_index.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/reactive_list.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/reactive_log.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/reactive_map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/reduce.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/repeat.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/replay.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/rescue.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/resolve_backoff_preset.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/restore_graph_checkpoint.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/retry.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/sample.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/save_graph_checkpoint.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/scan.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/share.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/skip.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/start_with.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/state.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/subscribe.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/switch_map.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/take.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/take_until.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/take_while.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/tap.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/throttle.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/throw_error.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/timeout.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/timeout_node.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/to_array.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/to_list.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/to_sse.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/to_websocket.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/token_bucket.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/token_tracker.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/verifiable.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/window.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/window_count.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/window_time.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/with_breaker.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/with_latest_from.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/with_max_attempts.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/with_status.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/api/zip.md +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/index.mdx +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content/docs/lab/python.mdx +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/content.config.ts +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/env.d.ts +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/src/styles/custom.css +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/theme-prototypes.html +0 -0
- {graphrefly-0.12.0 → graphrefly-0.13.0}/website/tsconfig.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphrefly
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: Reactive harness layer for agent workflows. Describe automations in plain language, trace every decision, enforce policies, persist checkpoints. Zero dependencies.
|
|
5
5
|
Project-URL: Homepage, https://py.graphrefly.dev
|
|
6
6
|
Project-URL: Repository, https://github.com/graphrefly/graphrefly-py
|
|
@@ -155,6 +155,8 @@ Union-find over node identity merges components when nodes list dependencies at
|
|
|
155
155
|
- **Feedback effect perpetually dirty status (Phase 8.1/8.2, noted 2026-04-06 QA — documented, accepted):** The `__feedback_effect_<condition>` node forwards DIRTY via default dispatch but consumes DATA. The effect stays in "dirty" status between emissions. **By design** — the status is technically correct. Effects don't need to settle. These nodes now carry `meta._internal: True` and can be filtered from `describe()` output when undesired.
|
|
156
156
|
- **Reduction primitives cross-language parity (Phase 8.1, noted 2026-04-06, QA 2026-04-06, updated 8.2 2026-04-06):** Both TS and PY implement `stratify`, `funnel`, `feedback`, `budget_gate`/`budgetGate`, and `scorer` in `patterns/reduction`. All five follow the orchestration factory pattern (`_base_meta`, `_register_step`). Key alignment: (1) `stratify` buffers DIRTY until DATA arrives — on classifier miss, emits `[DIRTY, RESOLVED]` to preserve spec §1.3.1 (both). (2) `funnel` now uses `bridge()` from `core/bridge` for inter-stage wiring — graph-visible effect nodes (`__bridge_<from>→<stage>_input`) replace the old subscribe-based bridges. Visible in `describe()`, participates in two-phase push. (3) `feedback` counter node is source of truth (resettable via `graph.set()`); feedback wiring now uses a graph-registered effect node (`__feedback_effect_<condition>`) instead of raw subscribe. Counter name is `__feedback_<condition>` to support multiple loops per graph. (4) `budget_gate`/`budgetGate` force-flushes all buffered items on terminal regardless of budget; sends RESUME before terminal if paused; forwards constraint ERROR downstream, silences constraint COMPLETE, forwards unknown constraint types via default. (5) `scorer` coerces `None`/`undefined` to 0 before multiplication (no TypeError/NaN divergence). TS `ScoredItem` is a plain object; PY `ScoredItem` is a class with `__slots__` and `__eq__`. Meta keys: `reduction: True`, `reduction_type: "<name>"`. Both repos: 22 tests each.
|
|
157
157
|
|
|
158
|
+
- **`stratify` two-dep gating (Phase 8.1/9.0, noted roadmap, resolved 2026-04-06, QA 2026-04-06):** `stratify` `on_message` previously intercepted only source messages (dep 0) and let rules messages (dep 1) fall through to default bitmask handling. When both source and rules updated in the same `batch()`, source DATA was classified against stale rules (rules DATA hadn't drained yet), and a spurious second emission occurred from default settlement of dep 1. Fix: `on_message` now intercepts **all** messages from both deps, implementing manual two-dep gating (same diamond-resolution pattern as `budget_gate`). Classification deferred until all dirty deps settle. Rules-only changes produce no downstream emission ("future items only"). QA hardening: (1) PAUSE/RESUME/INVALIDATE from rules dep swallowed (internal impl detail, not leaked downstream). (2) `classify` exceptions caught and treated as non-match (branch not killed). (3) `complete_when_deps_complete=False` explicit (branch lifecycle driven by source terminal, not auto-complete from both deps). Both TS and PY.
|
|
159
|
+
|
|
158
160
|
- **Cleanup wrapper `{"cleanup": fn, "value"?: v}` (all phases, noted 2026-04-06, resolved 2026-04-06):** Node fn can now return `{"cleanup": callable, "value": v}` to explicitly separate cleanup from data. When returned: `cleanup` is registered; if `"value"` key is present, it's emitted as data. Plain callable returns remain cleanup for backward compat. Exported as `CleanupResult<T>` (TS) / documented dict pattern (PY). Resolves the documented limitation where returning a callable as data was silently consumed as cleanup.
|
|
159
161
|
|
|
160
162
|
---
|
|
@@ -26,7 +26,7 @@ These fix protocol violations identified post-8.2 ship. Must land before §9.2 a
|
|
|
26
26
|
|
|
27
27
|
- [x] Rearchitect `feedback()` as graph-visible bridge node (8.2 → 9.0) — replaces subscribe-based shortcut; enables proper DIRTY→DATA two-phase on reentry/counter; resolves bare-DATA protocol gap
|
|
28
28
|
- [x] Rearchitect `funnel()` bridges as graph-visible nodes (8.2 → 9.0) — replaces subscribe forwarding; resolves §5.9 imperative trigger violation + teardown leak
|
|
29
|
-
- [
|
|
29
|
+
- [x] `stratify` two-dep gating (8.2 → 9.0) — gate classification on both source and rules settling (eliminates stale-rules race)
|
|
30
30
|
|
|
31
31
|
### Wave 2 (Python): Audit & accountability parity (Weeks 4-9)
|
|
32
32
|
|
|
@@ -555,7 +555,7 @@ Pre-wired graphs for common "info → action" domains. Users fork/extend.
|
|
|
555
555
|
- [x] `data_quality_graph(opts)` → Graph — DB/API ingest → schema validation → anomaly detection → drift alerting → remediation suggestions
|
|
556
556
|
- [x] Rearchitect `feedback()` as graph-visible bridge node (replaces subscribe-based shortcut; enables proper DIRTY→DATA two-phase on reentry/counter; resolves bare-DATA protocol gap)
|
|
557
557
|
- [x] Rearchitect `funnel()` bridges as graph-visible nodes (replaces subscribe forwarding; resolves §5.9 imperative trigger violation + teardown leak)
|
|
558
|
-
- [
|
|
558
|
+
- [x] `stratify` two-dep gating: gate classification on both source and rules settling (eliminates stale-rules race when both updated in same `batch()`)
|
|
559
559
|
|
|
560
560
|
### 8.3 — LLM graph composition
|
|
561
561
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "graphrefly"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.13.0"
|
|
4
4
|
description = "Reactive harness layer for agent workflows. Describe automations in plain language, trace every decision, enforce policies, persist checkpoints. Zero dependencies."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -106,13 +106,25 @@ def _status_after_message(status: NodeStatus, msg: Message) -> NodeStatus:
|
|
|
106
106
|
type Message = tuple[Any, Any] | tuple[Any]
|
|
107
107
|
|
|
108
108
|
|
|
109
|
+
_CLEANUP_RESULT = "__graphrefly_cleanup_result__"
|
|
110
|
+
"""Branded key that marks a :func:`cleanup_result` wrapper — prevents duck-type
|
|
111
|
+
collisions with domain dicts that happen to have a ``cleanup`` key."""
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def cleanup_result(cleanup: Callable[[], None], value: Any = _SENTINEL) -> dict[str, Any]:
|
|
115
|
+
"""Create a branded cleanup-result wrapper.
|
|
116
|
+
|
|
117
|
+
>>> node([dep], lambda vals, _: cleanup_result(release, computed))
|
|
118
|
+
"""
|
|
119
|
+
r: dict[str, Any] = {_CLEANUP_RESULT: True, "cleanup": cleanup}
|
|
120
|
+
if value is not _SENTINEL:
|
|
121
|
+
r["value"] = value
|
|
122
|
+
return r
|
|
123
|
+
|
|
124
|
+
|
|
109
125
|
def _is_cleanup_result(value: object) -> bool:
|
|
110
|
-
"""Check for
|
|
111
|
-
return (
|
|
112
|
-
isinstance(value, dict)
|
|
113
|
-
and "cleanup" in value
|
|
114
|
-
and callable(value["cleanup"])
|
|
115
|
-
)
|
|
126
|
+
"""Check for branded cleanup wrapper created by :func:`cleanup_result`."""
|
|
127
|
+
return isinstance(value, dict) and value.get(_CLEANUP_RESULT) is True
|
|
116
128
|
|
|
117
129
|
|
|
118
130
|
def _is_cleanup_fn(value: object) -> bool:
|
|
@@ -151,55 +151,105 @@ def _add_branch(
|
|
|
151
151
|
) -> None:
|
|
152
152
|
"""Add a stratify branch to the graph.
|
|
153
153
|
|
|
154
|
+
Two-dep gating: intercepts messages from **both** source (dep 0) and rules
|
|
155
|
+
(dep 1). Classification is deferred until all dirty deps have settled,
|
|
156
|
+
eliminating the stale-rules race when both are updated in the same
|
|
157
|
+
``batch()``.
|
|
158
|
+
|
|
154
159
|
Protocol: DIRTY is buffered until DATA arrives. If the classifier matches,
|
|
155
160
|
emit [DIRTY, DATA]. If not, emit [DIRTY, RESOLVED] so downstream exits
|
|
156
161
|
dirty status cleanly (spec §1.3.1). Source RESOLVED forwards as RESOLVED.
|
|
162
|
+
Rules-only changes produce no downstream emission ("future items only").
|
|
157
163
|
"""
|
|
158
164
|
branch_name = f"branch/{rule.name}"
|
|
159
|
-
|
|
165
|
+
_no_value = object()
|
|
166
|
+
|
|
167
|
+
# Per-branch two-dep gating state
|
|
168
|
+
_source_dirty = [False]
|
|
169
|
+
_rules_dirty = [False]
|
|
170
|
+
_source_phase2 = [False] # source delivered DATA or RESOLVED this cycle
|
|
171
|
+
_source_value: list[Any] = [_no_value] # DATA payload, or _no_value for RESOLVED
|
|
172
|
+
_pending_dirty = [False] # owe downstream a DIRTY
|
|
173
|
+
|
|
174
|
+
def _resolve(actions: Any) -> None:
|
|
175
|
+
if _source_phase2[0]:
|
|
176
|
+
_source_phase2[0] = False
|
|
177
|
+
value = _source_value[0]
|
|
178
|
+
_source_value[0] = _no_value
|
|
179
|
+
if value is not _no_value:
|
|
180
|
+
# Source emitted DATA — classify with settled rules
|
|
181
|
+
current_rules: list[StratifyRule] = rules_node.get() or []
|
|
182
|
+
current_rule = next((r for r in current_rules if r.name == rule.name), None)
|
|
183
|
+
try:
|
|
184
|
+
matches = current_rule is not None and current_rule.classify(value)
|
|
185
|
+
except Exception:
|
|
186
|
+
matches = False
|
|
187
|
+
if matches:
|
|
188
|
+
_pending_dirty[0] = False
|
|
189
|
+
actions.emit(value)
|
|
190
|
+
else:
|
|
191
|
+
if _pending_dirty[0]:
|
|
192
|
+
_pending_dirty[0] = False
|
|
193
|
+
actions.down([(MessageType.DIRTY,), (MessageType.RESOLVED,)])
|
|
194
|
+
else:
|
|
195
|
+
# Source RESOLVED (unchanged)
|
|
196
|
+
if _pending_dirty[0]:
|
|
197
|
+
_pending_dirty[0] = False
|
|
198
|
+
actions.down([(MessageType.DIRTY,), (MessageType.RESOLVED,)])
|
|
199
|
+
else:
|
|
200
|
+
actions.down([(MessageType.RESOLVED,)])
|
|
201
|
+
# else: rules-only change — no reclassification ("future items only")
|
|
160
202
|
|
|
161
203
|
def on_message(msg: Any, dep_index: int, actions: Any) -> bool:
|
|
162
|
-
# Only intercept source messages (dep 0)
|
|
163
|
-
if dep_index != 0:
|
|
164
|
-
return False
|
|
165
|
-
|
|
166
204
|
t = msg[0]
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
current_rules: list[StratifyRule] = rules_node.get() or []
|
|
170
|
-
current_rule = next((r for r in current_rules if r.name == rule.name), None)
|
|
171
|
-
if current_rule is not None and current_rule.classify(value):
|
|
172
|
-
# Match: emit (actions.emit handles DIRTY+DATA wrapping)
|
|
173
|
-
pending_dirty[0] = False
|
|
174
|
-
actions.emit(value)
|
|
175
|
-
else:
|
|
176
|
-
# No match: emit DIRTY + RESOLVED so downstream exits dirty
|
|
177
|
-
if pending_dirty[0]:
|
|
178
|
-
pending_dirty[0] = False
|
|
179
|
-
actions.down([(MessageType.DIRTY,), (MessageType.RESOLVED,)])
|
|
180
|
-
return True
|
|
205
|
+
|
|
206
|
+
# --- DIRTY (phase 1) ---
|
|
181
207
|
if t is MessageType.DIRTY:
|
|
182
|
-
|
|
208
|
+
if dep_index == 0:
|
|
209
|
+
_source_dirty[0] = True
|
|
210
|
+
_pending_dirty[0] = True
|
|
211
|
+
else:
|
|
212
|
+
_rules_dirty[0] = True
|
|
183
213
|
return True
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
|
|
215
|
+
# --- Phase 2 (DATA / RESOLVED) ---
|
|
216
|
+
if t is MessageType.DATA or t is MessageType.RESOLVED:
|
|
217
|
+
if dep_index == 0:
|
|
218
|
+
_source_dirty[0] = False
|
|
219
|
+
_source_phase2[0] = True
|
|
220
|
+
_source_value[0] = msg[1] if t is MessageType.DATA else _no_value
|
|
189
221
|
else:
|
|
190
|
-
|
|
222
|
+
_rules_dirty[0] = False
|
|
223
|
+
|
|
224
|
+
# Wait for all dirty deps to settle
|
|
225
|
+
if _source_dirty[0] or _rules_dirty[0]:
|
|
226
|
+
return True
|
|
227
|
+
|
|
228
|
+
_resolve(actions)
|
|
191
229
|
return True
|
|
230
|
+
|
|
231
|
+
# --- Terminal ---
|
|
192
232
|
if t in (MessageType.COMPLETE, MessageType.ERROR, MessageType.TEARDOWN):
|
|
193
|
-
|
|
194
|
-
|
|
233
|
+
_source_dirty[0] = False
|
|
234
|
+
_rules_dirty[0] = False
|
|
235
|
+
_source_phase2[0] = False
|
|
236
|
+
_source_value[0] = _no_value
|
|
237
|
+
_pending_dirty[0] = False
|
|
238
|
+
if dep_index == 0:
|
|
239
|
+
actions.down([msg])
|
|
240
|
+
# Rules terminal: swallow (branch stays alive)
|
|
195
241
|
return True
|
|
196
|
-
|
|
242
|
+
|
|
243
|
+
# Swallow PAUSE/RESUME/INVALIDATE from rules dep (internal impl detail);
|
|
244
|
+
# forward unknown source messages via default handling.
|
|
245
|
+
return dep_index == 1
|
|
197
246
|
|
|
198
247
|
filter_node = node(
|
|
199
248
|
[source, rules_node],
|
|
200
249
|
lambda _d, _a: None,
|
|
201
250
|
on_message=on_message,
|
|
202
251
|
describe_kind="operator",
|
|
252
|
+
complete_when_deps_complete=False,
|
|
203
253
|
meta=_base_meta("stratify_branch", {"branch": rule.name}),
|
|
204
254
|
)
|
|
205
255
|
|
|
@@ -112,6 +112,104 @@ class TestStratify:
|
|
|
112
112
|
source.down([(MessageType.COMPLETE,)])
|
|
113
113
|
assert completed[0] is True
|
|
114
114
|
|
|
115
|
+
def test_two_dep_gating_batch_source_and_rules(self) -> None:
|
|
116
|
+
"""Stale-rules race: both source and rules update in same batch."""
|
|
117
|
+
source = state("x")
|
|
118
|
+
rules = [StratifyRule("match", classify=lambda v: v == "a")]
|
|
119
|
+
|
|
120
|
+
g = stratify("gate", source, rules)
|
|
121
|
+
|
|
122
|
+
seen: list[str] = []
|
|
123
|
+
|
|
124
|
+
def _sink(msgs: list) -> None:
|
|
125
|
+
for msg in msgs:
|
|
126
|
+
if msg[0] is MessageType.DATA:
|
|
127
|
+
seen.append(msg[1])
|
|
128
|
+
|
|
129
|
+
g.resolve("branch/match").subscribe(_sink)
|
|
130
|
+
|
|
131
|
+
# Batch: update rules to match "b" AND send source "b" simultaneously.
|
|
132
|
+
# Without two-dep gating, classification would use old rules (match "a")
|
|
133
|
+
# and "b" would be dropped. With gating, both settle first.
|
|
134
|
+
from graphrefly.core.protocol import batch
|
|
135
|
+
|
|
136
|
+
with batch():
|
|
137
|
+
g.set("rules", [StratifyRule("match", classify=lambda v: v == "b")])
|
|
138
|
+
source.down([(MessageType.DATA, "b")])
|
|
139
|
+
|
|
140
|
+
assert seen == ["b"]
|
|
141
|
+
|
|
142
|
+
def test_source_only_update_still_works(self) -> None:
|
|
143
|
+
"""Regression: source-only updates must still classify correctly."""
|
|
144
|
+
source = state(0)
|
|
145
|
+
rules = [StratifyRule("pos", classify=lambda v: v > 0)]
|
|
146
|
+
|
|
147
|
+
g = stratify("src-only", source, rules)
|
|
148
|
+
seen: list[int] = []
|
|
149
|
+
|
|
150
|
+
def _sink(msgs: list) -> None:
|
|
151
|
+
for msg in msgs:
|
|
152
|
+
if msg[0] is MessageType.DATA:
|
|
153
|
+
seen.append(msg[1])
|
|
154
|
+
|
|
155
|
+
g.resolve("branch/pos").subscribe(_sink)
|
|
156
|
+
|
|
157
|
+
source.down([(MessageType.DATA, 5)])
|
|
158
|
+
source.down([(MessageType.DATA, -1)])
|
|
159
|
+
source.down([(MessageType.DATA, 3)])
|
|
160
|
+
assert seen == [5, 3]
|
|
161
|
+
|
|
162
|
+
def test_rules_only_update_no_downstream(self) -> None:
|
|
163
|
+
"""Rules-only change should not trigger reclassification."""
|
|
164
|
+
source = state("a")
|
|
165
|
+
rules = [StratifyRule("match", classify=lambda v: v == "a")]
|
|
166
|
+
|
|
167
|
+
g = stratify("rules-only", source, rules)
|
|
168
|
+
|
|
169
|
+
emissions: list[object] = []
|
|
170
|
+
|
|
171
|
+
def _sink(msgs: list) -> None:
|
|
172
|
+
for msg in msgs:
|
|
173
|
+
emissions.append(msg)
|
|
174
|
+
|
|
175
|
+
g.resolve("branch/match").subscribe(_sink)
|
|
176
|
+
|
|
177
|
+
# Initial source DATA
|
|
178
|
+
source.down([(MessageType.DATA, "a")])
|
|
179
|
+
initial_count = len(emissions)
|
|
180
|
+
|
|
181
|
+
# Change rules only — should NOT produce any new downstream messages
|
|
182
|
+
g.set("rules", [StratifyRule("match", classify=lambda v: v == "b")])
|
|
183
|
+
assert len(emissions) == initial_count
|
|
184
|
+
|
|
185
|
+
def test_both_settle_source_resolved(self) -> None:
|
|
186
|
+
"""Source DIRTY→RESOLVED + rules DATA in same batch → RESOLVED downstream."""
|
|
187
|
+
source = state("x")
|
|
188
|
+
rules = [StratifyRule("match", classify=lambda v: v == "x")]
|
|
189
|
+
|
|
190
|
+
g = stratify("resolved", source, rules)
|
|
191
|
+
|
|
192
|
+
data_seen: list[object] = []
|
|
193
|
+
resolved_count = [0]
|
|
194
|
+
|
|
195
|
+
def _sink(msgs: list) -> None:
|
|
196
|
+
for msg in msgs:
|
|
197
|
+
if msg[0] is MessageType.DATA:
|
|
198
|
+
data_seen.append(msg[1])
|
|
199
|
+
elif msg[0] is MessageType.RESOLVED:
|
|
200
|
+
resolved_count[0] += 1
|
|
201
|
+
|
|
202
|
+
g.resolve("branch/match").subscribe(_sink)
|
|
203
|
+
|
|
204
|
+
# Initial emission
|
|
205
|
+
source.down([(MessageType.DATA, "x")])
|
|
206
|
+
assert data_seen == ["x"]
|
|
207
|
+
|
|
208
|
+
# Now update rules in isolation — source not involved
|
|
209
|
+
g.set("rules", [StratifyRule("match", classify=lambda v: v == "y")])
|
|
210
|
+
# No new data should appear
|
|
211
|
+
assert data_seen == ["x"]
|
|
212
|
+
|
|
115
213
|
|
|
116
214
|
# ---------------------------------------------------------------------------
|
|
117
215
|
# funnel
|
|
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
|
{graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-cross-repo-implementation-audit.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-serialization-memory-footprint.md
RENAMED
|
File without changes
|
{graphrefly-0.12.0 → graphrefly-0.13.0}/archive/docs/SESSION-tier2-parity-nonlocal-forward-inner.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
|
{graphrefly-0.12.0 → graphrefly-0.13.0}/src/graphrefly/patterns/reactive_layout/reactive_layout.py
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
|