parmana-connector-sdk 0.1.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.
- parmana_connector_sdk-0.1.0/PKG-INFO +165 -0
- parmana_connector_sdk-0.1.0/README.md +133 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/__init__.py +90 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/connectors/__init__.py +5 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/connectors/oracle.py +29 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/connectors/salesforce.py +29 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/connectors/sap.py +29 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/connectors/workday.py +29 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/credentials.py +137 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/factory.py +24 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/metadata.py +66 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/mock_connector.py +72 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/py.typed +0 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/types.py +125 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk/version.py +1 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk.egg-info/PKG-INFO +165 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk.egg-info/SOURCES.txt +24 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk.egg-info/dependency_links.txt +1 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk.egg-info/requires.txt +6 -0
- parmana_connector_sdk-0.1.0/parmana_connector_sdk.egg-info/top_level.txt +1 -0
- parmana_connector_sdk-0.1.0/pyproject.toml +94 -0
- parmana_connector_sdk-0.1.0/setup.cfg +4 -0
- parmana_connector_sdk-0.1.0/tests/test_credentials.py +102 -0
- parmana_connector_sdk-0.1.0/tests/test_enterprise_mock_connectors.py +103 -0
- parmana_connector_sdk-0.1.0/tests/test_mock_connector.py +109 -0
- parmana_connector_sdk-0.1.0/tests/test_types.py +39 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parmana-connector-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Connector authoring contracts and reference implementations for Parmana, the authorization layer for AI execution.
|
|
5
|
+
Author: Parmana
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/pavancharak/parmana
|
|
8
|
+
Project-URL: Website, https://parmanasystems.com/
|
|
9
|
+
Project-URL: Documentation, https://docs.parmanasystems.com
|
|
10
|
+
Project-URL: Repository, https://github.com/pavancharak/parmana
|
|
11
|
+
Project-URL: Issues, https://github.com/pavancharak/parmana/issues
|
|
12
|
+
Keywords: ai,agentic-ai,governance,authorization,connector,integration,sdk
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
30
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
32
|
+
|
|
33
|
+
# parmana-connector-sdk
|
|
34
|
+
|
|
35
|
+
Contracts and reference implementations for building a Connector for [Parmana](https://github.com/pavancharak/parmana), the authorization layer for AI execution.
|
|
36
|
+
|
|
37
|
+
A Connector is the last hop in Parmana's pipeline: it receives an already-authorized, already-verified request and carries it out against a real system (an API, a database, anything). A Connector never evaluates policy, never authorizes execution, and never resolves its own credentials — it only executes, using a credential Parmana has already resolved for it.
|
|
38
|
+
|
|
39
|
+
This is the Python counterpart to the TypeScript [`@parmana/connector-sdk`](https://www.npmjs.com/package/@parmana/connector-sdk) package — same contract, same design, idiomatic Python (synchronous, dataclasses and `Protocol` instead of interfaces).
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install parmana-connector-sdk
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## The contract
|
|
48
|
+
|
|
49
|
+
Every Connector implements one protocol:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from parmana_connector_sdk import (
|
|
53
|
+
Connector,
|
|
54
|
+
ConnectorRequest,
|
|
55
|
+
ConnectorExecutionContext,
|
|
56
|
+
ConnectorResponse,
|
|
57
|
+
connector_capabilities,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class MyConnector:
|
|
62
|
+
connector_id = "my-service"
|
|
63
|
+
capabilities = connector_capabilities(["my-service:do-thing"])
|
|
64
|
+
|
|
65
|
+
def execute(
|
|
66
|
+
self, request: ConnectorRequest, context: ConnectorExecutionContext
|
|
67
|
+
) -> ConnectorResponse:
|
|
68
|
+
# request.parameters is exactly what was authorized -- never re-derive it.
|
|
69
|
+
# context.credential is an opaque, already-resolved CredentialHandle.
|
|
70
|
+
# context.timeout_ms is how long you have.
|
|
71
|
+
return ConnectorResponse(success=True, metadata={})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`Connector` is a `typing.Protocol` — `MyConnector` doesn't need to subclass anything, it just
|
|
75
|
+
needs to match the shape (`connector_id`, `capabilities`, `execute`).
|
|
76
|
+
|
|
77
|
+
`capabilities` must be namespaced verbs (`"namespace:verb"`, e.g. `"crm:update-contact"`),
|
|
78
|
+
validated eagerly by `connector_capabilities()` so a malformed capability fails at
|
|
79
|
+
construction, not mid-execution.
|
|
80
|
+
|
|
81
|
+
## One reference implementation, for testing
|
|
82
|
+
|
|
83
|
+
`MockConnector` gives you scripted responses or failure injection, and records every request
|
|
84
|
+
it receives, so you can test your own Connector's callers without standing up a real endpoint:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from parmana_connector_sdk import MockConnector, MockConnectorOptions, MockConnectorScript, connector_capabilities
|
|
88
|
+
|
|
89
|
+
connector = MockConnector(
|
|
90
|
+
MockConnectorOptions(
|
|
91
|
+
connector_id="billing",
|
|
92
|
+
capabilities=connector_capabilities(["billing:issue-refund"]),
|
|
93
|
+
script=MockConnectorScript(
|
|
94
|
+
respond=lambda request, context: ConnectorResponse(
|
|
95
|
+
success=True, metadata={"refund_id": "r_1"}
|
|
96
|
+
)
|
|
97
|
+
),
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
connector.execute(request, context)
|
|
102
|
+
connector.invocations # every request it received, for assertions
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This package intentionally ships no HTTP-calling reference implementation — only the contract
|
|
106
|
+
and the hermetic mock above. A REST-shaped connector is usually this simple to write directly
|
|
107
|
+
against the contract:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
import urllib.request
|
|
111
|
+
import json
|
|
112
|
+
|
|
113
|
+
from parmana_connector_sdk import (
|
|
114
|
+
ConnectorRequest,
|
|
115
|
+
ConnectorExecutionContext,
|
|
116
|
+
ConnectorResponse,
|
|
117
|
+
connector_capabilities,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class BillingConnector:
|
|
122
|
+
connector_id = "billing"
|
|
123
|
+
capabilities = connector_capabilities(["billing:issue-refund"])
|
|
124
|
+
|
|
125
|
+
def __init__(self, base_url: str) -> None:
|
|
126
|
+
self.base_url = base_url
|
|
127
|
+
|
|
128
|
+
def execute(
|
|
129
|
+
self, request: ConnectorRequest, context: ConnectorExecutionContext
|
|
130
|
+
) -> ConnectorResponse:
|
|
131
|
+
token = context.credential.value # shape is whatever your CredentialProvider resolved
|
|
132
|
+
body = json.dumps(request.parameters).encode()
|
|
133
|
+
req = urllib.request.Request(
|
|
134
|
+
f"{self.base_url}/{request.action}",
|
|
135
|
+
data=body,
|
|
136
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|
137
|
+
method="POST",
|
|
138
|
+
)
|
|
139
|
+
with urllib.request.urlopen(req, timeout=context.timeout_ms / 1000) as response:
|
|
140
|
+
if response.status >= 300:
|
|
141
|
+
raise RuntimeError(f"HTTP {response.status}")
|
|
142
|
+
return ConnectorResponse(success=True, metadata=json.load(response))
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Four worked examples
|
|
146
|
+
|
|
147
|
+
`parmana_connector_sdk.connectors.{oracle,salesforce,sap,workday}` are deterministic
|
|
148
|
+
`MockConnector`s shaped like a real enterprise integration — each declares one realistic
|
|
149
|
+
capability and nothing else. They are explicit mocks, not real integrations; read them as a
|
|
150
|
+
template for structuring your own connector's capability and metadata, not as something to
|
|
151
|
+
call in production.
|
|
152
|
+
|
|
153
|
+
## Credentials
|
|
154
|
+
|
|
155
|
+
A Connector never resolves its own credentials. `CredentialProvider` is the seam Parmana's
|
|
156
|
+
Execution Gateway uses upstream of a Connector — `EnvironmentCredentialProvider` (reads a
|
|
157
|
+
mapped env var) and `StaticCredentialProvider` (in-memory, for tests) are the two provided
|
|
158
|
+
implementations.
|
|
159
|
+
|
|
160
|
+
## Full reference
|
|
161
|
+
|
|
162
|
+
See the [Connector SDK reference](https://docs.parmanasystems.com/reference/connector-sdk) and
|
|
163
|
+
the [Connector Development Guide](https://docs.parmanasystems.com/integrations/connector-development-guide)
|
|
164
|
+
— written against the TypeScript package, but the contract this package implements is
|
|
165
|
+
identical.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# parmana-connector-sdk
|
|
2
|
+
|
|
3
|
+
Contracts and reference implementations for building a Connector for [Parmana](https://github.com/pavancharak/parmana), the authorization layer for AI execution.
|
|
4
|
+
|
|
5
|
+
A Connector is the last hop in Parmana's pipeline: it receives an already-authorized, already-verified request and carries it out against a real system (an API, a database, anything). A Connector never evaluates policy, never authorizes execution, and never resolves its own credentials — it only executes, using a credential Parmana has already resolved for it.
|
|
6
|
+
|
|
7
|
+
This is the Python counterpart to the TypeScript [`@parmana/connector-sdk`](https://www.npmjs.com/package/@parmana/connector-sdk) package — same contract, same design, idiomatic Python (synchronous, dataclasses and `Protocol` instead of interfaces).
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install parmana-connector-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## The contract
|
|
16
|
+
|
|
17
|
+
Every Connector implements one protocol:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from parmana_connector_sdk import (
|
|
21
|
+
Connector,
|
|
22
|
+
ConnectorRequest,
|
|
23
|
+
ConnectorExecutionContext,
|
|
24
|
+
ConnectorResponse,
|
|
25
|
+
connector_capabilities,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MyConnector:
|
|
30
|
+
connector_id = "my-service"
|
|
31
|
+
capabilities = connector_capabilities(["my-service:do-thing"])
|
|
32
|
+
|
|
33
|
+
def execute(
|
|
34
|
+
self, request: ConnectorRequest, context: ConnectorExecutionContext
|
|
35
|
+
) -> ConnectorResponse:
|
|
36
|
+
# request.parameters is exactly what was authorized -- never re-derive it.
|
|
37
|
+
# context.credential is an opaque, already-resolved CredentialHandle.
|
|
38
|
+
# context.timeout_ms is how long you have.
|
|
39
|
+
return ConnectorResponse(success=True, metadata={})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`Connector` is a `typing.Protocol` — `MyConnector` doesn't need to subclass anything, it just
|
|
43
|
+
needs to match the shape (`connector_id`, `capabilities`, `execute`).
|
|
44
|
+
|
|
45
|
+
`capabilities` must be namespaced verbs (`"namespace:verb"`, e.g. `"crm:update-contact"`),
|
|
46
|
+
validated eagerly by `connector_capabilities()` so a malformed capability fails at
|
|
47
|
+
construction, not mid-execution.
|
|
48
|
+
|
|
49
|
+
## One reference implementation, for testing
|
|
50
|
+
|
|
51
|
+
`MockConnector` gives you scripted responses or failure injection, and records every request
|
|
52
|
+
it receives, so you can test your own Connector's callers without standing up a real endpoint:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from parmana_connector_sdk import MockConnector, MockConnectorOptions, MockConnectorScript, connector_capabilities
|
|
56
|
+
|
|
57
|
+
connector = MockConnector(
|
|
58
|
+
MockConnectorOptions(
|
|
59
|
+
connector_id="billing",
|
|
60
|
+
capabilities=connector_capabilities(["billing:issue-refund"]),
|
|
61
|
+
script=MockConnectorScript(
|
|
62
|
+
respond=lambda request, context: ConnectorResponse(
|
|
63
|
+
success=True, metadata={"refund_id": "r_1"}
|
|
64
|
+
)
|
|
65
|
+
),
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
connector.execute(request, context)
|
|
70
|
+
connector.invocations # every request it received, for assertions
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This package intentionally ships no HTTP-calling reference implementation — only the contract
|
|
74
|
+
and the hermetic mock above. A REST-shaped connector is usually this simple to write directly
|
|
75
|
+
against the contract:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import urllib.request
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
from parmana_connector_sdk import (
|
|
82
|
+
ConnectorRequest,
|
|
83
|
+
ConnectorExecutionContext,
|
|
84
|
+
ConnectorResponse,
|
|
85
|
+
connector_capabilities,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class BillingConnector:
|
|
90
|
+
connector_id = "billing"
|
|
91
|
+
capabilities = connector_capabilities(["billing:issue-refund"])
|
|
92
|
+
|
|
93
|
+
def __init__(self, base_url: str) -> None:
|
|
94
|
+
self.base_url = base_url
|
|
95
|
+
|
|
96
|
+
def execute(
|
|
97
|
+
self, request: ConnectorRequest, context: ConnectorExecutionContext
|
|
98
|
+
) -> ConnectorResponse:
|
|
99
|
+
token = context.credential.value # shape is whatever your CredentialProvider resolved
|
|
100
|
+
body = json.dumps(request.parameters).encode()
|
|
101
|
+
req = urllib.request.Request(
|
|
102
|
+
f"{self.base_url}/{request.action}",
|
|
103
|
+
data=body,
|
|
104
|
+
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
|
105
|
+
method="POST",
|
|
106
|
+
)
|
|
107
|
+
with urllib.request.urlopen(req, timeout=context.timeout_ms / 1000) as response:
|
|
108
|
+
if response.status >= 300:
|
|
109
|
+
raise RuntimeError(f"HTTP {response.status}")
|
|
110
|
+
return ConnectorResponse(success=True, metadata=json.load(response))
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Four worked examples
|
|
114
|
+
|
|
115
|
+
`parmana_connector_sdk.connectors.{oracle,salesforce,sap,workday}` are deterministic
|
|
116
|
+
`MockConnector`s shaped like a real enterprise integration — each declares one realistic
|
|
117
|
+
capability and nothing else. They are explicit mocks, not real integrations; read them as a
|
|
118
|
+
template for structuring your own connector's capability and metadata, not as something to
|
|
119
|
+
call in production.
|
|
120
|
+
|
|
121
|
+
## Credentials
|
|
122
|
+
|
|
123
|
+
A Connector never resolves its own credentials. `CredentialProvider` is the seam Parmana's
|
|
124
|
+
Execution Gateway uses upstream of a Connector — `EnvironmentCredentialProvider` (reads a
|
|
125
|
+
mapped env var) and `StaticCredentialProvider` (in-memory, for tests) are the two provided
|
|
126
|
+
implementations.
|
|
127
|
+
|
|
128
|
+
## Full reference
|
|
129
|
+
|
|
130
|
+
See the [Connector SDK reference](https://docs.parmanasystems.com/reference/connector-sdk) and
|
|
131
|
+
the [Connector Development Guide](https://docs.parmanasystems.com/integrations/connector-development-guide)
|
|
132
|
+
— written against the TypeScript package, but the contract this package implements is
|
|
133
|
+
identical.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parmana Connector SDK (Python).
|
|
3
|
+
|
|
4
|
+
Connector authoring contracts: capability definitions, DTOs, metadata, and
|
|
5
|
+
the Connector/CredentialProvider protocols. Passive by design -- this
|
|
6
|
+
package describes capabilities, it does not execute them for real. No
|
|
7
|
+
HTTP-calling reference implementation ships from this package; see the
|
|
8
|
+
README for why, and for how short a real connector is to write directly
|
|
9
|
+
against the Connector protocol.
|
|
10
|
+
|
|
11
|
+
Mirrors the TypeScript @parmana/connector-sdk package's shape; see that
|
|
12
|
+
package for the canonical design rationale behind each contract.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .connectors.oracle import OracleMetadata, create_oracle_connector
|
|
16
|
+
from .connectors.salesforce import SalesforceMetadata, create_salesforce_connector
|
|
17
|
+
from .connectors.sap import SapMetadata, create_sap_connector
|
|
18
|
+
from .connectors.workday import WorkdayMetadata, create_workday_connector
|
|
19
|
+
from .credentials import (
|
|
20
|
+
CredentialHandle,
|
|
21
|
+
CredentialProvider,
|
|
22
|
+
EnvironmentCredentialProvider,
|
|
23
|
+
StaticCredentialProvider,
|
|
24
|
+
brand_credential_handle,
|
|
25
|
+
is_credential_handle,
|
|
26
|
+
)
|
|
27
|
+
from .factory import ConnectorFactory
|
|
28
|
+
from .metadata import (
|
|
29
|
+
ConnectorHealth,
|
|
30
|
+
ConnectorHealthStatus,
|
|
31
|
+
ConnectorMetadata,
|
|
32
|
+
ConnectorVersion,
|
|
33
|
+
connector_versions_equal,
|
|
34
|
+
format_connector_version,
|
|
35
|
+
healthy_now,
|
|
36
|
+
)
|
|
37
|
+
from .mock_connector import MockConnector, MockConnectorOptions, MockConnectorScript
|
|
38
|
+
from .types import (
|
|
39
|
+
Connector,
|
|
40
|
+
ConnectorCapabilities,
|
|
41
|
+
ConnectorCapability,
|
|
42
|
+
ConnectorExecutionContext,
|
|
43
|
+
ConnectorRequest,
|
|
44
|
+
ConnectorResponse,
|
|
45
|
+
connector_capabilities,
|
|
46
|
+
is_namespaced_capability,
|
|
47
|
+
)
|
|
48
|
+
from .version import __version__
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"__version__",
|
|
52
|
+
# Contract
|
|
53
|
+
"Connector",
|
|
54
|
+
"ConnectorCapability",
|
|
55
|
+
"ConnectorCapabilities",
|
|
56
|
+
"ConnectorExecutionContext",
|
|
57
|
+
"ConnectorRequest",
|
|
58
|
+
"ConnectorResponse",
|
|
59
|
+
"connector_capabilities",
|
|
60
|
+
"is_namespaced_capability",
|
|
61
|
+
"ConnectorFactory",
|
|
62
|
+
# Metadata
|
|
63
|
+
"ConnectorHealth",
|
|
64
|
+
"ConnectorHealthStatus",
|
|
65
|
+
"ConnectorMetadata",
|
|
66
|
+
"ConnectorVersion",
|
|
67
|
+
"connector_versions_equal",
|
|
68
|
+
"format_connector_version",
|
|
69
|
+
"healthy_now",
|
|
70
|
+
# Credentials
|
|
71
|
+
"CredentialHandle",
|
|
72
|
+
"CredentialProvider",
|
|
73
|
+
"EnvironmentCredentialProvider",
|
|
74
|
+
"StaticCredentialProvider",
|
|
75
|
+
"brand_credential_handle",
|
|
76
|
+
"is_credential_handle",
|
|
77
|
+
# Reference implementation
|
|
78
|
+
"MockConnector",
|
|
79
|
+
"MockConnectorOptions",
|
|
80
|
+
"MockConnectorScript",
|
|
81
|
+
# Four enterprise-named reference mocks
|
|
82
|
+
"OracleMetadata",
|
|
83
|
+
"create_oracle_connector",
|
|
84
|
+
"SalesforceMetadata",
|
|
85
|
+
"create_salesforce_connector",
|
|
86
|
+
"SapMetadata",
|
|
87
|
+
"create_sap_connector",
|
|
88
|
+
"WorkdayMetadata",
|
|
89
|
+
"create_workday_connector",
|
|
90
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock Oracle connector.
|
|
3
|
+
|
|
4
|
+
Deterministic, in-memory connector used until a real enterprise connector
|
|
5
|
+
is implemented.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..metadata import ConnectorMetadata, ConnectorVersion, healthy_now
|
|
11
|
+
from ..mock_connector import MockConnector, MockConnectorOptions
|
|
12
|
+
from ..types import connector_capabilities
|
|
13
|
+
|
|
14
|
+
OracleMetadata = ConnectorMetadata(
|
|
15
|
+
connector_id="oracle",
|
|
16
|
+
display_name="Oracle",
|
|
17
|
+
version=ConnectorVersion(major=1, minor=0, patch=0),
|
|
18
|
+
health=healthy_now(),
|
|
19
|
+
description="Mock connector for Oracle purchase order execution.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_oracle_connector() -> MockConnector:
|
|
24
|
+
return MockConnector(
|
|
25
|
+
MockConnectorOptions(
|
|
26
|
+
connector_id="oracle",
|
|
27
|
+
capabilities=connector_capabilities(["oracle:create-purchase-order"]),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock Salesforce connector.
|
|
3
|
+
|
|
4
|
+
Deterministic, in-memory connector used until a real enterprise connector
|
|
5
|
+
is implemented.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..metadata import ConnectorMetadata, ConnectorVersion, healthy_now
|
|
11
|
+
from ..mock_connector import MockConnector, MockConnectorOptions
|
|
12
|
+
from ..types import connector_capabilities
|
|
13
|
+
|
|
14
|
+
SalesforceMetadata = ConnectorMetadata(
|
|
15
|
+
connector_id="salesforce",
|
|
16
|
+
display_name="Salesforce",
|
|
17
|
+
version=ConnectorVersion(major=1, minor=0, patch=0),
|
|
18
|
+
health=healthy_now(),
|
|
19
|
+
description="Mock connector for Salesforce opportunity execution.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_salesforce_connector() -> MockConnector:
|
|
24
|
+
return MockConnector(
|
|
25
|
+
MockConnectorOptions(
|
|
26
|
+
connector_id="salesforce",
|
|
27
|
+
capabilities=connector_capabilities(["salesforce:update-opportunity"]),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock SAP connector.
|
|
3
|
+
|
|
4
|
+
Deterministic, in-memory connector used until a real enterprise connector
|
|
5
|
+
is implemented.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..metadata import ConnectorMetadata, ConnectorVersion, healthy_now
|
|
11
|
+
from ..mock_connector import MockConnector, MockConnectorOptions
|
|
12
|
+
from ..types import connector_capabilities
|
|
13
|
+
|
|
14
|
+
SapMetadata = ConnectorMetadata(
|
|
15
|
+
connector_id="sap",
|
|
16
|
+
display_name="SAP",
|
|
17
|
+
version=ConnectorVersion(major=1, minor=0, patch=0),
|
|
18
|
+
health=healthy_now(),
|
|
19
|
+
description="Mock connector for SAP invoice execution.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_sap_connector() -> MockConnector:
|
|
24
|
+
return MockConnector(
|
|
25
|
+
MockConnectorOptions(
|
|
26
|
+
connector_id="sap",
|
|
27
|
+
capabilities=connector_capabilities(["sap:post-invoice"]),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Mock Workday connector.
|
|
3
|
+
|
|
4
|
+
Deterministic, in-memory connector used until a real enterprise connector
|
|
5
|
+
is implemented.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from ..metadata import ConnectorMetadata, ConnectorVersion, healthy_now
|
|
11
|
+
from ..mock_connector import MockConnector, MockConnectorOptions
|
|
12
|
+
from ..types import connector_capabilities
|
|
13
|
+
|
|
14
|
+
WorkdayMetadata = ConnectorMetadata(
|
|
15
|
+
connector_id="workday",
|
|
16
|
+
display_name="Workday",
|
|
17
|
+
version=ConnectorVersion(major=1, minor=0, patch=0),
|
|
18
|
+
health=healthy_now(),
|
|
19
|
+
description="Mock connector for Workday expense report execution.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_workday_connector() -> MockConnector:
|
|
24
|
+
return MockConnector(
|
|
25
|
+
MockConnectorOptions(
|
|
26
|
+
connector_id="workday",
|
|
27
|
+
capabilities=connector_capabilities(["workday:submit-expense-report"]),
|
|
28
|
+
)
|
|
29
|
+
)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Credential resolution seam.
|
|
3
|
+
|
|
4
|
+
Credential resolution happens exclusively inside the Execution Gateway (via a
|
|
5
|
+
CredentialVault backed by one of these providers). Connectors never resolve,
|
|
6
|
+
retrieve, discover, or store credentials themselves -- they only ever receive
|
|
7
|
+
an opaque, already-resolved CredentialHandle.
|
|
8
|
+
|
|
9
|
+
This module defines the interface seam for future providers (HashiCorp
|
|
10
|
+
Vault, AWS Secrets Manager, Azure Key Vault, Google Secret Manager). Only the
|
|
11
|
+
seam is defined here; no cloud SDK integration is implemented.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
from collections.abc import Mapping
|
|
18
|
+
from dataclasses import dataclass, field, replace
|
|
19
|
+
from typing import Any, Protocol, runtime_checkable
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class CredentialHandle:
|
|
24
|
+
"""
|
|
25
|
+
Opaque credential material resolved by a CredentialProvider.
|
|
26
|
+
|
|
27
|
+
``value`` is never logged, never included in connector evidence, and
|
|
28
|
+
never serialized into an Execution Trust Record. It exists only to be
|
|
29
|
+
read, ephemerally, by a Connector's own execution call (e.g. an HTTP
|
|
30
|
+
header).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
provider_id: str
|
|
34
|
+
credential_id: str
|
|
35
|
+
value: Any
|
|
36
|
+
_branded: bool = field(default=False, repr=False, compare=False)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def brand_credential_handle(handle: CredentialHandle) -> CredentialHandle:
|
|
40
|
+
"""
|
|
41
|
+
Marks a CredentialHandle as having been produced by a CredentialProvider,
|
|
42
|
+
as opposed to a raw value supplied directly by a caller. Production
|
|
43
|
+
connector executors reject any credential that is not branded this way,
|
|
44
|
+
closing the "raw credential supplied" gap execution must fail closed on.
|
|
45
|
+
"""
|
|
46
|
+
return replace(handle, _branded=True)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_credential_handle(value: object) -> bool:
|
|
50
|
+
return isinstance(value, CredentialHandle) and value._branded
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@runtime_checkable
|
|
54
|
+
class CredentialProvider(Protocol):
|
|
55
|
+
"""
|
|
56
|
+
Resolves opaque credential material for a connector.
|
|
57
|
+
|
|
58
|
+
Implementations MUST NOT raise errors that embed the resolved secret
|
|
59
|
+
value -- only identifiers (connector_id, provider_id, credential_id) may
|
|
60
|
+
appear in error messages.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
provider_id: str
|
|
64
|
+
|
|
65
|
+
def resolve(self, connector_id: str) -> CredentialHandle: ...
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class EnvironmentCredentialProvider:
|
|
69
|
+
"""
|
|
70
|
+
Resolves credentials from process environment variables.
|
|
71
|
+
|
|
72
|
+
Future providers (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault,
|
|
73
|
+
Google Secret Manager) implement the same CredentialProvider protocol.
|
|
74
|
+
None is implemented in this package.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
provider_id = "environment"
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
connector_environment_variable: Mapping[str, str],
|
|
82
|
+
environment: Mapping[str, str | None] | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
self._connector_environment_variable = dict(connector_environment_variable)
|
|
85
|
+
self._environment: Mapping[str, str | None] = (
|
|
86
|
+
environment if environment is not None else os.environ
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def resolve(self, connector_id: str) -> CredentialHandle:
|
|
90
|
+
variable_name = self._connector_environment_variable.get(connector_id)
|
|
91
|
+
if variable_name is None:
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"No environment credential mapping configured for connector: "
|
|
94
|
+
f"{connector_id}."
|
|
95
|
+
)
|
|
96
|
+
value = self._environment.get(variable_name)
|
|
97
|
+
if value is None:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f'Environment variable "{variable_name}" for connector '
|
|
100
|
+
f'"{connector_id}" is not set.'
|
|
101
|
+
)
|
|
102
|
+
return brand_credential_handle(
|
|
103
|
+
CredentialHandle(
|
|
104
|
+
provider_id=self.provider_id,
|
|
105
|
+
credential_id=variable_name,
|
|
106
|
+
value={"token": value},
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class StaticCredentialProvider:
|
|
112
|
+
"""
|
|
113
|
+
Resolves credentials from an in-memory map.
|
|
114
|
+
|
|
115
|
+
Intended for hermetic tests and local development only.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
provider_id = "static"
|
|
119
|
+
|
|
120
|
+
def __init__(self, entries: Mapping[str, Any] | None = None) -> None:
|
|
121
|
+
self._credentials: dict[str, Any] = dict(entries or {})
|
|
122
|
+
|
|
123
|
+
def set(self, connector_id: str, value: Any) -> None:
|
|
124
|
+
self._credentials[connector_id] = value
|
|
125
|
+
|
|
126
|
+
def resolve(self, connector_id: str) -> CredentialHandle:
|
|
127
|
+
if connector_id not in self._credentials:
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"No static credential configured for connector: {connector_id}."
|
|
130
|
+
)
|
|
131
|
+
return brand_credential_handle(
|
|
132
|
+
CredentialHandle(
|
|
133
|
+
provider_id=self.provider_id,
|
|
134
|
+
credential_id=connector_id,
|
|
135
|
+
value=self._credentials[connector_id],
|
|
136
|
+
)
|
|
137
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Construction contract a connector factory can implement.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Generic, Protocol, TypeVar
|
|
8
|
+
|
|
9
|
+
from .types import Connector
|
|
10
|
+
|
|
11
|
+
TConfig = TypeVar("TConfig", contravariant=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConnectorFactory(Protocol, Generic[TConfig]):
|
|
15
|
+
"""
|
|
16
|
+
Constructs a Connector from configuration.
|
|
17
|
+
|
|
18
|
+
No concrete factory ships in this package; only the seam is defined so
|
|
19
|
+
connectors have a single, consistent construction contract to target.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
connector_type: str
|
|
23
|
+
|
|
24
|
+
def create(self, config: TConfig) -> Connector: ...
|