immich-api 0.2.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.
- immich_api/__init__.py +54 -0
- immich_api/__main__.py +4 -0
- immich_api/album_metadata.py +119 -0
- immich_api/api/__init__.py +3 -0
- immich_api/api/api_client_system.py +7 -0
- immich_api/auth.py +94 -0
- immich_api/connector_manifest.yml +212 -0
- immich_api/connectors/__init__.py +0 -0
- immich_api/connectors/mcp_source_presets.json +3 -0
- immich_api/connectors/tool_schema_fingerprints.json +1 -0
- immich_api/error_authority.py +248 -0
- immich_api/error_rendering.py +105 -0
- immich_api/immich_input_models.py +12 -0
- immich_api/immich_response_models.py +15 -0
- immich_api/kg_ingest.py +75 -0
- immich_api/mcp/__init__.py +21 -0
- immich_api/mcp/album_sample.py +38 -0
- immich_api/mcp_config.json +3 -0
- immich_api/mcp_server.py +50 -0
- immich_api/ontology/__init__.py +6 -0
- immich_api/ontology/immich.ttl +119 -0
- immich_api/ontology/schema-provenance.json +483 -0
- immich_api/ontology/shapes/immich.ttl +69 -0
- immich_api/openapi_auth_scheme.py +77 -0
- immich_api/openapi_contract.json +80122 -0
- immich_api/openapi_execution.py +30 -0
- immich_api/openapi_inventory.json +16154 -0
- immich_api/openapi_request.py +119 -0
- immich_api/openapi_response.py +76 -0
- immich_api/openapi_runtime.py +203 -0
- immich_api/parity/__init__.py +17 -0
- immich_api/parity/check_openapi_parity.py +173 -0
- immich_api/parity/openapi_discovery.py +95 -0
- immich_api/parity/openapi_parity.py +239 -0
- immich_api/parity/openapi_provider_evidence.py +143 -0
- immich_api/parity/openapi_provider_overlay.py +239 -0
- immich_api/parity/openapi_provider_responses.py +132 -0
- immich_api/parity/openapi_schema.py +206 -0
- immich_api/parity/openapi_schema_budget.py +104 -0
- immich_api/parity/openapi_schema_inputs.py +89 -0
- immich_api/parity/openapi_schema_types.py +163 -0
- immich_api/parity/openapi_security.py +157 -0
- immich_api/parity/openapi_source_parity.py +98 -0
- immich_api/parity/openapi_transport_parity.py +169 -0
- immich_api/parity/unicode_patterns.py +105 -0
- immich_api/parity/unicode_properties.json +4719 -0
- immich_api/prompts/__init__.py +0 -0
- immich_api/prompts/immich_album_review.json +18 -0
- immich_api/prompts/immich_asset_metadata.json +18 -0
- immich_api/prompts/immich_person_record.json +18 -0
- immich_api/prompts/immich_specialist.json +17 -0
- immich_api/prompts/main_agent.json +24 -0
- immich_api/skills/__init__.py +0 -0
- immich_api/skills/immich-album-review/SKILL.md +17 -0
- immich_api/skills/immich-asset-metadata/SKILL.md +17 -0
- immich_api/skills/immich-health/SKILL.md +17 -0
- immich_api/skills/immich-person-record/SKILL.md +17 -0
- immich_api-0.2.0.dist-info/METADATA +242 -0
- immich_api-0.2.0.dist-info/RECORD +63 -0
- immich_api-0.2.0.dist-info/WHEEL +5 -0
- immich_api-0.2.0.dist-info/entry_points.txt +14 -0
- immich_api-0.2.0.dist-info/licenses/LICENSE +21 -0
- immich_api-0.2.0.dist-info/top_level.txt +1 -0
immich_api/__init__.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
import warnings
|
|
6
|
+
|
|
7
|
+
warnings.filterwarnings("ignore", message=".*urllib3.*or chardet.*")
|
|
8
|
+
|
|
9
|
+
__all__: list[str] = []
|
|
10
|
+
|
|
11
|
+
CORE_MODULES = [
|
|
12
|
+
"immich_api.api",
|
|
13
|
+
"immich_api.error_authority",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
OPTIONAL_MODULES = {
|
|
17
|
+
"immich_api.mcp_server": "mcp",
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _import_module_safely(module_name: str):
|
|
22
|
+
"""Try to import a module and return it, or None if not available."""
|
|
23
|
+
try:
|
|
24
|
+
return importlib.import_module(module_name)
|
|
25
|
+
except ImportError:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _expose_members(module):
|
|
30
|
+
"""Expose public classes and functions from a module into globals and __all__."""
|
|
31
|
+
for name, obj in inspect.getmembers(module):
|
|
32
|
+
if (inspect.isclass(obj) or inspect.isfunction(obj)) and not name.startswith(
|
|
33
|
+
"_"
|
|
34
|
+
):
|
|
35
|
+
globals()[name] = obj
|
|
36
|
+
__all__.append(name)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
for module_name in CORE_MODULES:
|
|
40
|
+
try:
|
|
41
|
+
module = importlib.import_module(module_name)
|
|
42
|
+
_expose_members(module)
|
|
43
|
+
except ImportError:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
for module_name, extra_name in OPTIONAL_MODULES.items():
|
|
47
|
+
module = _import_module_safely(module_name)
|
|
48
|
+
if module is not None:
|
|
49
|
+
_expose_members(module)
|
|
50
|
+
globals()[f"_{extra_name.upper()}_AVAILABLE"] = True
|
|
51
|
+
else:
|
|
52
|
+
globals()[f"_{extra_name.upper()}_AVAILABLE"] = False
|
|
53
|
+
|
|
54
|
+
__all__.extend(["_MCP_AVAILABLE"])
|
immich_api/__main__.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Bounded, explicit projection of album metadata; no network or graph work."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TypedDict
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AlbumMetadata(TypedDict):
|
|
13
|
+
id: str
|
|
14
|
+
node_type: str
|
|
15
|
+
albumName: str
|
|
16
|
+
assetCount: int
|
|
17
|
+
shared: bool
|
|
18
|
+
hasSharedLink: bool
|
|
19
|
+
createdAt: str
|
|
20
|
+
updatedAt: str
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class AlbumSample(TypedDict):
|
|
24
|
+
records: list[AlbumMetadata]
|
|
25
|
+
complete: bool
|
|
26
|
+
authoritative: bool
|
|
27
|
+
source_action: str
|
|
28
|
+
source_schema_sha256: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
SOURCE_SHA256 = "7db8383741eeb5c5c8c87a6221cd1cdd1206c8b1190b83363c64223a00bb020b"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def album_graph_properties(record: dict) -> dict[str, str | int | bool]:
|
|
35
|
+
"""Map selected provider fields to the actual ontology predicate keys."""
|
|
36
|
+
projected = project_album(record)
|
|
37
|
+
provenance = json.loads(
|
|
38
|
+
(Path(__file__).parent / "ontology/schema-provenance.json").read_text()
|
|
39
|
+
)
|
|
40
|
+
targets = {
|
|
41
|
+
item["source_pointer"].rsplit("/", 1)[1]: item["property"]
|
|
42
|
+
for item in provenance["properties"]
|
|
43
|
+
if item["class"] == "ImmichAlbum" and item["range"].startswith("xsd:")
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
targets[field]: value
|
|
47
|
+
for field, value in projected.items()
|
|
48
|
+
if field != "node_type"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _validate_timestamp(value):
|
|
53
|
+
try:
|
|
54
|
+
valid = (
|
|
55
|
+
isinstance(value, str)
|
|
56
|
+
and datetime.fromisoformat(value.replace("Z", "+00:00")).tzinfo is not None
|
|
57
|
+
)
|
|
58
|
+
except ValueError:
|
|
59
|
+
valid = False
|
|
60
|
+
if not valid:
|
|
61
|
+
raise ValueError("album metadata requires timestamps with time zones")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _validate_album_fields(record):
|
|
65
|
+
name, count = record.get("albumName"), record.get("assetCount")
|
|
66
|
+
if not isinstance(name, str) or type(count) is not int:
|
|
67
|
+
raise ValueError("album metadata does not satisfy its source schema")
|
|
68
|
+
if not 0 <= count <= 9007199254740991:
|
|
69
|
+
raise ValueError("album metadata does not satisfy its source schema")
|
|
70
|
+
for key in ("shared", "hasSharedLink"):
|
|
71
|
+
if type(record.get(key)) is not bool:
|
|
72
|
+
raise ValueError("album metadata does not satisfy its source schema")
|
|
73
|
+
for key in ("createdAt", "updatedAt"):
|
|
74
|
+
_validate_timestamp(record.get(key))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def project_album(record: dict) -> AlbumMetadata:
|
|
78
|
+
"""Retain only declared fields; private nested values never leave this function."""
|
|
79
|
+
record_id = record.get("id")
|
|
80
|
+
if not isinstance(record_id, str) or UUID(record_id).version != 4:
|
|
81
|
+
raise ValueError("album requires a stable UUID v4 identity")
|
|
82
|
+
_validate_album_fields(record)
|
|
83
|
+
return AlbumMetadata(
|
|
84
|
+
id=record_id,
|
|
85
|
+
node_type="ImmichAlbum",
|
|
86
|
+
albumName=record["albumName"],
|
|
87
|
+
assetCount=record["assetCount"],
|
|
88
|
+
shared=record["shared"],
|
|
89
|
+
hasSharedLink=record["hasSharedLink"],
|
|
90
|
+
createdAt=record["createdAt"],
|
|
91
|
+
updatedAt=record["updatedAt"],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _validate_source(records):
|
|
96
|
+
if not isinstance(records, list) or any(
|
|
97
|
+
not isinstance(record, dict) for record in records
|
|
98
|
+
):
|
|
99
|
+
raise ValueError("album source must return a list of objects")
|
|
100
|
+
identities = [record.get("id") for record in records]
|
|
101
|
+
if any(not isinstance(value, str) for value in identities):
|
|
102
|
+
raise ValueError("album source identities must be unique strings")
|
|
103
|
+
if len(set(identities)) != len(identities):
|
|
104
|
+
raise ValueError("album source identities must be unique strings")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def album_sample(records: list[dict], limit: int = 1) -> AlbumSample:
|
|
108
|
+
"""Return a deterministic bounded sample, never an authoritative live set."""
|
|
109
|
+
if type(limit) is not int or not 1 <= limit <= 5:
|
|
110
|
+
raise ValueError("album sample limit must be between one and five")
|
|
111
|
+
_validate_source(records)
|
|
112
|
+
selected = sorted(records, key=lambda record: record["id"])[:limit]
|
|
113
|
+
return AlbumSample(
|
|
114
|
+
records=[project_album(record) for record in selected],
|
|
115
|
+
complete=len(records) <= limit,
|
|
116
|
+
authoritative=False,
|
|
117
|
+
source_action="getAllAlbums",
|
|
118
|
+
source_schema_sha256=SOURCE_SHA256,
|
|
119
|
+
)
|
immich_api/auth.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Resolve provider credentials and delegated caller auth through canonical SDK APIs."""
|
|
2
|
+
|
|
3
|
+
from contextlib import ExitStack
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from agent_connector_sdk.auth.delegation import DelegatedTokenAuth, DelegationSettings
|
|
7
|
+
from agent_connector_sdk.auth.static import api_key_auth, bearer_auth
|
|
8
|
+
from agent_connector_sdk.config import load_config, setting
|
|
9
|
+
from agent_connector_sdk.http.client import create_http_client
|
|
10
|
+
from agent_connector_sdk.http.options import HttpClientOptions
|
|
11
|
+
from agent_connector_sdk.tls.resolve import resolve_tls_profile
|
|
12
|
+
|
|
13
|
+
from .api import ApiClientSystem
|
|
14
|
+
from .openapi_auth_scheme import AuthKind, declared_scheme
|
|
15
|
+
from .openapi_runtime import CONTRACT
|
|
16
|
+
|
|
17
|
+
_client = None
|
|
18
|
+
_resources = ExitStack()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _bearer_scheme(definition):
|
|
22
|
+
return definition is not None and definition.kind is AuthKind.BEARER
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _delegated_auth(delegation, resources):
|
|
26
|
+
token_tls = resolve_tls_profile("oidc")
|
|
27
|
+
resources.callback(token_tls.cleanup)
|
|
28
|
+
token_client = create_http_client(
|
|
29
|
+
HttpClientOptions(base_url=delegation.token_endpoint, tls=token_tls)
|
|
30
|
+
)
|
|
31
|
+
resources.callback(token_client.close)
|
|
32
|
+
return DelegatedTokenAuth(delegation, http_client=token_client)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _static_auth(reference, definition):
|
|
36
|
+
if definition is None:
|
|
37
|
+
raise ValueError("A credential requires a declared authentication scheme")
|
|
38
|
+
if definition.kind is AuthKind.HEADER:
|
|
39
|
+
return api_key_auth(reference, header=definition.header)
|
|
40
|
+
return bearer_auth(reference)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _resolve_auth(resources):
|
|
44
|
+
scheme = setting("IMMICH_API_AUTH_SCHEME", "api_key")
|
|
45
|
+
definition = declared_scheme(scheme, CONTRACT["security_schemes"])
|
|
46
|
+
delegation = DelegationSettings.from_settings()
|
|
47
|
+
if delegation.enabled:
|
|
48
|
+
if not _bearer_scheme(definition):
|
|
49
|
+
raise ValueError("Delegation requires a declared HTTP bearer auth scheme")
|
|
50
|
+
return _delegated_auth(delegation, resources), scheme
|
|
51
|
+
reference = setting("IMMICH_API_TOKEN_REF", "")
|
|
52
|
+
if not reference:
|
|
53
|
+
return httpx.Auth(), None
|
|
54
|
+
return _static_auth(reference, definition), scheme
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_client():
|
|
58
|
+
"""Build one client; delegated auth resolves the verified caller per request."""
|
|
59
|
+
global _client, _resources
|
|
60
|
+
if _client is not None:
|
|
61
|
+
return _client
|
|
62
|
+
load_config()
|
|
63
|
+
base_url = setting("IMMICH_API_BASE_URL", "")
|
|
64
|
+
if not base_url:
|
|
65
|
+
raise ValueError("Provider base URL is required")
|
|
66
|
+
with ExitStack() as resources:
|
|
67
|
+
tls = resolve_tls_profile("immich-api")
|
|
68
|
+
resources.callback(tls.cleanup)
|
|
69
|
+
auth, credential_scheme = _resolve_auth(resources)
|
|
70
|
+
client = ApiClientSystem(
|
|
71
|
+
base_url=base_url,
|
|
72
|
+
auth=auth,
|
|
73
|
+
tls_profile=tls,
|
|
74
|
+
credential_scheme=credential_scheme,
|
|
75
|
+
write_mode=(
|
|
76
|
+
"read-write"
|
|
77
|
+
if setting("IMMICH_API_ALLOW_WRITES", False)
|
|
78
|
+
else "read-only"
|
|
79
|
+
),
|
|
80
|
+
allow_plaintext=setting("IMMICH_API_ALLOW_PLAINTEXT", False),
|
|
81
|
+
)
|
|
82
|
+
resources.callback(client.close)
|
|
83
|
+
_resources = resources.pop_all()
|
|
84
|
+
_client = client
|
|
85
|
+
return client
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def close_client():
|
|
89
|
+
"""Close the cached client, token client and materialized TLS resources."""
|
|
90
|
+
global _client
|
|
91
|
+
try:
|
|
92
|
+
_resources.close()
|
|
93
|
+
finally:
|
|
94
|
+
_client = None
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
{
|
|
2
|
+
"connector": "immich-api",
|
|
3
|
+
"ontology_source": "immich",
|
|
4
|
+
"schema_version": "1",
|
|
5
|
+
"resources": [
|
|
6
|
+
{
|
|
7
|
+
"name": "ImmichAsset",
|
|
8
|
+
"relations": [
|
|
9
|
+
{
|
|
10
|
+
"name": "immichAssetOwner",
|
|
11
|
+
"target": "ImmichUser",
|
|
12
|
+
"label": "owner"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "immichAssetPeople",
|
|
16
|
+
"target": "ImmichPersonRecord",
|
|
17
|
+
"label": "people"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"name": "immichAssetTags",
|
|
21
|
+
"target": "ImmichTag",
|
|
22
|
+
"label": "tags"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "immichAssetStack",
|
|
26
|
+
"target": "ImmichAssetStack",
|
|
27
|
+
"label": "stack"
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "ImmichAlbum",
|
|
33
|
+
"relations": [
|
|
34
|
+
{
|
|
35
|
+
"name": "immichAlbumAlbumUsers",
|
|
36
|
+
"target": "ImmichAlbumMembership",
|
|
37
|
+
"label": "albumUsers"
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "ImmichPersonRecord",
|
|
43
|
+
"relations": []
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"name": "ImmichUser",
|
|
47
|
+
"relations": []
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "ImmichLibrary",
|
|
51
|
+
"relations": []
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "ImmichAlbumMembership",
|
|
55
|
+
"relations": [
|
|
56
|
+
{
|
|
57
|
+
"name": "immichAlbumMembershipUser",
|
|
58
|
+
"target": "ImmichUser",
|
|
59
|
+
"label": "user"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"name": "ImmichTag",
|
|
65
|
+
"relations": []
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "ImmichAssetStack",
|
|
69
|
+
"relations": []
|
|
70
|
+
}
|
|
71
|
+
],
|
|
72
|
+
"identity": {
|
|
73
|
+
"id_field": {
|
|
74
|
+
"ImmichAsset": "immichAssetId",
|
|
75
|
+
"ImmichAlbum": "immichAlbumId",
|
|
76
|
+
"ImmichPersonRecord": "immichPersonRecordId",
|
|
77
|
+
"ImmichUser": "immichUserId",
|
|
78
|
+
"ImmichLibrary": "immichLibraryId",
|
|
79
|
+
"ImmichTag": "immichTagId",
|
|
80
|
+
"ImmichAssetStack": "immichAssetStackId"
|
|
81
|
+
},
|
|
82
|
+
"title_field": {
|
|
83
|
+
"ImmichAlbum": "immichAlbumAlbumName",
|
|
84
|
+
"ImmichPersonRecord": "immichPersonRecordName",
|
|
85
|
+
"ImmichUser": "immichUserName",
|
|
86
|
+
"ImmichLibrary": "immichLibraryName",
|
|
87
|
+
"ImmichTag": "immichTagName"
|
|
88
|
+
},
|
|
89
|
+
"updated_field": {
|
|
90
|
+
"ImmichAlbum": "immichAlbumUpdatedAt",
|
|
91
|
+
"ImmichAsset": "immichAssetUpdatedAt"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"schema_mappings": {
|
|
95
|
+
"ImmichAsset": {
|
|
96
|
+
"ontology_class": "ImmichAsset",
|
|
97
|
+
"fields": {
|
|
98
|
+
"immichAssetId": "xsd:string",
|
|
99
|
+
"immichAssetType": "xsd:string",
|
|
100
|
+
"immichAssetOriginalMimeType": "xsd:string",
|
|
101
|
+
"immichAssetDuration": "xsd:integer",
|
|
102
|
+
"immichAssetWidth": "xsd:integer",
|
|
103
|
+
"immichAssetHeight": "xsd:integer",
|
|
104
|
+
"immichAssetFileCreatedAt": "xsd:dateTime",
|
|
105
|
+
"immichAssetUpdatedAt": "xsd:dateTime",
|
|
106
|
+
"immichAssetIsFavorite": "xsd:boolean",
|
|
107
|
+
"immichAssetIsArchived": "xsd:boolean",
|
|
108
|
+
"immichAssetIsTrashed": "xsd:boolean",
|
|
109
|
+
"immichAssetVisibility": "xsd:string"
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"ImmichAlbum": {
|
|
113
|
+
"ontology_class": "ImmichAlbum",
|
|
114
|
+
"fields": {
|
|
115
|
+
"immichAlbumId": "xsd:string",
|
|
116
|
+
"immichAlbumAlbumName": "xsd:string",
|
|
117
|
+
"immichAlbumAssetCount": "xsd:integer",
|
|
118
|
+
"immichAlbumShared": "xsd:boolean",
|
|
119
|
+
"immichAlbumHasSharedLink": "xsd:boolean",
|
|
120
|
+
"immichAlbumCreatedAt": "xsd:dateTime",
|
|
121
|
+
"immichAlbumUpdatedAt": "xsd:dateTime",
|
|
122
|
+
"immichAlbumAlbumThumbnailAssetId": "xsd:string"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"ImmichPersonRecord": {
|
|
126
|
+
"ontology_class": "ImmichPersonRecord",
|
|
127
|
+
"fields": {
|
|
128
|
+
"immichPersonRecordId": "xsd:string",
|
|
129
|
+
"immichPersonRecordName": "xsd:string",
|
|
130
|
+
"immichPersonRecordIsHidden": "xsd:boolean",
|
|
131
|
+
"immichPersonRecordIsFavorite": "xsd:boolean",
|
|
132
|
+
"immichPersonRecordUpdatedAt": "xsd:dateTime"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"ImmichUser": {
|
|
136
|
+
"ontology_class": "ImmichUser",
|
|
137
|
+
"fields": {
|
|
138
|
+
"immichUserId": "xsd:string",
|
|
139
|
+
"immichUserName": "xsd:string"
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"ImmichLibrary": {
|
|
143
|
+
"ontology_class": "ImmichLibrary",
|
|
144
|
+
"fields": {
|
|
145
|
+
"immichLibraryId": "xsd:string",
|
|
146
|
+
"immichLibraryName": "xsd:string",
|
|
147
|
+
"immichLibraryAssetCount": "xsd:integer",
|
|
148
|
+
"immichLibraryCreatedAt": "xsd:dateTime",
|
|
149
|
+
"immichLibraryUpdatedAt": "xsd:dateTime"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"ImmichAlbumMembership": {
|
|
153
|
+
"ontology_class": "ImmichAlbumMembership",
|
|
154
|
+
"fields": {
|
|
155
|
+
"immichAlbumMembershipRole": "xsd:string"
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"ImmichTag": {
|
|
159
|
+
"ontology_class": "ImmichTag",
|
|
160
|
+
"fields": {
|
|
161
|
+
"immichTagId": "xsd:string",
|
|
162
|
+
"immichTagName": "xsd:string",
|
|
163
|
+
"immichTagCreatedAt": "xsd:dateTime",
|
|
164
|
+
"immichTagUpdatedAt": "xsd:dateTime"
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
"ImmichAssetStack": {
|
|
168
|
+
"ontology_class": "ImmichAssetStack",
|
|
169
|
+
"fields": {
|
|
170
|
+
"immichAssetStackId": "xsd:string",
|
|
171
|
+
"immichAssetStackAssetCount": "xsd:integer",
|
|
172
|
+
"immichAssetStackPrimaryAssetId": "xsd:string"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"sync": [],
|
|
177
|
+
"provenance": {
|
|
178
|
+
"generated_by": "scripts/generate_immich_content.py",
|
|
179
|
+
"source_artifacts": [
|
|
180
|
+
"openapi_contract.json",
|
|
181
|
+
"ontology/schema-provenance.json",
|
|
182
|
+
"ontology/immich.ttl",
|
|
183
|
+
"ontology/shapes/immich.ttl"
|
|
184
|
+
],
|
|
185
|
+
"integrity": {
|
|
186
|
+
"algorithm": "sha256",
|
|
187
|
+
"hash": "c68d2270fcfbcacb40369011d3f65fdf625e6ea1eabba10bf4309f7c007e391d"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"review_todos": [
|
|
191
|
+
"Structural schema declarations are not evidence of live EG capture or semantic identity alignment.",
|
|
192
|
+
"Only the bounded album metadata projection is implemented for ingestion; remaining DTO classes are structural declarations.",
|
|
193
|
+
"Require explicit source/account binding, existing scoped credentials and a verified EG receipt/readback before enabling ingestion.",
|
|
194
|
+
"Ontology integrity is a byte SHA256, not canonical RDF or release certification."
|
|
195
|
+
],
|
|
196
|
+
"policy": {
|
|
197
|
+
"pii_fields": {
|
|
198
|
+
"ImmichAlbum": [
|
|
199
|
+
"immichAlbumAlbumName"
|
|
200
|
+
],
|
|
201
|
+
"ImmichPersonRecord": [
|
|
202
|
+
"immichPersonRecordName"
|
|
203
|
+
],
|
|
204
|
+
"ImmichUser": [
|
|
205
|
+
"immichUserName"
|
|
206
|
+
],
|
|
207
|
+
"ImmichTag": [
|
|
208
|
+
"immichTagName"
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "No ingestion preset is enabled. The pinned SDK uses the preset name as stream identity, so this package cannot bind a shared preset to a provider/account scope. The read-only album_metadata_sample tool remains available. Its authoritative=false flag does not govern SDK source mode; no FULL/DELTA sweep is configured."
|
|
3
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"algorithm":"agent-connector-sdk:mcp-tool-contract-compat:v2","connector":"immich-api","tools":{}}
|