Problem
Schemas declaring x-openregister-archival (with retention.default + condition-based retention.rules[]) get the annotation silently stripped during import. Result: log-style schemas that are supposed to be immutable + auto-expire are deletable + non-expiring.
Reproducer in ConductionNL/openconnector/lib/Settings/openconnector_register.json:
"call_log": {
"x-openregister-archival": {
"retention": {
"default": "P30D",
"rules": [
{ "condition": "statusCode < 400", "retention": "PT1H", "reason": "..." },
{ "condition": "statusCode >= 400", "retention": "P30D", "reason": "..." }
]
}
}
}
On occ app:enable openconnector the import logs:
[OpenRegister.SchemaMapper] Dropped 2 unknown x-openregister-* key(s) on schema "call_log": x-openregister-seed, x-openregister-archival. Typo? See Schema::ANNOTATION_VOCABULARY for the declared keys.
Same for job_log, synchronization_log, synchronization_contract_log.
Schema::ANNOTATION_VOCABULARY (openregister/lib/Db/Schema.php:1853) currently:
private const ANNOTATION_VOCABULARY = [
'x-openregister-lifecycle',
'x-openregister-aggregations',
'x-openregister-calculations',
'x-openregister-notifications',
'x-openregister-widgets',
'x-openregister-relations',
'x-openregister-processing-activity',
];
x-openregister-archival (and x-openregister-seed) are absent → stripped on line 1697.
Real-world impact
The openconnector dashboard (ConductionNL/openconnector#838) reads call_log / job_log / synchronization_log via OR's groupBy primitive (#1611). Users expect:
If everything worked during a Playwright / Newman journey, the logs (which should be immutable) would still be visible.
Today: logs are deletable like any object, no retention sweep ever runs, so the dashboard can be empty even when integrations actually ran successfully. Reported by Ruben.
Proposed
- Add
'x-openregister-archival' to Schema::ANNOTATION_VOCABULARY. Same for 'x-openregister-seed' if it's intended to be honored elsewhere.
- Validate the annotation shape on import (
retention.default: ISO-8601 duration, retention.rules[i].condition: string, etc.) — same JSON-Schema pattern OR already uses for the other recognized annotations.
- Honor immutability: in
ObjectService::deleteObject() (or its mapper), if schema.x-openregister-archival is set AND the request is a user-driven delete (not a retention-sweep job), reject with 403 Forbidden — schema is archival. Cascading deletes from a parent object's removal should also stop at archival children.
- Retention sweep: a new scheduled job (
OCA\OpenRegister\Cron\ArchivalRetentionTask) that, per schema with archival declared, evaluates each row against the conditions in order (first match wins) and deletes rows past their effective retention. Use the same SettingsService::rebase()-style native SQL pattern openconnector currently has on the legacy tables.
- Surface the rule + retention state in the dashboard / object-detail UI so users can see WHY a row is being kept (which condition matched + how long until it expires).
Non-goals
- Cross-schema cascading archival (e.g. "synchronization_contract is archival because its parent synchronization is") — not needed for the fleet's current schemas.
- Per-tenant retention overrides — multi-tenant settings stay out of v1.
Suggested opsx change name
add-archival-annotation-support
Problem
Schemas declaring
x-openregister-archival(withretention.default+ condition-basedretention.rules[]) get the annotation silently stripped during import. Result: log-style schemas that are supposed to be immutable + auto-expire are deletable + non-expiring.Reproducer in
ConductionNL/openconnector/lib/Settings/openconnector_register.json:On
occ app:enable openconnectorthe import logs:Same for
job_log,synchronization_log,synchronization_contract_log.Schema::ANNOTATION_VOCABULARY(openregister/lib/Db/Schema.php:1853) currently:x-openregister-archival(andx-openregister-seed) are absent → stripped on line 1697.Real-world impact
The openconnector dashboard (ConductionNL/openconnector#838) reads call_log / job_log / synchronization_log via OR's groupBy primitive (#1611). Users expect:
Today: logs are deletable like any object, no retention sweep ever runs, so the dashboard can be empty even when integrations actually ran successfully. Reported by Ruben.
Proposed
'x-openregister-archival'toSchema::ANNOTATION_VOCABULARY. Same for'x-openregister-seed'if it's intended to be honored elsewhere.retention.default: ISO-8601 duration,retention.rules[i].condition: string, etc.) — same JSON-Schema pattern OR already uses for the other recognized annotations.ObjectService::deleteObject()(or its mapper), ifschema.x-openregister-archivalis set AND the request is a user-driven delete (not a retention-sweep job), reject with403 Forbidden — schema is archival. Cascading deletes from a parent object's removal should also stop at archival children.OCA\OpenRegister\Cron\ArchivalRetentionTask) that, per schema with archival declared, evaluates each row against the conditions in order (first match wins) and deletes rows past their effective retention. Use the same SettingsService::rebase()-style native SQL pattern openconnector currently has on the legacy tables.Non-goals
Suggested opsx change name
add-archival-annotation-support