CMIS compliance fixture integration and optional OpenCMIS TCK harness boundary

This commit is contained in:
2026-05-07 02:00:00 +02:00
parent 88f9df6288
commit 23c90b33a7
7 changed files with 251 additions and 6 deletions

View File

@@ -120,6 +120,22 @@ Relationship listings and change logs now apply the same asset visibility gates
as object reads. This prevents indirect leakage of confidential or restricted
asset IDs through relationship targets or audit-backed change entries.
## Fixture And Optional TCK Integration
CMIS fixtures now act as active compatibility contracts:
- `examples/cmis/capability-fixtures.json` defines profile expectations and
capability groups,
- `tests/cmis/test_cmis_fixture_integration.py` compares those expectations to
implemented profiles and access-point shapes,
- `tests/cmis/opencmis-tck/tck-subset-map.json` maps fixture capability groups
to selected OpenCMIS TCK groups,
- `tests/cmis/opencmis-tck/tck-result-template.json` captures optional TCK
result summaries and known capability gaps.
The default Python suite validates the fixture/TCK mapping without requiring
Java or Maven. Actual OpenCMIS TCK execution remains opt-in.
Route-level tests are present but skip when the optional FastAPI/httpx service
dependencies are not installed. Runtime-level Browser Binding tests cover the
same behavior in the default Python test suite.

View File

@@ -12,7 +12,7 @@
"binding": "browser",
"mutations": false,
"visibility": ["public", "internal"],
"deny": ["confidential"],
"deny": ["confidential", "restricted"],
"expected_tck_posture": "repository/type/read/navigation/query/content-read subset"
},
"governed-authoring": {
@@ -20,14 +20,14 @@
"binding": "browser",
"mutations": true,
"visibility": ["public", "internal"],
"deny": ["confidential"],
"deny": ["confidential", "restricted"],
"expected_tck_posture": "readonly subset plus selected create/update/delete checks"
},
"admin-export": {
"description": "Service-account export profile for broad governance inspection.",
"binding": "browser",
"mutations": false,
"visibility": ["public", "internal", "confidential"],
"visibility": ["public", "internal", "confidential", "restricted"],
"deny": [],
"expected_tck_posture": "internal contract tests, not general client compatibility"
},
@@ -36,7 +36,7 @@
"binding": "browser",
"mutations": true,
"visibility": ["public", "internal"],
"deny": ["confidential"],
"deny": ["confidential", "restricted"],
"expected_tck_posture": "selected OpenCMIS TCK Browser Binding subset"
}
},
@@ -200,4 +200,3 @@
}
}
}

View File

@@ -26,3 +26,15 @@ The first target is a selected Browser Binding subset:
Mutation groups should only be enabled for `governed-authoring` or
`compat-tck` profiles after the policy and audit gates are implemented.
Harness files:
- `tck-subset-map.json` maps engine capability groups to selected OpenCMIS TCK
groups and known gaps.
- `tck-result-template.json` is the compact result capture format for optional
TCK runs. Results should be stored outside the default repo history unless
they document an intentional compatibility baseline.
Default `pytest` does not execute Java/Maven. It validates the map and result
template so the optional harness does not drift away from the implemented
profiles and fixtures.

View File

@@ -0,0 +1,28 @@
{
"schema_version": 1,
"run": {
"tool": "Apache Chemistry OpenCMIS TCK",
"tool_version": "1.1.0",
"profile": "compat-tck",
"repository_id": "kontextual-compat-tck",
"browser_url": "http://127.0.0.1:8000/cmis/compat-tck/browser",
"started_at": null,
"finished_at": null
},
"summary": {
"passed": 0,
"failed": 0,
"skipped": 0,
"known_gap": 0
},
"groups": [
{
"capability_group": "repository-type",
"tck_group": "RepositoryInfoTestGroup",
"status": "not_run",
"notes": ""
}
],
"capability_gaps": []
}

View File

@@ -0,0 +1,66 @@
{
"schema_version": 1,
"tck": {
"name": "Apache Chemistry OpenCMIS TCK",
"version": "1.1.0",
"artifact": "org.apache.chemistry.opencmis:chemistry-opencmis-test-tck:1.1.0",
"execution": "optional"
},
"target": {
"profile": "compat-tck",
"repository_id": "kontextual-compat-tck",
"browser_url": "http://127.0.0.1:8000/cmis/compat-tck/browser"
},
"groups": [
{
"capability_group": "repository-type",
"tck_groups": ["RepositoryInfoTestGroup", "TypesTestGroup"],
"expected": "selected-pass"
},
{
"capability_group": "navigation",
"tck_groups": ["NavigationTestGroup"],
"expected": "selected-pass"
},
{
"capability_group": "object-content",
"tck_groups": ["ObjectServiceTestGroup", "ContentStreamTestGroup"],
"expected": "selected-pass"
},
{
"capability_group": "versioning",
"tck_groups": ["VersioningTestGroup"],
"expected": "partial-pass",
"known_gaps": ["private_working_copy"]
},
{
"capability_group": "discovery-query",
"tck_groups": ["QueryTestGroup"],
"expected": "partial-pass",
"known_gaps": ["full_cmis_sql_joins"]
},
{
"capability_group": "relationships",
"tck_groups": ["RelationshipTestGroup"],
"expected": "selected-pass"
},
{
"capability_group": "acl-policy",
"tck_groups": ["AclTestGroup", "PolicyTestGroup"],
"expected": "partial-pass",
"known_gaps": ["apply_policy", "remove_policy"]
},
{
"capability_group": "change-log",
"tck_groups": ["ChangeLogTestGroup"],
"expected": "selected-pass"
},
{
"capability_group": "retention-renditions-bulk",
"tck_groups": ["RenditionsTestGroup", "BulkUpdateTestGroup"],
"expected": "skip",
"known_gaps": ["retention_hold_mutation", "bulk_update_properties", "rendition_streams"]
}
]
}

View File

@@ -0,0 +1,122 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from kontextual_engine import (
Actor,
ActorType,
CMISAccessPoint,
CMISAccessProfile,
CMISAction,
OperationContext,
)
pytestmark = pytest.mark.cmis
ROOT = Path(__file__).resolve().parents[2]
FIXTURE_PATH = ROOT / "examples" / "cmis" / "capability-fixtures.json"
TCK_MAP_PATH = ROOT / "tests" / "cmis" / "opencmis-tck" / "tck-subset-map.json"
TCK_RESULT_TEMPLATE = ROOT / "tests" / "cmis" / "opencmis-tck" / "tck-result-template.json"
ACTION_MAP = {
"create_document": CMISAction.CREATE_DOCUMENT,
"update_properties": CMISAction.UPDATE_PROPERTIES,
"delete_object": CMISAction.DELETE_OBJECT,
"set_content_stream": CMISAction.SET_CONTENT_STREAM,
}
def _catalog() -> dict:
return json.loads(FIXTURE_PATH.read_text(encoding="utf-8"))
def _tck_map() -> dict:
return json.loads(TCK_MAP_PATH.read_text(encoding="utf-8"))
def _profiles() -> dict[str, CMISAccessProfile]:
return {
profile.name: profile
for profile in (
CMISAccessProfile.readonly_browser(),
CMISAccessProfile.governed_authoring(),
CMISAccessProfile.admin_export(),
CMISAccessProfile.compat_tck(),
)
}
def _context(actor_type: ActorType = ActorType.HUMAN) -> OperationContext:
return OperationContext.create(
Actor.create(actor_type, actor_id=f"fixture-{actor_type.value}"),
correlation_id="corr-cmis-fixture-integration",
)
def test_fixture_profiles_match_implemented_access_profiles() -> None:
catalog = _catalog()
implemented = _profiles()
assert set(catalog["profiles"]) == set(implemented)
for profile_name, fixture in catalog["profiles"].items():
profile = implemented[profile_name]
assert profile.binding.value == fixture["binding"]
assert profile.allow_mutations is fixture["mutations"]
assert [item.value for item in profile.visible_sensitivities] == fixture["visibility"]
assert [item.value for item in profile.denied_sensitivities] == fixture["deny"]
def test_fixture_mutation_expectations_match_profile_decisions() -> None:
catalog = _catalog()
implemented = _profiles()
for profile_name, expectations in catalog["profile_expectations"].items():
profile = implemented[profile_name]
context = _context(
ActorType.SERVICE_ACCOUNT if profile.required_actor_types else ActorType.HUMAN
)
for action_name in expectations.get("must_authorize_actions", []):
assert profile.decide_action(ACTION_MAP[action_name], context).allowed is True
for action_name in expectations.get("must_reject_actions", []):
assert profile.decide_action(ACTION_MAP[action_name], context).allowed is False
def test_every_fixture_profile_has_a_browser_access_point_shape() -> None:
for profile in _profiles().values():
access_point = CMISAccessPoint(
access_point_id=profile.name,
repository_id=f"kontextual-{profile.name}",
profile=profile,
base_path=f"/cmis/{profile.name}/browser",
)
serialized = access_point.to_dict()
assert serialized["base_path"] == f"/cmis/{profile.name}/browser"
assert serialized["repository_id"] == f"kontextual-{profile.name}"
assert serialized["profile"]["binding"] == "browser"
def test_optional_opencmis_tck_map_covers_fixture_capability_groups() -> None:
catalog = _catalog()
tck_map = _tck_map()
fixture_groups = {group["id"] for group in catalog["capability_groups"]}
mapped_groups = {group["capability_group"] for group in tck_map["groups"]}
assert tck_map["tck"]["execution"] == "optional"
assert tck_map["target"]["profile"] == "compat-tck"
assert mapped_groups == fixture_groups
assert all(group["tck_groups"] for group in tck_map["groups"])
def test_optional_tck_result_template_can_capture_gap_mapping() -> None:
template = json.loads(TCK_RESULT_TEMPLATE.read_text(encoding="utf-8"))
assert template["run"]["profile"] == "compat-tck"
assert template["summary"] == {"passed": 0, "failed": 0, "skipped": 0, "known_gap": 0}
assert "capability_gaps" in template
assert template["groups"][0]["status"] == "not_run"

View File

@@ -46,6 +46,8 @@ suite.
- `tests/cmis/test_cmis_domain_mapper.py`
- `tests/cmis/test_cmis_runtime_browser_binding.py`
- `tests/cmis/test_cmis_browser_binding_api.py`
- `tests/cmis/test_cmis_fixture_integration.py`
- `tests/cmis/opencmis-tck/tck-subset-map.json`
## Architecture Constraint
@@ -141,7 +143,7 @@ Acceptance:
```task
id: KONT-WP-0012-T006
status: todo
status: done
priority: medium
state_hub_task_id: "2f1e9075-395e-4ed0-9abd-ed7c4ecd774d"
```