diff --git a/docs/cmis-1-1-capability-scorecard.md b/docs/cmis-1-1-capability-scorecard.md
index 094941b..d7d29a2 100644
--- a/docs/cmis-1-1-capability-scorecard.md
+++ b/docs/cmis-1-1-capability-scorecard.md
@@ -13,6 +13,11 @@ The score below remains a product-depth estimate against mature CMIS products.
The selected OpenCMIS baseline is now stable preparation evidence for
repository/type and object/content services, not a full CMIS certification.
+Read-side contract update: `KONT-WP-0016` adds a documented bounded query
+subset, common CMIS `ORDER BY`, target/either relationship filters, enriched
+relationship and ACL projections, and explicit `notSupported` diagnostics for
+unsupported navigation selectors.
+
Status: baseline scorecard for the current Browser Binding subset.
## Purpose
@@ -72,9 +77,9 @@ CMIS interoperability importance rather than engine-internal importance.
| Metric | Score |
| --- | ---: |
| OpenCMIS selected-baseline infrastructure score | 99.1% |
-| Weighted CMIS 1.1 depth vs Hyland Alfresco benchmark | 60% |
-| Controlled-client Browser Binding usefulness | 82% |
-| Broad commodity CMIS client compatibility | 55% |
+| Weighted CMIS 1.1 depth vs Hyland Alfresco benchmark | 63% |
+| Controlled-client Browser Binding usefulness | 84% |
+| Broad commodity CMIS client compatibility | 57% |
Interpretation: the OpenCMIS infrastructure score measures the selected
`repository-type` and `object-content` harness baseline only. The current CMIS
@@ -94,9 +99,9 @@ broad ECM/CMIS replacement surface.
| Object write service | 8 | 72% | Hyland Alfresco ACS | - `createDocument`, `createFolder`, scoped `moveObject`, folder rename, selected standard property updates, custom metadata updates, content stream set/append/delete, `bulkUpdateProperties`, and `createDocumentFromSource` exist.
- No broad filing mutation, raw physical delete, checkout/checkin, or policy/item creation.
- Delete remains intentionally governed, not raw repository removal. |
| Content stream read/write | 8 | 86% | Hyland Alfresco ACS | - Byte streaming, explicit content headers, multipart Browser Binding create, deduplicating `setContentStream`, whole-object `appendContentStream`, no-content compatibility streams, content tombstones, partial body slicing, and offset-zero full-stream classification exist.
- Digest verification and governed access exist.
- Chunk-level blob composition remains a later optimization for very large append workloads. |
| Versioning service | 8 | 25% | Hyland Alfresco ACS | - Version properties can be projected from engine versions.
- No checkout/checkin/cancelCheckout/PWC services.
- No version history route or all-versions query behavior. |
-| Discovery/query | 8 | 25% | Hyland Alfresco ACS | - Narrow document select subset exists.
- Unsupported joins/order-by return diagnostics.
- Missing CMIS SQL predicates, type joins, full-text, ordering, and rich projection rules. |
-| Relationships | 5 | 60% | Hyland Alfresco ACS | - Relationship object projection and source filtering exist.
- Visibility gates prevent protected relationship leakage.
- Missing full relationship service filters, relationship creation through CMIS, and type hierarchy maturity. |
-| ACL service | 6 | 35% | Hyland Alfresco ACS | - Discover-only ACL projection exists.
- `applyACL` is blocked as not implemented.
- Missing inherited/direct ACL fidelity, propagation, ACL mutation, and repository principal model. |
+| Discovery/query | 8 | 42% | Hyland Alfresco ACS | - Bounded `SELECT *` document queries support equality, `LIKE`, `IN`, `AND`, paging, and common CMIS property ordering.
- Capability flags now advertise `capabilityOrderBy=common` rather than overclaiming custom ordering.
- Missing joins, full text, nested predicates, arbitrary projections, and broad CMIS SQL coverage. |
+| Relationships | 5 | 70% | Hyland Alfresco ACS | - Relationship object projection, source filters, target filters, either-direction filters, confidence, direction, provenance, and visibility gates exist.
- Protected relationship leakage is covered by profile gates.
- Missing relationship creation through CMIS and deeper relationship type hierarchy maturity. |
+| ACL service | 6 | 48% | Hyland Alfresco ACS | - Discover-only ACL projection has stable principal ids, principal kinds, permission mapping, direct/inherited markers, and policy authority metadata.
- `applyACL` is blocked as not implemented.
- Missing propagation, ACL mutation, and repository-wide principal/group enumeration. |
| Policy service | 3 | 10% | Hyland Alfresco ACS | - Native policy decisions govern exposure.
- No CMIS policy objects, `applyPolicy`, `removePolicy`, or `getAppliedPolicies` service surface.
- Explicitly unsupported. |
| Change log | 5 | 60% | Hyland Alfresco ACS | - Audit-backed object-id change entries and paging exist.
- CMIS change-token conflicts are now enforced for property/content mutations.
- Missing richer change event typing and broader token semantics across optional services. |
| Multi-filing and unfiling | 4 | 25% | Hyland Alfresco ACS | - Projection-only parent maps exist and are useful for navigation.
- Standard CMIS `capabilityMultifiling` is correctly false.
- No add/remove filing mutations or canonical folder membership model. |
@@ -108,7 +113,7 @@ broad ECM/CMIS replacement surface.
| Web Services binding | 2 | 0% | Hyland Alfresco ACS | - No SOAP/WSDL stack.
- Intentionally deferred until monetized need. |
| External conformance evidence | 3 | 86% | OpenCMIS TCK against Alfresco-like server behavior | - OpenCMIS Browser Binding session creation succeeds against `compat-tck`.
- Selected `repository-type` and `object-content` baselines complete with one local transport warning and no object/content warnings.
- Evidence still covers a selected baseline, not the full OpenCMIS TCK surface. |
-Weighted result from this table: **60%**.
+Weighted result from this table: **63%**.
## Most Important Gaps
@@ -126,9 +131,10 @@ Weighted result from this table: **60%**.
optional services are added.
3. **Query depth**
- - Add a real CMIS SQL subset parser instead of a two-query allowlist.
- - Support basic `WHERE`, equality predicates, paging, ordering where claimed,
- and diagnostics for everything outside the subset.
+ - Expand only where it stays natural: additional indexed metadata fields,
+ richer comparator support, and selected client-requested predicates.
+ - Keep joins, full text, and arbitrary CMIS SQL unsupported unless a real
+ integration need appears.
4. **Navigation depth**
- Decide whether `getDescendants` and `getFolderTree` are worth implementing
diff --git a/docs/cmis-compliance-assessment.md b/docs/cmis-compliance-assessment.md
index b36d520..761846e 100644
--- a/docs/cmis-compliance-assessment.md
+++ b/docs/cmis-compliance-assessment.md
@@ -50,15 +50,15 @@ Practical strategy:
| Object service write | Governed subset. | `createDocument`, custom metadata updates, `setContentStream`, and delete-request lifecycle transition are supported by authoring profiles. Unsupported standard property updates now fail with diagnostics. | Medium |
| Content streams | Implemented subset. | Descriptor and byte-stream routes exist; `setContentStream` and whole-object `appendContentStream` write through deduplicating blob storage, while `deleteContentStream` tombstones the CMIS projection. Chunk-level append composition remains deferred. | Low |
| Versioning | Projection only. | Latest-version properties can be projected from engine versions, but CMIS checkout/PWC/all-versions services are not advertised. | Low if unsupported remains acceptable |
-| Discovery/query | Implemented narrow subset. | `SELECT * FROM cmis:document` and `SELECT * FROM kontextual:document` are supported. Joins, order-by, full CMIS SQL predicates, and full-text are flagged unsupported. | Medium |
-| Relationships | Implemented subset. | Relationship object projections and source filters are covered and profile-gated. | Low |
-| ACL service | Discover only. | ACL projection is supported; `applyACL` is not authorized even for authoring profiles and returns an unimplemented diagnostic. | Low |
+| Discovery/query | Implemented bounded subset. | `SELECT *` document queries support equality, `LIKE`, `IN`, `AND`, paging, and common CMIS property ordering. Joins, full text, nested predicates, arbitrary projection lists, and custom-property ordering are flagged unsupported. | Medium |
+| Relationships | Implemented subset. | Relationship object projections, source filters, target filters, either-direction filters, provenance, confidence, and profile-gated visibility are covered. | Low |
+| ACL service | Discover only. | ACL projection is supported with stable principal/permission vocabulary, direct/inherited markers, and policy authority metadata; `applyACL` returns an unimplemented diagnostic. | Low |
| Policy service | Unsupported. | `applyPolicy`/`removePolicy` are explicitly unsupported; engine policy remains native, not CMIS policy objects. | Low |
| Change log | Implemented subset. | Audit-backed object-id change entries and paging are supported; full property-level change details are not advertised. | Low |
| Multi-filing/unfiling | Projection only. | Multiple virtual parents are exposed as a Kontextual repository feature, while CMIS `capabilityMultifiling` and unfiling stay false. | Low |
| Renditions | Unsupported. | Capability is `none`; derived representations are not exposed as CMIS rendition streams. | Low |
| Retention and hold | Unsupported. | Not advertised; left as native governance metadata until a real integration requires CMIS legal-hold semantics. | Low |
-| Bulk update | Unsupported. | `bulkUpdateProperties` is explicitly unsupported. | Low |
+| Bulk update | Profile-scoped subset. | `bulkUpdateProperties` is available for the TCK compatibility profile through existing governed property updates and change-token handling; it remains narrow and is not enabled on normal authoring profiles. | Low |
| Browser JSON binding | FastAPI JSON service already exists. | Need CMIS Browser Binding routes, selectors/actions, multipart/content stream behavior. | High |
| AtomPub binding | No AtomPub/XML binding. | Need XML/Atom feed generation and protocol semantics. | Very High |
| Web Services binding | No SOAP stack. | Need WSDL/SOAP implementation. | Very High |
@@ -72,7 +72,8 @@ Maintain a constrained CMIS 1.1 Browser Binding profile:
projection.
- Explicitly unsupported or read-only: AtomPub, Web Services, descendants/tree,
full ACL mutation, retention/hold, mutating multifiling/unfiling, PWC/versioning
- services, renditions, bulk updates, order-by, and full CMIS SQL joins.
+ services, renditions, custom-property ordering, broad bulk-update exposure,
+ and full CMIS SQL joins.
Then expand by profile:
diff --git a/docs/cmis-read-side-contract.md b/docs/cmis-read-side-contract.md
new file mode 100644
index 0000000..6eb29de
--- /dev/null
+++ b/docs/cmis-read-side-contract.md
@@ -0,0 +1,94 @@
+# CMIS Read-Side Contract
+
+Date: 2026-05-14
+Workplan: `KONT-WP-0016`
+Status: release-stable Browser Binding subset
+
+## Boundary
+
+The CMIS layer is a connector projection over native `kontextual-engine`
+services. Native assets, classifications, metadata records, relationships,
+audit events, policy decisions, and blob representations remain authoritative.
+CMIS does not own a second object store, a separate ACL model, or an independent
+filing graph.
+
+## Release-Stable Capabilities
+
+| Capability | Release posture | Contract |
+| --- | --- | --- |
+| Repository and type discovery | Supported | Browser Binding service document, repository info, base type definitions, and property definitions are stable. Optional unsupported features are declared in `unsupported_features`. |
+| Navigation | Bounded supported subset | `children`, `parent`, `parents`, `object`, `properties`, `allowableActions`, `policies`, and `content` are supported through profile-gated folder/path projections. |
+| Descendants and folder tree | Unsupported | `descendants` and `folderTree` return CMIS `notSupported`; repository flags remain false. This avoids creating a broad ECM tree contract before there is a native need. |
+| Query | Bounded supported subset | `SELECT * FROM cmis:document` and `SELECT * FROM kontextual:document` support simple `WHERE` predicates joined by `AND`, deterministic paging, and common-property ordering. |
+| Relationships | Supported read projection | Source, target, and either-direction filters map to the native relationship repository. Relationship objects expose source/target ids, predicate, confidence, direction, provenance, actor, and a stable relationship change token. |
+| ACL discovery | Supported projection | `getACL` exposes direct actor permissions plus synthetic/public inheritance markers. The native policy gateway remains the authority; `applyACL` stays unsupported. |
+| Change tokens | Supported for governed mutations | Asset `cmis:changeToken` is the current version id. Folder workspace tokens and relationship tokens are projection tokens. Stale asset mutation tokens map to CMIS `updateConflict`. |
+
+## Query Subset
+
+Accepted grammar:
+
+```sql
+SELECT * FROM cmis:document
+SELECT * FROM kontextual:document
+SELECT * FROM cmis:document WHERE = '' [AND ...]
+SELECT * FROM cmis:document WHERE LIKE '' [AND ...]
+SELECT * FROM cmis:document WHERE IN ('', ...) [AND ...]
+SELECT * FROM cmis:document ... ORDER BY [ASC|DESC]
+```
+
+Filterable fields:
+
+`cmis:objectId`, `cmis:name`, `cmis:objectTypeId`, `cmis:baseTypeId`,
+`cmis:description`, `kontextual:assetId`, `kontextual:assetType`,
+`kontextual:sensitivity`, `kontextual:lifecycle`, `kontextual:owner`,
+`kontextual:topics`, and `kontextual:reviewState`.
+
+Orderable fields:
+
+`cmis:objectId`, `cmis:name`, `cmis:creationDate`, and
+`cmis:lastModificationDate`.
+
+Unsupported joins, `OR`, nested expressions, full text, arbitrary projection
+lists, and custom-property ordering return CMIS `notSupported` diagnostics with
+the supported grammar and field sets included.
+
+## Compatibility Notes
+
+- `capabilityQuery` remains `metadataonly`.
+- `capabilityOrderBy` is now `common`, not `none`, because common CMIS property
+ ordering is implemented and covered by tests.
+- `capabilityGetDescendants` and `capabilityGetFolderTree` remain false.
+- Multifiling remains projection-only. Mutation semantics are deliberately out
+ of scope for this read-side contract.
+- The ACL vocabulary is intentionally small: `cmis:read`, `cmis:write`, and
+ `cmis:delete`, with `direct`, `inherited`, `principal_kind`, and `source`
+ markers.
+
+## Evidence
+
+Focused tests run on 2026-05-14:
+
+```text
+python3 -m pytest \
+ tests/cmis/test_cmis_runtime_browser_binding.py \
+ tests/cmis/test_cmis_browser_binding_api.py \
+ tests/cmis/test_cmis_compliance_flags.py \
+ tests/cmis/test_cmis_contract_examples.py
+
+Result: 21 passed, 16 skipped in 5.19s
+```
+
+Browser Binding API verification with optional service extras:
+
+```text
+.venv/bin/python -m pytest tests/cmis/test_cmis_browser_binding_api.py -q
+
+Result: 16 passed in 39.09s
+```
+
+The default system-Python run skips Browser Binding API tests when FastAPI/HTTPX
+are unavailable and skips capacity probes unless `KONTEXTUAL_RUN_CAPACITY=1` is
+set. The capacity probe exercises 400 documents and 250 relationships over
+query and target-filter paths, while relying on the shared performance history
+monitor for drift tracking.
diff --git a/docs/first-release-readiness.md b/docs/first-release-readiness.md
index 6b0d39d..3d2a3f6 100644
--- a/docs/first-release-readiness.md
+++ b/docs/first-release-readiness.md
@@ -29,11 +29,11 @@ Out of scope for `0.1.0`:
| Area | Gate | Current state |
| --- | --- | --- |
-| CMIS read-side contract | Query, navigation, relationship, ACL, and change-token contracts are release-stable or explicitly waived. | `KONT-WP-0016` created as a pre-release dependency. |
-| Tests | Full suite passes in the project venv. | `.venv/bin/python -m pytest -q` passed: `166 passed`, `14 skipped`; advisory performance drift warnings recorded. |
+| CMIS read-side contract | Query, navigation, relationship, ACL, and change-token contracts are release-stable or explicitly waived. | `KONT-WP-0016` implemented locally; full release verification still required. |
+| Tests | Full suite passes in the project venv. | `.venv/bin/python -m pytest -q` passed: `166 passed`, `15 skipped`; advisory performance drift warnings recorded. |
| CMIS evidence | OpenCMIS selected baseline completes with no unexpected findings. | `run-20260513T223537Z` completed; only local HTTP warning remains. |
| Transport | Released CMIS access points are served behind HTTPS. | Required deployment gate; local loopback warning is accepted only for harness runs. |
-| Capability honesty | Scorecard, unsupported catalog, and examples match behavior. | Updated for `appendContentStream`; final doc review required. |
+| Capability honesty | Scorecard, unsupported catalog, and examples match behavior. | Updated for `appendContentStream` and WP-0016 read-side contract; final doc review required. |
| Packaging | Version, dependencies, optional extras, and install smoke are checked. | `pyproject.toml` is already `0.1.0`; build/install smoke still required. |
| Configuration | Storage backend, S3 settings, local blob path, and environment defaults are documented. | Existing docs cover backends; release runbook should point to them. |
| Data safety | Blob cleanup, backups, restore path, and migration posture are documented. | Cleanup exists; backup/restore release notes still needed. |
@@ -54,23 +54,21 @@ Out of scope for `0.1.0`:
## Release Procedure
-1. Complete or explicitly waive `KONT-WP-0016`.
-2. Run `.venv/bin/python -m pytest -q`.
-3. Run the OpenCMIS selected baseline through `guide-board` and persist the
+1. Run `.venv/bin/python -m pytest -q`.
+2. Run the OpenCMIS selected baseline through `guide-board` and persist the
evidence document.
-4. Run State Hub consistency check and ensure workplans are registered.
-5. Run packaging smoke: build wheel/sdist and import `kontextual_engine` from a
+3. Run State Hub consistency check and ensure workplans are registered.
+4. Run packaging smoke: build wheel/sdist and import `kontextual_engine` from a
clean install.
-6. Review security/configuration: HTTPS termination, profile exposure, secrets,
+5. Review security/configuration: HTTPS termination, profile exposure, secrets,
local/S3 blob backend settings, and dependency licenses.
-7. Update `CHANGELOG.md` or release notes with capabilities, known limitations,
+6. Update `CHANGELOG.md` or release notes with capabilities, known limitations,
and accepted warnings.
-8. Tag the release only after the gates above are green or explicitly waived.
+7. Tag the release only after the gates above are green or explicitly waived.
## Release Decision
-The current foundation is close to a controlled `0.1.0` preview, but CMIS
-read-side contracts should be settled before release if external projects will
-build on the engine. After `KONT-WP-0016`, the remaining release work is mainly
-discipline around repeatable verification, packaging, deployment posture, and
-clearly documented limits.
+The current foundation is close to a controlled `0.1.0` preview. With the
+`KONT-WP-0016` read-side contract settled locally, the remaining release work is
+mainly discipline around repeatable verification, packaging, deployment
+posture, and clearly documented limits.
diff --git a/examples/cmis/capability-fixtures.json b/examples/cmis/capability-fixtures.json
index 4def144..f5a82df 100644
--- a/examples/cmis/capability-fixtures.json
+++ b/examples/cmis/capability-fixtures.json
@@ -125,10 +125,10 @@
{
"id": "discovery-query",
"service": "discovery",
- "examples": ["lexical-query", "metadata-filter", "relationship-scoped-query", "unsupported-join"],
+ "examples": ["lexical-query", "metadata-filter", "relationship-scoped-query", "bounded-order-by", "unsupported-join"],
"supported_profiles": ["readonly-browser", "governed-authoring", "admin-export", "compat-tck"],
- "must_validate": ["query_capabilities", "paging", "unsupported_grammar_diagnostics"],
- "unsupported": ["full_cmis_sql_joins", "order_by"]
+ "must_validate": ["query_capabilities", "paging", "bounded_order_by", "unsupported_grammar_diagnostics"],
+ "unsupported": ["full_cmis_sql_joins", "custom_order_by"]
},
{
"id": "relationships",
@@ -174,7 +174,7 @@
"private_working_copy": "capability_not_supported",
"all_versions_search": "capability_not_supported",
"full_cmis_sql_joins": "query_not_supported",
- "order_by": "query_not_supported",
+ "custom_order_by": "custom_order_by_not_supported",
"apply_acl": "operation_not_implemented",
"apply_policy": "capability_not_supported",
"remove_policy": "capability_not_supported",
diff --git a/src/kontextual_engine/api/app.py b/src/kontextual_engine/api/app.py
index 0cca7b4..37c0bde 100644
--- a/src/kontextual_engine/api/app.py
+++ b/src/kontextual_engine/api/app.py
@@ -7,6 +7,7 @@ requests into service/runtime contracts and must not own domain behavior.
from __future__ import annotations
import json
+import re
from dataclasses import dataclass, field, replace
from datetime import datetime
from email import policy
@@ -90,6 +91,43 @@ from kontextual_engine.services import (
API_VERSION = "v1"
OPENAPI_VERSION = "1.0.0"
CMIS_APPEND_MAX_COMPOSED_BYTES = 64 * 1024 * 1024
+CMIS_QUERY_SUPPORTED = [
+ "SELECT * FROM cmis:document",
+ "SELECT * FROM kontextual:document",
+ "SELECT * FROM cmis:document WHERE = '' [AND ...]",
+ "SELECT * FROM cmis:document WHERE LIKE '' [AND ...]",
+ "SELECT * FROM cmis:document WHERE IN ('', ...) [AND ...]",
+ "SELECT * FROM cmis:document ... ORDER BY [ASC|DESC]",
+]
+CMIS_QUERY_FILTERABLE_FIELDS = {
+ "cmis:objectId",
+ "cmis:name",
+ "cmis:objectTypeId",
+ "cmis:baseTypeId",
+ "cmis:description",
+ "kontextual:assetId",
+ "kontextual:assetType",
+ "kontextual:sensitivity",
+ "kontextual:lifecycle",
+ "kontextual:owner",
+ "kontextual:topics",
+ "kontextual:reviewState",
+}
+CMIS_QUERY_ORDERABLE_FIELDS = {
+ "cmis:objectId",
+ "cmis:name",
+ "cmis:creationDate",
+ "cmis:lastModificationDate",
+}
+CMIS_QUERY_LIKE_FIELDS = {
+ "cmis:name",
+ "cmis:description",
+ "kontextual:assetId",
+ "kontextual:assetType",
+ "kontextual:owner",
+ "kontextual:topics",
+ "kontextual:reviewState",
+}
AGENT_OPERATION_CATALOG: tuple[dict[str, Any], ...] = (
@@ -1593,19 +1631,13 @@ class ServiceRuntime:
decision = mapper.access_point.decide_action(CMISAction.QUERY, context)
if not decision.allowed:
raise _cmis_authorization_error(decision, "query")
- normalized = query.strip().lower()
- if normalized not in {"select * from cmis:document", "select * from kontextual:document"}:
- raise ValidationError(
- "Unsupported CMIS query subset",
- details={
- "query": query,
- "supported": ["SELECT * FROM cmis:document", "SELECT * FROM kontextual:document"],
- },
- )
+ query_spec = _parse_cmis_query(query)
projections = self._cmis_document_projections(mapper, context)
+ projections = _apply_cmis_query_spec(projections, query_spec)
paged = projections[max(skip_count, 0) : max(skip_count, 0) + max(max_items, 0)]
return {
"query": query,
+ "query_spec": query_spec,
"results": paged,
"num_items": len(paged),
"has_more_items": len(projections) > max(skip_count, 0) + len(paged),
@@ -1618,19 +1650,55 @@ class ServiceRuntime:
context: OperationContext,
*,
object_id: str | None = None,
+ target_id: str | None = None,
+ relationship_direction: str | None = None,
) -> dict[str, Any]:
mapper = self._cmis_mapper(access_point_id)
decision = mapper.access_point.decide_action(CMISAction.GET_RELATIONSHIPS, context)
if not decision.allowed:
raise _cmis_authorization_error(decision, "getRelationships")
- source_id = _cmis_asset_id(object_id) if object_id else None
+ direction = (relationship_direction or "source").strip().lower()
+ if direction not in {"source", "target", "either"}:
+ raise ValidationError(
+ "Unsupported CMIS relationship direction",
+ details={
+ "code": "cmis.relationship_direction_unsupported",
+ "operation": "getObjectRelationships",
+ "direction": relationship_direction,
+ "supported": ["source", "target", "either"],
+ },
+ )
+ source_filter = _cmis_asset_id(object_id) if object_id and direction == "source" else None
+ target_filter = _cmis_asset_id(object_id) if object_id and direction == "target" else None
+ if target_id:
+ target_filter = _cmis_asset_id(target_id)
+ relationships = self.repository.list_relationships(source_id=source_filter, target_id=target_filter)
+ if object_id and direction == "either":
+ asset_id = _cmis_asset_id(object_id)
+ relationships = [
+ relationship
+ for relationship in relationships
+ if relationship.source_id == asset_id
+ or (
+ relationship.target_kind == RelationshipTargetKind.ASSET
+ and relationship.target_id == asset_id
+ )
+ ]
projections = [
projection.to_dict()
- for relationship in self.repository.list_relationships(source_id=source_id)
+ for relationship in relationships
if self._cmis_relationship_visible(mapper, relationship, context)
if (projection := mapper.map_relationship(relationship, context))
]
- return {"items": projections, "count": len(projections)}
+ return {
+ "items": projections,
+ "count": len(projections),
+ "filters": {
+ "object_id": object_id,
+ "target_id": target_id,
+ "relationship_direction": direction,
+ },
+ }
def cmis_change_log(
self,
@@ -3629,26 +3697,38 @@ def create_app(runtime: ServiceRuntime | None = None):
)
def unsupported_browser_selector(selector: str | None) -> dict[str, Any]:
+ unsupported_details: dict[str, Any] = {
+ "cmisselector": selector,
+ "supported": [
+ "repositoryInfo",
+ "typeChildren",
+ "typeDescendants",
+ "typeDefinition",
+ "query",
+ "object",
+ "children",
+ "parent",
+ "parents",
+ "properties",
+ "allowableActions",
+ "policies",
+ "content",
+ ],
+ }
+ if selector in {"descendants", "folderTree"}:
+ unsupported_details.update(
+ {
+ "code": "cmis.not_supported",
+ "cmis_exception": "notSupported",
+ "unsupported_feature": "get_descendants"
+ if selector == "descendants"
+ else "get_folder_tree",
+ "release_contract": "Navigation tree selectors remain unsupported for the first release.",
+ }
+ )
raise ValidationError(
"Unsupported CMIS Browser Binding selector",
- details={
- "cmisselector": selector,
- "supported": [
- "repositoryInfo",
- "typeChildren",
- "typeDescendants",
- "typeDefinition",
- "query",
- "object",
- "children",
- "parent",
- "parents",
- "properties",
- "allowableActions",
- "policies",
- "content",
- ],
- },
+ details=unsupported_details,
)
async def browser_action_payload(request: Request) -> dict[str, Any]:
@@ -4230,9 +4310,18 @@ def create_app(runtime: ServiceRuntime | None = None):
def cmis_relationships(
access_point_id: str,
object_id: str | None = Query(None),
+ target_id: str | None = Query(None, alias="targetId"),
+ relationship_direction: str | None = Query(None, alias="relationshipDirection"),
context: OperationContext = Depends(context_from_headers),
) -> dict[str, Any]:
- return response(runtime.cmis_relationships, access_point_id, context, object_id=object_id)
+ return response(
+ runtime.cmis_relationships,
+ access_point_id,
+ context,
+ object_id=object_id,
+ target_id=target_id,
+ relationship_direction=relationship_direction,
+ )
@app.get("/cmis/{access_point_id}/browser/changes", tags=["cmis"])
def cmis_changes(
@@ -4702,6 +4791,212 @@ def _normalize_cmis_path(path: str) -> str:
return "/" + "/".join(parts)
+_CMIS_QUERY_RE = re.compile(
+ r"^\s*SELECT\s+(?P