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.
Files changed (330) hide show
  1. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/PKG-INFO +1 -1
  2. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/db_requirements.py +4 -0
  3. matrx_graph-0.2.78/matrx_graph/db/migrations/0110_run_log.sql +66 -0
  4. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/__init__.py +3 -0
  5. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/FEATURE.md +22 -0
  6. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/transform.py +5 -0
  7. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/FEATURE.md +10 -0
  8. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/noop.py +6 -0
  9. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/sleep.py +10 -0
  10. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/throttle.py +5 -0
  11. matrx_graph-0.2.78/matrx_graph/nodes/log/FEATURE.md +69 -0
  12. matrx_graph-0.2.78/matrx_graph/nodes/log/__init__.py +16 -0
  13. matrx_graph-0.2.78/matrx_graph/nodes/log/record.py +348 -0
  14. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/FEATURE.md +3 -1
  15. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/to_frontend.py +5 -0
  16. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/pyproject.toml +1 -1
  17. matrx_graph-0.2.78/tests/test_log_record_node.py +299 -0
  18. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/.gitignore +0 -0
  19. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/AGENTS.md +0 -0
  20. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/CLAUDE.md +0 -0
  21. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/README.md +0 -0
  22. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/ENGINE_SPEC.md +0 -0
  23. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/GAPS_AND_ROADMAP.md +0 -0
  24. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/OPERATING_PRIORITIES.md +0 -0
  25. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/README.md +0 -0
  26. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/RUN_LIFECYCLE.md +0 -0
  27. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/WORKFLOW_ARCHITECTURE.md +0 -0
  28. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/action-authoring.md +0 -0
  29. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/architecture.md +0 -0
  30. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/node-authoring.md +0 -0
  31. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/docs/testing.md +0 -0
  32. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/__init__.py +0 -0
  33. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/ACTION_STANDARDS.md +0 -0
  34. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/__init__.py +0 -0
  35. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/decorator.py +0 -0
  36. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/registry.py +0 -0
  37. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/actions/spec.py +0 -0
  38. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/__init__.py +0 -0
  39. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/memory.py +0 -0
  40. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/postgres.py +0 -0
  41. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/checkpoint/protocol.py +0 -0
  42. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/compile.py +0 -0
  43. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/composition.py +0 -0
  44. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/__init__.py +0 -0
  45. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/accessor.py +0 -0
  46. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/authoring_gate.py +0 -0
  47. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/availability.py +0 -0
  48. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/edge_routing.py +0 -0
  49. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/envelope.py +0 -0
  50. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/fingerprint.py +0 -0
  51. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/graph_adapter.py +0 -0
  52. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/markers.py +0 -0
  53. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/model.py +0 -0
  54. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/occurrences.py +0 -0
  55. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/partial.py +0 -0
  56. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/runtime_view.py +0 -0
  57. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/sdk.py +0 -0
  58. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/subscriptions.py +0 -0
  59. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/triggers.py +0 -0
  60. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/well_known.py +0 -0
  61. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/content_ir/wrappers.py +0 -0
  62. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contract_kinds.py +0 -0
  63. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contracts/__init__.py +0 -0
  64. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/contracts/audit.py +0 -0
  65. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/FEATURE.md +0 -0
  66. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/__init__.py +0 -0
  67. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/_registry.py +0 -0
  68. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/definition_store.py +0 -0
  69. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/event_store.py +0 -0
  70. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0020_wf_run_announce.sql +0 -0
  71. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0021_join_semantics.sql +0 -0
  72. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0022_node_data_slot.sql +0 -0
  73. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0023_fanout_identity.sql +0 -0
  74. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0024_definition_variables.sql +0 -0
  75. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0100_baseline_post_reorg.sql +0 -0
  76. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0101_run_request_attribution.sql +0 -0
  77. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0102_visibility_and_kind_verdicts.sql +0 -0
  78. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0103_work_item.sql +0 -0
  79. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0104_soft_delete_columns.sql +0 -0
  80. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0105_plan.sql +0 -0
  81. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0106_definition_visibility_internal_default.sql +0 -0
  82. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0107_plan_anchor_integrity.sql +0 -0
  83. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0108_plan_persistence_invariants.sql +0 -0
  84. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/0109_node_data_slot_created_by.sql +0 -0
  85. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/README.md +0 -0
  86. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0001_wf_initial.sql +0 -0
  87. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0002_wf_queue.sql +0 -0
  88. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0003_wf_node_events.sql +0 -0
  89. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0004_wf_trigger.sql +0 -0
  90. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0005_wf_rls.sql +0 -0
  91. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0006_wf_run_cancelling.sql +0 -0
  92. {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
  93. {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
  94. {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
  95. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0010_wf_idempotency.sql +0 -0
  96. {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
  97. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0012_wf_trigger_fire.sql +0 -0
  98. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0013_wf_recovery_audit.sql +0 -0
  99. {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
  100. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0016_wf_template.sql +0 -0
  101. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/0017_wf_hardening.sql +0 -0
  102. {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
  103. {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
  104. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/migrations/legacy_pre_reorg/README.md +0 -0
  105. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/node_data_slot_store.py +0 -0
  106. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/pool.py +0 -0
  107. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/run_store.py +0 -0
  108. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/standalone.py +0 -0
  109. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/trigger_store.py +0 -0
  110. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/db/wf_manager.py +0 -0
  111. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/decisions.py +0 -0
  112. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/diagnostics/__init__.py +0 -0
  113. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/diagnostics/compile_validation.py +0 -0
  114. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/dry_run.py +0 -0
  115. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/errors.py +0 -0
  116. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/examples/__init__.py +0 -0
  117. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/examples/self_validating_news.py +0 -0
  118. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/FEATURE.md +0 -0
  119. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/__init__.py +0 -0
  120. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/_retry.py +0 -0
  121. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/channels.py +0 -0
  122. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/client_payload.py +0 -0
  123. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/introspect.py +0 -0
  124. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/json_dump.py +0 -0
  125. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/primitives.py +0 -0
  126. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/registry.py +0 -0
  127. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/scheduler.py +0 -0
  128. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/executor/schema_validation.py +0 -0
  129. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/extract.py +0 -0
  130. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/failure.py +0 -0
  131. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/kinds.py +0 -0
  132. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/_sandbox.py +0 -0
  133. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/_template.py +0 -0
  134. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/FEATURE.md +0 -0
  135. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/__init__.py +0 -0
  136. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/branch.py +0 -0
  137. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/gather.py +0 -0
  138. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/human_input.py +0 -0
  139. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/loop.py +0 -0
  140. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/map.py +0 -0
  141. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/send.py +0 -0
  142. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/work_queue.py +0 -0
  143. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/control/work_seed.py +0 -0
  144. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/crypto/__init__.py +0 -0
  145. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/crypto/ops.py +0 -0
  146. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/__init__.py +0 -0
  147. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/assert_.py +0 -0
  148. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/collections.py +0 -0
  149. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/criteria_gate.py +0 -0
  150. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/filter.py +0 -0
  151. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/flatten.py +0 -0
  152. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/json_io.py +0 -0
  153. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/map_template.py +0 -0
  154. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/merge.py +0 -0
  155. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/pick_omit.py +0 -0
  156. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/data/tabular.py +0 -0
  157. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/datetime_/__init__.py +0 -0
  158. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/datetime_/ops.py +0 -0
  159. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/flow/__init__.py +0 -0
  160. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/__init__.py +0 -0
  161. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/_shared.py +0 -0
  162. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/custom_api.py +0 -0
  163. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/get.py +0 -0
  164. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/graphql.py +0 -0
  165. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/http/post.py +0 -0
  166. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/io/__init__.py +0 -0
  167. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/io/user_input.py +0 -0
  168. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/math_/__init__.py +0 -0
  169. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/math_/evaluate.py +0 -0
  170. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/output/__init__.py +0 -0
  171. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/FEATURE.md +0 -0
  172. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/__init__.py +0 -0
  173. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/contract.py +0 -0
  174. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/pipe/step.py +0 -0
  175. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/plan/__init__.py +0 -0
  176. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/plan/step.py +0 -0
  177. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/subgraph/__init__.py +0 -0
  178. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/subgraph/call.py +0 -0
  179. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/__init__.py +0 -0
  180. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/custom_extract.py +0 -0
  181. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/json_path.py +0 -0
  182. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/manipulate.py +0 -0
  183. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/regex.py +0 -0
  184. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/text/render_list.py +0 -0
  185. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/tool/__init__.py +0 -0
  186. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/nodes/tool/call.py +0 -0
  187. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/observability/__init__.py +0 -0
  188. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/patch.py +0 -0
  189. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/py.typed +0 -0
  190. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/references.py +0 -0
  191. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/__init__.py +0 -0
  192. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/canonical_payloads.py +0 -0
  193. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/channel.py +0 -0
  194. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/compiled.py +0 -0
  195. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/context.py +0 -0
  196. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/definition.py +0 -0
  197. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/events.py +0 -0
  198. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/handle.py +0 -0
  199. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/node_spec.py +0 -0
  200. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/primitives.py +0 -0
  201. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/result.py +0 -0
  202. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/run.py +0 -0
  203. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/send.py +0 -0
  204. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/usl.py +0 -0
  205. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/value_objects.py +0 -0
  206. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/types/variables.py +0 -0
  207. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/validation.py +0 -0
  208. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/FEATURE.md +0 -0
  209. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/__init__.py +0 -0
  210. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/cron_watcher.py +0 -0
  211. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/queue.py +0 -0
  212. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/runner.py +0 -0
  213. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workers/sleep_wake.py +0 -0
  214. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/matrx_graph/workset.py +0 -0
  215. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/__init__.py +0 -0
  216. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/_db_guard.py +0 -0
  217. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/_fixtures.py +0 -0
  218. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/00_supabase_shim.sql +0 -0
  219. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/10_canonical_reorg_shim.sql +0 -0
  220. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/ci/bootstrap_test_db.sh +0 -0
  221. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/conftest.py +0 -0
  222. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/judge/__init__.py +0 -0
  223. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/judge/test_self_validating_news_e2e.py +0 -0
  224. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/level2/__init__.py +0 -0
  225. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_action_output_handles.py +0 -0
  226. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_actions.py +0 -0
  227. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_engine_core.py +0 -0
  228. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_kind_routing.py +0 -0
  229. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_partial_wire.py +0 -0
  230. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_adversarial_registry.py +0 -0
  231. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_authoring_gate.py +0 -0
  232. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_back_edges.py +0 -0
  233. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_barrier_removed.py +0 -0
  234. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_channel.py +0 -0
  235. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_channels_reducers.py +0 -0
  236. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_checkpoint_summaries.py +0 -0
  237. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_envelope.py +0 -0
  238. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_markers.py +0 -0
  239. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_model.py +0 -0
  240. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_partial.py +0 -0
  241. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_content_ir_runtime_view.py +0 -0
  242. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_context_propagation.py +0 -0
  243. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_artifact_eviction.py +0 -0
  244. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_audit.py +0 -0
  245. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_contract_kinds.py +0 -0
  246. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_criteria_gate.py +0 -0
  247. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_data_collections.py +0 -0
  248. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_data_nodes.py +0 -0
  249. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_db_guard.py +0 -0
  250. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition.py +0 -0
  251. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition_store_list_skip.py +0 -0
  252. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_definition_store_version_load.py +0 -0
  253. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dry_run.py +0 -0
  254. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dry_run_source_input.py +0 -0
  255. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_dynamic_topology_guards.py +0 -0
  256. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_end_to_end.py +0 -0
  257. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_extract.py +0 -0
  258. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_failure.py +0 -0
  259. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_flow_nodes.py +0 -0
  260. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_http_nodes.py +0 -0
  261. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_ingestion_gate.py +0 -0
  262. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_invocation_budget.py +0 -0
  263. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_io_nodes.py +0 -0
  264. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_json_dump.py +0 -0
  265. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_accessor.py +0 -0
  266. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_availability.py +0 -0
  267. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_enforcement.py +0 -0
  268. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_native_node_library.py +0 -0
  269. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_routing.py +0 -0
  270. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kind_sdk.py +0 -0
  271. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_kinds.py +0 -0
  272. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_list_include_ids.py +0 -0
  273. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_list_shaping_nodes.py +0 -0
  274. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_live_injection.py +0 -0
  275. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_loop_scale.py +0 -0
  276. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_map_payload_template.py +0 -0
  277. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_multi_dispatch_fanin.py +0 -0
  278. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_cost_event.py +0 -0
  279. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_data_slot_store.py +0 -0
  280. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_data_slot_validation.py +0 -0
  281. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_error_kind.py +0 -0
  282. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_input_capture.py +0 -0
  283. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_result_envelope.py +0 -0
  284. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_node_spec.py +0 -0
  285. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_output_channel_binding.py +0 -0
  286. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_patch.py +0 -0
  287. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_pause_cancel_and_recovery.py +0 -0
  288. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_pipe_step.py +0 -0
  289. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_plan_identity_consistency.py +0 -0
  290. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_plan_step.py +0 -0
  291. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_postgres_checkpointer.py +0 -0
  292. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_postgres_workset_store.py +0 -0
  293. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_primitives.py +0 -0
  294. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_public_media_scope.py +0 -0
  295. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_references.py +0 -0
  296. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_registry.py +0 -0
  297. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_resume_payload_folding.py +0 -0
  298. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_conversation_identity.py +0 -0
  299. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_store_control.py +0 -0
  300. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_run_variables.py +0 -0
  301. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_conditional_edge.py +0 -0
  302. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_config_override.py +0 -0
  303. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_events.py +0 -0
  304. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_failsoft.py +0 -0
  305. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_failure_cost_settlement.py +0 -0
  306. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_fanout_identity.py +0 -0
  307. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_fork_to_new_run.py +0 -0
  308. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_interrupt_siblings.py +0 -0
  309. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_join_hardening.py +0 -0
  310. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_joins.py +0 -0
  311. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_loop_contract.py +0 -0
  312. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_mock_response.py +0 -0
  313. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_node_retry_override.py +0 -0
  314. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_node_timeout.py +0 -0
  315. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_retry_policy.py +0 -0
  316. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_skip_edge_ids.py +0 -0
  317. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_scheduler_step_mode.py +0 -0
  318. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_self_validating_news.py +0 -0
  319. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_send_unwired_items_is_loud.py +0 -0
  320. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_standalone_db_models.py +0 -0
  321. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_subgraph_call.py +0 -0
  322. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_text_nodes.py +0 -0
  323. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_transform_output_shape.py +0 -0
  324. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_triggers_and_observability.py +0 -0
  325. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_utility_nodes.py +0 -0
  326. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_validation_hints.py +0 -0
  327. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_validation_s4.py +0 -0
  328. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_workers.py +0 -0
  329. {matrx_graph-0.2.76 → matrx_graph-0.2.78}/tests/test_workset.py +0 -0
  330. {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.76
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` (runtime `__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