7 Commits
Author SHA1 Message Date
zemion 9ef79928b3 Release v0.1.16
Module Package Release / publish-packages (push) Successful in 11s
2026-08-05 19:52:13 +02:00
zemion 4d5c5c63ad Release v0.1.15
Module Package Release / publish-packages (push) Successful in 10s
2026-08-04 15:10:20 +02:00
zemion 52311178da Make package publication retries hash-safe 2026-08-04 14:32:20 +02:00
zemion 0263f01159 Harden module package publication 2026-08-04 14:02:41 +02:00
zemion 226b0fff0e Add protected package release workflow 2026-08-04 04:14:07 +02:00
zemion fd4fdf3373 docs: declare institutional architecture boundary 2026-08-01 17:48:39 +02:00
zemion c05d98da8d Release v0.1.8 2026-07-11 16:49:03 +02:00
8 changed files with 530 additions and 12 deletions
+270
View File
@@ -0,0 +1,270 @@
name: Module Package Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
release_tag:
description: Existing protected version tag to publish
required: true
type: string
jobs:
publish-packages:
runs-on: ubuntu-latest
env:
GITEA_REPOSITORY: ${{ gitea.repository }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
with:
python-version: "3.12"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "22"
- name: Select and validate protected release tag
shell: bash
env:
REQUESTED_TAG: ${{ inputs.release_tag }}
TRIGGER_TAG: ${{ gitea.ref_name }}
run: |
set -euo pipefail
tag="${REQUESTED_TAG:-$TRIGGER_TAG}"
case "$tag" in
v[0-9]*.[0-9]*.[0-9]*) ;;
*) echo "Release tag must start with a SemVer-shaped vX.Y.Z value" >&2; exit 1 ;;
esac
git fetch --force origin "refs/tags/$tag:refs/tags/$tag" refs/heads/main:refs/remotes/origin/main
tag_commit="$(git rev-list -n 1 "$tag")"
git merge-base --is-ancestor "$tag_commit" refs/remotes/origin/main || {
echo "Release tag is not contained in main" >&2
exit 1
}
git checkout --detach "$tag"
printf 'RELEASE_TAG=%s\n' "$tag" >> "$GITEA_ENV"
printf 'SOURCE_DATE_EPOCH=%s\n' "$(git show -s --format=%ct HEAD)" >> "$GITEA_ENV"
- name: Validate package versions
run: |
python - <<'PY'
import json
from pathlib import Path
import os
import re
import tomllib
tag = os.environ["RELEASE_TAG"]
expected = tag.removeprefix("v")
project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]
if project.get("version") != expected:
raise SystemExit(f"pyproject version {project.get('version')!r} does not match {tag}")
if re.fullmatch(r"govoplan-[a-z0-9-]+", str(project.get("name", ""))) is None:
raise SystemExit("Python distribution name must use the govoplan-* namespace")
webui = Path("webui/package.json")
if webui.is_file():
package = json.loads(webui.read_text(encoding="utf-8"))
if package.get("version") != expected:
raise SystemExit(f"WebUI version {package.get('version')!r} does not match {tag}")
if re.fullmatch(r"@govoplan/[a-z0-9-]+-webui", str(package.get("name", ""))) is None:
raise SystemExit("WebUI package name must use the @govoplan/*-webui namespace")
release = Path("webui/package.release.json")
if release.is_file():
release_package = json.loads(release.read_text(encoding="utf-8"))
if (
release_package.get("name") != package.get("name")
or release_package.get("version") != expected
):
raise SystemExit("WebUI release package identity does not match package.json and the release tag")
PY
- name: Build immutable package artifacts
shell: bash
run: |
set -euo pipefail
python -m pip install --disable-pip-version-check build==1.5.0 twine==7.0.0
rm -rf dist .package-webui
python -m build --wheel --outdir dist
python -m twine check dist/*.whl
if [[ -f webui/package.json ]]; then
mkdir .package-webui
cp -a webui/. .package-webui/
rm -rf .package-webui/node_modules .package-webui/dist
if [[ -f .package-webui/package.release.json ]]; then
cp .package-webui/package.release.json .package-webui/package.json
fi
node <<'NODE'
const fs = require("node:fs");
const path = ".package-webui/package.json";
const packageJson = JSON.parse(fs.readFileSync(path, "utf8"));
const groups = ["dependencies", "optionalDependencies", "peerDependencies"];
for (const group of groups) {
for (const [name, specifier] of Object.entries(packageJson[group] || {})) {
if (!name.startsWith("@govoplan/")) continue;
if (typeof specifier !== "string") {
throw new Error(`${group}.${name} must use a string version`);
}
const packageSlug = name.slice("@govoplan/".length);
if (!packageSlug.endsWith("-webui")) {
throw new Error(`${group}.${name} is outside the WebUI package namespace`);
}
const repository = `govoplan-${packageSlug.slice(0, -"-webui".length)}`;
const escapedRepository = repository.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const gitTag = specifier.match(
new RegExp(
`^git\\+(?:ssh://git@|https://)git\\.add-ideas\\.de/(?:GovOPlaN|add-ideas)/${escapedRepository}\\.git#v([0-9]+\\.[0-9]+\\.[0-9]+)$`,
),
);
if (gitTag) {
packageJson[group][name] = gitTag[1];
continue;
}
if (specifier.startsWith("file:") || specifier.startsWith("git+")) {
throw new Error(
`${group}.${name} must resolve to an exact registry version for publication`,
);
}
}
}
delete packageJson.private;
fs.writeFileSync(path, `${JSON.stringify(packageJson, null, 2)}\n`);
NODE
npm pkg delete private --prefix .package-webui
(cd .package-webui && npm pack --ignore-scripts --pack-destination ../dist)
fi
python - <<'PY'
import hashlib
import json
from pathlib import Path
import os
import subprocess
artifacts = []
for path in sorted(Path("dist").iterdir()):
if path.suffix not in {".whl", ".tgz"}:
continue
digest = hashlib.sha256(path.read_bytes()).hexdigest()
artifacts.append({"filename": path.name, "sha256": digest, "size": path.stat().st_size})
payload = {
"schema_version": "1",
"repository": os.environ["GITEA_REPOSITORY"],
"tag": os.environ["RELEASE_TAG"],
"commit": subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip(),
"artifacts": artifacts,
}
Path("dist/package-artifacts.json").write_text(
json.dumps(payload, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
PY
- name: Retain package hash evidence
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32
with:
name: module-packages-${{ gitea.ref_name }}
path: dist/package-artifacts.json
- name: Check immutable registry state
shell: bash
env:
PACKAGE_TOKEN: ${{ secrets.GOVOPLAN_PACKAGE_TOKEN }}
run: |
set -euo pipefail
test -n "$PACKAGE_TOKEN"
python - <<'PY'
import hashlib
import json
import os
from pathlib import Path
import tomllib
from urllib.error import HTTPError
from urllib.parse import quote
from urllib.request import Request, urlopen
api_root = "https://git.add-ideas.de/api/v1/packages/GovOPlaN"
token = os.environ["PACKAGE_TOKEN"]
def should_publish(kind, name, version, path):
package_url = "/".join(
(api_root, kind, quote(name, safe=""), quote(version, safe=""), "files")
)
request = Request(
package_url,
headers={"Accept": "application/json", "Authorization": f"token {token}"},
)
try:
with urlopen(request, timeout=30) as response:
files = json.load(response)
except HTTPError as exc:
if exc.code == 404:
print(f"{kind} package {name}=={version} is not published yet")
return True
raise
if not isinstance(files, list) or len(files) != 1:
raise SystemExit(
f"immutable {kind} package {name}=={version} has an unexpected file set"
)
expected_sha256 = hashlib.sha256(path.read_bytes()).hexdigest()
if files[0].get("sha256") != expected_sha256:
raise SystemExit(
f"immutable {kind} package {name}=={version} already exists with a different SHA-256"
)
print(f"verified existing {kind} package {name}=={version} ({expected_sha256})")
return False
project = tomllib.loads(Path("pyproject.toml").read_text(encoding="utf-8"))["project"]
wheels = tuple(Path("dist").glob("*.whl"))
if len(wheels) != 1:
raise SystemExit("release build must contain exactly one wheel")
publish_pypi = should_publish(
"pypi", str(project["name"]), str(project["version"]), wheels[0]
)
tarballs = tuple(Path("dist").glob("*.tgz"))
if len(tarballs) > 1:
raise SystemExit("release build must contain at most one npm package")
publish_npm = False
if tarballs:
webui = json.loads(
Path(".package-webui/package.json").read_text(encoding="utf-8")
)
publish_npm = should_publish(
"npm", str(webui["name"]), str(webui["version"]), tarballs[0]
)
with Path(os.environ["GITEA_ENV"]).open("a", encoding="utf-8") as env_file:
env_file.write(f"PUBLISH_PYPI={int(publish_pypi)}\n")
env_file.write(f"PUBLISH_NPM={int(publish_npm)}\n")
PY
- name: Publish wheel and WebUI package
shell: bash
env:
PACKAGE_USERNAME: ${{ secrets.GOVOPLAN_PACKAGE_USERNAME }}
PACKAGE_TOKEN: ${{ secrets.GOVOPLAN_PACKAGE_TOKEN }}
run: |
set -euo pipefail
test -n "$PACKAGE_USERNAME"
test -n "$PACKAGE_TOKEN"
if [[ "$PUBLISH_PYPI" == 1 ]]; then
TWINE_USERNAME="$PACKAGE_USERNAME" TWINE_PASSWORD="$PACKAGE_TOKEN" \
python -m twine upload --non-interactive \
--repository-url https://git.add-ideas.de/api/packages/GovOPlaN/pypi \
dist/*.whl
else
echo "Exact wheel is already present; skipping immutable retry."
fi
shopt -s nullglob
webui_packages=(dist/*.tgz)
if (( ${#webui_packages[@]} )) && [[ "$PUBLISH_NPM" == 1 ]]; then
npmrc="$(mktemp)"
trap 'rm -f "$npmrc"' EXIT
chmod 600 "$npmrc"
printf '%s\n' \
'@govoplan:registry=https://git.add-ideas.de/api/packages/GovOPlaN/npm/' \
"//git.add-ideas.de/api/packages/GovOPlaN/npm/:_authToken=$PACKAGE_TOKEN" \
> "$npmrc"
NPM_CONFIG_USERCONFIG="$npmrc" npm publish "./${webui_packages[0]}" \
--ignore-scripts --access public \
--registry https://git.add-ideas.de/api/packages/GovOPlaN/npm/
elif (( ${#webui_packages[@]} )); then
echo "Exact WebUI package is already present; skipping immutable retry."
fi
+6
View File
@@ -1,5 +1,11 @@
# GovOPlaN Records Codex Guide # GovOPlaN Records Codex Guide
## Documentation Contract
- Treat documentation as part of every behavior change. Update this module's manifest-driven `DocumentationTopic` contributions for affected user and administrator behavior.
- Keep feature content here; `govoplan-docs` projects it without importing Records internals.
- Maintain a static user/admin baseline and run `/mnt/DATA/git/govoplan/tools/checks/check-manifest-shapes.py` after behavior or manifest changes.
## Scope ## Scope
This repository owns the GovOPlaN Records platform module seed. This repository owns the GovOPlaN Records platform module seed.
+5 -1
View File
@@ -1,5 +1,9 @@
# GovOPlaN Records # GovOPlaN Records
<!-- govoplan-repository-type:start -->
**Repository type:** module (domain).
<!-- govoplan-repository-type:end -->
`govoplan-records` is the GovOPlaN platform module seed for records management for file plans, records classification, retention schedules, disposal holds, and archive handoff. `govoplan-records` is the GovOPlaN platform module seed for records management for file plans, records classification, retention schedules, disposal holds, and archive handoff.
This repository is initialized as a discoverable module seed. It exposes a module manifest, initial permissions, role templates, documentation metadata, Gitea workflow templates, and a focused manifest test. It intentionally does not yet add HTTP routes, database models, migrations, or WebUI navigation. This repository is initialized as a discoverable module seed. It exposes a module manifest, initial permissions, role templates, documentation metadata, Gitea workflow templates, and a focused manifest test. It intentionally does not yet add HTTP routes, database models, migrations, or WebUI navigation.
@@ -58,5 +62,5 @@ From the core checkout, labels can be synced once a local `GITEA_TOKEN` is avail
```bash ```bash
cd /mnt/DATA/git/govoplan-core cd /mnt/DATA/git/govoplan-core
./scripts/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-records --apply /mnt/DATA/git/govoplan/tools/gitea/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-records --apply
``` ```
+191
View File
@@ -0,0 +1,191 @@
# eAkte Architecture
## Purpose
The eAkte is the authoritative institutional record context for a matter,
procedure, subject, project, or responsibility. It answers what belongs to the
record, how it is structured, why an item was filed, which version was known,
who may use it for which purpose, when it closes, what must be retained, and
how it is offered, transferred, preserved, or disposed.
GovOPlaN Records should provide this governance lifecycle natively while being
able to place it over an external DMS, records system, long-term archive, or
specialist procedure. It must not duplicate all document editing or storage.
Implementation is tracked in
[Records #1](https://git.add-ideas.de/GovOPlaN/govoplan-records/issues/1).
## Ownership Boundary
Records owns:
- file plans and record classes;
- record, volume, process/file, and record-item identity;
- classification, filing decision, ordering, and relationship to a case or
other institutional context;
- effective retention rule application, closure, hold, appraisal,
disposition proposal, approval, transfer, and destruction evidence;
- authoritative record metadata and exact content/reference manifests;
- external records-system mappings and source-authority mode;
- export, transfer, archive-offer, and custody receipts.
Records does not own:
- file bytes, versions, previews, or malware handling (Files);
- collaborative document editing, check-in/check-out, document review, or DMS
provider behavior (DMS and connector owner);
- generated document definitions (Templates);
- case lifecycle (Cases), work coordination (Workflow Engine/Tasks), formal
outcomes (Decisions), or general audit events (Audit);
- generic retention and access policy authoring (Policy);
- archive preservation implementation or evidence-renewal cryptography
(external archive/TR-ESOR provider and Encryption/Identity Trust).
## Core Object Model
| Object | Meaning |
| --- | --- |
| File plan | Versioned hierarchy derived from institutional responsibilities |
| Record class | Rules for required metadata, allowed content, access, retention, closure, and disposition |
| Record | Stable legal/institutional record identity and context |
| Volume/part | Bounded subdivision for size, period, classification, or custody |
| Process/file | Optional business transaction grouping inside a record |
| Record item | Immutable filing event linking exact content or an external object revision |
| Filing note | Reason, source, relationship, ordering, actor/capacity, and evidence for inclusion |
| Hold | Effective-dated suspension of disposition with authority and scope |
| Appraisal | Archive value/offer decision and responsible archive interaction |
| Disposition case | Proposed retain, transfer, destroy, or reclassify action with review and evidence |
| Transfer package | Exact metadata/content manifest, profile, digest, encryption, and receipts |
| Custody event | Handoff, acceptance, rejection, correction, return, or destruction observation |
Every object carries tenant/institution, valid and recorded time, revision,
source authority, classification, purpose constraints, retention/hold state,
institutional context, provenance, and optimistic-concurrency token.
## Record Lifecycle
```text
planned -> open -> closed -> retention_running -> appraisal_due
| |
v v
held offered_to_archive
|
+-----------------------------+------------------+
v v v
accepted rejected retained
| | |
v v v
transferred disposal_due reappraise
|
v
destroyed
```
Reopening creates a governed transition and does not reset elapsed retention
without an explicit rule. A hold preserves the reason and affected scope. A
destruction action requires exact manifest, current authority, policy,
approval, preflight, idempotency, outcome-unknown recovery, and evidence.
## Filing Semantics
- Filing links an exact immutable document/file/object revision; a later source
revision is a new record item unless the record class permits an explicitly
tracked living reference.
- The item records valid time of the represented fact and recorded time of
filing independently.
- The current security context always controls browsing, including historical
views.
- Purpose-aware access may be narrower than ordinary read permission and can
require case assignment, represented function, mandate, legal basis, or
reason-for-access capture.
- A record item can reference a message, decision, form submission, report,
dataset materialization, external DMS object, physical item, or paper scan;
it is not limited to Files.
- Corrections and replacements link items and retain what was previously part
of the record.
## Native, External, And Hybrid Operation
| Mode | GovOPlaN behavior |
| --- | --- |
| Native authoritative | Records owns lifecycle and manifests; Files or object storage owns bytes |
| External authoritative | External eAkte/DMS owns structure and lifecycle; GovOPlaN keeps governed references and provider state |
| External mirror | GovOPlaN keeps a read/search projection and immutable evidence snapshots |
| Governed sync | Explicit metadata/filing fields can change on both sides with revision and conflict rules |
| Governance overlay | GovOPlaN owns case/workflow/policy/evidence around records held externally |
| Linked reference | Only stable identity, display metadata, authority, and launch link are retained |
The mode is configurable by tenant, record class, provider binding, and where
safe by field group. A migration assesses exact objects and receipts; enabling
a connector never silently copies all records.
## Standards And Provider Profiles
The domain contract remains neutral, while German public-sector deployments
can add profiles for:
- `xdomea` exchange of files, processes, documents, file plans, and
disposition messages. The IT-Planungsrat decision defines xdomea for
inter-authority exchange and disposition scenarios:
<https://www.it-planungsrat.de/beschluss/beschluss-2017-39>
- archive offering and transfer guidance from the Bundesarchiv, including
xdomea and XAIP/LXAIP packages:
<https://www.bundesarchiv.de/unterlagen-abgeben/aussonderung-von-unterlagen/elektronische-akten/>
- BSI TR-03125/TR-ESOR evidence preservation and archive information packages
where cryptographic evidentiary value must be maintained:
<https://www.bsi.bund.de/dok/TR-03125>
- provider-specific DMS/VBS, archive, and specialist-procedure adapters through
the standard external-provider declaration and recovery gate.
A profile declares supported operations, conformance version, metadata
mapping, content formats, evidence behavior, size limits, retries, conflicts,
and target-tested provider. Naming a standard is not a conformance claim.
## UI Model
The normal record workspace contains:
- file-plan tree and saved institutional contexts;
- record list with class, subject, responsibility, state, retention, holds,
source, and access explanation;
- one record surface with metadata, chronology, structured contents, related
case/service/decision/work, access reason, and evidence;
- filing action available from owner modules without exposing Records internals;
- close, reopen, hold, appraisal, transfer, and disposition workflows with
consequence preview;
- temporal current/at/all browsing, while clearly separating valid and recorded
time;
- search and export that honor current access, purpose, sealed content, and
minimization.
Technical provider IDs, hashes, package schemas, and source mappings remain
available in an evidence/details view.
## Recovery And Scale
- PostgreSQL is the durable record-state authority; content uses shared object
storage or an external provider, never node-local paths.
- Every external filing, transfer, or destruction uses intent-before-effect,
idempotency, durable receipts, outcome-unknown state, and reconciliation.
- Backup evidence binds record rows, object manifests, provider mappings,
policy/configuration versions, and key references.
- Restore verifies content digests, missing keys/objects, provider reachability,
and disposition holds before reopening effects.
- Search indexes are rebuildable projections and cannot become record
authority.
## Delivery Order
1. Persist file plans, record classes, records, record items, exact references,
chronology, permissions, temporal reads, institutional context, and search.
2. Integrate filing from Cases, Forms Runtime, Decisions, Campaign/Postbox,
Files, and Reporting.
3. Add closure, retention calculation, holds, appraisal, and reviewed
disposition without destructive provider effects.
4. Add native transfer packages and one target-tested xdomea/archive provider.
5. Add destruction/recovery, TR-ESOR provider integration, migration, and
signed reference-journey evidence.
The first reference package should file the digital and assisted variants of
the same service-to-decision journey into equivalent records and prove search,
historical reconstruction, hold, transfer, restore, and access explanation.
+4
View File
@@ -44,3 +44,7 @@ No runtime API, database model, migration, WebUI route, or navigation item is re
## First Implementation Slice ## First Implementation Slice
Define record class, file plan node, retention schedule, disposal hold, archive transfer, and source document links. Define record class, file plan node, retention schedule, disposal hold, archive transfer, and source document links.
The complete native/external boundary, temporal and purpose-aware record model,
disposition lifecycle, German public-sector provider profiles, and staged
implementation are specified in [eAkte Architecture](EAKTE_ARCHITECTURE.md).
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/records", "name": "@govoplan/records",
"version": "0.1.7", "version": "0.1.16",
"private": true, "private": true,
"description": "GovOPlaN Records platform module seed.", "description": "GovOPlaN Records platform module seed.",
"type": "module", "type": "module",
+3 -3
View File
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-records" name = "govoplan-records"
version = "0.1.7" version = "0.1.16"
description = "GovOPlaN Records platform module seed." description = "GovOPlaN Records platform module seed."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
license = { file = "LICENSE" } license = { file = "LICENSE" }
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = [ dependencies = [
"govoplan-core>=0.1.7", "govoplan-core>=0.1.16",
"govoplan-access>=0.1.7", "govoplan-access>=0.1.16",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
+50 -7
View File
@@ -2,10 +2,11 @@ from __future__ import annotations
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.modules import DocumentationLink, DocumentationTopic, ModuleManifest, PermissionDefinition, RoleTemplate from govoplan_core.core.modules import DocumentationLink, DocumentationTopic, ModuleManifest, PermissionDefinition, RoleTemplate
from govoplan_core.core.provider_governance import declared_module_architecture
MODULE_ID = "records" MODULE_ID = "records"
MODULE_NAME = "Records" MODULE_NAME = "Records"
MODULE_VERSION = "0.1.7" MODULE_VERSION = "0.1.16"
READ_SCOPE = "records:workspace:read" READ_SCOPE = "records:workspace:read"
WRITE_SCOPE = "records:workspace:write" WRITE_SCOPE = "records:workspace:write"
ADMIN_SCOPE = "records:workspace:admin" ADMIN_SCOPE = "records:workspace:admin"
@@ -34,8 +35,8 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio
PERMISSIONS = ( PERMISSIONS = (
_permission(READ_SCOPE, "View records workspace", "Read records records, configuration, and workflow context."), _permission(READ_SCOPE, "View records workspace", "Read records, configuration, and workflow context."),
_permission(WRITE_SCOPE, "Manage records workspace", "Create and update records records and workflow state."), _permission(WRITE_SCOPE, "Manage records workspace", "Create and update records and workflow state."),
_permission(ADMIN_SCOPE, "Administer records workspace", "Configure records policies, templates, and tenant-level administration."), _permission(ADMIN_SCOPE, "Administer records workspace", "Configure records policies, templates, and tenant-level administration."),
) )
@@ -43,13 +44,13 @@ ROLE_TEMPLATES = (
RoleTemplate( RoleTemplate(
slug="records_manager", slug="records_manager",
name="Records manager", name="Records manager",
description="Manage records records and workflow state.", description="Manage records and workflow state.",
permissions=(READ_SCOPE, WRITE_SCOPE), permissions=(READ_SCOPE, WRITE_SCOPE),
), ),
RoleTemplate( RoleTemplate(
slug="records_viewer", slug="records_viewer",
name="Records viewer", name="Records viewer",
description="Read records records and workflow context.", description="Read records and workflow context.",
permissions=(READ_SCOPE,), permissions=(READ_SCOPE,),
), ),
) )
@@ -65,8 +66,8 @@ DOCUMENTATION = (
"database models, migrations, and WebUI routes are introduced." "database models, migrations, and WebUI routes are introduced."
), ),
layer="available", layer="available",
documentation_types=("admin",), documentation_types=("admin", "user"),
audience=("operator", "module_admin", "product_owner"), audience=("user", "operator", "module_admin", "product_owner"),
order=100, order=100,
related_modules=OPTIONAL_DEPENDENCIES, related_modules=OPTIONAL_DEPENDENCIES,
links=( links=(
@@ -75,6 +76,11 @@ DOCUMENTATION = (
href="govoplan-records/docs/RECORDS_DOMAIN_BOUNDARY.md", href="govoplan-records/docs/RECORDS_DOMAIN_BOUNDARY.md",
kind="repository", kind="repository",
), ),
DocumentationLink(
label="eAkte architecture",
href="govoplan-records/docs/EAKTE_ARCHITECTURE.md",
kind="repository",
),
), ),
metadata={ metadata={
"seed": True, "seed": True,
@@ -82,6 +88,34 @@ DOCUMENTATION = (
"first_slice": "Define record class, file plan node, retention schedule, disposal hold, archive transfer, and source document links.", "first_slice": "Define record class, file plan node, retention schedule, disposal hold, archive transfer, and source document links.",
}, },
), ),
DocumentationTopic(
id=f"{MODULE_ID}.eakte-architecture",
title="eAkte and digital record lifecycle",
summary="Defines native and external record operation, filing, temporal and purpose-aware access, retention, holds, appraisal, transfer, and disposition.",
body=(
"Records owns the legal and institutional record identity, file plan, filing decisions, "
"retention and disposition lifecycle, and transfer evidence. Files owns bytes, DMS owns "
"document editing, Policy owns reusable rules, and external archives remain supported "
"through explicit source-authority and provider profiles."
),
layer="available",
documentation_types=("admin", "user"),
audience=("user", "records_manager", "operator", "module_admin", "product_owner"),
order=110,
related_modules=OPTIONAL_DEPENDENCIES,
links=(
DocumentationLink(
label="eAkte architecture",
href="govoplan-records/docs/EAKTE_ARCHITECTURE.md",
kind="repository",
),
),
metadata={
"kind": "concept",
"help_contexts": ["records.page", "records.record", "records.disposition"],
"known_limit": "The architecture is accepted, but persistence and user-visible record workflows remain a scaffold.",
},
),
) )
manifest = ModuleManifest( manifest = ModuleManifest(
@@ -94,6 +128,15 @@ manifest = ModuleManifest(
permissions=PERMISSIONS, permissions=PERMISSIONS,
role_templates=ROLE_TEMPLATES, role_templates=ROLE_TEMPLATES,
documentation=DOCUMENTATION, documentation=DOCUMENTATION,
architecture=declared_module_architecture(
layer="content_records_evidence",
kind="domain",
maturity="scaffold",
documentation_ref="docs/RECORDS_DOMAIN_BOUNDARY.md",
known_limits=("Record declaration, retention, hold, transfer, and disposal are not implemented yet.",),
owned_concepts=("record", "record classification", "disposition"),
non_owned_concepts=("file content", "audit event", "domain object"),
),
) )