Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
100170cea2 | ||
|
|
9ef79928b3 | ||
|
|
4d5c5c63ad | ||
|
|
52311178da | ||
|
|
0263f01159 | ||
|
|
226b0fff0e | ||
|
|
fd4fdf3373 |
@@ -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
|
||||
@@ -1,5 +1,11 @@
|
||||
# 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
|
||||
|
||||
This repository owns the GovOPlaN Records platform module seed.
|
||||
|
||||
@@ -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.
|
||||
@@ -44,3 +44,7 @@ No runtime API, database model, migration, WebUI route, or navigation item is re
|
||||
## First Implementation Slice
|
||||
|
||||
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
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@govoplan/records",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.17",
|
||||
"private": true,
|
||||
"description": "GovOPlaN Records platform module seed.",
|
||||
"type": "module",
|
||||
|
||||
+3
-3
@@ -4,15 +4,15 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-records"
|
||||
version = "0.1.8"
|
||||
version = "0.1.17"
|
||||
description = "GovOPlaN Records platform module seed."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
license = { file = "LICENSE" }
|
||||
authors = [{ name = "GovOPlaN" }]
|
||||
dependencies = [
|
||||
"govoplan-core>=0.1.8",
|
||||
"govoplan-access>=0.1.8",
|
||||
"govoplan-core>=0.1.17",
|
||||
"govoplan-access>=0.1.17",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
|
||||
@@ -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.modules import DocumentationLink, DocumentationTopic, ModuleManifest, PermissionDefinition, RoleTemplate
|
||||
from govoplan_core.core.provider_governance import declared_module_architecture
|
||||
|
||||
MODULE_ID = "records"
|
||||
MODULE_NAME = "Records"
|
||||
MODULE_VERSION = "0.1.8"
|
||||
MODULE_VERSION = "0.1.17"
|
||||
READ_SCOPE = "records:workspace:read"
|
||||
WRITE_SCOPE = "records:workspace:write"
|
||||
ADMIN_SCOPE = "records:workspace:admin"
|
||||
@@ -34,8 +35,8 @@ def _permission(scope: str, label: str, description: str) -> PermissionDefinitio
|
||||
|
||||
|
||||
PERMISSIONS = (
|
||||
_permission(READ_SCOPE, "View records workspace", "Read records records, configuration, and workflow context."),
|
||||
_permission(WRITE_SCOPE, "Manage records workspace", "Create and update records records and workflow state."),
|
||||
_permission(READ_SCOPE, "View records workspace", "Read records, configuration, and workflow context."),
|
||||
_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."),
|
||||
)
|
||||
|
||||
@@ -43,13 +44,13 @@ ROLE_TEMPLATES = (
|
||||
RoleTemplate(
|
||||
slug="records_manager",
|
||||
name="Records manager",
|
||||
description="Manage records records and workflow state.",
|
||||
description="Manage records and workflow state.",
|
||||
permissions=(READ_SCOPE, WRITE_SCOPE),
|
||||
),
|
||||
RoleTemplate(
|
||||
slug="records_viewer",
|
||||
name="Records viewer",
|
||||
description="Read records records and workflow context.",
|
||||
description="Read records and workflow context.",
|
||||
permissions=(READ_SCOPE,),
|
||||
),
|
||||
)
|
||||
@@ -65,8 +66,8 @@ DOCUMENTATION = (
|
||||
"database models, migrations, and WebUI routes are introduced."
|
||||
),
|
||||
layer="available",
|
||||
documentation_types=("admin",),
|
||||
audience=("operator", "module_admin", "product_owner"),
|
||||
documentation_types=("admin", "user"),
|
||||
audience=("user", "operator", "module_admin", "product_owner"),
|
||||
order=100,
|
||||
related_modules=OPTIONAL_DEPENDENCIES,
|
||||
links=(
|
||||
@@ -75,6 +76,11 @@ DOCUMENTATION = (
|
||||
href="govoplan-records/docs/RECORDS_DOMAIN_BOUNDARY.md",
|
||||
kind="repository",
|
||||
),
|
||||
DocumentationLink(
|
||||
label="eAkte architecture",
|
||||
href="govoplan-records/docs/EAKTE_ARCHITECTURE.md",
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
metadata={
|
||||
"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.",
|
||||
},
|
||||
),
|
||||
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(
|
||||
@@ -94,6 +128,15 @@ manifest = ModuleManifest(
|
||||
permissions=PERMISSIONS,
|
||||
role_templates=ROLE_TEMPLATES,
|
||||
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"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user