agent-connector-sdk 0.1.0__py3-none-any.whl
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.
- agent_connector_sdk/__init__.py +10 -0
- agent_connector_sdk/_contract_fields.py +92 -0
- agent_connector_sdk/_turtle.py +116 -0
- agent_connector_sdk/_version.py +3 -0
- agent_connector_sdk/access_contract.py +125 -0
- agent_connector_sdk/adapters/__init__.py +1 -0
- agent_connector_sdk/adapters/event_envelope.py +109 -0
- agent_connector_sdk/adapters/event_errors.py +51 -0
- agent_connector_sdk/adapters/event_feed.py +192 -0
- agent_connector_sdk/adapters/event_feed_bindings.py +27 -0
- agent_connector_sdk/adapters/event_feed_records.py +171 -0
- agent_connector_sdk/adapters/event_identity.py +46 -0
- agent_connector_sdk/adapters/mcp_tool.py +201 -0
- agent_connector_sdk/adapters/mcp_tool_extract.py +130 -0
- agent_connector_sdk/adapters/mcp_tool_lifecycle.py +248 -0
- agent_connector_sdk/adapters/mcp_tool_paging.py +173 -0
- agent_connector_sdk/adapters/mcp_tool_records.py +111 -0
- agent_connector_sdk/adapters/tool_contract.py +77 -0
- agent_connector_sdk/adapters/web_fetch.py +221 -0
- agent_connector_sdk/adapters/web_fetch_helpers.py +73 -0
- agent_connector_sdk/artifacts/__init__.py +17 -0
- agent_connector_sdk/artifacts/annotations.py +109 -0
- agent_connector_sdk/artifacts/common.py +57 -0
- agent_connector_sdk/artifacts/ontology_imports.py +132 -0
- agent_connector_sdk/artifacts/pack.py +185 -0
- agent_connector_sdk/artifacts/prompts.py +127 -0
- agent_connector_sdk/artifacts/prompts_helpers.py +128 -0
- agent_connector_sdk/artifacts/resources.py +78 -0
- agent_connector_sdk/artifacts/skills.py +64 -0
- agent_connector_sdk/artifacts/tools.py +74 -0
- agent_connector_sdk/auth/__init__.py +17 -0
- agent_connector_sdk/auth/client_credentials.py +165 -0
- agent_connector_sdk/auth/delegation.py +218 -0
- agent_connector_sdk/auth/oidc.py +151 -0
- agent_connector_sdk/auth/static.py +85 -0
- agent_connector_sdk/auth/tokens.py +91 -0
- agent_connector_sdk/auth/values.py +33 -0
- agent_connector_sdk/certify/__init__.py +1 -0
- agent_connector_sdk/certify/certification.py +102 -0
- agent_connector_sdk/certify/checkout.py +145 -0
- agent_connector_sdk/certify/cli.py +210 -0
- agent_connector_sdk/certify/fingerprints.py +106 -0
- agent_connector_sdk/certify/listing.py +62 -0
- agent_connector_sdk/certify/options.py +46 -0
- agent_connector_sdk/certify/pin_journal.py +111 -0
- agent_connector_sdk/certify/pins.py +136 -0
- agent_connector_sdk/certify/transaction.py +107 -0
- agent_connector_sdk/certify/verdicts.py +128 -0
- agent_connector_sdk/config.py +274 -0
- agent_connector_sdk/config_git.py +20 -0
- agent_connector_sdk/contracts.py +105 -0
- agent_connector_sdk/credentials/__init__.py +15 -0
- agent_connector_sdk/credentials/openbao.py +122 -0
- agent_connector_sdk/credentials/provider.py +55 -0
- agent_connector_sdk/credentials/references.py +93 -0
- agent_connector_sdk/credentials/resolution.py +86 -0
- agent_connector_sdk/credentials/resolver.py +55 -0
- agent_connector_sdk/decide/__init__.py +110 -0
- agent_connector_sdk/decide/consumers.py +115 -0
- agent_connector_sdk/decide/epistemic_graph.py +190 -0
- agent_connector_sdk/decide/options.py +78 -0
- agent_connector_sdk/decide/outcome.py +137 -0
- agent_connector_sdk/decide/points.py +121 -0
- agent_connector_sdk/decide/transport.py +120 -0
- agent_connector_sdk/discovery.py +170 -0
- agent_connector_sdk/earth_observations/__init__.py +8 -0
- agent_connector_sdk/earth_observations/records.py +113 -0
- agent_connector_sdk/entitlements.py +217 -0
- agent_connector_sdk/exceptions.py +69 -0
- agent_connector_sdk/finance/__init__.py +7 -0
- agent_connector_sdk/finance/account.py +79 -0
- agent_connector_sdk/finance/alpaca_account.py +71 -0
- agent_connector_sdk/finance/alpaca_stocks.py +107 -0
- agent_connector_sdk/finance/central_bank_calendar.py +106 -0
- agent_connector_sdk/finance/csv_import.py +189 -0
- agent_connector_sdk/finance/etf_flows.py +89 -0
- agent_connector_sdk/finance/fred_series.py +109 -0
- agent_connector_sdk/finance/multi_asset.py +103 -0
- agent_connector_sdk/finance/records.py +134 -0
- agent_connector_sdk/http/__init__.py +20 -0
- agent_connector_sdk/http/bodies.py +81 -0
- agent_connector_sdk/http/client.py +115 -0
- agent_connector_sdk/http/egress.py +131 -0
- agent_connector_sdk/http/errors.py +136 -0
- agent_connector_sdk/http/flow.py +112 -0
- agent_connector_sdk/http/options.py +97 -0
- agent_connector_sdk/http/pagination.py +139 -0
- agent_connector_sdk/http/problems.py +126 -0
- agent_connector_sdk/http/redaction.py +54 -0
- agent_connector_sdk/http/responses.py +112 -0
- agent_connector_sdk/http/retry.py +145 -0
- agent_connector_sdk/http/safe_post.py +70 -0
- agent_connector_sdk/http/transport.py +136 -0
- agent_connector_sdk/identity.py +87 -0
- agent_connector_sdk/ingest/__init__.py +111 -0
- agent_connector_sdk/ingest/channel.py +64 -0
- agent_connector_sdk/ingest/engine.py +162 -0
- agent_connector_sdk/ingest/errors.py +17 -0
- agent_connector_sdk/ingest/model.py +178 -0
- agent_connector_sdk/ingest/records.py +48 -0
- agent_connector_sdk/ingest/request.py +222 -0
- agent_connector_sdk/ingest/service.py +176 -0
- agent_connector_sdk/ingest/transport.py +82 -0
- agent_connector_sdk/library_state.py +76 -0
- agent_connector_sdk/manifest/__init__.py +1 -0
- agent_connector_sdk/manifest/live_contract.py +229 -0
- agent_connector_sdk/manifest/loader.py +134 -0
- agent_connector_sdk/manifest/model.py +229 -0
- agent_connector_sdk/manifest/ontology_pack.py +199 -0
- agent_connector_sdk/manifest/package_validation.py +172 -0
- agent_connector_sdk/manifest/presets.py +269 -0
- agent_connector_sdk/manifest/provider_runtime.py +64 -0
- agent_connector_sdk/manifest/tool_schema.py +174 -0
- agent_connector_sdk/mcp/__init__.py +4 -0
- agent_connector_sdk/mcp/action_dispatch.py +235 -0
- agent_connector_sdk/mcp/auth/__init__.py +14 -0
- agent_connector_sdk/mcp/auth/factory.py +60 -0
- agent_connector_sdk/mcp/auth/jwt.py +140 -0
- agent_connector_sdk/mcp/auth/policy.py +103 -0
- agent_connector_sdk/mcp/auth/proxies.py +127 -0
- agent_connector_sdk/mcp/auth/static.py +108 -0
- agent_connector_sdk/mcp/auth/verifiers.py +74 -0
- agent_connector_sdk/mcp/body_boundary.py +99 -0
- agent_connector_sdk/mcp/change_events.py +78 -0
- agent_connector_sdk/mcp/concurrency.py +98 -0
- agent_connector_sdk/mcp/confirmation.py +141 -0
- agent_connector_sdk/mcp/content.py +184 -0
- agent_connector_sdk/mcp/context.py +145 -0
- agent_connector_sdk/mcp/exposure.py +22 -0
- agent_connector_sdk/mcp/http_boundary.py +126 -0
- agent_connector_sdk/mcp/method_tools.py +195 -0
- agent_connector_sdk/mcp/middleware.py +51 -0
- agent_connector_sdk/mcp/network.py +225 -0
- agent_connector_sdk/mcp/network_inputs.py +197 -0
- agent_connector_sdk/mcp/network_validation.py +133 -0
- agent_connector_sdk/mcp/parser.py +125 -0
- agent_connector_sdk/mcp/registry.py +162 -0
- agent_connector_sdk/mcp/schema_disclosure.py +157 -0
- agent_connector_sdk/mcp/schema_disclosure_models.py +62 -0
- agent_connector_sdk/mcp/schema_disclosure_provider.py +112 -0
- agent_connector_sdk/mcp/server.py +165 -0
- agent_connector_sdk/mcp/tool_surface.py +186 -0
- agent_connector_sdk/mcp/visibility.py +62 -0
- agent_connector_sdk/mcp/visibility_filters.py +61 -0
- agent_connector_sdk/mcp/visibility_policy.py +148 -0
- agent_connector_sdk/ontology/access_contract.ttl +50 -0
- agent_connector_sdk/ports/__init__.py +21 -0
- agent_connector_sdk/ports/artifact_kind.py +27 -0
- agent_connector_sdk/ports/audit_reservation.py +26 -0
- agent_connector_sdk/ports/authorization_verifier.py +23 -0
- agent_connector_sdk/ports/change_source.py +43 -0
- agent_connector_sdk/ports/connector_registry.py +30 -0
- agent_connector_sdk/ports/decide_runner.py +80 -0
- agent_connector_sdk/ports/errors.py +21 -0
- agent_connector_sdk/ports/repair_proposals.py +28 -0
- agent_connector_sdk/ports/session.py +103 -0
- agent_connector_sdk/ports/sink.py +51 -0
- agent_connector_sdk/ports/source_adapter.py +44 -0
- agent_connector_sdk/ports/transport.py +23 -0
- agent_connector_sdk/ports/writeback.py +36 -0
- agent_connector_sdk/ports/writeback_ledger.py +50 -0
- agent_connector_sdk/ports/writeback_transport.py +46 -0
- agent_connector_sdk/privacy.py +128 -0
- agent_connector_sdk/privacy_reference.py +44 -0
- agent_connector_sdk/privacy_rules.py +251 -0
- agent_connector_sdk/progress.py +38 -0
- agent_connector_sdk/py.typed +0 -0
- agent_connector_sdk/repository/__init__.py +54 -0
- agent_connector_sdk/repository/errors.py +7 -0
- agent_connector_sdk/repository/identity.py +43 -0
- agent_connector_sdk/repository/indexing.py +107 -0
- agent_connector_sdk/repository/local_git.py +178 -0
- agent_connector_sdk/repository/manifest.py +65 -0
- agent_connector_sdk/repository/models.py +163 -0
- agent_connector_sdk/repository/provider.py +37 -0
- agent_connector_sdk/repository/ref_walk_provider.py +42 -0
- agent_connector_sdk/repository/refs.py +59 -0
- agent_connector_sdk/repository/transport.py +144 -0
- agent_connector_sdk/repository/tree_batch.py +117 -0
- agent_connector_sdk/repository/walk.py +104 -0
- agent_connector_sdk/runner/__init__.py +16 -0
- agent_connector_sdk/runner/backoff.py +26 -0
- agent_connector_sdk/runner/cli.py +189 -0
- agent_connector_sdk/runner/composition.py +212 -0
- agent_connector_sdk/runner/credentialed_endpoint.py +31 -0
- agent_connector_sdk/runner/descriptors.py +129 -0
- agent_connector_sdk/runner/endpoints.py +99 -0
- agent_connector_sdk/runner/errors.py +57 -0
- agent_connector_sdk/runner/health_reporting.py +58 -0
- agent_connector_sdk/runner/health_server.py +142 -0
- agent_connector_sdk/runner/health_state.py +185 -0
- agent_connector_sdk/runner/logs.py +53 -0
- agent_connector_sdk/runner/plans.py +198 -0
- agent_connector_sdk/runner/provisioning.py +82 -0
- agent_connector_sdk/runner/services.py +34 -0
- agent_connector_sdk/runner/sink_probe.py +25 -0
- agent_connector_sdk/runner/static_registry.py +63 -0
- agent_connector_sdk/runner/supervisor.py +126 -0
- agent_connector_sdk/runner/syncing.py +226 -0
- agent_connector_sdk/runner/worker.py +205 -0
- agent_connector_sdk/schema_contract.py +108 -0
- agent_connector_sdk/schema_drift.py +202 -0
- agent_connector_sdk/schema_repair.py +61 -0
- agent_connector_sdk/sinks/__init__.py +1 -0
- agent_connector_sdk/sinks/epistemic_graph.py +180 -0
- agent_connector_sdk/skills/__init__.py +9 -0
- agent_connector_sdk/skills/agent-connector-sdk-development/SKILL.md +194 -0
- agent_connector_sdk/taxonomy/__init__.py +8 -0
- agent_connector_sdk/taxonomy/records.py +78 -0
- agent_connector_sdk/testing/__init__.py +19 -0
- agent_connector_sdk/testing/artifact_kinds.py +125 -0
- agent_connector_sdk/testing/certificates.py +113 -0
- agent_connector_sdk/testing/duplicate_symbol_scan.py +88 -0
- agent_connector_sdk/testing/fleet_migration.py +106 -0
- agent_connector_sdk/testing/http_clients.py +144 -0
- agent_connector_sdk/testing/http_server.py +122 -0
- agent_connector_sdk/testing/readiness.py +10 -0
- agent_connector_sdk/testing/repair_proposals.py +27 -0
- agent_connector_sdk/testing/results.py +41 -0
- agent_connector_sdk/testing/sinks.py +275 -0
- agent_connector_sdk/testing/source_adapters.py +174 -0
- agent_connector_sdk/testing/writeback.py +219 -0
- agent_connector_sdk/testing/writeback_audit.py +41 -0
- agent_connector_sdk/tls/__init__.py +16 -0
- agent_connector_sdk/tls/context.py +62 -0
- agent_connector_sdk/tls/errors.py +13 -0
- agent_connector_sdk/tls/material.py +108 -0
- agent_connector_sdk/tls/profile.py +116 -0
- agent_connector_sdk/tls/resolve.py +244 -0
- agent_connector_sdk/tls/selection.py +174 -0
- agent_connector_sdk/transports/__init__.py +1 -0
- agent_connector_sdk/transports/mcp.py +86 -0
- agent_connector_sdk/transports/mcp_changes.py +130 -0
- agent_connector_sdk/transports/mcp_session.py +155 -0
- agent_connector_sdk/utilities.py +134 -0
- agent_connector_sdk/vendor_extractors/__init__.py +36 -0
- agent_connector_sdk/vendor_extractors/_records.py +66 -0
- agent_connector_sdk/vendor_extractors/a2a.py +125 -0
- agent_connector_sdk/vendor_extractors/a2a_helpers.py +101 -0
- agent_connector_sdk/vendor_extractors/ansible.py +70 -0
- agent_connector_sdk/vendor_extractors/archer.py +118 -0
- agent_connector_sdk/vendor_extractors/archimate.py +103 -0
- agent_connector_sdk/vendor_extractors/aris.py +110 -0
- agent_connector_sdk/vendor_extractors/caddy.py +93 -0
- agent_connector_sdk/vendor_extractors/camunda.py +169 -0
- agent_connector_sdk/vendor_extractors/ciso_assistant.py +167 -0
- agent_connector_sdk/vendor_extractors/ciso_assistant_helpers.py +73 -0
- agent_connector_sdk/vendor_extractors/contract.py +97 -0
- agent_connector_sdk/vendor_extractors/egeria.py +254 -0
- agent_connector_sdk/vendor_extractors/egeria_helpers.py +79 -0
- agent_connector_sdk/vendor_extractors/emerald.py +126 -0
- agent_connector_sdk/vendor_extractors/erpnext.py +113 -0
- agent_connector_sdk/vendor_extractors/erpnext_helpers.py +196 -0
- agent_connector_sdk/vendor_extractors/grafana.py +152 -0
- agent_connector_sdk/vendor_extractors/grafana_helpers.py +50 -0
- agent_connector_sdk/vendor_extractors/home_assistant.py +86 -0
- agent_connector_sdk/vendor_extractors/infra.py +105 -0
- agent_connector_sdk/vendor_extractors/infra_helpers.py +95 -0
- agent_connector_sdk/vendor_extractors/kafka.py +98 -0
- agent_connector_sdk/vendor_extractors/keycloak.py +121 -0
- agent_connector_sdk/vendor_extractors/leanix.py +226 -0
- agent_connector_sdk/vendor_extractors/lgtm.py +129 -0
- agent_connector_sdk/vendor_extractors/mealie.py +128 -0
- agent_connector_sdk/vendor_extractors/microsoft.py +106 -0
- agent_connector_sdk/vendor_extractors/nextcloud.py +136 -0
- agent_connector_sdk/vendor_extractors/odoo.py +127 -0
- agent_connector_sdk/vendor_extractors/okta.py +161 -0
- agent_connector_sdk/vendor_extractors/portainer.py +136 -0
- agent_connector_sdk/vendor_extractors/salesforce.py +102 -0
- agent_connector_sdk/vendor_extractors/servicenow.py +163 -0
- agent_connector_sdk/vendor_extractors/servicenow_helpers.py +111 -0
- agent_connector_sdk/vendor_extractors/social.py +201 -0
- agent_connector_sdk/vendor_extractors/technitium_dns.py +111 -0
- agent_connector_sdk/vendor_extractors/twenty.py +122 -0
- agent_connector_sdk/vendor_extractors/twenty_helpers.py +88 -0
- agent_connector_sdk/vendor_extractors/uptime_kuma.py +74 -0
- agent_connector_sdk/vendor_extractors/wger.py +140 -0
- agent_connector_sdk/writeback/__init__.py +1 -0
- agent_connector_sdk/writeback/authorization.py +95 -0
- agent_connector_sdk/writeback/connector.py +122 -0
- agent_connector_sdk/writeback/durable_files.py +33 -0
- agent_connector_sdk/writeback/durable_ledger.py +151 -0
- agent_connector_sdk/writeback/durable_transport.py +167 -0
- agent_connector_sdk/writeback/epistemic_graph.py +121 -0
- agent_connector_sdk/writeback/errors.py +56 -0
- agent_connector_sdk/writeback/memory.py +184 -0
- agent_connector_sdk/writeback/models.py +33 -0
- agent_connector_sdk/writeback/recovery.py +175 -0
- agent_connector_sdk/writeback/service.py +152 -0
- agent_connector_sdk/writeback/validation.py +97 -0
- agent_connector_sdk-0.1.0.dist-info/METADATA +161 -0
- agent_connector_sdk-0.1.0.dist-info/RECORD +296 -0
- agent_connector_sdk-0.1.0.dist-info/WHEEL +5 -0
- agent_connector_sdk-0.1.0.dist-info/entry_points.txt +22 -0
- agent_connector_sdk-0.1.0.dist-info/licenses/LICENSE +21 -0
- agent_connector_sdk-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""agent-connector-sdk: the connector-facing SDK for the agent-packages fleet.
|
|
2
|
+
|
|
3
|
+
Public modules are imported by their own path (``agent_connector_sdk.mcp.server``,
|
|
4
|
+
``agent_connector_sdk.ports`` and so on); this package root deliberately exports
|
|
5
|
+
only the version so that importing it never pulls in the MCP server stack.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from agent_connector_sdk._version import __version__
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Typed field readers for ``ac:AccessContract`` subjects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ._turtle import AccessContractError, Term
|
|
6
|
+
from .credentials.references import SecretReferenceError, parse_secret_reference
|
|
7
|
+
|
|
8
|
+
AC = "https://knuckles-team.github.io/agent-connector-sdk/access#"
|
|
9
|
+
ACCESS_KINDS = frozenset({"mcp_tool", "http_endpoint", "graphql_query", "a2a_skill"})
|
|
10
|
+
PAGINATION_MODES = frozenset({"none", "cursor", "offset", "page"})
|
|
11
|
+
PUSHDOWN_OPS = frozenset({"filter", "sort", "limit", "project"})
|
|
12
|
+
COST_HINTS = frozenset({"low", "medium", "high"})
|
|
13
|
+
|
|
14
|
+
Props = dict[str, list[Term]]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _one(props: Props, name: str, subject: str) -> Term | None:
|
|
18
|
+
values = props.get(AC + name, [])
|
|
19
|
+
if len(values) > 1:
|
|
20
|
+
raise AccessContractError(f"{subject}: ac:{name} appears more than once")
|
|
21
|
+
return values[0] if values else None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def text(props: Props, name: str, subject: str) -> str:
|
|
25
|
+
"""Return a required non-empty string property."""
|
|
26
|
+
value = _one(props, name, subject)
|
|
27
|
+
if not isinstance(value, str) or not value.strip():
|
|
28
|
+
raise AccessContractError(f"{subject}: missing ac:{name}")
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def integer(props: Props, name: str, subject: str) -> int | None:
|
|
33
|
+
"""Return an optional non-negative integer property."""
|
|
34
|
+
value = _one(props, name, subject)
|
|
35
|
+
if value is not None and (not isinstance(value, int) or value < 0):
|
|
36
|
+
raise AccessContractError(
|
|
37
|
+
f"{subject}: ac:{name} must be a non-negative integer"
|
|
38
|
+
)
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def choice(
|
|
43
|
+
props: Props,
|
|
44
|
+
name: str,
|
|
45
|
+
subject: str,
|
|
46
|
+
*,
|
|
47
|
+
allowed: frozenset[str],
|
|
48
|
+
default: str | None = None,
|
|
49
|
+
) -> str:
|
|
50
|
+
"""Return a property constrained to ``allowed``; ``default`` makes it optional."""
|
|
51
|
+
raw = _one(props, name, subject) if default is not None else None
|
|
52
|
+
value = text(props, name, subject) if default is None else str(raw or default)
|
|
53
|
+
if value not in allowed:
|
|
54
|
+
raise AccessContractError(f"{subject}: unknown ac:{name} {value!r}")
|
|
55
|
+
return value
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def pushdown(props: Props, subject: str) -> frozenset[str]:
|
|
59
|
+
"""Return the declared pushdown operations, refusing unknown ones."""
|
|
60
|
+
ops = frozenset(str(op) for op in props.get(AC + "pushdown", []))
|
|
61
|
+
unknown = sorted(ops - PUSHDOWN_OPS)
|
|
62
|
+
if unknown:
|
|
63
|
+
raise AccessContractError(f"{subject}: unknown ac:pushdown {unknown}")
|
|
64
|
+
return ops
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def parameters(props: Props) -> tuple[str, ...]:
|
|
68
|
+
"""Return the declared operation parameters in sorted order."""
|
|
69
|
+
return tuple(sorted(str(p) for p in props.get(AC + "parameter", [])))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def auth_ref(props: Props, subject: str) -> str | None:
|
|
73
|
+
"""Return a rendered credential reference, refusing a literal secret."""
|
|
74
|
+
value = _one(props, "authRef", subject)
|
|
75
|
+
if value is None:
|
|
76
|
+
return None
|
|
77
|
+
try:
|
|
78
|
+
return parse_secret_reference(str(value)).render()
|
|
79
|
+
except SecretReferenceError:
|
|
80
|
+
raise AccessContractError(
|
|
81
|
+
f"{subject}: ac:authRef must be a credential reference, not a secret"
|
|
82
|
+
) from None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def bindings(graph: dict[str, Props], contract: str) -> tuple[tuple[str, str], ...]:
|
|
86
|
+
"""Return the sorted (property, field) pairs bound to ``contract``."""
|
|
87
|
+
pairs = []
|
|
88
|
+
for subject, props in graph.items():
|
|
89
|
+
if contract in props.get(AC + "contract", []):
|
|
90
|
+
prop = text(props, "property", subject)
|
|
91
|
+
pairs.append((prop, text(props, "field", subject)))
|
|
92
|
+
return tuple(sorted(pairs))
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Flat Turtle reader used by the access-contract parser.
|
|
2
|
+
|
|
3
|
+
It tokenizes one restricted profile (see :mod:`agent_connector_sdk.access_contract`)
|
|
4
|
+
and raises :class:`AccessContractError` on anything outside it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from collections import defaultdict
|
|
11
|
+
|
|
12
|
+
RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
|
|
13
|
+
|
|
14
|
+
_TOKEN_RE = re.compile(
|
|
15
|
+
r"(?P<ws>\s+|#[^\n]*)"
|
|
16
|
+
r"|(?P<iri><[^<>\s]*>)"
|
|
17
|
+
r'|(?P<str>"(?:[^"\\\n]|\\.)*")'
|
|
18
|
+
r"|(?P<int>-?\d+)"
|
|
19
|
+
r"|(?P<prefix>@prefix)"
|
|
20
|
+
r"|(?P<pname>[A-Za-z_]?[\w-]*:(?:[\w-](?:[\w.-]*[\w-])?)?)"
|
|
21
|
+
r"|(?P<a>a(?=\s))"
|
|
22
|
+
r"|(?P<punct>[;,.])"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
Term = str | int
|
|
26
|
+
Triple = tuple[str, str, Term]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AccessContractError(ValueError):
|
|
30
|
+
"""A connector ontology declares an invalid access contract."""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _tokens(text: str) -> list[tuple[str, str]]:
|
|
34
|
+
out: list[tuple[str, str]] = []
|
|
35
|
+
pos = 0
|
|
36
|
+
while pos < len(text):
|
|
37
|
+
match = _TOKEN_RE.match(text, pos)
|
|
38
|
+
if match is None or match.end() == pos:
|
|
39
|
+
raise AccessContractError(f"unsupported Turtle at offset {pos}")
|
|
40
|
+
kind = match.lastgroup or ""
|
|
41
|
+
if kind != "ws":
|
|
42
|
+
out.append((kind, match.group()))
|
|
43
|
+
pos = match.end()
|
|
44
|
+
return out
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class _Reader:
|
|
48
|
+
def __init__(self, text: str) -> None:
|
|
49
|
+
self.toks = _tokens(text)
|
|
50
|
+
self.pos = 0
|
|
51
|
+
self.prefixes: dict[str, str] = {}
|
|
52
|
+
self.triples: list[Triple] = []
|
|
53
|
+
|
|
54
|
+
def next(self) -> tuple[str, str]:
|
|
55
|
+
if self.pos >= len(self.toks):
|
|
56
|
+
raise AccessContractError("unexpected end of Turtle")
|
|
57
|
+
self.pos += 1
|
|
58
|
+
return self.toks[self.pos - 1]
|
|
59
|
+
|
|
60
|
+
def expect(self, value: str) -> None:
|
|
61
|
+
if self.next()[1] != value:
|
|
62
|
+
raise AccessContractError(f"expected {value!r}")
|
|
63
|
+
|
|
64
|
+
def term(self) -> Term:
|
|
65
|
+
kind, value = self.next()
|
|
66
|
+
if kind == "iri":
|
|
67
|
+
return value[1:-1]
|
|
68
|
+
if kind == "a":
|
|
69
|
+
return RDF_TYPE
|
|
70
|
+
if kind == "pname":
|
|
71
|
+
prefix, _, local = value.partition(":")
|
|
72
|
+
if prefix not in self.prefixes:
|
|
73
|
+
raise AccessContractError(f"undeclared prefix {prefix!r}")
|
|
74
|
+
return self.prefixes[prefix] + local
|
|
75
|
+
if kind == "str":
|
|
76
|
+
return value[1:-1].replace('\\"', '"').replace("\\\\", "\\")
|
|
77
|
+
if kind == "int":
|
|
78
|
+
return int(value)
|
|
79
|
+
raise AccessContractError(f"unexpected token {value!r}")
|
|
80
|
+
|
|
81
|
+
def prefix(self) -> None:
|
|
82
|
+
kind, name = self.next()
|
|
83
|
+
iri = self.next()
|
|
84
|
+
if kind != "pname" or not name.endswith(":") or iri[0] != "iri":
|
|
85
|
+
raise AccessContractError("malformed @prefix")
|
|
86
|
+
self.prefixes[name[:-1]] = iri[1][1:-1]
|
|
87
|
+
self.expect(".")
|
|
88
|
+
|
|
89
|
+
def statement(self) -> None:
|
|
90
|
+
subject = str(self.term())
|
|
91
|
+
sep = ";"
|
|
92
|
+
while sep == ";":
|
|
93
|
+
predicate = str(self.term())
|
|
94
|
+
sep = ","
|
|
95
|
+
while sep == ",":
|
|
96
|
+
self.triples.append((subject, predicate, self.term()))
|
|
97
|
+
sep = self.next()[1]
|
|
98
|
+
if sep != ".":
|
|
99
|
+
raise AccessContractError("statement must end with '.'")
|
|
100
|
+
|
|
101
|
+
def read(self) -> list[Triple]:
|
|
102
|
+
while self.pos < len(self.toks):
|
|
103
|
+
if self.toks[self.pos][0] == "prefix":
|
|
104
|
+
self.pos += 1
|
|
105
|
+
self.prefix()
|
|
106
|
+
else:
|
|
107
|
+
self.statement()
|
|
108
|
+
return self.triples
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def parse_graph(text: str) -> dict[str, dict[str, list[Term]]]:
|
|
112
|
+
"""Read flat Turtle into a subject -> predicate -> objects index."""
|
|
113
|
+
graph: dict[str, dict[str, list[Term]]] = defaultdict(lambda: defaultdict(list))
|
|
114
|
+
for subject, predicate, obj in _Reader(text).read():
|
|
115
|
+
graph[subject][predicate].append(obj)
|
|
116
|
+
return graph
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Connector access contracts: how each ontology class is answered live.
|
|
2
|
+
|
|
3
|
+
A connector ontology declares ``ac:AccessContract`` subjects with the
|
|
4
|
+
vocabulary in ``ontology/access_contract.ttl`` (SDK-CONNECTOR-CONTROL-R021).
|
|
5
|
+
:func:`parse_access_contracts` reads them into typed :class:`AccessContract`
|
|
6
|
+
values. It refuses a missing operation, an unknown access kind, pagination
|
|
7
|
+
or pushdown operation, and an ``ac:authRef`` that is not a credential
|
|
8
|
+
reference. A literal secret therefore never becomes a contract.
|
|
9
|
+
|
|
10
|
+
The parser reads the flat Turtle profile: ``@prefix`` lines, prefixed names,
|
|
11
|
+
``<IRIs>``, ``a``, string and integer literals, and ``;`` ``,`` ``.``.
|
|
12
|
+
Blank nodes and collections are refused.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from importlib import resources
|
|
19
|
+
|
|
20
|
+
from . import _contract_fields as f
|
|
21
|
+
from ._contract_fields import (
|
|
22
|
+
AC,
|
|
23
|
+
ACCESS_KINDS,
|
|
24
|
+
COST_HINTS,
|
|
25
|
+
PAGINATION_MODES,
|
|
26
|
+
PUSHDOWN_OPS,
|
|
27
|
+
)
|
|
28
|
+
from ._turtle import RDF_TYPE, AccessContractError, parse_graph
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"AC",
|
|
32
|
+
"ACCESS_KINDS",
|
|
33
|
+
"PAGINATION_MODES",
|
|
34
|
+
"PUSHDOWN_OPS",
|
|
35
|
+
"AccessContract",
|
|
36
|
+
"AccessContractError",
|
|
37
|
+
"access_contract_vocabulary",
|
|
38
|
+
"parse_access_contracts",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True)
|
|
43
|
+
class AccessContract:
|
|
44
|
+
"""One ontology class bound to one live source operation.
|
|
45
|
+
|
|
46
|
+
``source_id``, ``entity``, ``class_iri``, ``key_field`` and ``predicates``
|
|
47
|
+
match the agent-utilities ``VirtualMapping`` fields of the same name.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
contract_iri: str
|
|
51
|
+
class_iri: str
|
|
52
|
+
source_id: str
|
|
53
|
+
entity: str
|
|
54
|
+
key_field: str
|
|
55
|
+
access_kind: str
|
|
56
|
+
operation: str
|
|
57
|
+
parameters: tuple[str, ...] = ()
|
|
58
|
+
pagination: str = "none"
|
|
59
|
+
pushdown: frozenset[str] = field(default_factory=frozenset)
|
|
60
|
+
auth_ref: str | None = None
|
|
61
|
+
rate_limit_per_minute: int | None = None
|
|
62
|
+
freshness_seconds: int | None = None
|
|
63
|
+
cost_hint: str = "unknown"
|
|
64
|
+
predicates: tuple[tuple[str, str], ...] = ()
|
|
65
|
+
|
|
66
|
+
def virtual_mapping_fields(self) -> dict[str, object]:
|
|
67
|
+
"""Return the keyword arguments of an unapproved ``VirtualMapping``."""
|
|
68
|
+
return {
|
|
69
|
+
"mapping_id": self.contract_iri,
|
|
70
|
+
"source_id": self.source_id,
|
|
71
|
+
"entity": self.entity,
|
|
72
|
+
"class_iri": self.class_iri,
|
|
73
|
+
"key_field": self.key_field,
|
|
74
|
+
"predicates": self.predicates,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def access_contract_vocabulary() -> str:
|
|
79
|
+
"""Return the packaged access-contract vocabulary as Turtle text."""
|
|
80
|
+
path = resources.files("agent_connector_sdk") / "ontology" / "access_contract.ttl"
|
|
81
|
+
return path.read_text(encoding="utf-8")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _contract(graph: dict[str, f.Props], subject: str) -> AccessContract:
|
|
85
|
+
props = graph[subject]
|
|
86
|
+
return AccessContract(
|
|
87
|
+
contract_iri=subject,
|
|
88
|
+
class_iri=f.text(props, "classIri", subject),
|
|
89
|
+
source_id=f.text(props, "sourceId", subject),
|
|
90
|
+
entity=f.text(props, "entity", subject),
|
|
91
|
+
key_field=f.text(props, "keyField", subject),
|
|
92
|
+
access_kind=f.choice(props, "accessKind", subject, allowed=ACCESS_KINDS),
|
|
93
|
+
operation=f.text(props, "operation", subject),
|
|
94
|
+
parameters=f.parameters(props),
|
|
95
|
+
pagination=f.choice(
|
|
96
|
+
props, "pagination", subject, allowed=PAGINATION_MODES, default="none"
|
|
97
|
+
),
|
|
98
|
+
pushdown=f.pushdown(props, subject),
|
|
99
|
+
auth_ref=f.auth_ref(props, subject),
|
|
100
|
+
rate_limit_per_minute=f.integer(props, "rateLimitPerMinute", subject),
|
|
101
|
+
freshness_seconds=f.integer(props, "freshnessSeconds", subject),
|
|
102
|
+
cost_hint=f.choice(
|
|
103
|
+
props,
|
|
104
|
+
"costHint",
|
|
105
|
+
subject,
|
|
106
|
+
allowed=COST_HINTS | {"unknown"},
|
|
107
|
+
default="unknown",
|
|
108
|
+
),
|
|
109
|
+
predicates=f.bindings(graph, subject),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def parse_access_contracts(turtle: str) -> tuple[AccessContract, ...]:
|
|
114
|
+
"""Parse every ``ac:AccessContract`` declared in one connector ontology.
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
AccessContractError: for unsupported Turtle or an invalid contract.
|
|
118
|
+
"""
|
|
119
|
+
graph = parse_graph(turtle)
|
|
120
|
+
subjects = sorted(
|
|
121
|
+
s
|
|
122
|
+
for s, props in graph.items()
|
|
123
|
+
if AC + "AccessContract" in props.get(RDF_TYPE, [])
|
|
124
|
+
)
|
|
125
|
+
return tuple(_contract(graph, s) for s in subjects)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reference source adapters (``agent_connector_sdk.source_adapters`` entry points)."""
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""The operational event envelope (SDK-OBSERVABILITY-FEEDS-R001).
|
|
2
|
+
|
|
3
|
+
RUM, security-audit and CI/CD events carry one standard envelope before they
|
|
4
|
+
are mapped into a generated ``SourceRecord``: stable identity, a three-point
|
|
5
|
+
time basis, actor/target references, a privacy label, retention metadata and
|
|
6
|
+
a payload digest. Validating the envelope does not interpret the event as a
|
|
7
|
+
verified incident, vulnerability, release, or user outcome; that conclusion
|
|
8
|
+
belongs to graph-side policy and analysis.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import hashlib
|
|
14
|
+
import json
|
|
15
|
+
from collections.abc import Mapping
|
|
16
|
+
from enum import StrEnum
|
|
17
|
+
|
|
18
|
+
from pydantic import (
|
|
19
|
+
AwareDatetime,
|
|
20
|
+
BaseModel,
|
|
21
|
+
ConfigDict,
|
|
22
|
+
Field,
|
|
23
|
+
JsonValue,
|
|
24
|
+
model_validator,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"EventEnvelope",
|
|
29
|
+
"FeedKind",
|
|
30
|
+
"VisibilityLabel",
|
|
31
|
+
"compute_payload_digest",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class FeedKind(StrEnum):
|
|
36
|
+
"""The three operational feed kinds SDK-OBSERVABILITY-FEEDS-R001 admits."""
|
|
37
|
+
|
|
38
|
+
RUM = "rum"
|
|
39
|
+
SECURITY_AUDIT = "security_audit"
|
|
40
|
+
CI_CD = "ci_cd"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class VisibilityLabel(StrEnum):
|
|
44
|
+
"""The sensitivity label every event envelope must carry."""
|
|
45
|
+
|
|
46
|
+
PUBLIC = "public"
|
|
47
|
+
INTERNAL = "internal"
|
|
48
|
+
RESTRICTED = "restricted"
|
|
49
|
+
SENSITIVE = "sensitive"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def compute_payload_digest(payload: Mapping[str, JsonValue]) -> str:
|
|
53
|
+
"""The SDK-owned sha256 of an event payload's canonical JSON encoding.
|
|
54
|
+
|
|
55
|
+
The digest is always computed here, never trusted from the provider: it
|
|
56
|
+
is what makes a repeated event id with an unchanged payload idempotent
|
|
57
|
+
and a repeated id with a changed payload a detectable conflict.
|
|
58
|
+
"""
|
|
59
|
+
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
60
|
+
return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class EventEnvelope(BaseModel):
|
|
64
|
+
"""The standard shape every operational event carries into the pipeline.
|
|
65
|
+
|
|
66
|
+
Missing required identity or an impossible time order fails construction
|
|
67
|
+
instead of being patched with the current time.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
71
|
+
|
|
72
|
+
tenant_id: str = Field(min_length=1)
|
|
73
|
+
source: str = Field(min_length=1)
|
|
74
|
+
stream: str = Field(min_length=1)
|
|
75
|
+
feed_kind: FeedKind
|
|
76
|
+
event_kind: str = Field(min_length=1)
|
|
77
|
+
schema_version: str = Field(min_length=1)
|
|
78
|
+
provider_event_id: str | None = None
|
|
79
|
+
canonical_source_digest: str | None = None
|
|
80
|
+
event_time: AwareDatetime
|
|
81
|
+
source_observed_time: AwareDatetime
|
|
82
|
+
ingest_time: AwareDatetime
|
|
83
|
+
actor_ref: str = Field(min_length=1)
|
|
84
|
+
target_ref: str = Field(min_length=1)
|
|
85
|
+
trace_id: str | None = None
|
|
86
|
+
visibility_label: VisibilityLabel
|
|
87
|
+
license: str | None = None
|
|
88
|
+
retention_expires_at: AwareDatetime | None = None
|
|
89
|
+
payload: dict[str, JsonValue]
|
|
90
|
+
payload_digest: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def identity(self) -> str:
|
|
94
|
+
"""The event's stable identity: its provider id, or its canonical digest."""
|
|
95
|
+
return self.provider_event_id or self.canonical_source_digest or ""
|
|
96
|
+
|
|
97
|
+
@model_validator(mode="after")
|
|
98
|
+
def _check_identity_and_time_basis(self) -> EventEnvelope:
|
|
99
|
+
if not (self.provider_event_id or self.canonical_source_digest):
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"event envelope needs a provider_event_id or canonical_source_digest"
|
|
102
|
+
)
|
|
103
|
+
if self.event_time > self.source_observed_time:
|
|
104
|
+
raise ValueError("event_time is after source_observed_time")
|
|
105
|
+
if self.source_observed_time > self.ingest_time:
|
|
106
|
+
raise ValueError("source_observed_time is after ingest_time")
|
|
107
|
+
if self.payload_digest != compute_payload_digest(self.payload):
|
|
108
|
+
raise ValueError("payload_digest does not match its payload")
|
|
109
|
+
return self
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Typed failures specific to operational event feeds (SDK-OBSERVABILITY-FEEDS-R001).
|
|
2
|
+
|
|
3
|
+
These sit beside the generic :mod:`agent_connector_sdk.ports.errors` taxonomy:
|
|
4
|
+
a conflicting duplicate is still malformed source data, and backpressure or an
|
|
5
|
+
expired retention window are still the live source failing its contract, but
|
|
6
|
+
callers need to distinguish them from the generic cases to log and retry
|
|
7
|
+
correctly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from agent_connector_sdk.ports.errors import (
|
|
13
|
+
MalformedSourceDataError,
|
|
14
|
+
SourceContractError,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"EventIdentityConflictError",
|
|
19
|
+
"FeedBackpressureError",
|
|
20
|
+
"FeedRetentionExpiredError",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class EventIdentityConflictError(MalformedSourceDataError):
|
|
25
|
+
"""A repeated event id arrived with a changed payload digest.
|
|
26
|
+
|
|
27
|
+
A conflict is refused rather than silently overwriting the earlier
|
|
28
|
+
version: the provider must emit a new identity for a changed event.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FeedBackpressureError(SourceContractError):
|
|
33
|
+
"""The provider signaled rate limiting (HTTP 429) or an outage.
|
|
34
|
+
|
|
35
|
+
``retry_after_seconds`` is advisory and is never used to fabricate a
|
|
36
|
+
completed page or to advance the checkpoint.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(
|
|
40
|
+
self, message: str, *, retry_after_seconds: float | None = None
|
|
41
|
+
) -> None:
|
|
42
|
+
super().__init__(message)
|
|
43
|
+
self.retry_after_seconds = retry_after_seconds
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FeedRetentionExpiredError(SourceContractError):
|
|
47
|
+
"""The checkpoint position fell outside the provider's retention window.
|
|
48
|
+
|
|
49
|
+
The checkpoint cannot resume from where it left off; the stream needs an
|
|
50
|
+
explicit re-baseline, which is outside this adapter's authority.
|
|
51
|
+
"""
|