generated from coulomb/repo-seed
Warnings cleanup
This commit is contained in:
@@ -78,6 +78,8 @@ CMIS integration:
|
||||
|
||||
- `getContentStream` returns actual bytes/stream with content headers,
|
||||
- `setContentStream` stores through deduplicating representation service,
|
||||
- `appendContentStream` composes the current stream plus appended bytes and
|
||||
stores the resulting representation through the same deduplicating service,
|
||||
- content stream changes produce versions and audit events,
|
||||
- descriptors remain available for clients that only need metadata.
|
||||
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# CMIS 1.1 Capability Scorecard
|
||||
|
||||
Date: 2026-05-13
|
||||
Date: 2026-05-14
|
||||
|
||||
Evidence update: the 2026-05-13 WP-0014 OpenCMIS pass completed the selected
|
||||
Evidence update: the 2026-05-14 release-warning pass completed the selected
|
||||
Browser Binding `repository-type` and `object-content` baseline. The latest
|
||||
run, `run-20260513T210255Z`, reports `0` unexpected findings:
|
||||
`repository-type` is warning-only because the local harness uses HTTP, and
|
||||
`object-content` is warning-only because `appendContentStream()` is not
|
||||
supported. See
|
||||
`docs/cmis-opencmis-tck-wp0014-evidence-2026-05-13T210255Z.md`.
|
||||
run, `run-20260513T223537Z`, reports `0` unexpected findings. The
|
||||
`object-content` group now passes without warnings; the only remaining
|
||||
OpenCMIS warning is the local harness using HTTP rather than HTTPS. See
|
||||
`docs/cmis-opencmis-tck-release-readiness-evidence-2026-05-13T223537Z.md`.
|
||||
|
||||
The score below remains a product-depth estimate against mature CMIS products.
|
||||
The selected OpenCMIS baseline is now stable preparation evidence for
|
||||
@@ -72,10 +71,10 @@ CMIS interoperability importance rather than engine-internal importance.
|
||||
|
||||
| Metric | Score |
|
||||
| --- | ---: |
|
||||
| OpenCMIS selected-baseline infrastructure score | 98.3% |
|
||||
| Weighted CMIS 1.1 depth vs Hyland Alfresco benchmark | 59% |
|
||||
| Controlled-client Browser Binding usefulness | 80% |
|
||||
| Broad commodity CMIS client compatibility | 54% |
|
||||
| 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% |
|
||||
|
||||
Interpretation: the OpenCMIS infrastructure score measures the selected
|
||||
`repository-type` and `object-content` harness baseline only. The current CMIS
|
||||
@@ -92,8 +91,8 @@ broad ECM/CMIS replacement surface.
|
||||
| Type definitions | 6 | 55% | Hyland Alfresco ACS | - Base types, Browser Binding type definitions, secondary type projection, and nullable content stream properties exist.<br>- No mutable types or custom schema/type management.<br>- Property definition depth remains intentionally narrow. |
|
||||
| Navigation service | 8 | 62% | Hyland Alfresco ACS | - Root and folder-scoped children, path lookup, folder parent lookup, parent path segments, move, and delete-tree work in the selected baseline.<br>- Projection-only parents exist.<br>- Missing `getDescendants`, `getFolderTree`, and real filing mutations. |
|
||||
| Object read service | 10 | 84% | Hyland Alfresco ACS | - Object envelopes, properties, content descriptors, ACL projection, relationships, allowable actions, property filters, and path-addressed Browser Binding reads exist.<br>- Deleted/hidden objects are correctly not exposed.<br>- OpenCMIS object/content read-side baseline now completes with warnings only. |
|
||||
| Object write service | 8 | 70% | Hyland Alfresco ACS | - `createDocument`, `createFolder`, scoped `moveObject`, folder rename, selected standard property updates, custom metadata updates, content stream set/delete, `bulkUpdateProperties`, and `createDocumentFromSource` exist.<br>- No broad filing mutation, raw physical delete, checkout/checkin, or policy/item creation.<br>- Delete remains intentionally governed, not raw repository removal. |
|
||||
| Content stream read/write | 8 | 82% | Hyland Alfresco ACS | - Byte streaming, explicit content headers, multipart Browser Binding create, deduplicating `setContentStream`, no-content compatibility streams, content tombstones, partial body slicing, and offset-zero full-stream classification exist.<br>- Digest verification and governed access exist.<br>- Remaining warning is unsupported append semantics. |
|
||||
| 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.<br>- No broad filing mutation, raw physical delete, checkout/checkin, or policy/item creation.<br>- 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.<br>- Digest verification and governed access exist.<br>- 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.<br>- No checkout/checkin/cancelCheckout/PWC services.<br>- No version history route or all-versions query behavior. |
|
||||
| Discovery/query | 8 | 25% | Hyland Alfresco ACS | - Narrow document select subset exists.<br>- Unsupported joins/order-by return diagnostics.<br>- 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.<br>- Visibility gates prevent protected relationship leakage.<br>- Missing full relationship service filters, relationship creation through CMIS, and type hierarchy maturity. |
|
||||
@@ -104,19 +103,19 @@ broad ECM/CMIS replacement surface.
|
||||
| Renditions | 3 | 15% | Hyland Alfresco ACS | - Native representations could become rendition candidates later.<br>- CMIS rendition capability is currently `none`.<br>- No rendition taxonomy or rendition stream routes. |
|
||||
| Retention and hold | 2 | 5% | OpenText / Hyland governance stacks | - Native governance metadata can represent intent later.<br>- No CMIS retention/hold model or mutation services.<br>- Explicitly unsupported. |
|
||||
| Bulk update | 2 | 65% | Hyland Alfresco ACS | - `bulkUpdateProperties` works for the `compat-tck` profile by batching existing property updates with change-token handling.<br>- It is intentionally narrow and not enabled on the normal governed-authoring profile yet.<br>- No advanced partial-success envelope beyond the Browser Binding response list. |
|
||||
| Browser Binding protocol fidelity | 7 | 78% | Hyland Alfresco ACS | - Browser-style routes, JSON envelopes, CMIS exception names, action aliases, multipart forms, path-addressed root routes, property filters, path segments, and range responses exist.<br>- Selected OpenCMIS Browser Binding repository/type and object/content baseline completes with warnings only.<br>- Optional services and broader CMIS SQL/versioning protocol surfaces remain incomplete. |
|
||||
| Browser Binding protocol fidelity | 7 | 80% | Hyland Alfresco ACS | - Browser-style routes, JSON envelopes, CMIS exception names, action aliases, multipart forms, path-addressed root routes, property filters, path segments, and range responses exist.<br>- Selected OpenCMIS Browser Binding repository/type and object/content baseline completes with only the local HTTP warning.<br>- Optional services and broader CMIS SQL/versioning protocol surfaces remain incomplete. |
|
||||
| AtomPub binding | 2 | 0% | Hyland Alfresco ACS | - No AtomPub/XML service document or feeds.<br>- Intentionally deferred until monetized need. |
|
||||
| Web Services binding | 2 | 0% | Hyland Alfresco ACS | - No SOAP/WSDL stack.<br>- Intentionally deferred until monetized need. |
|
||||
| External conformance evidence | 3 | 82% | OpenCMIS TCK against Alfresco-like server behavior | - OpenCMIS Browser Binding session creation succeeds against `compat-tck`.<br>- Selected `repository-type` and `object-content` baselines complete with warnings only.<br>- Evidence still covers a selected baseline, not the full OpenCMIS TCK surface. |
|
||||
| External conformance evidence | 3 | 86% | OpenCMIS TCK against Alfresco-like server behavior | - OpenCMIS Browser Binding session creation succeeds against `compat-tck`.<br>- Selected `repository-type` and `object-content` baselines complete with one local transport warning and no object/content warnings.<br>- Evidence still covers a selected baseline, not the full OpenCMIS TCK surface. |
|
||||
|
||||
Weighted result from this table: **59%**.
|
||||
Weighted result from this table: **60%**.
|
||||
|
||||
## Most Important Gaps
|
||||
|
||||
1. **External conformance expansion**
|
||||
- Keep the selected OpenCMIS TCK baseline running against `compat-tck`.
|
||||
- Treat the current repository/type and object/content warnings as known:
|
||||
local HTTP and unsupported append.
|
||||
- Treat the remaining local HTTP warning as a harness/deployment topology
|
||||
issue, not an adapter behavior failure.
|
||||
- Expand selected groups after the supported baseline remains stable.
|
||||
|
||||
2. **Browser Binding fidelity**
|
||||
@@ -145,10 +144,11 @@ Weighted result from this table: **59%**.
|
||||
- Map selected derived representations to CMIS renditions only after we have
|
||||
stable representation taxonomy and real preview/thumbnail use cases.
|
||||
|
||||
7. **Append streams**
|
||||
- Keep `appendContentStream()` unsupported unless a real client requires it.
|
||||
- If implemented later, design it through blob composition/deduplication
|
||||
rather than byte concatenation in the CMIS adapter.
|
||||
7. **Release transport and operational posture**
|
||||
- Terminate CMIS access over HTTPS in deployable environments.
|
||||
- Keep the local HTTP warning accepted only for loopback TCK runs.
|
||||
- Revisit blob composition if large append workloads become a real usage
|
||||
pattern.
|
||||
|
||||
## Product Positioning Takeaway
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ Practical strategy:
|
||||
| Navigation service | Implemented subset. | `getChildren` and projected parents are supported. `getDescendants`, `getFolderTree`, mutating multifiling, and unfiling are explicitly flagged unsupported. | Low unless full folder tree is required |
|
||||
| Object service read | Implemented subset. | Object envelopes, allowable actions, content stream descriptors, content stream properties, visibility redaction, and relationship IDs are covered. | Low |
|
||||
| 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` writes through deduplicating blob storage. Append/delete content stream are unsupported. | Low |
|
||||
| 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 |
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# CMIS OpenCMIS TCK Evidence - Release Readiness - 2026-05-13T22:35:37Z
|
||||
|
||||
## Run Summary
|
||||
|
||||
- Run ID: `run-20260513T223537Z`
|
||||
- Local date: 2026-05-14 Europe/Berlin
|
||||
- Harness: `guide-board` with `open-cmis-tck` extension
|
||||
- Assessment: `cmis-browser-baseline`
|
||||
- Target: `kontextual-cmis-compat`
|
||||
- Endpoint: `http://127.0.0.1:8010/cmis/compat-tck/browser`
|
||||
- OpenCMIS TCK: CMIS 1.1.0, revision `1789681`
|
||||
- Result: `completed`
|
||||
- Policy: `0` unexpected findings, `0` applied waivers
|
||||
- Run directory: `/tmp/kontextual-cmis-release-20260514-toolchain`
|
||||
- Internal verification: `.venv/bin/python -m pytest -q` -> `166 passed`,
|
||||
`14 skipped`
|
||||
|
||||
This run was executed after adding Browser Binding `appendContent` /
|
||||
`appendContentStream` support through the existing content representation
|
||||
service.
|
||||
|
||||
## Normalized Results
|
||||
|
||||
| Group | Result | Counts | Remaining non-green findings |
|
||||
| --- | --- | --- | --- |
|
||||
| `repository-type` | `warning` | `38 pass`, `2 info`, `1 skipped`, `1 warning` | Local loopback endpoint uses HTTP rather than HTTPS. |
|
||||
| `object-content` | `pass` | `10 info`, `5 skipped` | None. |
|
||||
|
||||
Guide Board summary:
|
||||
|
||||
- `pass`: 2
|
||||
- `warning`: 1
|
||||
|
||||
## Score
|
||||
|
||||
This is a compatibility-infrastructure score for the selected Browser Binding
|
||||
baseline, not a CMIS certification score.
|
||||
|
||||
| Metric | Score | Basis |
|
||||
| --- | ---: | --- |
|
||||
| Selected baseline completion | 100.0% | Guide Board result `completed`; both selected groups returned `0`. |
|
||||
| Unexpected finding clearance | 100.0% | `0` unexpected findings, `0` fail, `0` infrastructure_error. |
|
||||
| Warning-adjusted normalized case score | 99.1% | `(56 accepted + 0.5 * 1 warning) / 57 normalized cases`. |
|
||||
| Strict no-warning normalized case score | 98.2% | `56 accepted / 57 normalized cases`. |
|
||||
|
||||
Digest versus `run-20260513T210255Z`:
|
||||
|
||||
- OpenCMIS selected-baseline infrastructure score improved from `98.3%` to
|
||||
`99.1%`.
|
||||
- `object-content` improved from warning to pass.
|
||||
- The previous `appendContentStream()` warning is closed.
|
||||
- The only remaining warning is local HTTP transport in the loopback harness.
|
||||
|
||||
## Interpretation
|
||||
|
||||
The selected Browser Binding repository/type and object/content baseline is now
|
||||
release-ready for a controlled preview. The remaining HTTP warning should be
|
||||
handled as a deployment gate: released access points need HTTPS termination,
|
||||
while loopback TCK runs may keep the warning as an accepted local harness
|
||||
condition.
|
||||
|
||||
Skipped object/content cases remain aligned with declared capability
|
||||
boundaries:
|
||||
|
||||
- Relationship, policy, and item creation are not advertised as creatable.
|
||||
- Folder-name change-token subcases are skipped by the TCK.
|
||||
|
||||
This evidence still does not cover full CMIS 1.1 certification, AtomPub, Web
|
||||
Services, PWC/checkin/checkout, full CMIS SQL, renditions, retention, or policy
|
||||
mutation depth.
|
||||
72
docs/first-release-readiness.md
Normal file
72
docs/first-release-readiness.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# First Release Readiness
|
||||
|
||||
Date: 2026-05-14
|
||||
|
||||
Target posture: `0.1.0` controlled preview. This release can be good to go
|
||||
when it is honest, reproducible, observable, and recoverable. It does not need
|
||||
to pretend to be a full ECM or certified CMIS repository.
|
||||
|
||||
## Release Scope
|
||||
|
||||
In scope:
|
||||
|
||||
- native asset registry, metadata, relationship, retrieval, transformation,
|
||||
workflow, service API, blob, S3-backend, and observability foundations,
|
||||
- profiled CMIS Browser Binding subset for controlled integrations,
|
||||
- OpenCMIS selected baseline for `repository-type` and `object-content`,
|
||||
- explicit unsupported flags for out-of-scope CMIS capabilities,
|
||||
- State Hub registered workplans and evidence documents.
|
||||
|
||||
Out of scope for `0.1.0`:
|
||||
|
||||
- AtomPub and Web Services bindings,
|
||||
- full CMIS SQL, checkout/checkin/PWC, policy mutation, retention/hold,
|
||||
renditions, and broad filing mutation,
|
||||
- full ECM/records-management semantics,
|
||||
- formal CMIS certification claim.
|
||||
|
||||
## Go / No-Go Gates
|
||||
|
||||
| Area | Gate | Current state |
|
||||
| --- | --- | --- |
|
||||
| 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 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. |
|
||||
| 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. |
|
||||
| Security | Actor headers, access-point profiles, secrets, and dependency/license review are done. | Profile model exists; release security review still required. |
|
||||
| Operations | Health checks, logs, performance history, and known warnings are documented. | Health and performance monitor exist; release runbook still needed. |
|
||||
| State | State Hub consistency check passes after release docs/workplan updates. | Passed after WP-0015 registration. |
|
||||
|
||||
## Known Accepted Limitations
|
||||
|
||||
- CMIS Browser Binding is the supported external CMIS protocol surface.
|
||||
- CMIS multifiling remains projection-only.
|
||||
- CMIS append is implemented as whole-object append through deduplicating
|
||||
representation storage; chunk-level blob composition is deferred.
|
||||
- Local OpenCMIS loopback runs warn about HTTP. This must not be carried into
|
||||
production-facing access points.
|
||||
- Performance drift warnings from pytest are advisory unless repeated under a
|
||||
quiet system baseline; the current run flagged several CMIS API tests.
|
||||
|
||||
## Release Procedure
|
||||
|
||||
1. Run `.venv/bin/python -m pytest -q`.
|
||||
2. Run the OpenCMIS selected baseline through `guide-board` and persist the
|
||||
evidence document.
|
||||
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.
|
||||
5. Review security/configuration: HTTPS termination, profile exposure, secrets,
|
||||
local/S3 blob backend settings, and dependency licenses.
|
||||
6. Update `CHANGELOG.md` or release notes with capabilities, known limitations,
|
||||
and accepted warnings.
|
||||
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. The main
|
||||
remaining release work is not feature depth; it is discipline around repeatable
|
||||
verification, packaging, deployment posture, and clearly documented limits.
|
||||
@@ -111,8 +111,8 @@
|
||||
"service": "object",
|
||||
"examples": ["source-document", "normalized-representation", "metadata-rich-document", "streamless-document"],
|
||||
"supported_profiles": ["readonly-browser", "governed-authoring", "admin-export", "compat-tck"],
|
||||
"must_validate": ["get_object", "get_properties", "get_content_stream", "allowable_actions"],
|
||||
"unsupported": ["append_content_stream"]
|
||||
"must_validate": ["get_object", "get_properties", "get_content_stream", "append_content_stream", "allowable_actions"],
|
||||
"unsupported": []
|
||||
},
|
||||
{
|
||||
"id": "versioning",
|
||||
@@ -170,7 +170,6 @@
|
||||
"get_folder_tree": "capability_not_supported",
|
||||
"multifiling": "projection_only",
|
||||
"unfiling": "capability_not_supported",
|
||||
"append_content_stream": "capability_not_supported",
|
||||
"versioning_services": "capability_not_supported",
|
||||
"private_working_copy": "capability_not_supported",
|
||||
"all_versions_search": "capability_not_supported",
|
||||
@@ -188,22 +187,22 @@
|
||||
"readonly-browser": {
|
||||
"must_expose": ["repository-type", "navigation", "object-content", "versioning", "discovery-query", "relationships", "acl-policy", "change-log"],
|
||||
"must_not_expose_objects": ["doc-confidential-risk"],
|
||||
"must_reject_actions": ["create_document", "update_properties", "delete_object", "set_content_stream"]
|
||||
"must_reject_actions": ["create_document", "update_properties", "delete_object", "set_content_stream", "append_content_stream"]
|
||||
},
|
||||
"governed-authoring": {
|
||||
"must_expose": ["repository-type", "navigation", "object-content", "versioning", "discovery-query", "relationships", "acl-policy", "change-log"],
|
||||
"must_not_expose_objects": ["doc-confidential-risk"],
|
||||
"must_authorize_actions": ["create_document", "update_properties", "delete_object", "set_content_stream"]
|
||||
"must_authorize_actions": ["create_document", "update_properties", "delete_object", "set_content_stream", "append_content_stream"]
|
||||
},
|
||||
"admin-export": {
|
||||
"must_expose": ["repository-type", "navigation", "object-content", "versioning", "discovery-query", "relationships", "acl-policy", "change-log", "retention-renditions-bulk"],
|
||||
"must_not_expose_objects": [],
|
||||
"must_reject_actions": ["create_document", "update_properties", "delete_object", "set_content_stream"]
|
||||
"must_reject_actions": ["create_document", "update_properties", "delete_object", "set_content_stream", "append_content_stream"]
|
||||
},
|
||||
"compat-tck": {
|
||||
"must_expose": ["repository-type", "navigation", "object-content", "versioning", "discovery-query", "relationships", "acl-policy", "change-log"],
|
||||
"must_not_expose_objects": ["doc-confidential-risk"],
|
||||
"must_authorize_actions": ["create_document", "update_properties", "delete_object", "set_content_stream"]
|
||||
"must_authorize_actions": ["create_document", "update_properties", "delete_object", "set_content_stream", "append_content_stream"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ from kontextual_engine.services import (
|
||||
|
||||
API_VERSION = "v1"
|
||||
OPENAPI_VERSION = "1.0.0"
|
||||
CMIS_APPEND_MAX_COMPOSED_BYTES = 64 * 1024 * 1024
|
||||
|
||||
|
||||
AGENT_OPERATION_CATALOG: tuple[dict[str, Any], ...] = (
|
||||
@@ -1305,6 +1306,73 @@ class ServiceRuntime:
|
||||
)
|
||||
return self.cmis_object(access_point_id, object_id, context)
|
||||
|
||||
def cmis_append_content_stream(
|
||||
self,
|
||||
access_point_id: str,
|
||||
object_id: str,
|
||||
payload: dict[str, Any],
|
||||
context: OperationContext,
|
||||
) -> dict[str, Any]:
|
||||
mapper = self._cmis_mapper(access_point_id)
|
||||
decision = mapper.access_point.decide_action(CMISAction.SET_CONTENT_STREAM, context, resource=object_id)
|
||||
if not decision.allowed:
|
||||
raise _cmis_authorization_error(decision, "appendContentStream")
|
||||
asset_id = _cmis_asset_id(object_id)
|
||||
asset = self.repository.get_asset(asset_id)
|
||||
if not mapper.access_point.exposes_asset(asset, context):
|
||||
raise NotFoundError(
|
||||
"CMIS object not found",
|
||||
details={"object_id": object_id, "access_point_id": access_point_id},
|
||||
)
|
||||
expected = (
|
||||
payload.get("expected_current_version_id")
|
||||
or payload.get("changeToken")
|
||||
or payload.get("change_token")
|
||||
)
|
||||
self._cmis_assert_change_token(asset, expected, operation="appendContentStream")
|
||||
appended = _cmis_payload_bytes(payload.get("content", b""))
|
||||
is_last_chunk = _cmis_form_bool(payload.get("isLastChunk") or payload.get("is_last_chunk"), default=True)
|
||||
base = b""
|
||||
kind = payload.get("kind", RepresentationKind.SOURCE.value)
|
||||
media_type = _cmis_media_type(payload.get("media_type", "application/octet-stream"))
|
||||
if self._cmis_asset_representations(asset):
|
||||
stream = self.content_service().get_content_stream(asset_id, context)
|
||||
base = stream.content
|
||||
kind = stream.representation.kind.value
|
||||
media_type = _cmis_media_type(payload.get("media_type") or stream.representation.media_type)
|
||||
elif asset.metadata.get("cmis_content_deleted"):
|
||||
metadata = dict(asset.metadata)
|
||||
metadata.pop("cmis_content_deleted", None)
|
||||
self.repository.save_asset(replace(asset, metadata=metadata))
|
||||
composed_size = len(base) + len(appended)
|
||||
if composed_size > CMIS_APPEND_MAX_COMPOSED_BYTES:
|
||||
raise ValidationError(
|
||||
"CMIS append content stream exceeds the composed append limit",
|
||||
details={
|
||||
"code": "cmis.append_content_limit_exceeded",
|
||||
"cmis_exception": "constraint",
|
||||
"operation": "appendContentStream",
|
||||
"max_size_bytes": CMIS_APPEND_MAX_COMPOSED_BYTES,
|
||||
"composed_size_bytes": composed_size,
|
||||
},
|
||||
)
|
||||
self.content_service().add_representation_from_bytes(
|
||||
asset_id,
|
||||
kind,
|
||||
media_type,
|
||||
base + appended,
|
||||
context,
|
||||
expected_current_version_id=expected,
|
||||
metadata={
|
||||
"cmis": {
|
||||
"operation": "appendContentStream",
|
||||
"is_last_chunk": is_last_chunk,
|
||||
"appended_bytes": len(appended),
|
||||
}
|
||||
},
|
||||
)
|
||||
return self.cmis_object(access_point_id, object_id, context)
|
||||
|
||||
def cmis_delete_content_stream(
|
||||
self,
|
||||
access_point_id: str,
|
||||
@@ -3676,6 +3744,11 @@ def create_app(runtime: ServiceRuntime | None = None):
|
||||
raise ValidationError("CMIS object id is required", details={"operation": "setContentStream"})
|
||||
response(runtime.cmis_set_content_stream, access_point_id, object_id, payload, context)
|
||||
return response(runtime.cmis_browser_object, access_point_id, object_id, context)
|
||||
if action in {"appendContentStream", "appendContent"}:
|
||||
if not object_id:
|
||||
raise ValidationError("CMIS object id is required", details={"operation": "appendContentStream"})
|
||||
response(runtime.cmis_append_content_stream, access_point_id, object_id, payload, context)
|
||||
return response(runtime.cmis_browser_object, access_point_id, object_id, context)
|
||||
if action in {"deleteContent", "deleteContentStream"}:
|
||||
if not object_id:
|
||||
raise ValidationError("CMIS object id is required", details={"operation": "deleteContentStream"})
|
||||
@@ -3699,6 +3772,8 @@ def create_app(runtime: ServiceRuntime | None = None):
|
||||
"move",
|
||||
"setContentStream",
|
||||
"setContent",
|
||||
"appendContentStream",
|
||||
"appendContent",
|
||||
"deleteContent",
|
||||
"deleteContentStream",
|
||||
],
|
||||
@@ -4632,6 +4707,24 @@ def _cmis_media_type(value: Any) -> str:
|
||||
return media_type or "application/octet-stream"
|
||||
|
||||
|
||||
def _cmis_payload_bytes(value: Any) -> bytes:
|
||||
if value is None:
|
||||
return b""
|
||||
if isinstance(value, bytes):
|
||||
return value
|
||||
if isinstance(value, bytearray):
|
||||
return bytes(value)
|
||||
return str(value).encode("utf-8")
|
||||
|
||||
|
||||
def _cmis_form_bool(value: Any, *, default: bool = False) -> bool:
|
||||
if value in (None, ""):
|
||||
return default
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
return str(value).strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
|
||||
def _cmis_value_list(value: Any) -> list[str]:
|
||||
if value is None or value == "":
|
||||
return []
|
||||
|
||||
@@ -191,11 +191,6 @@ UNSUPPORTED_FEATURES: dict[str, dict[str, Any]] = {
|
||||
"standard_flag": "capability_join",
|
||||
},
|
||||
"order_by": {"status": "unsupported", "reason": "query_not_supported", "standard_flag": "capability_order_by"},
|
||||
"append_content_stream": {
|
||||
"status": "unsupported",
|
||||
"reason": "capability_not_supported",
|
||||
"standard_flag": "capability_content_stream_updatability",
|
||||
},
|
||||
"apply_acl": {"status": "unsupported", "reason": "operation_not_implemented", "standard_flag": "capability_acl"},
|
||||
"apply_policy": {"status": "unsupported", "reason": "capability_not_supported"},
|
||||
"remove_policy": {"status": "unsupported", "reason": "capability_not_supported"},
|
||||
|
||||
@@ -366,6 +366,54 @@ def test_cmis_browser_binding_delete_content_stream_tombstones_content(cmis_clie
|
||||
assert content_after_delete.json()["exception"] == "constraint"
|
||||
|
||||
|
||||
def test_cmis_browser_binding_append_content_stream_adds_versioned_content(cmis_client) -> None:
|
||||
document = cmis_client.post(
|
||||
"/cmis/compat-tck/browser/root",
|
||||
data={
|
||||
"cmisaction": "createDocument",
|
||||
"propertyId[0]": "cmis:objectTypeId",
|
||||
"propertyValue[0]": "cmis:document",
|
||||
"propertyId[1]": "cmis:name",
|
||||
"propertyValue[1]": "Append Content Document",
|
||||
},
|
||||
files={"content": ("append-content.txt", b"one", "text/plain")},
|
||||
).json()
|
||||
object_id = document["properties"]["cmis:objectId"]["value"]
|
||||
token = document["properties"]["cmis:changeToken"]["value"]
|
||||
|
||||
appended = cmis_client.post(
|
||||
"/cmis/compat-tck/browser/root",
|
||||
data={
|
||||
"cmisaction": "appendContent",
|
||||
"objectId": object_id,
|
||||
"changeToken": token,
|
||||
"isLastChunk": "true",
|
||||
},
|
||||
files={"content": ("append-content.txt", b" two", "text/plain")},
|
||||
)
|
||||
content = cmis_client.get(
|
||||
"/cmis/compat-tck/browser/root",
|
||||
params={"cmisselector": "content", "objectId": object_id},
|
||||
)
|
||||
stale_append = cmis_client.post(
|
||||
"/cmis/compat-tck/browser/root",
|
||||
data={
|
||||
"cmisaction": "appendContentStream",
|
||||
"objectId": object_id,
|
||||
"changeToken": token,
|
||||
},
|
||||
files={"content": ("append-content.txt", b" stale", "text/plain")},
|
||||
)
|
||||
|
||||
assert appended.status_code == 200
|
||||
assert appended.json()["properties"]["cmis:contentStreamLength"]["value"] == 7
|
||||
assert appended.json()["properties"]["cmis:contentStreamMimeType"]["value"] == "text/plain"
|
||||
assert appended.json()["properties"]["cmis:changeToken"]["value"] != token
|
||||
assert content.content == b"one two"
|
||||
assert stale_append.status_code == 409
|
||||
assert stale_append.json()["exception"] == "updateConflict"
|
||||
|
||||
|
||||
def test_cmis_browser_binding_change_tokens_conflict_on_stale_updates(cmis_client) -> None:
|
||||
document = cmis_client.post(
|
||||
"/cmis/compat-tck/browser/root",
|
||||
|
||||
@@ -80,6 +80,7 @@ def test_profile_visibility_and_mutation_expectations_are_explicit() -> None:
|
||||
"update_properties",
|
||||
"delete_object",
|
||||
"set_content_stream",
|
||||
"append_content_stream",
|
||||
]
|
||||
else:
|
||||
assert expectations["must_reject_actions"] == [
|
||||
@@ -87,6 +88,7 @@ def test_profile_visibility_and_mutation_expectations_are_explicit() -> None:
|
||||
"update_properties",
|
||||
"delete_object",
|
||||
"set_content_stream",
|
||||
"append_content_stream",
|
||||
]
|
||||
|
||||
|
||||
@@ -117,4 +119,3 @@ def test_admin_export_is_the_only_profile_for_deferred_extension_group() -> None
|
||||
for profile_name, expectations in catalog["profile_expectations"].items():
|
||||
exposes_extension = "retention-renditions-bulk" in expectations["must_expose"]
|
||||
assert exposes_extension is (profile_name == "admin-export")
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ ACTION_MAP = {
|
||||
"update_properties": CMISAction.UPDATE_PROPERTIES,
|
||||
"delete_object": CMISAction.DELETE_OBJECT,
|
||||
"set_content_stream": CMISAction.SET_CONTENT_STREAM,
|
||||
"append_content_stream": CMISAction.SET_CONTENT_STREAM,
|
||||
}
|
||||
|
||||
|
||||
@@ -119,4 +120,3 @@ def test_optional_tck_result_template_can_capture_gap_mapping() -> None:
|
||||
assert template["summary"] == {"passed": 0, "failed": 0, "skipped": 0, "known_gap": 0}
|
||||
assert "capability_gaps" in template
|
||||
assert template["groups"][0]["status"] == "not_run"
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ topic_slug: markitect
|
||||
planning_priority: high
|
||||
planning_order: 14
|
||||
created: "2026-05-08"
|
||||
updated: "2026-05-13"
|
||||
updated: "2026-05-14"
|
||||
state_hub_workstream_id: "ccfa90ee-be23-499b-a727-451a0d289df7"
|
||||
---
|
||||
|
||||
@@ -226,12 +226,20 @@ Latest verification:
|
||||
- `repository-type`: `38 pass`, `2 info`, `1 skipped`, `1 warning`.
|
||||
- `object-content`: `10 info`, `5 skipped`, `1 warning`.
|
||||
|
||||
Release-warning pass:
|
||||
|
||||
- OpenCMIS: `run-20260513T223537Z` in
|
||||
`/tmp/kontextual-cmis-release-20260514-toolchain`.
|
||||
- Guide Board status: `completed`, with `0` unexpected findings.
|
||||
- `repository-type`: `38 pass`, `2 info`, `1 skipped`, `1 warning`.
|
||||
- `object-content`: `10 info`, `5 skipped`, `0 warning`.
|
||||
- Warning-adjusted selected-baseline score improved from `98.3%` to `99.1%`.
|
||||
|
||||
Current external frontier:
|
||||
|
||||
- The selected OpenCMIS Browser Binding repository/type and object/content
|
||||
baseline is completed with warnings only.
|
||||
- Remaining warnings are local HTTP instead of HTTPS and unsupported
|
||||
`appendContentStream()`.
|
||||
- The only remaining warning is local HTTP instead of HTTPS.
|
||||
- Further maturity work should move to deliberately selected optional CMIS
|
||||
areas: query depth, descendants/folder-tree read navigation, version-history
|
||||
reads, relationship service depth, ACL detail, and rendition projection.
|
||||
@@ -338,7 +346,9 @@ Progress:
|
||||
`Content-Length`, `Content-Type`, `ETag`, and `Content-Range`.
|
||||
- Done for `deleteContentStream` tombstone semantics and offset-zero range
|
||||
classification.
|
||||
- `appendContentStream()` remains explicitly unsupported by design.
|
||||
- Done for Browser Binding `appendContent` / `appendContentStream` as
|
||||
whole-object append through the deduplicating representation service, with a
|
||||
composed-size guard.
|
||||
|
||||
## D14.6 - Add natural navigation and query depth
|
||||
|
||||
|
||||
Reference in New Issue
Block a user