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 as object reads. This prevents indirect leakage of confidential or restricted
asset IDs through relationship targets or audit-backed change entries. 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 Route-level tests are present but skip when the optional FastAPI/httpx service
dependencies are not installed. Runtime-level Browser Binding tests cover the dependencies are not installed. Runtime-level Browser Binding tests cover the
same behavior in the default Python test suite. same behavior in the default Python test suite.

View File

@@ -12,7 +12,7 @@
"binding": "browser", "binding": "browser",
"mutations": false, "mutations": false,
"visibility": ["public", "internal"], "visibility": ["public", "internal"],
"deny": ["confidential"], "deny": ["confidential", "restricted"],
"expected_tck_posture": "repository/type/read/navigation/query/content-read subset" "expected_tck_posture": "repository/type/read/navigation/query/content-read subset"
}, },
"governed-authoring": { "governed-authoring": {
@@ -20,14 +20,14 @@
"binding": "browser", "binding": "browser",
"mutations": true, "mutations": true,
"visibility": ["public", "internal"], "visibility": ["public", "internal"],
"deny": ["confidential"], "deny": ["confidential", "restricted"],
"expected_tck_posture": "readonly subset plus selected create/update/delete checks" "expected_tck_posture": "readonly subset plus selected create/update/delete checks"
}, },
"admin-export": { "admin-export": {
"description": "Service-account export profile for broad governance inspection.", "description": "Service-account export profile for broad governance inspection.",
"binding": "browser", "binding": "browser",
"mutations": false, "mutations": false,
"visibility": ["public", "internal", "confidential"], "visibility": ["public", "internal", "confidential", "restricted"],
"deny": [], "deny": [],
"expected_tck_posture": "internal contract tests, not general client compatibility" "expected_tck_posture": "internal contract tests, not general client compatibility"
}, },
@@ -36,7 +36,7 @@
"binding": "browser", "binding": "browser",
"mutations": true, "mutations": true,
"visibility": ["public", "internal"], "visibility": ["public", "internal"],
"deny": ["confidential"], "deny": ["confidential", "restricted"],
"expected_tck_posture": "selected OpenCMIS TCK Browser Binding subset" "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 Mutation groups should only be enabled for `governed-authoring` or
`compat-tck` profiles after the policy and audit gates are implemented. `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_domain_mapper.py`
- `tests/cmis/test_cmis_runtime_browser_binding.py` - `tests/cmis/test_cmis_runtime_browser_binding.py`
- `tests/cmis/test_cmis_browser_binding_api.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 ## Architecture Constraint
@@ -141,7 +143,7 @@ Acceptance:
```task ```task
id: KONT-WP-0012-T006 id: KONT-WP-0012-T006
status: todo status: done
priority: medium priority: medium
state_hub_task_id: "2f1e9075-395e-4ed0-9abd-ed7c4ecd774d" state_hub_task_id: "2f1e9075-395e-4ed0-9abd-ed7c4ecd774d"
``` ```