matrx-graph 0.2.76__tar.gz → 0.2.78__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.76 → matrx_graph-0.2.78}/PKG-INFO +1 -1
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/db_requirements.py +4 -0
- matrx_graph-0.2.78/matrx_graph/db/migrations/0110_run_log.sql +66 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/__init__.py +3 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/FEATURE.md +22 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/transform.py +5 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/FEATURE.md +10 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/noop.py +6 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/sleep.py +10 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/throttle.py +5 -0
- matrx_graph-0.2.78/matrx_graph/nodes/log/FEATURE.md +69 -0
- matrx_graph-0.2.78/matrx_graph/nodes/log/__init__.py +16 -0
- matrx_graph-0.2.78/matrx_graph/nodes/log/record.py +348 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/FEATURE.md +3 -1
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/to_frontend.py +5 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/pyproject.toml +1 -1
- matrx_graph-0.2.78/tests/test_log_record_node.py +299 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/.gitignore +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/AGENTS.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/CLAUDE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/README.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/ENGINE_SPEC.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/GAPS_AND_ROADMAP.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/OPERATING_PRIORITIES.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/README.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/RUN_LIFECYCLE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/WORKFLOW_ARCHITECTURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/action-authoring.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/architecture.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/node-authoring.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/testing.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/ACTION_STANDARDS.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/decorator.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/registry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/spec.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/memory.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/postgres.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/protocol.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/compile.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/composition.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/accessor.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/authoring_gate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/availability.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/edge_routing.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/envelope.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/fingerprint.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/graph_adapter.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/markers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/model.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/occurrences.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/partial.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/runtime_view.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/sdk.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/subscriptions.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/triggers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/well_known.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/wrappers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contract_kinds.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contracts/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contracts/audit.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/FEATURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/_registry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/definition_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/event_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0020_wf_run_announce.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0021_join_semantics.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0022_node_data_slot.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0023_fanout_identity.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0024_definition_variables.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0100_baseline_post_reorg.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0101_run_request_attribution.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0102_visibility_and_kind_verdicts.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0103_work_item.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0104_soft_delete_columns.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0105_plan.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0106_definition_visibility_internal_default.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0107_plan_anchor_integrity.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0108_plan_persistence_invariants.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0109_node_data_slot_created_by.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/README.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0001_wf_initial.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0002_wf_queue.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0003_wf_node_events.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0004_wf_trigger.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0005_wf_rls.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0006_wf_run_cancelling.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0007_wf_run_paused_and_errored.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0008_wf_node_outcome_source.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0009_wf_run_recovery_limit.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0010_wf_idempotency.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0011_wf_definition_concurrency_limit.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0012_wf_trigger_fire.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0013_wf_recovery_audit.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0014_wf_node_events_notify.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0016_wf_template.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0017_wf_hardening.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0018_wf_node_events_seq.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0019_wf_node_events_drop_check.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/README.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/node_data_slot_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/pool.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/run_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/standalone.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/trigger_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/wf_manager.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/decisions.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/diagnostics/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/diagnostics/compile_validation.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/dry_run.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/errors.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/examples/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/examples/self_validating_news.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/FEATURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/_retry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/channels.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/client_payload.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/introspect.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/json_dump.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/primitives.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/registry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/scheduler.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/schema_validation.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/extract.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/failure.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/kinds.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/_sandbox.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/_template.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/FEATURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/branch.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/gather.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/human_input.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/loop.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/map.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/send.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/work_queue.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/work_seed.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/crypto/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/crypto/ops.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/assert_.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/collections.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/criteria_gate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/filter.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/flatten.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/json_io.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/map_template.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/merge.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/pick_omit.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/tabular.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/datetime_/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/datetime_/ops.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/_shared.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/custom_api.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/get.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/graphql.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/post.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/io/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/io/user_input.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/math_/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/math_/evaluate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/FEATURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/contract.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/step.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/plan/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/plan/step.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/subgraph/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/subgraph/call.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/custom_extract.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/json_path.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/manipulate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/regex.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/render_list.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/tool/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/tool/call.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/observability/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/patch.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/py.typed +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/references.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/canonical_payloads.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/channel.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/compiled.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/context.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/definition.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/events.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/handle.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/node_spec.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/primitives.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/result.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/run.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/send.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/usl.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/value_objects.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/variables.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/validation.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/FEATURE.md +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/cron_watcher.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/queue.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/runner.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/sleep_wake.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workset.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/_db_guard.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/_fixtures.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/00_supabase_shim.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/10_canonical_reorg_shim.sql +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/bootstrap_test_db.sh +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/conftest.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/judge/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/judge/test_self_validating_news_e2e.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/level2/__init__.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_action_output_handles.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_actions.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_engine_core.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_kind_routing.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_partial_wire.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_registry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_authoring_gate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_back_edges.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_barrier_removed.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_channel.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_channels_reducers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_checkpoint_summaries.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_envelope.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_markers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_model.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_partial.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_runtime_view.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_context_propagation.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_artifact_eviction.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_audit.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_kinds.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_criteria_gate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_data_collections.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_data_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_db_guard.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition_store_list_skip.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition_store_version_load.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dry_run.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dry_run_source_input.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dynamic_topology_guards.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_end_to_end.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_extract.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_failure.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_flow_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_http_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_ingestion_gate.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_invocation_budget.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_io_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_json_dump.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_accessor.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_availability.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_enforcement.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_native_node_library.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_routing.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_sdk.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kinds.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_list_include_ids.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_list_shaping_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_live_injection.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_loop_scale.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_map_payload_template.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_multi_dispatch_fanin.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_cost_event.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_data_slot_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_data_slot_validation.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_error_kind.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_input_capture.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_result_envelope.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_spec.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_output_channel_binding.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_patch.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_pause_cancel_and_recovery.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_pipe_step.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_plan_identity_consistency.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_plan_step.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_postgres_checkpointer.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_postgres_workset_store.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_primitives.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_public_media_scope.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_references.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_registry.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_resume_payload_folding.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_conversation_identity.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_store_control.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_variables.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_conditional_edge.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_config_override.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_events.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_failsoft.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_failure_cost_settlement.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_fanout_identity.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_fork_to_new_run.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_interrupt_siblings.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_join_hardening.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_joins.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_loop_contract.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_mock_response.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_node_retry_override.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_node_timeout.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_retry_policy.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_skip_edge_ids.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_step_mode.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_self_validating_news.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_send_unwired_items_is_loud.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_standalone_db_models.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_subgraph_call.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_text_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_transform_output_shape.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_triggers_and_observability.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_utility_nodes.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_validation_hints.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_validation_s4.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_workers.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_workset.py +0 -0
- {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_workset_chaos.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.78
|
|
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
|
|
@@ -27,6 +27,10 @@ DB_REQUIREMENTS = {
|
|
|
27
27
|
{"key": "WfRun", "table": "workflow.run"},
|
|
28
28
|
{"key": "WfCheckpoint", "table": "workflow.checkpoint"},
|
|
29
29
|
{"key": "WfNodeOutcome", "table": "workflow.node_outcome"},
|
|
30
|
+
# The USER's diagnostic log (log.record). A ledger table: SELECT-only
|
|
31
|
+
# for users, written by the server. Never the platform system_error
|
|
32
|
+
# queue — workflows are authored by users.
|
|
33
|
+
{"key": "WfRunLog", "table": "workflow.run_log"},
|
|
30
34
|
{"key": "WfTrigger", "table": "workflow.trigger"},
|
|
31
35
|
{"key": "WfJob", "table": "workflow.job"},
|
|
32
36
|
{"key": "WfNodeEvents", "table": "workflow.node_events"},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
-- 0110 — workflow.run_log: THE WORKFLOW'S OWN DIAGNOSTIC RECORD.
|
|
2
|
+
-- RECORD of a live migration applied via the Supabase MCP on 2026-08-23
|
|
3
|
+
-- (migration `workflow_run_log_user_diagnostics`). `.sql` files here are
|
|
4
|
+
-- records, not mechanisms — but a standalone install against a bare Matrx
|
|
5
|
+
-- Postgres runs this same provisioner call.
|
|
6
|
+
--
|
|
7
|
+
-- Arman, 2026-08-23:
|
|
8
|
+
-- "we need to give users a way to be able to track their own internal logs
|
|
9
|
+
-- and problems… a place where you can pass it whatever thing you want, and
|
|
10
|
+
-- then it will write to the database in a table that can later be used to
|
|
11
|
+
-- diagnose problems that happened within a workflow that you were able to
|
|
12
|
+
-- work around, but should never have happened."
|
|
13
|
+
--
|
|
14
|
+
-- Nothing on the platform could do this. A workflow that hit a serious problem
|
|
15
|
+
-- and recovered had two options: fail the run, or say nothing. The Agent
|
|
16
|
+
-- Foundry took the second — both Component Artisans refused to build, each
|
|
17
|
+
-- with a correct diagnosis, and the run reported `completed` with no component
|
|
18
|
+
-- and no trace of why.
|
|
19
|
+
--
|
|
20
|
+
-- NOT the platform system_error queue: workflows are authored by USERS, so a
|
|
21
|
+
-- node writing there would let any author inject into our operational alarms.
|
|
22
|
+
-- `ledger` variant — SELECT-only for users (org membership + the
|
|
23
|
+
-- global-readable system org), every write through the server.
|
|
24
|
+
--
|
|
25
|
+
-- Certified: iam.canonical_certify_ok('workflow','run_log','workflow_run_log') = true.
|
|
26
|
+
|
|
27
|
+
select platform.create_entity_table(
|
|
28
|
+
p_schema => 'workflow',
|
|
29
|
+
p_table => 'run_log',
|
|
30
|
+
p_token => 'workflow_run_log',
|
|
31
|
+
p_label => 'Workflow Run Log',
|
|
32
|
+
p_fields => ARRAY[
|
|
33
|
+
'run_id uuid NOT NULL REFERENCES workflow.run(id)',
|
|
34
|
+
'definition_id uuid REFERENCES workflow.definition(id)',
|
|
35
|
+
'node_id text',
|
|
36
|
+
'step integer',
|
|
37
|
+
'attempt integer',
|
|
38
|
+
'severity text NOT NULL DEFAULT ''warning''',
|
|
39
|
+
'label text NOT NULL',
|
|
40
|
+
'message text NOT NULL',
|
|
41
|
+
'payload jsonb NOT NULL DEFAULT ''{}''::jsonb',
|
|
42
|
+
'payload_kind text',
|
|
43
|
+
'occurred_at timestamptz NOT NULL DEFAULT now()'
|
|
44
|
+
],
|
|
45
|
+
p_variant => 'ledger',
|
|
46
|
+
p_versioned => false,
|
|
47
|
+
p_soft_delete => false,
|
|
48
|
+
p_visibility => 'none',
|
|
49
|
+
p_category => false,
|
|
50
|
+
p_listed => false,
|
|
51
|
+
p_org_default => true,
|
|
52
|
+
p_gin_jsonb => true
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
-- Fixed code-level vocabulary (db-rules §0.4) — a sixth level is a migration,
|
|
56
|
+
-- never a string somebody types.
|
|
57
|
+
alter table workflow.run_log
|
|
58
|
+
add constraint run_log_severity_check
|
|
59
|
+
check (severity in ('debug', 'info', 'warning', 'error', 'critical'));
|
|
60
|
+
|
|
61
|
+
create index if not exists run_log_run_idx
|
|
62
|
+
on workflow.run_log (run_id, occurred_at desc);
|
|
63
|
+
create index if not exists run_log_definition_label_idx
|
|
64
|
+
on workflow.run_log (definition_id, label, occurred_at desc);
|
|
65
|
+
create index if not exists run_log_org_severity_idx
|
|
66
|
+
on workflow.run_log (organization_id, severity, occurred_at desc);
|
|
@@ -58,6 +58,7 @@ from matrx_graph.nodes.http import (
|
|
|
58
58
|
HttpPostExecutor,
|
|
59
59
|
)
|
|
60
60
|
from matrx_graph.nodes.io.user_input import UserInputExecutor
|
|
61
|
+
from matrx_graph.nodes.log import RecordIssueExecutor
|
|
61
62
|
from matrx_graph.nodes.math_ import MathEvaluateExecutor
|
|
62
63
|
from matrx_graph.nodes.output.to_frontend import ToFrontendExecutor
|
|
63
64
|
from matrx_graph.nodes.pipe.step import PipeStepExecutor
|
|
@@ -120,6 +121,8 @@ def register_builtin_nodes(
|
|
|
120
121
|
WaitUntilExecutor(),
|
|
121
122
|
ThrottleExecutor(),
|
|
122
123
|
NoopExecutor(),
|
|
124
|
+
# Diagnostics — the workflow's own record of what went wrong
|
|
125
|
+
RecordIssueExecutor(),
|
|
123
126
|
# Math
|
|
124
127
|
MathEvaluateExecutor(),
|
|
125
128
|
# HTTP
|
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
Data nodes are deterministic transformations and validation boundaries. They
|
|
4
4
|
must not call models or silently invent missing values.
|
|
5
5
|
|
|
6
|
+
## `data.transform` declares itself DYNAMIC — and the kind lives on the INSTANCE
|
|
7
|
+
|
|
8
|
+
Its output shape IS the author's expression, so no single `output_kind` can
|
|
9
|
+
describe every instance without lying. The spec sets `dynamic_output=True`
|
|
10
|
+
(2026-08-23, vision audit GAP 2 — it always behaved this way and never said
|
|
11
|
+
so, which is how the boundary lint could not tell it apart from a node that
|
|
12
|
+
forgot). **The honest declaration belongs on the node instance**, as
|
|
13
|
+
`data.output_kind` in the definition, alongside an expression that emits
|
|
14
|
+
`'__kind'` so the payload carries its own identity:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
"config": {"expression": "{'__kind': 'agent_foundry_report', 'kind': inputs['kind'], …}"},
|
|
18
|
+
"output_kind": "agent_foundry_report",
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Reference instances: `aidream/workflows/agent_foundry_v1.py`
|
|
22
|
+
(`kind_brief` / `component_briefs` / `foundry_report`) and
|
|
23
|
+
`study_pack_v2.py` (`build_sources`, `pack`). A composition step whose result
|
|
24
|
+
nests other kinds keeps them SECTIONED — each nested object carries its own
|
|
25
|
+
`__kind` and earns a real `content_ir.kind_edge` row; a flattened
|
|
26
|
+
`vars_a_component_key` prefix is addressable by nothing.
|
|
27
|
+
|
|
6
28
|
## `data.criteria_gate`
|
|
7
29
|
|
|
8
30
|
The criteria gate proves workflow completion properties before a consequential
|
|
@@ -87,6 +87,11 @@ class TransformExecutor:
|
|
|
87
87
|
HandleSpec(id="in", direction=HandleDirection.INPUT),
|
|
88
88
|
HandleSpec(id="out", direction=HandleDirection.OUTPUT),
|
|
89
89
|
),
|
|
90
|
+
# DYNAMIC BY DESIGN — the output shape IS the author's expression, so
|
|
91
|
+
# no single kind can describe every instance. Declared dynamic (GAP 2,
|
|
92
|
+
# §10g); the honest declaration lives on the node INSTANCE as
|
|
93
|
+
# ``data.output_kind`` (see aidream/workflows/study_pack_v2.py::pack).
|
|
94
|
+
dynamic_output=True,
|
|
90
95
|
determinism=ActionTier.PURE,
|
|
91
96
|
icon="function-square",
|
|
92
97
|
)
|
|
@@ -9,6 +9,16 @@ All four are **pass-through**: the input payload flows downstream unchanged,
|
|
|
9
9
|
so none declares an `output_kind` — runtime `__kind` propagation carries the
|
|
10
10
|
payload's identity (the `data.transform` posture; never declare `"json"`).
|
|
11
11
|
|
|
12
|
+
🚨 **They DECLARE that** (2026-08-23, vision audit GAP 2): all four set
|
|
13
|
+
`dynamic_output=True` on their `NodeSpec`. They always behaved this way and
|
|
14
|
+
the burndown ledger always said so, but the flag was never set — so the
|
|
15
|
+
coverage board counted them UNCOVERED and the boundary lint could not tell a
|
|
16
|
+
deliberate pass-through from a node that simply forgot. **Dynamic is a
|
|
17
|
+
declaration, not a gap**, and it is what the palette reads to say "depends on
|
|
18
|
+
your setup" instead of "shape not declared" (register item §9.3). Adding a
|
|
19
|
+
pass-through node here without the flag now FAILS CI
|
|
20
|
+
(`scripts/check_kind_boundaries.py`, `NODE_OUTPUT_UNDECLARED`).
|
|
21
|
+
|
|
12
22
|
## The durable timer (`flow.sleep` / `flow.wait_until`)
|
|
13
23
|
|
|
14
24
|
A sleep that blocks a worker coroutine dies with the worker. Ours is the
|
|
@@ -68,6 +68,12 @@ class NoopExecutor:
|
|
|
68
68
|
HandleSpec(id="in", direction=HandleDirection.INPUT, required=False),
|
|
69
69
|
HandleSpec(id="out", direction=HandleDirection.OUTPUT, required=False),
|
|
70
70
|
),
|
|
71
|
+
# PASS-THROUGH BY DESIGN — the payload flows out byte-identical, so
|
|
72
|
+
# this node's real output shape is whatever reached it. Declared
|
|
73
|
+
# dynamic (GAP 2, KINDS_EVERYWHERE_PLAN §10g) so the coverage board
|
|
74
|
+
# and the boundary lint read the same posture the burndown ledger
|
|
75
|
+
# already recorded; runtime __kind propagation carries identity.
|
|
76
|
+
dynamic_output=True,
|
|
71
77
|
determinism=ActionTier.PURE,
|
|
72
78
|
icon="circle-dot",
|
|
73
79
|
)
|
|
@@ -132,6 +132,11 @@ class SleepExecutor:
|
|
|
132
132
|
HandleSpec(id="in", direction=HandleDirection.INPUT, required=False),
|
|
133
133
|
HandleSpec(id="out", direction=HandleDirection.OUTPUT, required=False),
|
|
134
134
|
),
|
|
135
|
+
# PASS-THROUGH BY DESIGN — the payload flows through unchanged (see
|
|
136
|
+
# module docstring). Declared dynamic (GAP 2, KINDS_EVERYWHERE_PLAN
|
|
137
|
+
# §10g) so runtime __kind propagation is the recorded posture rather
|
|
138
|
+
# than an undeclared NULL.
|
|
139
|
+
dynamic_output=True,
|
|
135
140
|
determinism=ActionTier.NON_DETERMINISTIC,
|
|
136
141
|
can_interrupt=True,
|
|
137
142
|
icon="clock",
|
|
@@ -195,6 +200,11 @@ class WaitUntilExecutor:
|
|
|
195
200
|
HandleSpec(id="in", direction=HandleDirection.INPUT, required=False),
|
|
196
201
|
HandleSpec(id="out", direction=HandleDirection.OUTPUT, required=False),
|
|
197
202
|
),
|
|
203
|
+
# PASS-THROUGH BY DESIGN — the payload flows through unchanged (see
|
|
204
|
+
# module docstring). Declared dynamic (GAP 2, KINDS_EVERYWHERE_PLAN
|
|
205
|
+
# §10g) so runtime __kind propagation is the recorded posture rather
|
|
206
|
+
# than an undeclared NULL.
|
|
207
|
+
dynamic_output=True,
|
|
198
208
|
determinism=ActionTier.NON_DETERMINISTIC,
|
|
199
209
|
can_interrupt=True,
|
|
200
210
|
icon="calendar-clock",
|
|
@@ -88,6 +88,11 @@ class ThrottleExecutor:
|
|
|
88
88
|
HandleSpec(id="in", direction=HandleDirection.INPUT, required=False),
|
|
89
89
|
HandleSpec(id="out", direction=HandleDirection.OUTPUT, required=False),
|
|
90
90
|
),
|
|
91
|
+
# PASS-THROUGH BY DESIGN — the payload flows through unchanged (see
|
|
92
|
+
# module docstring). Declared dynamic (GAP 2, KINDS_EVERYWHERE_PLAN
|
|
93
|
+
# §10g) so runtime __kind propagation is the recorded posture rather
|
|
94
|
+
# than an undeclared NULL.
|
|
95
|
+
dynamic_output=True,
|
|
91
96
|
determinism=ActionTier.NON_DETERMINISTIC,
|
|
92
97
|
icon="gauge",
|
|
93
98
|
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# `log.*` — the workflow's own diagnostic record
|
|
2
|
+
|
|
3
|
+
Verified against `record.py`, `workflow.run_log` (live), and
|
|
4
|
+
`tests/test_log_record_node.py` on 2026-08-23.
|
|
5
|
+
|
|
6
|
+
One node: **`log.record`** — *"Record a Problem"*.
|
|
7
|
+
|
|
8
|
+
## Why it exists
|
|
9
|
+
|
|
10
|
+
A workflow that hit a serious problem and **worked around it** had exactly two
|
|
11
|
+
options: fail the whole run, or say nothing. The Agent Foundry took the second
|
|
12
|
+
— both Component Artisans refused to build a component, each with a correct
|
|
13
|
+
and well-argued diagnosis, and the run reported `completed` with no component
|
|
14
|
+
and no trace of why. The answer sat in an agent's prose and nothing was ever
|
|
15
|
+
going to read it (Arman, 2026-08-23).
|
|
16
|
+
|
|
17
|
+
## Why it is not `system_error`
|
|
18
|
+
|
|
19
|
+
Workflows are authored by **users**. A node that wrote into the platform's own
|
|
20
|
+
error queue would let any author inject rows into our operational alarm
|
|
21
|
+
surface. `workflow.run_log` is the user's own table: `ledger` variant,
|
|
22
|
+
org-scoped, readable by the run's owner, written only by the server. Genuine
|
|
23
|
+
platform defects still go to `system_error` through the server's own paths.
|
|
24
|
+
|
|
25
|
+
## The contract
|
|
26
|
+
|
|
27
|
+
| field | why it is separate |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `label` | the STABLE key — "this happened 40 times" needs a key with no run id or timestamp in it |
|
|
30
|
+
| `message` | the human sentence — one field cannot be both, which is why the table splits them too |
|
|
31
|
+
| `payload` + `payload_kind` | the evidence, verbatim, and its registered Shape when it has one. A diagnostic that throws away the evidence is a log line |
|
|
32
|
+
| `severity` | `debug`/`info`/`warning`/`error`/`critical`, CHECK-constrained; closed vocabulary |
|
|
33
|
+
|
|
34
|
+
Plus the provenance a diagnosis is useless without: `run_id`, `definition_id`,
|
|
35
|
+
`node_id`, `step`, `attempt`, `occurred_at`.
|
|
36
|
+
|
|
37
|
+
## Four invariants, each pinned by a test
|
|
38
|
+
|
|
39
|
+
1. **It never fails the run.** Every failure path returns `recorded=false` with
|
|
40
|
+
a reason and the workflow carries on. A recorder that can break the run it
|
|
41
|
+
is reporting on would make authors stop reaching for it — the only way this
|
|
42
|
+
node can truly fail.
|
|
43
|
+
2. **It returns a typed receipt** (`run_log_entry`), whose FIRST field is
|
|
44
|
+
`recorded`. Arman's rule for agents binds our own nodes identically: *"a key
|
|
45
|
+
that confirms success, and that is the key you read."* `recorded=false`
|
|
46
|
+
means the problem happened AND we failed to write it down — strictly worse
|
|
47
|
+
than the problem, and branchable.
|
|
48
|
+
3. **The organization is the RUN's**, read off the run row (87/87 live runs
|
|
49
|
+
carry one), falling back to the request. Refuses only when neither has one —
|
|
50
|
+
the database never guesses (db-rules §0.9). *Reading it off `AppContext`
|
|
51
|
+
alone refused to write on the very first real run: a detached workflow
|
|
52
|
+
context legitimately carries no org.*
|
|
53
|
+
4. **Bounded.** `MAX_ENTRIES_PER_RUN` (200) caps a runaway loop, loudly once;
|
|
54
|
+
`MAX_PAYLOAD_CHARS` (100k) truncates fat evidence with a marker rather than
|
|
55
|
+
losing the record.
|
|
56
|
+
|
|
57
|
+
## Traps paid for
|
|
58
|
+
|
|
59
|
+
* **An input default masks a config.** `severity` is `Severity | None`, not
|
|
60
|
+
`Severity = "warning"` — with a non-null default, `inputs.severity or
|
|
61
|
+
config.severity` can never see the author's configured value. Caught on the
|
|
62
|
+
first live run: a step configured `error` recorded a `warning`. Absence must
|
|
63
|
+
be representable wherever a config can override.
|
|
64
|
+
|
|
65
|
+
## Proof
|
|
66
|
+
|
|
67
|
+
`uv run python scripts/_verify_run_log_node.py` — a real run through the real
|
|
68
|
+
Scheduler + PostgresCheckpointer against the live database, reading the row
|
|
69
|
+
back (10/10). Unit: `packages/matrx-graph/tests/test_log_record_node.py` (8).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""``log.*`` — the workflow's own diagnostic record.
|
|
2
|
+
|
|
3
|
+
A workflow that hits a serious problem and recovers from it must be able to
|
|
4
|
+
SAY SO. Before this pack it could only fail the run or stay silent, and
|
|
5
|
+
silence is what the Agent Foundry chose while shipping no component (Arman,
|
|
6
|
+
2026-08-23). Rows land in ``workflow.run_log`` — the USER's table, org-scoped
|
|
7
|
+
and read by the run's owner, never the platform's ``system_error`` queue.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from matrx_graph.nodes.log.record import (
|
|
11
|
+
MAX_ENTRIES_PER_RUN,
|
|
12
|
+
SEVERITIES,
|
|
13
|
+
RecordIssueExecutor,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = ["MAX_ENTRIES_PER_RUN", "SEVERITIES", "RecordIssueExecutor"]
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""``log.record`` — the workflow's own diagnostic record.
|
|
2
|
+
|
|
3
|
+
ARMAN, 2026-08-23, on the Agent Foundry failure that produced this node:
|
|
4
|
+
|
|
5
|
+
"we need to trigger some sort of node that is going to write to a
|
|
6
|
+
persistent place and render a very loud error… we need to give users a
|
|
7
|
+
way to be able to track their own internal logs and problems… a place
|
|
8
|
+
where you can pass it whatever thing you want, and then it will write to
|
|
9
|
+
the database in a table that can later be used to diagnose problems that
|
|
10
|
+
happened within a workflow that you were able to work around, but should
|
|
11
|
+
never have happened."
|
|
12
|
+
|
|
13
|
+
Nothing on the platform could do this. A workflow that hit a serious problem
|
|
14
|
+
and recovered from it had exactly two options — fail the whole run, or say
|
|
15
|
+
nothing at all. The Foundry took the second: both Component Artisans refused
|
|
16
|
+
to build a component, each for a correct and well-argued reason, and the run
|
|
17
|
+
reported `completed` with no component and no trace of why. The diagnosis was
|
|
18
|
+
sitting in an agent's prose and nothing was ever going to read it.
|
|
19
|
+
|
|
20
|
+
WHY THIS IS NOT `system_error`
|
|
21
|
+
------------------------------
|
|
22
|
+
Workflows are authored by USERS. A node that wrote into the platform's own
|
|
23
|
+
error queue would let any author inject rows into our operational alarm
|
|
24
|
+
surface — noise at best, a channel for abuse at worst. `workflow.run_log` is
|
|
25
|
+
the user's own diagnostic table: org-scoped, readable by the run's owner
|
|
26
|
+
(`ledger` RLS variant), written only by the server. Genuine PLATFORM defects
|
|
27
|
+
still go to `system_error` through the server's own paths; this is for
|
|
28
|
+
"something happened inside MY workflow that shouldn't have."
|
|
29
|
+
|
|
30
|
+
WHAT MAKES IT USEFUL RATHER THAN A SHRUG
|
|
31
|
+
----------------------------------------
|
|
32
|
+
* **`label` is a stable key, `message` is the sentence.** Grouping "this
|
|
33
|
+
happened 40 times" needs a key that does not embed a run id or a timestamp;
|
|
34
|
+
explaining it to a human needs a sentence. Asking one field to be both gives
|
|
35
|
+
a table you cannot query and prose you cannot read.
|
|
36
|
+
* **`payload` takes whatever you have** — and `payload_kind` records what that
|
|
37
|
+
was, when it is a registered kind. A diagnostic that throws away the
|
|
38
|
+
evidence is a log line, not a record.
|
|
39
|
+
* **The node never fails the run.** It exists for the case where the workflow
|
|
40
|
+
DID work around the problem. A recorder that can break the run it is
|
|
41
|
+
reporting on would make authors stop reaching for it, which is the only way
|
|
42
|
+
this node can truly fail.
|
|
43
|
+
* **It returns a typed receipt** (`run_log_entry`), not prose — so a
|
|
44
|
+
downstream step can branch on `recorded` and the run can prove it told
|
|
45
|
+
somebody.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
from __future__ import annotations
|
|
49
|
+
|
|
50
|
+
import logging
|
|
51
|
+
from typing import Any, ClassVar, Literal
|
|
52
|
+
|
|
53
|
+
from pydantic import BaseModel, ConfigDict, Field, JsonValue
|
|
54
|
+
|
|
55
|
+
from matrx_graph.types.context import NodeExecutionContext
|
|
56
|
+
from matrx_graph.types.handle import HandleDirection, HandleSpec
|
|
57
|
+
from matrx_graph.types.node_spec import NodeSpec
|
|
58
|
+
from matrx_graph.types.primitives import ActionTier, NodeCategory
|
|
59
|
+
from matrx_graph.types.result import NodeResult, success
|
|
60
|
+
from matrx_graph.types.usl import field_extras
|
|
61
|
+
|
|
62
|
+
log = logging.getLogger(__name__)
|
|
63
|
+
|
|
64
|
+
Severity = Literal["debug", "info", "warning", "error", "critical"]
|
|
65
|
+
|
|
66
|
+
#: Mirrors the `run_log_severity_check` CHECK constraint. A closed, code-level
|
|
67
|
+
#: vocabulary (db-rules §0.4) — adding a sixth is a deliberate migration, not a
|
|
68
|
+
#: row somebody types.
|
|
69
|
+
SEVERITIES: tuple[str, ...] = ("debug", "info", "warning", "error", "critical")
|
|
70
|
+
|
|
71
|
+
#: One record is a diagnostic; ten thousand is an outage in the logger. A run
|
|
72
|
+
#: that trips this is looping, and the cap makes that visible instead of
|
|
73
|
+
#: filling the table.
|
|
74
|
+
MAX_ENTRIES_PER_RUN = 200
|
|
75
|
+
|
|
76
|
+
#: Payloads are evidence, not archives. Anything past this is truncated with a
|
|
77
|
+
#: marker rather than refused — losing the record because the evidence was fat
|
|
78
|
+
#: is the wrong trade.
|
|
79
|
+
MAX_PAYLOAD_CHARS = 100_000
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RecordIssueInput(BaseModel):
|
|
83
|
+
model_config = ConfigDict(extra="forbid")
|
|
84
|
+
|
|
85
|
+
label: str = Field(
|
|
86
|
+
min_length=1,
|
|
87
|
+
max_length=200,
|
|
88
|
+
description=(
|
|
89
|
+
"A short, STABLE name for this problem — the same words every time "
|
|
90
|
+
"it happens (e.g. 'component_not_written'). This is what lets you "
|
|
91
|
+
"ask 'how often does this happen?' later."
|
|
92
|
+
),
|
|
93
|
+
json_schema_extra=field_extras(widget="text", placeholder="component_not_written"),
|
|
94
|
+
)
|
|
95
|
+
message: str = Field(
|
|
96
|
+
default="",
|
|
97
|
+
description=(
|
|
98
|
+
"What happened, in a sentence a person can read. Defaults to the "
|
|
99
|
+
"label when you have nothing more to say."
|
|
100
|
+
),
|
|
101
|
+
json_schema_extra=field_extras(widget="textarea", multiline_rows=2),
|
|
102
|
+
)
|
|
103
|
+
# NULLABLE ON PURPOSE. A non-null default here is indistinguishable from
|
|
104
|
+
# the author choosing "warning", so `inputs.severity or config.severity`
|
|
105
|
+
# made the step's own configured severity unreachable — caught on the
|
|
106
|
+
# first live run, which recorded a 'warning' for a step configured
|
|
107
|
+
# 'error'. Absence must be representable wherever a config can override.
|
|
108
|
+
severity: Severity | None = Field(
|
|
109
|
+
default=None,
|
|
110
|
+
description=(
|
|
111
|
+
"How bad it is. 'warning' = the workflow carried on but something "
|
|
112
|
+
"was wrong; 'error'/'critical' = the result is compromised even "
|
|
113
|
+
"though the run continued. Falls back to the step's configured "
|
|
114
|
+
"severity."
|
|
115
|
+
),
|
|
116
|
+
json_schema_extra=field_extras(widget="select"),
|
|
117
|
+
)
|
|
118
|
+
payload: JsonValue = Field(
|
|
119
|
+
default=None,
|
|
120
|
+
description=(
|
|
121
|
+
"Anything you want kept as evidence — the agent's own answer, the "
|
|
122
|
+
"value that failed, the step's inputs. Stored verbatim."
|
|
123
|
+
),
|
|
124
|
+
json_schema_extra=field_extras(widget="json"),
|
|
125
|
+
)
|
|
126
|
+
payload_kind: str | None = Field(
|
|
127
|
+
default=None,
|
|
128
|
+
description=(
|
|
129
|
+
"The registered Shape of the evidence, when it has one — so the "
|
|
130
|
+
"record renders through that Shape's component later instead of a "
|
|
131
|
+
"key/value dump."
|
|
132
|
+
),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class RunLogEntry(BaseModel):
|
|
137
|
+
"""The receipt. Mirrored by the registered ``run_log_entry`` kind."""
|
|
138
|
+
|
|
139
|
+
model_config = ConfigDict(extra="forbid")
|
|
140
|
+
|
|
141
|
+
recorded: bool = Field(
|
|
142
|
+
description=(
|
|
143
|
+
"True when the record landed in the database. FALSE means the "
|
|
144
|
+
"problem happened AND we failed to write it down — branch on this."
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
entry_id: str | None = Field(default=None, description="The workflow.run_log row id.")
|
|
148
|
+
label: str = ""
|
|
149
|
+
severity: str = "warning"
|
|
150
|
+
message: str = ""
|
|
151
|
+
occurred_at: str | None = None
|
|
152
|
+
#: Populated only when `recorded` is false — why the write did not land.
|
|
153
|
+
not_recorded_reason: str | None = None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class RecordIssueConfig(BaseModel):
|
|
157
|
+
model_config = ConfigDict(extra="forbid")
|
|
158
|
+
|
|
159
|
+
label: str = Field(
|
|
160
|
+
default="",
|
|
161
|
+
description=(
|
|
162
|
+
"Author-time label, used when the step is not given one at run "
|
|
163
|
+
"time. The input wins when both are present."
|
|
164
|
+
),
|
|
165
|
+
json_schema_extra=field_extras(widget="text"),
|
|
166
|
+
)
|
|
167
|
+
severity: Severity = Field(
|
|
168
|
+
default="warning",
|
|
169
|
+
json_schema_extra=field_extras(widget="select"),
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
#: Per-run entry counters, process-local. A hard cap needs no durability: the
|
|
174
|
+
#: point is to stop one runaway loop from writing a million rows in one
|
|
175
|
+
#: process, and a restart legitimately starts a fresh budget.
|
|
176
|
+
_entry_counts: dict[str, int] = {}
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _truncate(value: JsonValue) -> tuple[JsonValue, bool]:
|
|
180
|
+
import json
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
rendered = json.dumps(value, default=str)
|
|
184
|
+
except Exception: # noqa: BLE001 — evidence is never worth an exception
|
|
185
|
+
return {"unserializable": repr(value)[:2000]}, True
|
|
186
|
+
if len(rendered) <= MAX_PAYLOAD_CHARS:
|
|
187
|
+
return value, False
|
|
188
|
+
return {
|
|
189
|
+
"truncated": True,
|
|
190
|
+
"original_chars": len(rendered),
|
|
191
|
+
"preview": rendered[:MAX_PAYLOAD_CHARS],
|
|
192
|
+
}, True
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class RecordIssueExecutor:
|
|
196
|
+
"""Write one diagnostic row. Never fails the run."""
|
|
197
|
+
|
|
198
|
+
spec: ClassVar[NodeSpec] = NodeSpec(
|
|
199
|
+
type="log.record",
|
|
200
|
+
category=NodeCategory.DATA,
|
|
201
|
+
display_name="Record a Problem",
|
|
202
|
+
description=(
|
|
203
|
+
"Write down something that went wrong, with whatever evidence you "
|
|
204
|
+
"have, so you can find it later. The workflow keeps going."
|
|
205
|
+
),
|
|
206
|
+
input_schema=RecordIssueInput,
|
|
207
|
+
output_schema=RunLogEntry,
|
|
208
|
+
config_schema=RecordIssueConfig,
|
|
209
|
+
handles=(
|
|
210
|
+
HandleSpec(id="in", direction=HandleDirection.INPUT, required=False),
|
|
211
|
+
HandleSpec(id="out", direction=HandleDirection.OUTPUT, required=False),
|
|
212
|
+
),
|
|
213
|
+
determinism=ActionTier.SIDE_EFFECT,
|
|
214
|
+
icon="clipboard-alert",
|
|
215
|
+
output_kind="run_log_entry",
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
async def execute(
|
|
219
|
+
self,
|
|
220
|
+
ctx: NodeExecutionContext,
|
|
221
|
+
inputs: RecordIssueInput,
|
|
222
|
+
config: RecordIssueConfig,
|
|
223
|
+
) -> NodeResult[RunLogEntry]:
|
|
224
|
+
label = (inputs.label or config.label or "").strip() or "unlabelled_issue"
|
|
225
|
+
severity = inputs.severity or config.severity or "warning"
|
|
226
|
+
message = (inputs.message or "").strip() or label
|
|
227
|
+
|
|
228
|
+
seen = _entry_counts.get(ctx.run_id, 0)
|
|
229
|
+
if seen >= MAX_ENTRIES_PER_RUN:
|
|
230
|
+
# Loud once, then quiet — a capped logger that keeps logging its
|
|
231
|
+
# own cap is the same runaway with extra steps.
|
|
232
|
+
if seen == MAX_ENTRIES_PER_RUN:
|
|
233
|
+
_entry_counts[ctx.run_id] = seen + 1
|
|
234
|
+
log.error(
|
|
235
|
+
"[wf:%s] log.record hit %d entries — capping. The run is looping on '%s'.",
|
|
236
|
+
ctx.run_id[:8],
|
|
237
|
+
MAX_ENTRIES_PER_RUN,
|
|
238
|
+
label,
|
|
239
|
+
)
|
|
240
|
+
return success(
|
|
241
|
+
RunLogEntry(
|
|
242
|
+
recorded=False,
|
|
243
|
+
label=label,
|
|
244
|
+
severity=severity,
|
|
245
|
+
message=message,
|
|
246
|
+
not_recorded_reason=(
|
|
247
|
+
f"this run already recorded {MAX_ENTRIES_PER_RUN} entries"
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
_entry_counts[ctx.run_id] = seen + 1
|
|
252
|
+
|
|
253
|
+
payload, truncated = _truncate(inputs.payload)
|
|
254
|
+
if truncated:
|
|
255
|
+
message = f"{message} (evidence truncated)"
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
from datetime import UTC, datetime
|
|
259
|
+
|
|
260
|
+
from matrx_graph.db._registry import get_model
|
|
261
|
+
|
|
262
|
+
# THE ORG IS THE RUN'S, not whatever AppContext happens to carry.
|
|
263
|
+
# An explicit organization is required on every write and the
|
|
264
|
+
# database never chooses one (db-rules §0.9) — but the authority
|
|
265
|
+
# here is the run row, which always has one (verified live: 87/87
|
|
266
|
+
# runs over three days). Reading it off AppContext refused to
|
|
267
|
+
# write on the very first real run, because a background/detached
|
|
268
|
+
# workflow context legitimately carries no org.
|
|
269
|
+
definition_id: str | None = None
|
|
270
|
+
org: str | None = None
|
|
271
|
+
run_rows = await (
|
|
272
|
+
get_model("WfRun").filter(id=ctx.run_id).values("definition_id", "organization_id")
|
|
273
|
+
)
|
|
274
|
+
if run_rows:
|
|
275
|
+
raw_def = run_rows[0].get("definition_id")
|
|
276
|
+
definition_id = str(raw_def) if raw_def else None
|
|
277
|
+
raw_org = run_rows[0].get("organization_id")
|
|
278
|
+
org = str(raw_org) if raw_org else None
|
|
279
|
+
org = org or ctx.organization_id
|
|
280
|
+
if not org:
|
|
281
|
+
return success(
|
|
282
|
+
RunLogEntry(
|
|
283
|
+
recorded=False,
|
|
284
|
+
label=label,
|
|
285
|
+
severity=severity,
|
|
286
|
+
message=message,
|
|
287
|
+
not_recorded_reason=(
|
|
288
|
+
"neither the run row nor the request carries an "
|
|
289
|
+
"organization — refusing to let the database guess one"
|
|
290
|
+
),
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
now = datetime.now(UTC)
|
|
295
|
+
row: Any = await get_model("WfRunLog").create_item(
|
|
296
|
+
run_id=ctx.run_id,
|
|
297
|
+
definition_id=definition_id,
|
|
298
|
+
node_id=ctx.node_id,
|
|
299
|
+
step=ctx.step,
|
|
300
|
+
attempt=ctx.attempt,
|
|
301
|
+
severity=severity,
|
|
302
|
+
label=label,
|
|
303
|
+
message=message,
|
|
304
|
+
payload={"value": payload} if not isinstance(payload, dict) else payload,
|
|
305
|
+
payload_kind=(inputs.payload_kind or "").strip() or None,
|
|
306
|
+
occurred_at=now,
|
|
307
|
+
organization_id=str(org),
|
|
308
|
+
created_by=str(ctx.user_id) if ctx.user_id else None,
|
|
309
|
+
)
|
|
310
|
+
entry_id = str(getattr(row, "id", "") or "") or None
|
|
311
|
+
|
|
312
|
+
# LOUD on the server too. The row is the durable record; this line
|
|
313
|
+
# is what an operator greps at 3am before anybody opens a board.
|
|
314
|
+
log.warning(
|
|
315
|
+
"[wf:%s node=%s] RUN LOG %s — %s: %s (entry %s)",
|
|
316
|
+
ctx.run_id[:8],
|
|
317
|
+
ctx.node_id,
|
|
318
|
+
severity.upper(),
|
|
319
|
+
label,
|
|
320
|
+
message[:300],
|
|
321
|
+
entry_id,
|
|
322
|
+
)
|
|
323
|
+
return success(
|
|
324
|
+
RunLogEntry(
|
|
325
|
+
recorded=True,
|
|
326
|
+
entry_id=entry_id,
|
|
327
|
+
label=label,
|
|
328
|
+
severity=severity,
|
|
329
|
+
message=message,
|
|
330
|
+
occurred_at=now.isoformat(),
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
except Exception as exc: # noqa: BLE001 — the recorder never fails the run
|
|
334
|
+
log.exception(
|
|
335
|
+
"[wf:%s node=%s] log.record could NOT write '%s'",
|
|
336
|
+
ctx.run_id[:8],
|
|
337
|
+
ctx.node_id,
|
|
338
|
+
label,
|
|
339
|
+
)
|
|
340
|
+
return success(
|
|
341
|
+
RunLogEntry(
|
|
342
|
+
recorded=False,
|
|
343
|
+
label=label,
|
|
344
|
+
severity=severity,
|
|
345
|
+
message=message,
|
|
346
|
+
not_recorded_reason=f"{type(exc).__name__}: {exc}"[:500],
|
|
347
|
+
)
|
|
348
|
+
)
|
|
@@ -7,7 +7,9 @@ Verified against `to_frontend.py`, `matrx_graph/types/events.py`
|
|
|
7
7
|
One node: `output.to_frontend` ("Show on Screen"). It fires a non-blocking
|
|
8
8
|
`node_emitted` event (recorded in `workflow.node_events`, so a re-opened run
|
|
9
9
|
re-renders it) and passes its input through UNCHANGED — it sits on any edge
|
|
10
|
-
without altering routing, and declares no `output_kind`
|
|
10
|
+
without altering routing, and declares no `output_kind` — it declares
|
|
11
|
+
`dynamic_output=True` instead (2026-08-23, GAP 2: the honest posture is a
|
|
12
|
+
DECLARATION, not silence), so runtime `__kind`
|
|
11
13
|
propagation carries the payload's identity). Emission failures are logged,
|
|
12
14
|
never load-bearing — the `ctx.progress` contract.
|
|
13
15
|
|