matrx-graph 0.2.331__tar.gz → 0.2.332__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.
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/PKG-INFO +1 -1
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/scheduler.py +12 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/FEATURE.md +1 -1
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/job_lease.py +46 -2
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/queue.py +59 -1
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/runner.py +13 -2
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/pyproject.toml +1 -1
- matrx_graph-0.2.332/tests/test_a_lost_lease_never_strands_or_lingers.py +191 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/.gitignore +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/AGENTS.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/CLAUDE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/README.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/ENGINE_SPEC.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/GAPS_AND_ROADMAP.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/OPERATING_PRIORITIES.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/README.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/RUN_LIFECYCLE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/WORKFLOW_ARCHITECTURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/action-authoring.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/architecture.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/node-authoring.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/docs/testing.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/actions/ACTION_STANDARDS.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/actions/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/actions/decorator.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/actions/registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/actions/spec.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/candidate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/memory.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/postgres.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/protocol.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/value_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checkpoint/values.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/checks.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/compile.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/composition.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/accessor.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/audit.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/authoring_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/availability.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/converters.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/directives.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/edge_routing.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/envelope.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/fingerprint.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/graph_adapter.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/markers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/model.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/occurrences.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/partial.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/pg_emitter.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/runtime_view.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/sample.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/sdk.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/subscriptions.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/translate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/triggers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/well_known.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/content_ir/wrappers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/contract_kinds.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/contracts/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/contracts/audit.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/_registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/db_requirements.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/definition_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/event_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0020_wf_run_announce.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0021_join_semantics.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0022_node_data_slot.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0023_fanout_identity.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0024_definition_variables.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0100_baseline_post_reorg.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0101_run_request_attribution.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0102_visibility_and_kind_verdicts.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0103_work_item.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0104_soft_delete_columns.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0105_plan.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0106_definition_visibility_internal_default.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0107_plan_anchor_integrity.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0108_plan_persistence_invariants.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0109_node_data_slot_created_by.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0110_run_log.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0111_awaiting_input_status.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0111_event_triggers.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0112_definition_io_kind_declaration.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0113_definition_published_to_web_and_shown_to.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0114_definition_workflow_type.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0115_checkpoint_value.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0116_standalone_checkpoint_organization_and_values.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0117_review_flags.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/README.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0001_wf_initial.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0002_wf_queue.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0003_wf_node_events.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0004_wf_trigger.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0005_wf_rls.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0006_wf_run_cancelling.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0007_wf_run_paused_and_errored.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0008_wf_node_outcome_source.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0009_wf_run_recovery_limit.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0010_wf_idempotency.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0011_wf_definition_concurrency_limit.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0012_wf_trigger_fire.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0013_wf_recovery_audit.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0014_wf_node_events_notify.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0016_wf_template.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0017_wf_hardening.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0018_wf_node_events_seq.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/0019_wf_node_events_drop_check.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/legacy_pre_reorg/README.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/node_data_slot_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/pool.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/run_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/standalone.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/store_change.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/trigger_event_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/trigger_proof.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/trigger_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/wf_manager.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/decision_spec.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/decisions.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/diagnostics/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/diagnostics/compile_validation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/dry_run.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/emptiness.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/errors.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/examples/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/examples/self_validating_news.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/_retry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/channels.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/client_payload.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/execution.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/introspect.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/json_dump.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/primitives.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/resume_chain.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/resume_preflight.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/review_flags.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/run_stop.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/executor/schema_validation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/extract.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/failure.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/input_bindings.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/kind_control_keys.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/kind_fields.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/kinds.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/knobs.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/mapping_sources.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/_sandbox.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/_template.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/content_ir/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/content_ir/validate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/branch.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/gather.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/human_input.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/loop.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/map.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/send.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/work_queue.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/control/work_seed.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/crypto/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/crypto/ops.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/assert_.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/collections.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/criteria_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/data_table.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/filter.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/flatten.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/json_io.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/map_template.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/merge.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/pick_omit.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/read_record.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/tabular.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/data/transform.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/datetime_/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/datetime_/ops.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/flow/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/flow/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/flow/noop.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/flow/sleep.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/flow/throttle.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/_shared.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/custom_api.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/get.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/graphql.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/http/post.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/io/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/io/user_input.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/log/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/log/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/log/record.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/math_/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/math_/evaluate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/output/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/output/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/output/to_frontend.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/pipe/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/pipe/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/pipe/contract.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/pipe/step.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/plan/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/plan/step.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/settings/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/settings/read.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/speech/FEATURE.md +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/speech/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/speech/budget.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/subgraph/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/subgraph/call.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/custom_extract.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/json_path.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/manipulate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/regex.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/text/render_list.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/tool/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/nodes/tool/call.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/observability/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/overview.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/patch.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/paths.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/py.typed +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/records.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/references.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/run_log.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/seats.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/settings.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/shape_probe.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/speech_pace.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/table_kind_mapping.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/table_kinds.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/canonical_payloads.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/channel.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/compiled.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/context.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/definition.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/deliverables.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/events.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/handle.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/identity_inputs.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/input_surface.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/node_spec.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/primitives.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/result.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/review.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/run.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/send.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/usl.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/value_objects.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/types/variables.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/validation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/capability.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/cron_watcher.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/event_watcher.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workers/sleep_wake.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/workset.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/_db_guard.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/_fixtures.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/ci/00_supabase_shim.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/ci/10_canonical_reorg_shim.sql +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/ci/bootstrap_test_db.sh +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/conftest.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/fixtures/kind_control_keys_golden.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/fixtures/kind_registry_variants.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/fixtures/table_kind_golden.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/fixtures/w59_empty_prescriber_output.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/fixtures/watson_rulebook_ids.json +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/judge/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/judge/test_self_validating_news_e2e.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/level2/__init__.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/level2/test_anthropic_provider.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_bound_graph_hash_moves_with_content_only.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_condition_stop_is_said_at_run_level.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_failed_final_write_ends_the_run_failed.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_large_step_output_stays_out_of_the_run_row.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_leg_knows_whether_it_ran_fresh.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_node_fires_when_its_facts_are_confirmed.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_started_leg_keeps_the_queues_guarantees.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_step_that_was_answered_is_not_called_skipped.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_a_terminal_run_keeps_settled_job_markers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_action_output_handles.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_actions.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_adversarial_engine_core.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_adversarial_kind_routing.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_adversarial_partial_wire.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_adversarial_registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_an_optional_step_failure_completes_the_run.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_authoring_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_back_edges.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_barrier_removed.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_branch_passthrough_keeps_its_own_kind.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_candidate_node_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_capability_aware_claim.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_channel.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_channels_reducers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_checkpoint_postgres_org_write.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_checkpoint_rows_stay_small.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_checkpoint_summaries.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_checkpoint_write_budget.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_client_bound_error_redaction.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_config_output_handles_normalized.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_audit.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_converters.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_envelope.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_markers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_model.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_partial.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_pg_emitter.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_runtime_view.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_sample.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_stamped_marker_equality.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_content_ir_translate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_context_propagation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_contract_artifact_eviction.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_contract_audit.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_contract_kinds.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_criteria_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_data_collections.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_data_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_db_guard.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_decision_attribution.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_decision_default_and_edge_key_guards.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition_store_archive_filter.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition_store_list_skip.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition_store_publish_is_a_change.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition_store_save_draft_author.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_definition_store_version_load.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_deliverables_rule.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_dry_run.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_dry_run_source_input.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_dynamic_topology_guards.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_edge_field_selection.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_empty_deliverable_is_named.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_empty_fanout.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_end_to_end.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_event_payloads_stay_small.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_event_store_error_redaction.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_event_triggers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_every_dispatcher_is_seen_by_lineage.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_every_terminal_status_stamps_completed_at.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_extract.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_failure.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_failure_is_not_the_users_words.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_flow_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_gather_ai_projection.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_gather_channel_item_indexes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_http_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_http_nodes_refuse_internal_addresses.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_answer_kind_declaration.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_gate_decision.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_gate_seat.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_input_bare_context.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_input_pause_round_trip.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_human_input_presentation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_identity_input_broadcast.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_inert_predicate_guard.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_ingestion_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_input_gate_failure_is_routable.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_input_surface.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_input_surface_variant_registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_invocation_budget.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_io_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_json_dump.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_accessor.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_availability.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_control_keys.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_enforcement.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_fields.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_native_node_library.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_routing.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kind_sdk.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_kinds.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_knobs.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_list_include_ids.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_list_shaping_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_live_injection.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_log_record_node.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_loop_scale.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_map_payload_template.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_mapping_source_sigils.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_multi_dispatch_fanin.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_cost_event.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_data_slot_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_data_slot_validation.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_emitter_settle.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_error_kind.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_input_capture.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_result_envelope.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_node_spec.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_outage_path_is_not_an_agent_choice.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_outcome_inputs_by_reference.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_outcome_outputs_by_reference.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_output_channel_binding.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_output_kind_stamping.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_overview_collapse.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_patch.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_pause_cancel_and_recovery.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_pipe_step.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_plan_identity_consistency.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_plan_step.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_postgres_checkpointer.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_postgres_workset_store.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_predicate_preview_per_item.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_primitives.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_public_media_scope.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_raised_error_details_reach_outcome.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_read_record_node.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_references.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_registry.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_required_inputs_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_response_checks.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_resume_payload_folding.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_resume_preflight_soundness.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_review_flags_raised_by_the_engine.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_run_conversation_identity.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_run_store_control.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_run_variables.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_conditional_edge.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_config_override.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_events.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_failsoft.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_failure_cost_settlement.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_fanout_identity.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_fork_to_new_run.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_interrupt_siblings.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_join_hardening.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_joins.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_loop_contract.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_mock_response.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_node_retry_override.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_node_timeout.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_ready_nodes_do_not_wait.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_retry_policy.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_skip_edge_ids.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_step_mode.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_scheduler_step_wall_clock.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_screen_value.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_self_validating_news.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_send_unwired_items_is_loud.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_settings_read_step.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_site_blocked_is_not_not_authorized.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_speech_budget.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_standalone_db_models.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_store_event_source.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_subgraph_call.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_table_kind_round_trip.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_template_path_shape_gate.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_text_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_tool_loop_stall_is_a_cause.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_transform_output_shape.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_triggers_and_observability.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_user_input_filled_from_data.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_utility_nodes.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_validation_expression_sandbox.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_validation_fanned_edge_needs_items.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_validation_hints.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_validation_s4.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_values_held_on_server.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_w0_agent_build_engine_proofs.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_workers.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_workset.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_workset_chaos.py +0 -0
- {matrx_graph-0.2.331 → matrx_graph-0.2.332}/tests/test_zip_by_position.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: matrx-graph
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.332
|
|
4
4
|
Summary: Channel-based, super-step workflow orchestration engine for the Matrx ecosystem
|
|
5
5
|
Project-URL: Homepage, https://github.com/AI-Matrix-Engine/aidream-current
|
|
6
6
|
Project-URL: Repository, https://github.com/AI-Matrix-Engine/aidream-current
|
|
@@ -2181,6 +2181,11 @@ class Scheduler:
|
|
|
2181
2181
|
try:
|
|
2182
2182
|
while open_members:
|
|
2183
2183
|
task, result, result_step = await done_queue.get()
|
|
2184
|
+
if task.cancelled() and _job_lease().lost():
|
|
2185
|
+
# A member stopped because this copy lost its job lease (``JobLeaseLost`` is a cancellation):
|
|
2186
|
+
# the whole copy stops here, never routing or checkpointing on (lane K1b, B2).
|
|
2187
|
+
lease = _job_lease().current()
|
|
2188
|
+
raise _job_lease().JobLeaseLost(f"job lease lost: {lease.why if lease else ''}")
|
|
2184
2189
|
if id(result) in member_order and result_step == step:
|
|
2185
2190
|
# Kept in frontier order by insertion — O(log n) search per finished member, never a re-sort.
|
|
2186
2191
|
bisect.insort(finished_members, result, key=lambda r: member_order[id(r)])
|
|
@@ -6490,3 +6495,10 @@ def _settle_default_on_decision_gate(
|
|
|
6490
6495
|
logger.info("decision gate settled by its default %r → %s", raw, shaped["answer"])
|
|
6491
6496
|
value["answer"] = shaped["answer"]
|
|
6492
6497
|
return value
|
|
6498
|
+
|
|
6499
|
+
|
|
6500
|
+
def _job_lease():
|
|
6501
|
+
"""``matrx_graph.workers.job_lease``, imported on use (the workers package imports the executor)."""
|
|
6502
|
+
from matrx_graph.workers import job_lease
|
|
6503
|
+
|
|
6504
|
+
return job_lease
|
|
@@ -17,7 +17,7 @@ and `workflow.job`'s post-reorg baseline schema on 2026-08-09.
|
|
|
17
17
|
- **Jobs run in parallel, bounded, each in its own task (2026-09-17).** `WorkerRunner` holds an `asyncio.Semaphore(max_concurrent)`: it keeps claiming while slots are free, waits on the *clock* (`poll_interval`) only when the queue is empty, and waits on a *slot* when every one is busy. Each claimed job runs as its own `asyncio.Task` with its own lease renewer, and `complete`/`fail`/cancel semantics are per job and unchanged. **`max_concurrent` defaults to 1** — a host that has not thought about the bound keeps exactly the behavior it had; aidream passes `workflow_worker.max_concurrent_jobs` (a `platform.feature_knob` row, currently 8, read once at worker start). Why: the loop was strictly serial, so ONE slow job held every other workflow on the platform. Measured in production 2026-09-17, five simultaneous Cloud Browser activations (each a workflow job that waits on a container coming up) came up as a 5.5-second-per-run ladder, and for those 22 seconds nothing else in the queue moved; at the 50-run fleet ceiling that is a ~4.5-minute tail. **Per-job isolation is structural, not conventional**: a job task is created from the claim loop's context, so `contextvars` it sets (an ambient `AppContext`, an ORM connection) belong to that task alone — they cannot reach a sibling job and cannot outlive the job in the loop. Under the old serial `await` they did: aidream's `_run_one` installs an ambient AppContext at the tail of every job, and that context stayed installed in the claim loop afterwards. Proof: `tests/test_workers.py` — the wall-clock test, the peak-concurrency test, the neighbour-isolation test, the two drain tests, and the context-isolation test (each demonstrated failing against the serial loop before the change).
|
|
18
18
|
- **Stopping is a bounded drain.** `stop()` takes no new claims; `run_forever()` then waits out the jobs already running for `stop_grace_seconds` (aidream passes its share of THE DEPLOY WINDOW) and cancels whatever is left. A canceled job fails retryable and its run resumes from its last checkpoint — the same outcome a serial worker's single job always had when the container was replaced.
|
|
19
19
|
- **A worker only claims what it can run (2026-10-12, `capability.py`).** Every server — deployed fleet and developer laptops — shares one database, so a job may belong to a graph whose node types this build has not shipped (live: a local build's `brand.setup.land_logo` run was resumed by production and errored "Unknown node type"). `enqueue()` / `enqueue_claimed()` stamp `workflow.job.metadata.required_node_types` (canonical spellings, removed types excluded) from the run's stored definition; `claim_one(capabilities=...)` adds `COALESCE(j.metadata->'required_node_types','[]') <@ caps` (`matrx_orm.JsonbContainedBy`), so an unstamped legacy job stays claimable by anyone. `WorkerRunner` and `run_single_job` pass `worker_capabilities()` (the default registry) on every claim. Belt two: a host job runner that finds a gap at run time raises `JobNeedsCapability`; the runner calls `defer_for_capability()` (un-claim, attempt refunded, requirement stamped, immediately visible) — never `fail()`, never `complete()`, never a run-status write. The run says what it waits for in `workflow.run.metadata._awaiting_capability` (`missing`, `message`, `since`, `checked_at`, `checked_by`) plus one `workflow.run_log` line (`run_awaiting_capability`); every incapable worker refreshes it via `stamp_runs_awaiting_capability()` and `checked_at` is a lifecycle-watchdog liveness signal, so a waiting run is not force-failed as stranded while a fleet is watching it. The first capable worker clears the marker. Proof: `tests/test_capability_aware_claim.py` (incl. the predicate evaluated in live Postgres; fails against the pre-change queue/runner).
|
|
20
|
-
- **A copy that loses its lease stops itself (lane K1, 2026-10-21).** A renewal that answers "not ours" (reaped — including a FALSE "dead" after a database outage longer than the lease, live job c1d89868 — or settled elsewhere) marks the copy's `job_lease.JobLease` lost and cancels its task; the host skips its terminal write (the copy that holds the job owns the run); many-item work calls `job_lease.check()` before each item (it also reads the job row, at most every tenth of a renewal beat). The reaper does not try to tell an outage from a death: a false "dead" is harmless by construction. Proof: aidream `aidream/services/tests/test_a_reaped_run_never_waits_on_itself.py`.
|
|
20
|
+
- **A copy that loses its lease stops itself (lane K1, 2026-10-21).** A renewal that answers "not ours" (reaped — including a FALSE "dead" after a database outage longer than the lease, live job c1d89868 — or settled elsewhere) marks the copy's `job_lease.JobLease` lost and cancels its task; the host skips its terminal write (the copy that holds the job owns the run); many-item work calls `job_lease.check()` before each item (it also reads the job row, at most every tenth of a renewal beat). The reaper does not try to tell an outage from a death: a false "dead" is harmless by construction. Proof: aidream `aidream/services/tests/test_a_reaped_run_never_waits_on_itself.py`. **Lane K1b:** whoever notices the loss first (the renewal or the work's own `check`) cancels the copy's owner task, and run-ahead re-raises a member that ended cancelled on a lost lease (it used to swallow it and route on). A job the reaper only DEAD-LETTERED (a false "dead" on its last attempt) is taken back by the live copy (`queue.take_back_false_dead_letter`, compare-and-set) and finished — stopping would strand the run, since nothing claims a dead-lettered job; a requeued or re-claimed job still stops the old copy. A renewal landing after `complete()` is not a loss (`JobLease.settling`). Each copy's `JobLease.generation` (the job's attempt) fences what it holds (import slots). A platform-wide outage longer than the lease plus grace cancels EVERY in-flight job at once and they all restart from their checkpoints together: the same spend per job as before, but as one burst. Proof: `tests/test_a_lost_lease_never_strands_or_lingers.py`.
|
|
21
21
|
- **Recovery starts before work.** `WorkerRunner.run_forever()` calls `reap_expired_leases()` before its first claim. The host's recurring reaper remains a backstop.
|
|
22
22
|
- **A transient claim outage never kills the worker.** A database timeout or
|
|
23
23
|
connection failure while claiming `workflow.job` is captured durably and
|
|
@@ -41,15 +41,31 @@ class JobLease:
|
|
|
41
41
|
#: fewest seconds between two reads of the job row by ``check`` (a many-item run checks before every item); the
|
|
42
42
|
#: runner sets it from its lease (a tenth of the renewal beat), and the renewal's own answer is used in between
|
|
43
43
|
look_every: float = 0.0
|
|
44
|
+
#: the task running this copy of the job: whoever notices the loss first (the renewal, or the work's own
|
|
45
|
+
#: ``check``) cancels it, so the WHOLE copy stops at once (lane K1b, B2: run-ahead swallowed a ``JobLeaseLost``
|
|
46
|
+
#: raised inside one node, and the copy kept checkpointing and routing until the next renewal)
|
|
47
|
+
owner: asyncio.Task | None = field(default=None, repr=False)
|
|
48
|
+
#: the copy finished its work and is settling the job (``complete``): a renewal that lands after the settle
|
|
49
|
+
#: commits answers "not ours", and that is not a loss (K1b, M1)
|
|
50
|
+
settling: bool = False
|
|
51
|
+
#: per-copy fencing token for anything this copy holds (an import slot): a newer copy's is always greater
|
|
52
|
+
generation: float = field(default_factory=time.time)
|
|
53
|
+
visibility_timeout: int = 600
|
|
44
54
|
lost: bool = False
|
|
45
55
|
why: str = ""
|
|
46
56
|
_looked: float = field(default=0.0, repr=False)
|
|
47
57
|
|
|
48
58
|
def mark_lost(self, why: str) -> None:
|
|
59
|
+
if self.settling:
|
|
60
|
+
log.info("job %s: settled while a renewal was in flight; not a lost lease", self.job_id[:8])
|
|
61
|
+
return
|
|
49
62
|
if not self.lost:
|
|
50
63
|
log.error("job %s (run %s) lost its lease (%s): this copy stops and writes nothing more",
|
|
51
64
|
self.job_id[:8], self.run_id[:8], why)
|
|
52
65
|
self.lost, self.why = True, why
|
|
66
|
+
owner = self.owner
|
|
67
|
+
if owner is not None and not owner.done() and owner is not _running_task():
|
|
68
|
+
owner.cancel(f"job {self.job_id[:8]} lease lost: {why}")
|
|
53
69
|
|
|
54
70
|
async def still_held(self) -> bool | None:
|
|
55
71
|
"""The job row's answer: True while this claim holds it, False once it does not, None when unreadable."""
|
|
@@ -66,7 +82,28 @@ class JobLease:
|
|
|
66
82
|
get = row.get if isinstance(row, dict) else lambda k: getattr(row, k, None)
|
|
67
83
|
if get("claimed_at") is None:
|
|
68
84
|
return False
|
|
69
|
-
|
|
85
|
+
# every claim stamps a token: a token that is not this copy's (or one where this copy had none) is another
|
|
86
|
+
# claim (K1b, M4: a token-less copy used to read every later claim as its own)
|
|
87
|
+
return get("claimed_by") == self.claimed_by
|
|
88
|
+
|
|
89
|
+
async def not_ours(self, why: str) -> bool:
|
|
90
|
+
"""The queue says this copy no longer holds its job. When the reaper only DEAD-LETTERED it (a false "dead" on
|
|
91
|
+
the last attempt), no other copy can ever own the run: take the job back and keep going, so the run is
|
|
92
|
+
finished and its terminal status written (K1b, B1). Otherwise mark the lease lost (which stops the copy).
|
|
93
|
+
Returns True when the copy keeps the job."""
|
|
94
|
+
from matrx_graph.workers.queue import take_back_false_dead_letter
|
|
95
|
+
|
|
96
|
+
if self.settling:
|
|
97
|
+
return True
|
|
98
|
+
try:
|
|
99
|
+
kept = await take_back_false_dead_letter(
|
|
100
|
+
self.job_id, claimed_by=self.claimed_by, visibility_timeout_seconds=self.visibility_timeout)
|
|
101
|
+
except Exception: # noqa: BLE001 — unknown: the copy stops (the safe side for a run another copy may own)
|
|
102
|
+
log.warning("job %s: could not try to take back a dead-lettered job", self.job_id[:8], exc_info=True)
|
|
103
|
+
kept = False
|
|
104
|
+
if not kept:
|
|
105
|
+
self.mark_lost(why)
|
|
106
|
+
return kept
|
|
70
107
|
|
|
71
108
|
async def check(self) -> None:
|
|
72
109
|
"""Raise :class:`JobLeaseLost` when this copy no longer holds its job; read the row at most every
|
|
@@ -74,11 +111,18 @@ class JobLease:
|
|
|
74
111
|
if not self.lost and time.monotonic() - self._looked >= self.look_every:
|
|
75
112
|
self._looked = time.monotonic()
|
|
76
113
|
if await self.still_held() is False:
|
|
77
|
-
self.
|
|
114
|
+
await self.not_ours("the job row is claimed by another copy or was requeued")
|
|
78
115
|
if self.lost:
|
|
79
116
|
raise JobLeaseLost(f"job {self.job_id[:8]} lease lost: {self.why}")
|
|
80
117
|
|
|
81
118
|
|
|
119
|
+
def _running_task() -> asyncio.Task | None:
|
|
120
|
+
try:
|
|
121
|
+
return asyncio.current_task()
|
|
122
|
+
except RuntimeError:
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
|
|
82
126
|
_current: ContextVar[JobLease | None] = ContextVar("matrx_graph_job_lease", default=None)
|
|
83
127
|
|
|
84
128
|
|
|
@@ -707,6 +707,62 @@ async def renew_lease(
|
|
|
707
707
|
return result.rows_affected > 0
|
|
708
708
|
|
|
709
709
|
|
|
710
|
+
async def take_back_false_dead_letter(
|
|
711
|
+
job_id: str,
|
|
712
|
+
*,
|
|
713
|
+
claimed_by: str | None,
|
|
714
|
+
visibility_timeout_seconds: int = 600,
|
|
715
|
+
) -> bool:
|
|
716
|
+
"""A live copy whose job the reaper DEAD-LETTERED (a false "dead": an outage outlasted the lease on the job's last
|
|
717
|
+
attempt) takes the job back, so it finishes the run and writes its terminal status (lane K1b, B1, 2026-10-21).
|
|
718
|
+
|
|
719
|
+
Without this the live copy read "not ours", cancelled itself and skipped its terminal write, and no other copy would
|
|
720
|
+
ever claim a dead-lettered job: the run stayed non-terminal (stranded) for every job kind. A dead-lettered job has
|
|
721
|
+
no other owner, so taking it back can never run two copies. Compare-and-set: only a row that is still unclaimed,
|
|
722
|
+
still dead-lettered BY THE REAPER (``last_error.type == 'lease_expired'``) and still parked in the far future
|
|
723
|
+
(an operator's retry moves ``visible_at`` back) is taken; anything else answers False and the copy stops.
|
|
724
|
+
"""
|
|
725
|
+
from matrx_graph.db._registry import get_model
|
|
726
|
+
|
|
727
|
+
model = get_model("WfJob")
|
|
728
|
+
try:
|
|
729
|
+
rows = await model.filter(id=str(UUID(job_id))).all(use_cache=False)
|
|
730
|
+
except Exception: # noqa: BLE001 — unreadable: not taken back (the caller decides on the renewal's answer)
|
|
731
|
+
log.warning("could not read job %s to see whether the reaper dead-lettered it", job_id[:8], exc_info=True)
|
|
732
|
+
return False
|
|
733
|
+
if not rows:
|
|
734
|
+
return False
|
|
735
|
+
row = rows[0]
|
|
736
|
+
get = row.get if isinstance(row, dict) else lambda k: getattr(row, k, None)
|
|
737
|
+
note = _parse_json(get("last_error")) or {}
|
|
738
|
+
if get("claimed_at") is not None or not isinstance(note, dict):
|
|
739
|
+
return False
|
|
740
|
+
if note.get("queue_state") != "dead_letter" or note.get("type") != "lease_expired":
|
|
741
|
+
return False
|
|
742
|
+
now = datetime.now(UTC)
|
|
743
|
+
taken = (
|
|
744
|
+
await model.filter(
|
|
745
|
+
id=str(UUID(job_id)),
|
|
746
|
+
claimed_at__isnull=True,
|
|
747
|
+
visible_at__gt=now + timedelta(days=_FAR_FUTURE_DAYS // 2),
|
|
748
|
+
).update(
|
|
749
|
+
claimed_at=now,
|
|
750
|
+
claimed_by=claimed_by,
|
|
751
|
+
visible_at=now + timedelta(seconds=visibility_timeout_seconds),
|
|
752
|
+
last_error={
|
|
753
|
+
"queue_state": "in_progress",
|
|
754
|
+
"type": "false_dead_taken_back",
|
|
755
|
+
"message": "the reaper dead-lettered a job its worker was still running (an outage outlasted the "
|
|
756
|
+
"lease); the live copy took it back to finish the run",
|
|
757
|
+
},
|
|
758
|
+
)
|
|
759
|
+
).updated_rows
|
|
760
|
+
if taken:
|
|
761
|
+
log.warning("job %s: the reaper dead-lettered it while this worker still ran it; taken back to finish the run",
|
|
762
|
+
job_id[:8])
|
|
763
|
+
return bool(taken)
|
|
764
|
+
|
|
765
|
+
|
|
710
766
|
async def reap_expired_leases(*, grace_seconds: int = 120) -> int:
|
|
711
767
|
"""Requeue jobs whose lease expired ≥ ``grace_seconds`` ago (audit P1-14).
|
|
712
768
|
|
|
@@ -731,7 +787,9 @@ async def reap_expired_leases(*, grace_seconds: int = 120) -> int:
|
|
|
731
787
|
new copy at once, so the run never waits on its own earlier copy.
|
|
732
788
|
|
|
733
789
|
Jobs with attempts left are requeued (claim_one re-claims and bumps
|
|
734
|
-
``attempt``); exhausted jobs are dead-lettered
|
|
790
|
+
``attempt``); exhausted jobs are dead-lettered (a live copy whose job was
|
|
791
|
+
falsely dead-lettered takes it back, ``take_back_false_dead_letter``, lane
|
|
792
|
+
K1b, so its run is never stranded) with the same far-future
|
|
735
793
|
visible_at shape ``fail()`` uses. Returns how many rows were touched.
|
|
736
794
|
"""
|
|
737
795
|
from matrx_graph.db._registry import get_model
|
|
@@ -328,6 +328,10 @@ class WorkerRunner:
|
|
|
328
328
|
run_id=job.run_id,
|
|
329
329
|
claimed_by=job.claimed_by,
|
|
330
330
|
look_every=max(self._visibility_timeout // 3, 10) / 10,
|
|
331
|
+
owner=asyncio.current_task(),
|
|
332
|
+
visibility_timeout=self._visibility_timeout,
|
|
333
|
+
# the job's attempt: every re-claim bumps it, so a later copy's fencing token is always greater (K1b, B5)
|
|
334
|
+
generation=float(job.attempt or 0),
|
|
331
335
|
)
|
|
332
336
|
renewer = asyncio.create_task(
|
|
333
337
|
self._renew_loop(job.job_id, claimed_by=job.claimed_by, lease=lease, owner=asyncio.current_task())
|
|
@@ -336,6 +340,7 @@ class WorkerRunner:
|
|
|
336
340
|
app = await self._app_factory(job)
|
|
337
341
|
with job_lease.holding(lease):
|
|
338
342
|
await self._run_job(job, app)
|
|
343
|
+
lease.settling = True # a renewal answering "not ours" after this settles is not a loss (K1b, M1)
|
|
339
344
|
await complete(job.job_id, claimed_by=job.claimed_by)
|
|
340
345
|
except JobNeedsCapability as need:
|
|
341
346
|
await hand_back_for_capability(job, need)
|
|
@@ -452,13 +457,19 @@ class WorkerRunner:
|
|
|
452
457
|
# slot and the restarted copy waited on itself (run 8749d414). Another copy may own the run now, so
|
|
453
458
|
# this one stops: the lease is marked lost (many-item work checks it before each item), the job
|
|
454
459
|
# task is cancelled, and the host skips its terminal write.
|
|
460
|
+
# Lane K1b, B1: when the reaper only DEAD-LETTERED the job (a false "dead" on its last attempt), no
|
|
461
|
+
# other copy can ever own the run, so this copy takes the job back and finishes it (stopping would
|
|
462
|
+
# strand the run: nothing would ever write its terminal status).
|
|
463
|
+
if lease is not None:
|
|
464
|
+
if lease.settling:
|
|
465
|
+
return
|
|
466
|
+
if await lease.not_ours("the queue reaped or settled the job"):
|
|
467
|
+
continue
|
|
455
468
|
log.error(
|
|
456
469
|
"job %s lease is no longer ours (reaped or deleted) — "
|
|
457
470
|
"this copy stops; the current holder owns the run",
|
|
458
471
|
job_id,
|
|
459
472
|
)
|
|
460
|
-
if lease is not None:
|
|
461
|
-
lease.mark_lost("the queue reaped or settled the job")
|
|
462
473
|
if owner is not None and not owner.done():
|
|
463
474
|
owner.cancel()
|
|
464
475
|
return
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Lane K1b (2026-10-21): a copy that is told "not ours" never strands its run, and a copy that really lost its job
|
|
2
|
+
stops at once — whoever notices first.
|
|
3
|
+
|
|
4
|
+
- B1: on a job's LAST attempt the reaper dead-letters (never requeues) an expired lease. After a database outage that
|
|
5
|
+
outlasted the lease, the live copy then read "not ours", cancelled itself and skipped its terminal write — and no
|
|
6
|
+
copy ever claims a dead-lettered job, so the run was stranded. Now the live copy takes the job back and finishes.
|
|
7
|
+
A job the reaper REQUEUED (another copy may run it) still stops the old copy.
|
|
8
|
+
- B2: the work's own ``check`` (it reads the job row more often than the renewal) raised ``JobLeaseLost`` inside one
|
|
9
|
+
node; run-ahead swallowed it and the copy kept checkpointing and routing until the next renewal. Now marking the
|
|
10
|
+
lease lost cancels the copy's owner task at once.
|
|
11
|
+
- M1: a renewal that lands just after ``complete()`` commits is not a lost lease.
|
|
12
|
+
- M4: a copy with no claim token read every later claim as its own.
|
|
13
|
+
|
|
14
|
+
The job row lives in an in-memory stand-in of ``WfJob`` that answers the queue's real queries.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
import types
|
|
21
|
+
from datetime import UTC, datetime, timedelta
|
|
22
|
+
from types import SimpleNamespace
|
|
23
|
+
|
|
24
|
+
import pytest
|
|
25
|
+
|
|
26
|
+
from matrx_graph.db import _registry
|
|
27
|
+
from matrx_graph.workers import job_lease
|
|
28
|
+
from matrx_graph.workers import queue as queue_mod
|
|
29
|
+
from matrx_graph.workers import runner as runner_mod
|
|
30
|
+
|
|
31
|
+
JOB = "c1d89868-ef16-4d9b-82e6-dd5279775886"
|
|
32
|
+
RUN = "8749d414-0000-4c39-9a5e-00000000k1bb"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _Jobs:
|
|
36
|
+
"""One wf_job row, filtered and updated the way the ORM does for the lookups the queue uses."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, **row):
|
|
39
|
+
self.row = {"id": JOB, "attempt": 3, "max_attempts": 3, **row}
|
|
40
|
+
|
|
41
|
+
def _match(self, filters):
|
|
42
|
+
for key, want in filters.items():
|
|
43
|
+
name, _, op = key.partition("__")
|
|
44
|
+
have = self.row.get(name)
|
|
45
|
+
if op == "isnull":
|
|
46
|
+
if (have is None) != bool(want):
|
|
47
|
+
return False
|
|
48
|
+
elif op == "gt":
|
|
49
|
+
if have is None or not have > want:
|
|
50
|
+
return False
|
|
51
|
+
elif op == "lt":
|
|
52
|
+
if have is None or not have < want:
|
|
53
|
+
return False
|
|
54
|
+
elif have != want:
|
|
55
|
+
return False
|
|
56
|
+
return True
|
|
57
|
+
|
|
58
|
+
def filter(self, *_compare, **filters):
|
|
59
|
+
jobs = self
|
|
60
|
+
|
|
61
|
+
class _Q:
|
|
62
|
+
async def all(self, use_cache=True): # noqa: ARG002
|
|
63
|
+
return [dict(jobs.row)] if jobs._match(filters) else []
|
|
64
|
+
|
|
65
|
+
async def update(self, **updates):
|
|
66
|
+
if not jobs._match(filters):
|
|
67
|
+
return SimpleNamespace(updated_rows=[])
|
|
68
|
+
jobs.row.update(updates)
|
|
69
|
+
return SimpleNamespace(updated_rows=[dict(jobs.row)])
|
|
70
|
+
|
|
71
|
+
return _Q()
|
|
72
|
+
|
|
73
|
+
async def update_where(self, filters, **updates):
|
|
74
|
+
if not self._match(filters):
|
|
75
|
+
return SimpleNamespace(rows_affected=0)
|
|
76
|
+
self.row.update(updates)
|
|
77
|
+
return SimpleNamespace(rows_affected=1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _dead_lettered_by_the_reaper() -> dict:
|
|
81
|
+
return {"claimed_at": None, "claimed_by": None, "visible_at": datetime.now(UTC) + timedelta(days=36_500),
|
|
82
|
+
"last_error": {"queue_state": "dead_letter", "type": "lease_expired", "retry_exhausted": True}}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _requeued_by_the_reaper() -> dict:
|
|
86
|
+
return {"claimed_at": None, "claimed_by": None, "visible_at": datetime.now(UTC),
|
|
87
|
+
"last_error": {"queue_state": "pending", "type": "lease_expired"}}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.fixture
|
|
91
|
+
def jobs(monkeypatch):
|
|
92
|
+
store = _Jobs(claimed_at=datetime.now(UTC), claimed_by="worker-1:A", visible_at=datetime.now(UTC) + timedelta(minutes=10),
|
|
93
|
+
last_error=None)
|
|
94
|
+
monkeypatch.setattr(_registry, "get_model", lambda _key: store)
|
|
95
|
+
|
|
96
|
+
async def capture_error(*_a, **_k):
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
real_sleep = asyncio.sleep
|
|
100
|
+
monkeypatch.setattr(runner_mod, "capture_error", capture_error)
|
|
101
|
+
monkeypatch.setattr(runner_mod, "asyncio", types.SimpleNamespace(sleep=lambda s: real_sleep(0.02),
|
|
102
|
+
Task=asyncio.Task, CancelledError=asyncio.CancelledError))
|
|
103
|
+
return store
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _copy(jobs: _Jobs, *, work_seconds: float, reaper_turns_row_into: dict):
|
|
107
|
+
"""One copy of the run as the worker runs it: the runner's real renewal loop beside the work; part way the reaper
|
|
108
|
+
rewrites the row (the outage outlasted the lease). Returns (outcome, lease)."""
|
|
109
|
+
|
|
110
|
+
async def main():
|
|
111
|
+
out: dict = {}
|
|
112
|
+
me = asyncio.current_task()
|
|
113
|
+
lease = job_lease.JobLease(job_id=JOB, run_id=RUN, claimed_by="worker-1:A", owner=me, visibility_timeout=600)
|
|
114
|
+
stub = types.SimpleNamespace(_visibility_timeout=30)
|
|
115
|
+
renewer = asyncio.create_task(runner_mod.WorkerRunner._renew_loop(stub, JOB, claimed_by="worker-1:A", lease=lease, owner=me))
|
|
116
|
+
try:
|
|
117
|
+
await asyncio.sleep(0.03)
|
|
118
|
+
jobs.row.update(reaper_turns_row_into)
|
|
119
|
+
await asyncio.sleep(work_seconds)
|
|
120
|
+
await job_lease.JobLease.check(lease)
|
|
121
|
+
out["outcome"] = "finished"
|
|
122
|
+
except asyncio.CancelledError:
|
|
123
|
+
out["outcome"] = "cancelled"
|
|
124
|
+
finally:
|
|
125
|
+
renewer.cancel()
|
|
126
|
+
return out["outcome"], lease
|
|
127
|
+
|
|
128
|
+
return asyncio.run(main())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_a_false_dead_letter_on_the_last_attempt_is_taken_back_and_the_run_finishes(jobs):
|
|
132
|
+
outcome, lease = _copy(jobs, work_seconds=0.2, reaper_turns_row_into=_dead_lettered_by_the_reaper())
|
|
133
|
+
assert outcome == "finished", "the live copy stopped on a dead-lettered job: nothing would ever end its run"
|
|
134
|
+
assert not lease.lost
|
|
135
|
+
assert jobs.row["claimed_by"] == "worker-1:A" and jobs.row["claimed_at"] is not None, "the job was not taken back"
|
|
136
|
+
assert jobs.row["last_error"]["type"] == "false_dead_taken_back"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_a_requeued_job_still_stops_the_old_copy(jobs):
|
|
140
|
+
outcome, lease = _copy(jobs, work_seconds=0.2, reaper_turns_row_into=_requeued_by_the_reaper())
|
|
141
|
+
assert outcome == "cancelled", "a copy whose job was requeued (another copy may run it) kept going"
|
|
142
|
+
assert lease.lost
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_a_job_another_worker_claimed_is_never_taken_back(jobs):
|
|
146
|
+
claimed = {"claimed_at": datetime.now(UTC), "claimed_by": "worker-2:B", "visible_at": datetime.now(UTC) + timedelta(minutes=10),
|
|
147
|
+
"last_error": None}
|
|
148
|
+
outcome, lease = _copy(jobs, work_seconds=0.2, reaper_turns_row_into=claimed)
|
|
149
|
+
assert outcome == "cancelled" and lease.lost
|
|
150
|
+
assert jobs.row["claimed_by"] == "worker-2:B"
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_the_work_noticing_the_loss_first_stops_the_whole_copy_at_once(jobs):
|
|
154
|
+
"""B2: the node's own check raises inside a child task (run-ahead never reads its result); the copy's owner task
|
|
155
|
+
must be cancelled right then, not at the next renewal."""
|
|
156
|
+
|
|
157
|
+
async def main():
|
|
158
|
+
me = asyncio.current_task()
|
|
159
|
+
lease = job_lease.JobLease(job_id=JOB, run_id=RUN, claimed_by="worker-1:A", owner=me)
|
|
160
|
+
jobs.row.update(_requeued_by_the_reaper())
|
|
161
|
+
with job_lease.holding(lease):
|
|
162
|
+
node = asyncio.create_task(job_lease.check()) # a node in run-ahead: nobody awaits its result
|
|
163
|
+
try:
|
|
164
|
+
await asyncio.sleep(5) # the copy would go on to checkpoint and route
|
|
165
|
+
return "kept going", node
|
|
166
|
+
except asyncio.CancelledError:
|
|
167
|
+
return "stopped", node
|
|
168
|
+
|
|
169
|
+
outcome, node = asyncio.run(main())
|
|
170
|
+
assert outcome == "stopped"
|
|
171
|
+
assert node.done()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_a_renewal_landing_after_the_settle_is_not_a_loss(jobs):
|
|
175
|
+
async def main():
|
|
176
|
+
me = asyncio.current_task()
|
|
177
|
+
lease = job_lease.JobLease(job_id=JOB, run_id=RUN, claimed_by="worker-1:A", owner=me)
|
|
178
|
+
lease.settling = True
|
|
179
|
+
jobs.row.update({"claimed_at": None, "claimed_by": None, "last_error": {"queue_state": "succeeded"}})
|
|
180
|
+
kept = await lease.not_ours("the queue reaped or settled the job")
|
|
181
|
+
await asyncio.sleep(0)
|
|
182
|
+
return kept, lease
|
|
183
|
+
|
|
184
|
+
kept, lease = asyncio.run(main())
|
|
185
|
+
assert kept and not lease.lost
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_a_copy_without_a_claim_token_sees_another_claim(jobs):
|
|
189
|
+
jobs.row.update({"claimed_at": datetime.now(UTC), "claimed_by": "worker-2:B"})
|
|
190
|
+
lease = job_lease.JobLease(job_id=JOB, run_id=RUN, claimed_by=None)
|
|
191
|
+
assert asyncio.run(lease.still_held()) is False
|
|
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
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0020_wf_run_announce.sql
RENAMED
|
File without changes
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0021_join_semantics.sql
RENAMED
|
File without changes
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0022_node_data_slot.sql
RENAMED
|
File without changes
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0023_fanout_identity.sql
RENAMED
|
File without changes
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0024_definition_variables.sql
RENAMED
|
File without changes
|
{matrx_graph-0.2.331 → matrx_graph-0.2.332}/matrx_graph/db/migrations/0100_baseline_post_reorg.sql
RENAMED
|
File without changes
|