dirigent-core 0.23.4__tar.gz → 0.25.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/PKG-INFO +4 -4
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/pyproject.toml +4 -4
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/pyproject.toml.orig +4 -4
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/alembic/versions/0001_baseline_schema.py +118 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/blockdocs.py +43 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/config.py +30 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/documents.py +55 -17
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/__init__.py +2 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/definition.py +176 -6
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/executor.py +14 -1
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/expansion.py +32 -3
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/references.py +12 -3
- dirigent_core-0.25.0/src/dirigent_core/engine/replies.py +194 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/runs.py +117 -4
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/services.py +8 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/state.py +154 -7
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/examples.py +31 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/messages.py +215 -13
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/models.py +88 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/pipelines.py +13 -1
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/plugins.py +33 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/ratelimit.py +9 -4
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/reporting.py +1 -1
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/retention.py +15 -2
- dirigent_core-0.25.0/src/dirigent_core/sources/__init__.py +1 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/endpoint.py +281 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/entries.py +79 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/intake.py +416 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/kinds.py +22 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/rows.py +407 -0
- dirigent_core-0.25.0/src/dirigent_core/sources/rules.py +310 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/telemetry.py +10 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/watches.py +2 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/wakeups.py +7 -2
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/LICENSE +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/README.md +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/__init__.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/alembic/env.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/alembic/script.py.mako +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/alerting.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/artifacts.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/auth.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/configdocs.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/database.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/directory.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/documentschema.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/claim.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/context.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/failure.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/engine/recovery.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/errors.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/ids.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/logging.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/migrations.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/protocol.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/py.typed +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/registry.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/scheduler.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/schemas.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/secrets.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/storage.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/trigger_documents.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/__init__.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/backfill.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/materialize.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/schedules.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/triggers/webhooks.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/types.py +0 -0
- {dirigent_core-0.23.4 → dirigent_core-0.25.0}/src/dirigent_core/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dirigent-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.25.0
|
|
4
4
|
Summary: Dirigent engine core: schema, configuration, and plugin host.
|
|
5
5
|
License-Expression: LicenseRef-Proprietary
|
|
6
6
|
License-File: LICENSE
|
|
@@ -12,9 +12,9 @@ Requires-Dist: argon2-cffi>=25.1.0
|
|
|
12
12
|
Requires-Dist: asyncpg>=0.31.0
|
|
13
13
|
Requires-Dist: cronsim>=2.7
|
|
14
14
|
Requires-Dist: cryptography>=46.0.5
|
|
15
|
-
Requires-Dist: dirigent-client==0.
|
|
16
|
-
Requires-Dist: dirigent-common==0.
|
|
17
|
-
Requires-Dist: dirigent-plugin==0.
|
|
15
|
+
Requires-Dist: dirigent-client==0.25.0
|
|
16
|
+
Requires-Dist: dirigent-common==0.25.0
|
|
17
|
+
Requires-Dist: dirigent-plugin==0.25.0
|
|
18
18
|
Requires-Dist: jsonschema>=4.26.0
|
|
19
19
|
Requires-Dist: opentelemetry-api>=1.44.0
|
|
20
20
|
Requires-Dist: pydantic-settings>=2.15.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-core"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.25.0"
|
|
4
4
|
description = "Dirigent engine core: schema, configuration, and plugin host."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -17,9 +17,9 @@ dependencies = [
|
|
|
17
17
|
"asyncpg>=0.31.0",
|
|
18
18
|
"cronsim>=2.7",
|
|
19
19
|
"cryptography>=46.0.5",
|
|
20
|
-
"dirigent-client==0.
|
|
21
|
-
"dirigent-common==0.
|
|
22
|
-
"dirigent-plugin==0.
|
|
20
|
+
"dirigent-client==0.25.0",
|
|
21
|
+
"dirigent-common==0.25.0",
|
|
22
|
+
"dirigent-plugin==0.25.0",
|
|
23
23
|
"jsonschema>=4.26.0",
|
|
24
24
|
"opentelemetry-api>=1.44.0",
|
|
25
25
|
"pydantic-settings>=2.15.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dirigent-core"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.25.0"
|
|
4
4
|
description = "Dirigent engine core: schema, configuration, and plugin host."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.13"
|
|
@@ -17,9 +17,9 @@ dependencies = [
|
|
|
17
17
|
"asyncpg>=0.31.0",
|
|
18
18
|
"cronsim>=2.7",
|
|
19
19
|
"cryptography>=46.0.5",
|
|
20
|
-
"dirigent-client==0.
|
|
21
|
-
"dirigent-common==0.
|
|
22
|
-
"dirigent-plugin==0.
|
|
20
|
+
"dirigent-client==0.25.0",
|
|
21
|
+
"dirigent-common==0.25.0",
|
|
22
|
+
"dirigent-plugin==0.25.0",
|
|
23
23
|
"jsonschema>=4.26.0",
|
|
24
24
|
"opentelemetry-api>=1.44.0",
|
|
25
25
|
"pydantic-settings>=2.15.0",
|
|
@@ -545,6 +545,71 @@ def upgrade() -> None:
|
|
|
545
545
|
batch_op.create_index(batch_op.f("ix_watches_pipeline_id"), ["pipeline_id"], unique=False)
|
|
546
546
|
batch_op.create_index(batch_op.f("ix_watches_trigger_document_id"), ["trigger_document_id"], unique=False)
|
|
547
547
|
|
|
548
|
+
op.create_table(
|
|
549
|
+
"sources",
|
|
550
|
+
sa.Column("id", sa.Uuid(), nullable=False),
|
|
551
|
+
sa.Column("pipeline_id", sa.Uuid(), nullable=False),
|
|
552
|
+
sa.Column("code", sa.String(length=200), nullable=False),
|
|
553
|
+
sa.Column("name", sa.String(length=200), nullable=True),
|
|
554
|
+
sa.Column("description", sa.Text(), nullable=True),
|
|
555
|
+
sa.Column("kind", sa.String(length=200), nullable=False),
|
|
556
|
+
sa.Column("config", sa.JSON().with_variant(postgresql.JSONB(astext_type=Text()), "postgresql"), nullable=False),
|
|
557
|
+
sa.Column("token_hash", sa.String(length=128), nullable=True),
|
|
558
|
+
sa.Column("token_prefix", sa.String(length=16), nullable=True),
|
|
559
|
+
sa.Column("paused", sa.Boolean(), server_default=sa.false(), nullable=False),
|
|
560
|
+
sa.Column("accepted", sa.Integer(), server_default=sa.text("0"), nullable=False),
|
|
561
|
+
sa.Column("refused", sa.Integer(), server_default=sa.text("0"), nullable=False),
|
|
562
|
+
sa.Column("last_input_at", dirigent_core.types.UtcDateTime(timezone=True), nullable=True),
|
|
563
|
+
sa.Column(
|
|
564
|
+
"created_at",
|
|
565
|
+
dirigent_core.types.UtcDateTime(timezone=True),
|
|
566
|
+
server_default=sa.func.now(),
|
|
567
|
+
nullable=False,
|
|
568
|
+
),
|
|
569
|
+
sa.Column(
|
|
570
|
+
"updated_at",
|
|
571
|
+
dirigent_core.types.UtcDateTime(timezone=True),
|
|
572
|
+
server_default=sa.func.now(),
|
|
573
|
+
nullable=False,
|
|
574
|
+
),
|
|
575
|
+
sa.ForeignKeyConstraint(
|
|
576
|
+
["pipeline_id"], ["pipelines.id"], name=op.f("fk_sources_pipeline_id_pipelines"), ondelete="CASCADE"
|
|
577
|
+
),
|
|
578
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_sources")),
|
|
579
|
+
sa.UniqueConstraint("pipeline_id", "code", name=op.f("uq_sources_pipeline_id_code")),
|
|
580
|
+
sa.UniqueConstraint("token_hash", name=op.f("uq_sources_token_hash")),
|
|
581
|
+
)
|
|
582
|
+
with op.batch_alter_table("sources", schema=None) as batch_op:
|
|
583
|
+
batch_op.create_index(batch_op.f("ix_sources_pipeline_id"), ["pipeline_id"], unique=False)
|
|
584
|
+
|
|
585
|
+
op.create_table(
|
|
586
|
+
"source_routes",
|
|
587
|
+
sa.Column("id", sa.Uuid(), nullable=False),
|
|
588
|
+
sa.Column("source_id", sa.Uuid(), nullable=False),
|
|
589
|
+
sa.Column("method", sa.String(length=16), nullable=False),
|
|
590
|
+
sa.Column("template", sa.String(length=1000), nullable=False),
|
|
591
|
+
sa.Column("normalised", sa.String(length=1000), nullable=False),
|
|
592
|
+
sa.Column(
|
|
593
|
+
"created_at",
|
|
594
|
+
dirigent_core.types.UtcDateTime(timezone=True),
|
|
595
|
+
server_default=sa.func.now(),
|
|
596
|
+
nullable=False,
|
|
597
|
+
),
|
|
598
|
+
sa.Column(
|
|
599
|
+
"updated_at",
|
|
600
|
+
dirigent_core.types.UtcDateTime(timezone=True),
|
|
601
|
+
server_default=sa.func.now(),
|
|
602
|
+
nullable=False,
|
|
603
|
+
),
|
|
604
|
+
sa.ForeignKeyConstraint(
|
|
605
|
+
["source_id"], ["sources.id"], name=op.f("fk_source_routes_source_id_sources"), ondelete="CASCADE"
|
|
606
|
+
),
|
|
607
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_source_routes")),
|
|
608
|
+
sa.UniqueConstraint("method", "normalised", name=op.f("uq_source_routes_method_normalised")),
|
|
609
|
+
)
|
|
610
|
+
with op.batch_alter_table("source_routes", schema=None) as batch_op:
|
|
611
|
+
batch_op.create_index(batch_op.f("ix_source_routes_source_id"), ["source_id"], unique=False)
|
|
612
|
+
|
|
548
613
|
op.create_table(
|
|
549
614
|
"runs",
|
|
550
615
|
sa.Column("id", sa.Uuid(), nullable=False),
|
|
@@ -591,6 +656,7 @@ def upgrade() -> None:
|
|
|
591
656
|
),
|
|
592
657
|
sa.Column("triggered_by_id", sa.Uuid(), nullable=True),
|
|
593
658
|
sa.Column("triggered_by_label", sa.String(length=200), nullable=True),
|
|
659
|
+
sa.Column("source_step", sa.String(length=200), nullable=True),
|
|
594
660
|
sa.Column("traceparent", sa.String(length=64), nullable=True),
|
|
595
661
|
sa.Column("error", sa.Text(), nullable=True),
|
|
596
662
|
sa.Column("error_code", sa.String(length=128), nullable=True),
|
|
@@ -639,6 +705,7 @@ def upgrade() -> None:
|
|
|
639
705
|
"ix_runs_pipeline_id_status_created_at", ["pipeline_id", "status", "created_at"], unique=False
|
|
640
706
|
)
|
|
641
707
|
batch_op.create_index(batch_op.f("ix_runs_status"), ["status"], unique=False)
|
|
708
|
+
batch_op.create_index("ix_runs_triggered_by_id_status", ["triggered_by_id", "status"], unique=False)
|
|
642
709
|
|
|
643
710
|
op.create_table(
|
|
644
711
|
"notifications",
|
|
@@ -838,6 +905,43 @@ def upgrade() -> None:
|
|
|
838
905
|
batch_op.create_index("ix_webhook_deliveries_created_at", ["created_at"], unique=False)
|
|
839
906
|
batch_op.create_index("ix_webhook_deliveries_webhook_id_id", ["webhook_id", "id"], unique=False)
|
|
840
907
|
|
|
908
|
+
op.create_table(
|
|
909
|
+
"source_inputs",
|
|
910
|
+
sa.Column("id", sa.BigInteger().with_variant(sa.Integer(), "sqlite"), autoincrement=True, nullable=False),
|
|
911
|
+
sa.Column("source_id", sa.Uuid(), nullable=False),
|
|
912
|
+
sa.Column("run_id", sa.Uuid(), nullable=True),
|
|
913
|
+
sa.Column(
|
|
914
|
+
"outcome",
|
|
915
|
+
sa.Enum("accepted", "refused", name="input_outcome", native_enum=False, length=32),
|
|
916
|
+
nullable=False,
|
|
917
|
+
),
|
|
918
|
+
sa.Column("status", sa.Integer(), nullable=False),
|
|
919
|
+
sa.Column("code", sa.String(length=128), nullable=True),
|
|
920
|
+
sa.Column("reason", sa.Text(), nullable=True),
|
|
921
|
+
sa.Column("params", sa.JSON().with_variant(postgresql.JSONB(astext_type=Text()), "postgresql"), nullable=True),
|
|
922
|
+
sa.Column("method", sa.String(length=16), nullable=False),
|
|
923
|
+
sa.Column("path", sa.String(length=2000), nullable=False),
|
|
924
|
+
sa.Column("body_bytes", sa.Integer(), server_default=sa.text("0"), nullable=False),
|
|
925
|
+
sa.Column("duration_ms", sa.Integer(), server_default=sa.text("0"), nullable=False),
|
|
926
|
+
sa.Column("caller", sa.String(length=64), nullable=True),
|
|
927
|
+
sa.Column(
|
|
928
|
+
"created_at",
|
|
929
|
+
dirigent_core.types.UtcDateTime(timezone=True),
|
|
930
|
+
server_default=sa.func.now(),
|
|
931
|
+
nullable=False,
|
|
932
|
+
),
|
|
933
|
+
sa.ForeignKeyConstraint(
|
|
934
|
+
["run_id"], ["runs.id"], name=op.f("fk_source_inputs_run_id_runs"), ondelete="SET NULL"
|
|
935
|
+
),
|
|
936
|
+
sa.ForeignKeyConstraint(
|
|
937
|
+
["source_id"], ["sources.id"], name=op.f("fk_source_inputs_source_id_sources"), ondelete="CASCADE"
|
|
938
|
+
),
|
|
939
|
+
sa.PrimaryKeyConstraint("id", name=op.f("pk_source_inputs")),
|
|
940
|
+
)
|
|
941
|
+
with op.batch_alter_table("source_inputs", schema=None) as batch_op:
|
|
942
|
+
batch_op.create_index("ix_source_inputs_created_at", ["created_at"], unique=False)
|
|
943
|
+
batch_op.create_index("ix_source_inputs_source_id_id", ["source_id", "id"], unique=False)
|
|
944
|
+
|
|
841
945
|
op.create_table(
|
|
842
946
|
"step_attempts",
|
|
843
947
|
sa.Column("id", sa.Uuid(), nullable=False),
|
|
@@ -1034,6 +1138,11 @@ def downgrade() -> None:
|
|
|
1034
1138
|
batch_op.drop_index("ix_webhook_deliveries_created_at")
|
|
1035
1139
|
|
|
1036
1140
|
op.drop_table("webhook_deliveries")
|
|
1141
|
+
with op.batch_alter_table("source_inputs", schema=None) as batch_op:
|
|
1142
|
+
batch_op.drop_index("ix_source_inputs_source_id_id")
|
|
1143
|
+
batch_op.drop_index("ix_source_inputs_created_at")
|
|
1144
|
+
|
|
1145
|
+
op.drop_table("source_inputs")
|
|
1037
1146
|
with op.batch_alter_table("schedule_firings", schema=None) as batch_op:
|
|
1038
1147
|
batch_op.drop_index("ix_schedule_firings_schedule_id_id")
|
|
1039
1148
|
batch_op.drop_index("ix_schedule_firings_created_at")
|
|
@@ -1052,6 +1161,7 @@ def downgrade() -> None:
|
|
|
1052
1161
|
|
|
1053
1162
|
op.drop_table("notifications")
|
|
1054
1163
|
with op.batch_alter_table("runs", schema=None) as batch_op:
|
|
1164
|
+
batch_op.drop_index("ix_runs_triggered_by_id_status")
|
|
1055
1165
|
batch_op.drop_index(batch_op.f("ix_runs_status"))
|
|
1056
1166
|
batch_op.drop_index("ix_runs_pipeline_id_status_created_at")
|
|
1057
1167
|
batch_op.drop_index("ix_runs_finished_at")
|
|
@@ -1062,6 +1172,14 @@ def downgrade() -> None:
|
|
|
1062
1172
|
batch_op.drop_index(batch_op.f("ix_watches_pipeline_id"))
|
|
1063
1173
|
|
|
1064
1174
|
op.drop_table("watches")
|
|
1175
|
+
with op.batch_alter_table("source_routes", schema=None) as batch_op:
|
|
1176
|
+
batch_op.drop_index(batch_op.f("ix_source_routes_source_id"))
|
|
1177
|
+
|
|
1178
|
+
op.drop_table("source_routes")
|
|
1179
|
+
with op.batch_alter_table("sources", schema=None) as batch_op:
|
|
1180
|
+
batch_op.drop_index(batch_op.f("ix_sources_pipeline_id"))
|
|
1181
|
+
|
|
1182
|
+
op.drop_table("sources")
|
|
1065
1183
|
with op.batch_alter_table("webhook_triggers", schema=None) as batch_op:
|
|
1066
1184
|
batch_op.drop_index(batch_op.f("ix_webhook_triggers_trigger_document_id"))
|
|
1067
1185
|
batch_op.drop_index(batch_op.f("ix_webhook_triggers_pipeline_id"))
|
|
@@ -7,7 +7,7 @@ catalog disagree.
|
|
|
7
7
|
import json
|
|
8
8
|
from typing import Final, cast
|
|
9
9
|
|
|
10
|
-
from dirigent_client.schemas import BlockEntry, BlockKind, Catalog, SurfaceEntry
|
|
10
|
+
from dirigent_client.schemas import BlockEntry, BlockKind, Catalog, SourceEntry, SurfaceEntry
|
|
11
11
|
from dirigent_common import JsonMap
|
|
12
12
|
from dirigent_core.configdocs import first_paragraph, one_line
|
|
13
13
|
|
|
@@ -53,7 +53,8 @@ SURFACES_PREAMBLE: Final = """## Other surfaces
|
|
|
53
53
|
Blocks are two of the six surfaces a plugin contributes to. Three of the other four -- storage
|
|
54
54
|
backends, notifiers and connection kinds -- are listed here by id, since a document references
|
|
55
55
|
them by code and never by id of any other kind. The sixth, a JSON Schema format checker, is in
|
|
56
|
-
[JSON Schema](json-schema.md).
|
|
56
|
+
[JSON Schema](json-schema.md). Source kinds, which a source node names as ``source.kind``, are
|
|
57
|
+
listed after them; see [Source and reply](concepts.md#source-and-reply).
|
|
57
58
|
"""
|
|
58
59
|
|
|
59
60
|
NO_DESCRIPTION: Final = "--"
|
|
@@ -73,6 +74,7 @@ def render(catalog: Catalog) -> str:
|
|
|
73
74
|
parts.append(_surfaces("Storage schemes", "scheme", catalog.storage_schemes))
|
|
74
75
|
parts.append(_surfaces("Notifiers", "id", catalog.notifiers))
|
|
75
76
|
parts.append(_surfaces("Connection kinds", "id", catalog.connection_kinds))
|
|
77
|
+
parts.append(_sources(catalog.sources))
|
|
76
78
|
return "\n".join(parts).rstrip() + "\n"
|
|
77
79
|
|
|
78
80
|
|
|
@@ -210,6 +212,45 @@ def _surfaces(title: str, label: str, entries: list[SurfaceEntry]) -> str:
|
|
|
210
212
|
)
|
|
211
213
|
|
|
212
214
|
|
|
215
|
+
def _sources(entries: list[SourceEntry]) -> str:
|
|
216
|
+
"""Render the source kinds as a short table, and the input each one hands on."""
|
|
217
|
+
if not entries:
|
|
218
|
+
return "### Source kinds\n\n_None installed._\n"
|
|
219
|
+
rows = [
|
|
220
|
+
f"| `{entry.kind}` | {entry.hosting} | {_yes(entry.replies)} | {entry.summary} | `{entry.plugin}` |"
|
|
221
|
+
for entry in entries
|
|
222
|
+
]
|
|
223
|
+
lines = [
|
|
224
|
+
"### Source kinds",
|
|
225
|
+
"",
|
|
226
|
+
"| Kind | Hosting | Replies | Summary | Contributed by |",
|
|
227
|
+
"| --- | --- | --- | --- | --- |",
|
|
228
|
+
*rows,
|
|
229
|
+
"",
|
|
230
|
+
]
|
|
231
|
+
for entry in entries:
|
|
232
|
+
lines.extend(
|
|
233
|
+
[
|
|
234
|
+
f"#### `{entry.kind}`",
|
|
235
|
+
"",
|
|
236
|
+
"**Config**",
|
|
237
|
+
"",
|
|
238
|
+
_schema_table(entry.config_schema),
|
|
239
|
+
"",
|
|
240
|
+
"**Input it hands on**",
|
|
241
|
+
"",
|
|
242
|
+
_schema_table(entry.output_schema),
|
|
243
|
+
"",
|
|
244
|
+
]
|
|
245
|
+
)
|
|
246
|
+
return "\n".join(lines)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _yes(flag: bool) -> str:
|
|
250
|
+
"""Render a flag as a table cell reads it."""
|
|
251
|
+
return "yes" if flag else "no"
|
|
252
|
+
|
|
253
|
+
|
|
213
254
|
def _anchor(block_id: str) -> str:
|
|
214
255
|
"""Render the anchor mkdocs gives a heading whose text is a backticked block id."""
|
|
215
256
|
return block_id.replace(".", "")
|
|
@@ -290,6 +290,9 @@ class Settings(BaseSettings):
|
|
|
290
290
|
retention_deliveries: Duration | None = None
|
|
291
291
|
"""How long a webhook delivery is kept."""
|
|
292
292
|
|
|
293
|
+
retention_inputs: Duration | None = None
|
|
294
|
+
"""How long the record of an input that reached a source is kept."""
|
|
295
|
+
|
|
293
296
|
retention_firings: Duration | None = None
|
|
294
297
|
"""How long a schedule firing is kept."""
|
|
295
298
|
|
|
@@ -355,6 +358,31 @@ class Settings(BaseSettings):
|
|
|
355
358
|
|
|
356
359
|
A real webhook's own ``rate_limit_per_minute`` applies on top of this."""
|
|
357
360
|
|
|
361
|
+
endpoint_prefix: str = "/endpoints"
|
|
362
|
+
"""Where endpoint sources are served, outside the versioned API.
|
|
363
|
+
|
|
364
|
+
A proxy in front of an instance publishes this prefix and ``/hooks`` and keeps
|
|
365
|
+
``/api/v1`` inside, so a source's route is public without the API being so."""
|
|
366
|
+
|
|
367
|
+
source_max_body: Size = Field(default=1_048_576, ge=1)
|
|
368
|
+
"""The largest body any endpoint reads, whatever its own ``max_body`` says; reading stops one byte past it."""
|
|
369
|
+
|
|
370
|
+
source_intake_rate_per_minute: int = Field(default=600, ge=1)
|
|
371
|
+
"""How fast calls to one method and path are taken, before any source is looked up.
|
|
372
|
+
|
|
373
|
+
A source's own ``rate`` applies on top of this."""
|
|
374
|
+
|
|
375
|
+
source_reply_max: Duration = Field(default=timedelta(seconds=60), gt=timedelta(0))
|
|
376
|
+
"""The longest a caller is held for a reply, whatever a reply node's ``timeout`` says."""
|
|
377
|
+
|
|
378
|
+
source_max_waiting: int = Field(default=256, ge=1)
|
|
379
|
+
"""How many callers one server process holds open for their replies at once.
|
|
380
|
+
|
|
381
|
+
A call past it is answered 503 before its run is created."""
|
|
382
|
+
|
|
383
|
+
source_reply_poll: Duration = Field(default=timedelta(milliseconds=50), gt=timedelta(0))
|
|
384
|
+
"""How often one server process reads the replies its held callers wait for, in one query."""
|
|
385
|
+
|
|
358
386
|
login_rate_per_minute: int = Field(default=10, ge=1)
|
|
359
387
|
"""How many login attempts one address, and one username, get a minute.
|
|
360
388
|
|
|
@@ -383,10 +411,10 @@ class Settings(BaseSettings):
|
|
|
383
411
|
"""Accept a comma-separated ``key=value`` string for a mapping setting."""
|
|
384
412
|
return parse_string_mapping(value)
|
|
385
413
|
|
|
386
|
-
@field_validator("api_prefix")
|
|
414
|
+
@field_validator("api_prefix", "endpoint_prefix")
|
|
387
415
|
@classmethod
|
|
388
416
|
def _normalise_prefix(cls, value: str) -> str:
|
|
389
|
-
"""Keep
|
|
417
|
+
"""Keep a prefix rooted and free of a trailing slash."""
|
|
390
418
|
prefix = "/" + value.strip("/")
|
|
391
419
|
return prefix.rstrip("/") or "/"
|
|
392
420
|
|
|
@@ -45,8 +45,10 @@ from dirigent_core.messages import (
|
|
|
45
45
|
NOT_A_MAPPING,
|
|
46
46
|
NOT_YAML,
|
|
47
47
|
PARAMS_SCHEMA_INVALID,
|
|
48
|
+
REFERENCE_ERROR_NOT_UPSTREAM,
|
|
48
49
|
REFERENCE_GRID_IN_CONFIG,
|
|
49
50
|
REFERENCE_MALFORMED_ARTIFACTS,
|
|
51
|
+
REFERENCE_MALFORMED_ERROR,
|
|
50
52
|
REFERENCE_MALFORMED_ITEM,
|
|
51
53
|
REFERENCE_MALFORMED_RUN,
|
|
52
54
|
REFERENCE_MALFORMED_STEP,
|
|
@@ -357,8 +359,8 @@ def validate_against_catalog(
|
|
|
357
359
|
definition: Document,
|
|
358
360
|
catalog: Catalog,
|
|
359
361
|
*,
|
|
360
|
-
connections: Iterable[str] = (),
|
|
361
|
-
pipelines: Iterable[str] = (),
|
|
362
|
+
connections: Iterable[str] | None = (),
|
|
363
|
+
pipelines: Iterable[str] | None = (),
|
|
362
364
|
check_blocks: bool = True,
|
|
363
365
|
unsafe_allowed: Iterable[str] | None = None,
|
|
364
366
|
storage_schemes: Iterable[str] | None = None,
|
|
@@ -366,10 +368,13 @@ def validate_against_catalog(
|
|
|
366
368
|
blocks: Mapping[str, Any] | None = None,
|
|
367
369
|
format_checker: FormatChecker | None = None,
|
|
368
370
|
target: TriggerTarget | None = None,
|
|
371
|
+
source_kinds: Mapping[str, Any] | None = None,
|
|
369
372
|
) -> list[ValidationIssue]:
|
|
370
373
|
"""Check a document against what this instance actually has, reporting everything at once.
|
|
371
374
|
|
|
372
|
-
``check_blocks`` is off
|
|
375
|
+
``check_blocks`` is off where no catalog is available to check against. ``connections`` and
|
|
376
|
+
``pipelines`` are what the instance holds; None means the caller does not know, as on the
|
|
377
|
+
offline path, and then neither a step's connection nor the ones ``requires`` names is checked.
|
|
373
378
|
``unsafe_allowed`` and ``storage_schemes`` are what the instance permits and what its
|
|
374
379
|
backends claim; left unset, neither is checked, because a caller that does not know them
|
|
375
380
|
would otherwise refuse a document the instance would have run. ``blocks`` is the live
|
|
@@ -379,16 +384,23 @@ def validate_against_catalog(
|
|
|
379
384
|
against; unset, they are checked against the base formats alone, which is all a caller
|
|
380
385
|
holding no host can know. ``target`` is the pipeline a triggers document names, as the
|
|
381
386
|
instance holds it now; on the offline path there is none to read, and only the clocks and
|
|
382
|
-
the codes are checked.
|
|
387
|
+
the codes are checked. ``source_kinds`` is the source kinds the instance knows; unset, the
|
|
388
|
+
built-in ones.
|
|
383
389
|
"""
|
|
390
|
+
from dirigent_core.sources.kinds import built_in_kinds
|
|
391
|
+
from dirigent_core.sources.rules import source_issues
|
|
392
|
+
|
|
393
|
+
kinds = source_kinds if source_kinds is not None else built_in_kinds()
|
|
384
394
|
checker = format_checker if format_checker is not None else base_format_checker()
|
|
385
395
|
sensors = _sensors(catalog) if check_blocks else None
|
|
386
396
|
if isinstance(definition, TriggersDefinition):
|
|
387
|
-
return _triggers_document_issues(
|
|
397
|
+
return _triggers_document_issues(
|
|
398
|
+
definition, checker, target, check_target=check_blocks and pipelines is not None, sensors=sensors
|
|
399
|
+
)
|
|
388
400
|
# A document that brings its own connections or schemas satisfies its own references to them.
|
|
389
|
-
known = {name for name in connections} | set(definition.connections)
|
|
401
|
+
known = None if connections is None else {name for name in connections} | set(definition.connections)
|
|
390
402
|
known_schemas = None if schemas is None else {name for name in schemas} | set(definition.schemas)
|
|
391
|
-
held = {name for name in pipelines}
|
|
403
|
+
held = None if pipelines is None else {name for name in pipelines}
|
|
392
404
|
issues: list[ValidationIssue] = []
|
|
393
405
|
if check_blocks:
|
|
394
406
|
issues.extend(
|
|
@@ -406,6 +418,7 @@ def validate_against_catalog(
|
|
|
406
418
|
issues.extend(_carried_schema_issues(definition))
|
|
407
419
|
issues.extend(_reference_issues(definition))
|
|
408
420
|
issues.extend(_trigger_issues(definition, checker, check_params=not malformed_params, sensors=sensors))
|
|
421
|
+
issues.extend(source_issues(definition, kinds))
|
|
409
422
|
if check_blocks:
|
|
410
423
|
issues.extend(
|
|
411
424
|
_step_block_issues(
|
|
@@ -607,18 +620,25 @@ def _triggers_document_issues(
|
|
|
607
620
|
return [ValidationIssue.of(TARGET_PIPELINE_MISSING, location="pipeline", code=repr(definition.pipeline))]
|
|
608
621
|
if not target.active:
|
|
609
622
|
return [ValidationIssue.of(TARGET_PIPELINE_INACTIVE, location="pipeline", code=repr(definition.pipeline))]
|
|
623
|
+
if target.definition is not None and target.definition.sources:
|
|
624
|
+
from dirigent_core.sources.rules import triggers_issues
|
|
625
|
+
|
|
626
|
+
return triggers_issues(target.code, definition.triggers)
|
|
610
627
|
return _trigger_issues(definition, format_checker, against=target.definition, sensors=sensors)
|
|
611
628
|
|
|
612
629
|
|
|
613
630
|
def _requirement_issues(
|
|
614
631
|
definition: PipelineDefinition,
|
|
615
632
|
catalog: Catalog,
|
|
616
|
-
connections: set[str],
|
|
617
|
-
pipelines: set[str],
|
|
633
|
+
connections: set[str] | None,
|
|
634
|
+
pipelines: set[str] | None,
|
|
618
635
|
schemes: set[str] | None,
|
|
619
636
|
schemas: set[str] | None,
|
|
620
637
|
) -> list[ValidationIssue]:
|
|
621
|
-
"""Check the ``requires`` preflight first, with one complete list of what is missing.
|
|
638
|
+
"""Check the ``requires`` preflight first, with one complete list of what is missing.
|
|
639
|
+
|
|
640
|
+
A family passed as None is one the caller does not know, and is not checked.
|
|
641
|
+
"""
|
|
622
642
|
installed = {entry.id for entry in catalog.blocks}
|
|
623
643
|
issues = [
|
|
624
644
|
ValidationIssue.of(REQUIRED_BLOCK_MISSING, location=f"requires.blocks.{index}", block=repr(block))
|
|
@@ -628,12 +648,12 @@ def _requirement_issues(
|
|
|
628
648
|
issues.extend(
|
|
629
649
|
ValidationIssue.of(REQUIRED_CONNECTION_MISSING, location=f"requires.connections.{index}", code=repr(name))
|
|
630
650
|
for index, name in enumerate(definition.requires.connections)
|
|
631
|
-
if name not in connections
|
|
651
|
+
if connections is not None and name not in connections
|
|
632
652
|
)
|
|
633
653
|
issues.extend(
|
|
634
654
|
ValidationIssue.of(REQUIRED_PIPELINE_MISSING, location=f"requires.pipelines.{index}", code=repr(name))
|
|
635
655
|
for index, name in enumerate(definition.requires.pipelines)
|
|
636
|
-
if name not in pipelines and name != definition.code
|
|
656
|
+
if pipelines is not None and name not in pipelines and name != definition.code
|
|
637
657
|
)
|
|
638
658
|
if schemes is not None:
|
|
639
659
|
registered = ", ".join(sorted(schemes)) or "none are registered"
|
|
@@ -662,7 +682,7 @@ def _requirement_issues(
|
|
|
662
682
|
def _step_block_issues(
|
|
663
683
|
definition: PipelineDefinition,
|
|
664
684
|
catalog: Catalog,
|
|
665
|
-
connections: set[str],
|
|
685
|
+
connections: set[str] | None,
|
|
666
686
|
allowed: set[str] | None = None,
|
|
667
687
|
schemes: set[str] | None = None,
|
|
668
688
|
blocks: Mapping[str, Any] | None = None,
|
|
@@ -672,6 +692,8 @@ def _step_block_issues(
|
|
|
672
692
|
issues: list[ValidationIssue] = []
|
|
673
693
|
for name in sorted(definition.steps):
|
|
674
694
|
step = definition.steps[name]
|
|
695
|
+
if step.block is None:
|
|
696
|
+
continue
|
|
675
697
|
entry = catalog.block(step.block)
|
|
676
698
|
if entry is None:
|
|
677
699
|
issues.append(
|
|
@@ -794,10 +816,10 @@ def _config_issues(step: str, config: JsonMap, schema: JsonMap) -> list[Validati
|
|
|
794
816
|
return issues
|
|
795
817
|
|
|
796
818
|
|
|
797
|
-
def _connection_issues(step: str, config: JsonMap, connections: set[str]) -> list[ValidationIssue]:
|
|
798
|
-
"""Refuse a step naming a connection this instance does not hold."""
|
|
819
|
+
def _connection_issues(step: str, config: JsonMap, connections: set[str] | None) -> list[ValidationIssue]:
|
|
820
|
+
"""Refuse a step naming a connection this instance does not hold, when the caller knows what it holds."""
|
|
799
821
|
named = config.get("connection")
|
|
800
|
-
if not isinstance(named, str) or has_reference(named) or named in connections:
|
|
822
|
+
if connections is None or not isinstance(named, str) or has_reference(named) or named in connections:
|
|
801
823
|
return []
|
|
802
824
|
available = ", ".join(sorted(connections)) or "this instance has no connections"
|
|
803
825
|
return [
|
|
@@ -851,6 +873,12 @@ def _reference_issues(definition: PipelineDefinition) -> list[ValidationIssue]:
|
|
|
851
873
|
problem = _reference_problem(reference.strip(), definition, name, upstream, declared, family)
|
|
852
874
|
if problem is not None:
|
|
853
875
|
issues.append(_at(f"steps.{name}.config", problem))
|
|
876
|
+
if step.reply is not None:
|
|
877
|
+
written = cast("JsonValue", step.reply.model_dump(mode="json", exclude={"timeout"}))
|
|
878
|
+
for reference in sorted(set(references_in(written))):
|
|
879
|
+
problem = _reference_problem(reference.strip(), definition, name, upstream, declared, family)
|
|
880
|
+
if problem is not None:
|
|
881
|
+
issues.append(_at(f"steps.{name}.reply", problem))
|
|
854
882
|
issues.extend(_fan_out_literal_issues(name, step.for_each))
|
|
855
883
|
issues.extend(_adoption_issues(definition, name))
|
|
856
884
|
for reference in sorted(set(references_in(cast("JsonValue", step.for_each)))):
|
|
@@ -952,6 +980,14 @@ def _reference_problem(
|
|
|
952
980
|
if target not in upstream:
|
|
953
981
|
return Issue.of(REFERENCE_NOT_UPSTREAM, reference=reference, target=repr(target), step=repr(step))
|
|
954
982
|
return None
|
|
983
|
+
case ["steps", target, "error", *path]:
|
|
984
|
+
if target not in definition.steps:
|
|
985
|
+
return Issue.of(REFERENCE_UNKNOWN_STEP, reference=reference, step=repr(target))
|
|
986
|
+
if target not in upstream:
|
|
987
|
+
return Issue.of(REFERENCE_ERROR_NOT_UPSTREAM, reference=reference, target=repr(target), step=repr(step))
|
|
988
|
+
if path[:1] not in (["code"], ["message"], ["params"]) or (path[:1] != ["params"] and len(path) > 1):
|
|
989
|
+
return Issue.of(REFERENCE_MALFORMED_ERROR, reference=reference)
|
|
990
|
+
return None
|
|
955
991
|
case ["steps", target, "items"]:
|
|
956
992
|
return Issue.of(REFERENCE_GRID_IN_CONFIG, reference=reference, target=repr(target), bare_target=target)
|
|
957
993
|
case ["steps", target, "item", "output", *_]:
|
|
@@ -1032,7 +1068,9 @@ def summarize(definition: Document) -> DocumentSummary:
|
|
|
1032
1068
|
pipeline = definition.pipeline if isinstance(definition, TriggersDefinition) else None
|
|
1033
1069
|
steps = sorted(definition.steps) if isinstance(definition, PipelineDefinition) else []
|
|
1034
1070
|
blocks = (
|
|
1035
|
-
sorted({step.block for step in definition.steps.values()
|
|
1071
|
+
sorted({step.block for step in definition.steps.values() if step.block is not None})
|
|
1072
|
+
if isinstance(definition, PipelineDefinition)
|
|
1073
|
+
else []
|
|
1036
1074
|
)
|
|
1037
1075
|
return DocumentSummary(
|
|
1038
1076
|
kind=definition.kind,
|
|
@@ -36,6 +36,7 @@ from dirigent_core.engine.references import ReferenceScope, UnknownReference, re
|
|
|
36
36
|
from dirigent_core.engine.runs import (
|
|
37
37
|
Attribution,
|
|
38
38
|
EngineRuns,
|
|
39
|
+
Entry,
|
|
39
40
|
FanOutError,
|
|
40
41
|
Provenance,
|
|
41
42
|
RunCreationError,
|
|
@@ -62,6 +63,7 @@ __all__ = [
|
|
|
62
63
|
"KIND_PIPELINE",
|
|
63
64
|
"KIND_TRIGGERS",
|
|
64
65
|
"Attribution",
|
|
66
|
+
"Entry",
|
|
65
67
|
"BufferedLogger",
|
|
66
68
|
"ClaimedUnit",
|
|
67
69
|
"ConcurrencyPolicy",
|