Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4bb52d8b8 | ||
|
|
ff4380ffef | ||
|
|
5ba42d3d68 | ||
|
|
b8fceff3f4 | ||
|
|
40d6d91c4a | ||
|
|
d3731caf8d | ||
|
|
3f4a25111f | ||
|
|
3662c102c5 | ||
|
|
51f7353953 | ||
|
|
55332fa88c | ||
|
|
129cad410c | ||
|
|
a6db06630c | ||
|
|
245a571c60 |
@@ -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,16 +1,24 @@
|
|||||||
# GovOPlaN Issue Reporting Codex Guide
|
# GovOPlaN Tickets 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 Tickets 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 Issue Reporting platform module seed.
|
This repository owns the GovOPlaN Tickets domain module seed.
|
||||||
|
|
||||||
Public or internal problem reporting for broken infrastructure, damaged rooms, IT outages, safety issues, accessibility issues, triage, and routing.
|
Tickets are queue-oriented reports, requests, incidents, problems, and service
|
||||||
|
work. Formal administrative procedures remain owned by `govoplan-cases`.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
- Depend on kernel contracts from `govoplan-core` and access contracts from `govoplan-access`.
|
- Depend on kernel contracts from `govoplan-core` and access contracts from `govoplan-access`.
|
||||||
- Keep domain behavior in this module; expose integration through manifests, capabilities, API routes, events, typed DTOs, and documentation topics.
|
- Keep domain behavior in this module; expose integration through manifests, capabilities, API routes, events, typed DTOs, and documentation topics.
|
||||||
- Do not import internals from sibling feature modules. Use optional dependencies and capabilities for cross-module behavior.
|
- Do not import internals from sibling feature modules. Use optional dependencies and capabilities for cross-module behavior.
|
||||||
|
- Escalation to a case creates a stable cross-module reference; never mutate a ticket into a case or copy a case's authoritative procedural state.
|
||||||
- Keep the seed non-invasive until runtime routes, persistence, and WebUI flows are intentionally designed.
|
- Keep the seed non-invasive until runtime routes, persistence, and WebUI flows are intentionally designed.
|
||||||
|
|
||||||
## Local Workflow
|
## Local Workflow
|
||||||
|
|||||||
@@ -1,39 +1,56 @@
|
|||||||
# GovOPlaN Issue Reporting
|
# GovOPlaN Tickets
|
||||||
|
|
||||||
`govoplan-issue-reporting` is the GovOPlaN platform module seed for public or internal problem reporting for broken infrastructure, damaged rooms, IT outages, safety issues, accessibility issues, triage, and routing.
|
<!-- govoplan-repository-type:start -->
|
||||||
|
**Repository type:** module (domain).
|
||||||
|
<!-- govoplan-repository-type:end -->
|
||||||
|
|
||||||
|
`govoplan-tickets` is the generic GovOPlaN work-intake and service-ticket
|
||||||
|
module. It covers public and internal reports, requests, incidents, problems,
|
||||||
|
queues, triage, routing, assignment, service-level state, and auditable
|
||||||
|
resolution.
|
||||||
|
|
||||||
|
Its runtime module ID is `tickets`.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
## Initial Ownership
|
## Initial Ownership
|
||||||
|
|
||||||
- issue intake
|
- ticket identity, type, priority, state, and queue
|
||||||
- problem category taxonomy
|
- public and internal intake profiles
|
||||||
- triage and routing facts
|
- reporter/requester and affected-object references
|
||||||
- public reporter references
|
- triage, routing, assignment, and service-level facts
|
||||||
- handoff targets
|
- resolution evidence and escalation links
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
This module does not own:
|
Tickets are operational work items. They may be resolved entirely within a
|
||||||
|
queue or linked to a task, project, asset, facility, or formal case.
|
||||||
|
|
||||||
- internal service desk execution
|
Cases are different: `govoplan-cases` owns the authoritative procedural record
|
||||||
- legal case handling
|
for an administrative matter, including parties, evidence, decisions,
|
||||||
- asset lifecycle management
|
procedural deadlines, and retention. Escalating a ticket creates a stable link;
|
||||||
|
it does not rewrite the ticket as a case.
|
||||||
|
|
||||||
Detailed boundary notes are in [docs/ISSUE_REPORTING_DOMAIN_BOUNDARY.md](docs/ISSUE_REPORTING_DOMAIN_BOUNDARY.md).
|
Detailed boundary notes are in
|
||||||
|
[docs/TICKETS_DOMAIN_BOUNDARY.md](docs/TICKETS_DOMAIN_BOUNDARY.md).
|
||||||
|
|
||||||
## Integrations
|
## Integrations
|
||||||
|
|
||||||
Expected optional integrations:
|
Expected optional integrations:
|
||||||
|
|
||||||
- helpdesk
|
|
||||||
- cases
|
- cases
|
||||||
|
- projects
|
||||||
|
- wiki
|
||||||
- assets
|
- assets
|
||||||
- facilities
|
- facilities
|
||||||
- forms-runtime
|
- forms_runtime
|
||||||
- portal
|
- portal
|
||||||
- files
|
- files
|
||||||
- workflow
|
- workflow
|
||||||
|
- tasks
|
||||||
|
- mail
|
||||||
|
- notifications
|
||||||
|
- search
|
||||||
|
|
||||||
## Development Install
|
## Development Install
|
||||||
|
|
||||||
@@ -41,23 +58,24 @@ From the core checkout:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/DATA/git/govoplan-core
|
cd /mnt/DATA/git/govoplan-core
|
||||||
./.venv/bin/python -m pip install -e ../govoplan-issue-reporting
|
./.venv/bin/python -m pip install -e ../govoplan-tickets
|
||||||
```
|
```
|
||||||
|
|
||||||
Focused manifest verification:
|
Focused manifest verification:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/DATA/git/govoplan-issue-reporting
|
cd /mnt/DATA/git/govoplan-tickets
|
||||||
PYTHONPATH=src:/mnt/DATA/git/govoplan-core/src /mnt/DATA/git/govoplan-core/.venv/bin/python -m unittest discover -s tests
|
PYTHONPATH=src:/mnt/DATA/git/govoplan-core/src /mnt/DATA/git/govoplan-core/.venv/bin/python -m unittest discover -s tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gitea Workflow
|
## Gitea Workflow
|
||||||
|
|
||||||
Issue templates are installed under `.gitea/`, and the shared label taxonomy is copied to `docs/gitea-labels.json` with the module label `module/issue-reporting`.
|
Issue templates are installed under `.gitea/`, and the shared label taxonomy is
|
||||||
|
copied to `docs/gitea-labels.json` with the module label `module/tickets`.
|
||||||
|
|
||||||
From the core checkout, labels can be synced once a local `GITEA_TOKEN` is available:
|
From the core checkout, labels can be synced once a local `GITEA_TOKEN` is available:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /mnt/DATA/git/govoplan-core
|
cd /mnt/DATA/git/govoplan-core
|
||||||
./scripts/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-issue-reporting --apply
|
/mnt/DATA/git/govoplan/tools/gitea/gitea-sync-labels.py --root /mnt/DATA/git/govoplan-tickets --apply
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
# Issue Reporting Domain Boundary
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Public or internal problem reporting for broken infrastructure, damaged rooms, IT outages, safety issues, accessibility issues, triage, and routing.
|
|
||||||
|
|
||||||
## Owns
|
|
||||||
|
|
||||||
- issue intake
|
|
||||||
- problem category taxonomy
|
|
||||||
- triage and routing facts
|
|
||||||
- public reporter references
|
|
||||||
- handoff targets
|
|
||||||
|
|
||||||
## Does Not Own
|
|
||||||
|
|
||||||
- internal service desk execution
|
|
||||||
- legal case handling
|
|
||||||
- asset lifecycle management
|
|
||||||
|
|
||||||
## Integration Candidates
|
|
||||||
|
|
||||||
- helpdesk
|
|
||||||
- cases
|
|
||||||
- assets
|
|
||||||
- facilities
|
|
||||||
- forms-runtime
|
|
||||||
- portal
|
|
||||||
- files
|
|
||||||
- workflow
|
|
||||||
|
|
||||||
## Seed State
|
|
||||||
|
|
||||||
The current repository state is intentionally small:
|
|
||||||
|
|
||||||
- module manifest and entry point
|
|
||||||
- tenant-level permission definitions
|
|
||||||
- manager and viewer role templates
|
|
||||||
- documentation topic describing the module boundary
|
|
||||||
- Gitea issue workflow templates
|
|
||||||
- manifest contract test
|
|
||||||
|
|
||||||
No runtime API, database model, migration, WebUI route, or navigation item is registered yet. The first implementation slice should preserve the boundary above and only add user-visible surfaces once the workflow model is clear.
|
|
||||||
|
|
||||||
## First Implementation Slice
|
|
||||||
|
|
||||||
Define intake submission, triage status, category, location, evidence, and handoff contracts to helpdesk, cases, assets, or facilities.
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Tickets Domain Boundary
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Queue-oriented operational work: public or internal reports, service requests,
|
||||||
|
incidents, problems, triage, routing, assignment, service-level tracking, and
|
||||||
|
auditable resolution.
|
||||||
|
|
||||||
|
## Owns
|
||||||
|
|
||||||
|
- ticket identity, type, category, priority, state, and queue
|
||||||
|
- public and internal intake profiles
|
||||||
|
- reporter/requester and affected-object references
|
||||||
|
- triage, routing, assignment, escalation, and service-level facts
|
||||||
|
- discussion and resolution evidence references
|
||||||
|
- stable links to tasks, projects, assets, facilities, and cases
|
||||||
|
|
||||||
|
## Does Not Own
|
||||||
|
|
||||||
|
- formal administrative case identity, parties, evidence, decisions, and retention
|
||||||
|
- project plans, milestones, and portfolios
|
||||||
|
- asset lifecycle management
|
||||||
|
- workflow definitions and task execution
|
||||||
|
|
||||||
|
## Integration Candidates
|
||||||
|
|
||||||
|
- cases
|
||||||
|
- projects
|
||||||
|
- wiki
|
||||||
|
- assets
|
||||||
|
- facilities
|
||||||
|
- forms_runtime
|
||||||
|
- portal
|
||||||
|
- files
|
||||||
|
- workflow
|
||||||
|
- tasks
|
||||||
|
- mail
|
||||||
|
- notifications
|
||||||
|
- search
|
||||||
|
|
||||||
|
## Ticket Versus Case
|
||||||
|
|
||||||
|
A ticket is the operational record of work entering and moving through a queue.
|
||||||
|
It can be reported with incomplete information, assigned and reassigned,
|
||||||
|
discussed, resolved, reopened, or linked to another work object. Its lifecycle
|
||||||
|
answers: what needs attention, who owns the next action, and was it resolved?
|
||||||
|
|
||||||
|
A case is the authoritative procedural record for an administrative matter. It
|
||||||
|
has formal parties, evidence, decisions, procedural deadlines, access rules,
|
||||||
|
retention obligations, and a durable case history. Its lifecycle answers: what
|
||||||
|
matter is being administered, under which procedure, and what formal outcome
|
||||||
|
was reached?
|
||||||
|
|
||||||
|
Escalation does not convert or duplicate records. It creates an auditable
|
||||||
|
relation containing the ticket reference, case reference, relation type,
|
||||||
|
actor, timestamp, and optional handoff note. The ticket remains the intake and
|
||||||
|
service history; the case becomes authoritative for the formal procedure.
|
||||||
|
|
||||||
|
The former Issue Reporting and Helpdesk concepts become ticket type, intake,
|
||||||
|
queue, and policy profiles. They do not need separate persistence models.
|
||||||
|
|
||||||
|
## Seed State
|
||||||
|
|
||||||
|
The current repository state is intentionally small:
|
||||||
|
|
||||||
|
- module manifest and entry point
|
||||||
|
- tenant-level permission definitions
|
||||||
|
- manager and viewer role templates
|
||||||
|
- documentation topic describing the module boundary
|
||||||
|
- Gitea issue workflow templates
|
||||||
|
- manifest contract test
|
||||||
|
|
||||||
|
No runtime API, database model, migration, WebUI route, or navigation item is registered yet. The first implementation slice should preserve the boundary above and only add user-visible surfaces once the workflow model is clear.
|
||||||
|
|
||||||
|
## First Implementation Slice
|
||||||
|
|
||||||
|
Define ticket identity, intake profiles, queues, triage, assignment,
|
||||||
|
resolution, and stable escalation links to cases.
|
||||||
+143
-11
@@ -11,6 +11,12 @@
|
|||||||
"description": "New user-visible behavior or platform capability.",
|
"description": "New user-visible behavior or platform capability.",
|
||||||
"exclusive": true
|
"exclusive": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "type/user-story",
|
||||||
|
"color": "1d76db",
|
||||||
|
"description": "End-to-end user journey or real-world process story used to steer product slices.",
|
||||||
|
"exclusive": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "type/task",
|
"name": "type/task",
|
||||||
"color": "1d76db",
|
"color": "1d76db",
|
||||||
@@ -143,6 +149,24 @@
|
|||||||
"description": "GovOPlaN core runner, shared primitives, shell, or extension points.",
|
"description": "GovOPlaN core runner, shared primitives, shell, or extension points.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/dashboard",
|
||||||
|
"color": "1d76db",
|
||||||
|
"description": "GovOPlaN Dashboard module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/dataflow",
|
||||||
|
"color": "1d76db",
|
||||||
|
"description": "GovOPlaN Dataflow module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/datasources",
|
||||||
|
"color": "006b75",
|
||||||
|
"description": "GovOPlaN governed datasource contracts, catalogs, and integrations.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/dms",
|
"name": "module/dms",
|
||||||
"color": "c5def5",
|
"color": "c5def5",
|
||||||
@@ -151,8 +175,14 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "module/docs",
|
"name": "module/docs",
|
||||||
"color": "5319e7",
|
"color": "c5def5",
|
||||||
"description": "GovOPlaN documentation layer behavior or integration.",
|
"description": "GovOPlaN Docs module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/dist-lists",
|
||||||
|
"color": "0e8a16",
|
||||||
|
"description": "GovOPlaN Distribution Lists module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -161,6 +191,12 @@
|
|||||||
"description": "GovOPlaN Erp module behavior or integration.",
|
"description": "GovOPlaN Erp module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/evaluation",
|
||||||
|
"color": "bfdadc",
|
||||||
|
"description": "GovOPlaN Evaluation module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/files",
|
"name": "module/files",
|
||||||
"color": "006b75",
|
"color": "006b75",
|
||||||
@@ -185,6 +221,12 @@
|
|||||||
"description": "GovOPlaN Identity Trust module behavior or integration.",
|
"description": "GovOPlaN Identity Trust module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/identity",
|
||||||
|
"color": "bfd4f2",
|
||||||
|
"description": "GovOPlaN Identity module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/idm",
|
"name": "module/idm",
|
||||||
"color": "0052cc",
|
"color": "0052cc",
|
||||||
@@ -215,30 +257,66 @@
|
|||||||
"description": "GovOPlaN Ops module behavior or integration.",
|
"description": "GovOPlaN Ops module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/organizations",
|
||||||
|
"color": "bfdadc",
|
||||||
|
"description": "GovOPlaN Organizations module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/payments",
|
"name": "module/payments",
|
||||||
"color": "bfd4f2",
|
"color": "bfd4f2",
|
||||||
"description": "GovOPlaN Payments module behavior or integration.",
|
"description": "GovOPlaN Payments module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/permits",
|
||||||
|
"color": "fbca04",
|
||||||
|
"description": "GovOPlaN Permits module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/policy",
|
"name": "module/policy",
|
||||||
"color": "d93f0b",
|
"color": "d93f0b",
|
||||||
"description": "GovOPlaN Policy module behavior or integration.",
|
"description": "GovOPlaN Policy module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/poll",
|
||||||
|
"color": "e4e669",
|
||||||
|
"description": "GovOPlaN Poll module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/portal",
|
"name": "module/portal",
|
||||||
"color": "1d76db",
|
"color": "1d76db",
|
||||||
"description": "GovOPlaN Portal module behavior or integration.",
|
"description": "GovOPlaN Portal module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/postbox",
|
||||||
|
"color": "d93f0b",
|
||||||
|
"description": "GovOPlaN Postbox module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/projects",
|
||||||
|
"color": "5319e7",
|
||||||
|
"description": "GovOPlaN Projects module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/reporting",
|
"name": "module/reporting",
|
||||||
"color": "c2e0c6",
|
"color": "c2e0c6",
|
||||||
"description": "GovOPlaN Reporting module behavior or integration.",
|
"description": "GovOPlaN Reporting module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "module/risk-compliance",
|
||||||
|
"color": "b60205",
|
||||||
|
"description": "GovOPlaN Risk Compliance module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "module/search",
|
"name": "module/search",
|
||||||
"color": "bfdadc",
|
"color": "bfdadc",
|
||||||
@@ -270,9 +348,21 @@
|
|||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "module/web",
|
"name": "module/tickets",
|
||||||
"color": "bfd4f2",
|
"color": "0e8a16",
|
||||||
"description": "GovOPlaN public website, product page, or publication content.",
|
"description": "GovOPlaN Tickets module behavior or integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/views",
|
||||||
|
"color": "c5def5",
|
||||||
|
"description": "GovOPlaN governed task views, interface projections, and workflow view integration.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "module/wiki",
|
||||||
|
"color": "006b75",
|
||||||
|
"description": "GovOPlaN Wiki module behavior or integration.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -365,6 +455,12 @@
|
|||||||
"description": "Versioning, release locks, tags, packaging, or dependency pins.",
|
"description": "Versioning, release locks, tags, packaging, or dependency pins.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "area/security",
|
||||||
|
"color": "b60205",
|
||||||
|
"description": "Security posture, static analysis, supply-chain hardening, or vulnerability remediation.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "area/docs",
|
"name": "area/docs",
|
||||||
"color": "5319e7",
|
"color": "5319e7",
|
||||||
@@ -389,6 +485,48 @@
|
|||||||
"description": "Imported from markdown backlog, roadmap, plan, or TODO files.",
|
"description": "Imported from markdown backlog, roadmap, plan, or TODO files.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "source/security-audit",
|
||||||
|
"color": "d4c5f9",
|
||||||
|
"description": "Created from a structured security or code-quality audit report.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/quick-fix",
|
||||||
|
"color": "0e8a16",
|
||||||
|
"description": "Audit finding that appears narrow and directly fixable.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/structural",
|
||||||
|
"color": "d93f0b",
|
||||||
|
"description": "Audit finding that needs design, refactoring, or behavior review.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/complexity",
|
||||||
|
"color": "fbca04",
|
||||||
|
"description": "Complexity finding from Radon, Xenon, or equivalent maintainability scans.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/duplication",
|
||||||
|
"color": "c2e0c6",
|
||||||
|
"description": "Duplicated-code finding from jscpd or equivalent similarity scans.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/false-positive",
|
||||||
|
"color": "cccccc",
|
||||||
|
"description": "Audit finding reviewed as a narrow false positive or acceptable risk.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "audit/needs-design",
|
||||||
|
"color": "f9d0c4",
|
||||||
|
"description": "Audit finding that needs an architectural or product decision before implementation.",
|
||||||
|
"exclusive": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "codex/ready",
|
"name": "codex/ready",
|
||||||
"color": "0e8a16",
|
"color": "0e8a16",
|
||||||
@@ -400,11 +538,5 @@
|
|||||||
"color": "f9d0c4",
|
"color": "f9d0c4",
|
||||||
"description": "Needs an explicit human decision before Codex should implement.",
|
"description": "Needs an explicit human decision before Codex should implement.",
|
||||||
"exclusive": false
|
"exclusive": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "module/issue-reporting",
|
|
||||||
"color": "0e8a16",
|
|
||||||
"description": "GovOPlaN Issue Reporting module behavior or integration.",
|
|
||||||
"exclusive": false
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/issue-reporting",
|
"name": "@govoplan/tickets",
|
||||||
"version": "0.1.7",
|
"version": "0.1.19",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "GovOPlaN Issue Reporting platform module seed.",
|
"description": "GovOPlaN Tickets platform module seed.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"peerDependencies": {}
|
"peerDependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-7
@@ -3,23 +3,23 @@ requires = ["setuptools>=69", "wheel"]
|
|||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-issue-reporting"
|
name = "govoplan-tickets"
|
||||||
version = "0.1.7"
|
version = "0.1.19"
|
||||||
description = "GovOPlaN Issue Reporting platform module seed."
|
description = "GovOPlaN Tickets 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.18",
|
||||||
"govoplan-access>=0.1.7",
|
"govoplan-access>=0.1.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
where = ["src"]
|
where = ["src"]
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
govoplan_issue_reporting = ["py.typed"]
|
govoplan_tickets = ["py.typed"]
|
||||||
|
|
||||||
[project.entry-points."govoplan.modules"]
|
[project.entry-points."govoplan.modules"]
|
||||||
"issue-reporting" = "govoplan_issue_reporting.backend.manifest:get_manifest"
|
tickets = "govoplan_tickets.backend.manifest:get_manifest"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
"""GovOPlaN Issue Reporting module."""
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
"""Backend integration for the GovOPlaN Issue Reporting module."""
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
MODULE_ID = "issue-reporting"
|
|
||||||
MODULE_NAME = "Issue Reporting"
|
|
||||||
MODULE_VERSION = "0.1.7"
|
|
||||||
READ_SCOPE = "issue-reporting:workspace:read"
|
|
||||||
WRITE_SCOPE = "issue-reporting:workspace:write"
|
|
||||||
ADMIN_SCOPE = "issue-reporting:workspace:admin"
|
|
||||||
OPTIONAL_DEPENDENCIES = (
|
|
||||||
"helpdesk",
|
|
||||||
"cases",
|
|
||||||
"assets",
|
|
||||||
"facilities",
|
|
||||||
"forms-runtime",
|
|
||||||
"portal",
|
|
||||||
"files",
|
|
||||||
"workflow",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
|
||||||
module_id, resource, action = scope.split(":", 2)
|
|
||||||
return PermissionDefinition(
|
|
||||||
scope=scope,
|
|
||||||
label=label,
|
|
||||||
description=description,
|
|
||||||
category="Issue Reporting",
|
|
||||||
level="tenant",
|
|
||||||
module_id=module_id,
|
|
||||||
resource=resource,
|
|
||||||
action=action,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
PERMISSIONS = (
|
|
||||||
_permission(READ_SCOPE, "View issue reporting workspace", "Read issue reporting records, configuration, and workflow context."),
|
|
||||||
_permission(WRITE_SCOPE, "Manage issue reporting workspace", "Create and update issue reporting records and workflow state."),
|
|
||||||
_permission(ADMIN_SCOPE, "Administer issue reporting workspace", "Configure issue reporting policies, templates, and tenant-level administration."),
|
|
||||||
)
|
|
||||||
|
|
||||||
ROLE_TEMPLATES = (
|
|
||||||
RoleTemplate(
|
|
||||||
slug="issue_reporting_manager",
|
|
||||||
name="Issue Reporting manager",
|
|
||||||
description="Manage issue reporting records and workflow state.",
|
|
||||||
permissions=(READ_SCOPE, WRITE_SCOPE),
|
|
||||||
),
|
|
||||||
RoleTemplate(
|
|
||||||
slug="issue_reporting_viewer",
|
|
||||||
name="Issue Reporting viewer",
|
|
||||||
description="Read issue reporting records and workflow context.",
|
|
||||||
permissions=(READ_SCOPE,),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
DOCUMENTATION = (
|
|
||||||
DocumentationTopic(
|
|
||||||
id=f"{MODULE_ID}.module-boundary",
|
|
||||||
title=f"{MODULE_NAME} module boundary",
|
|
||||||
summary="Public or internal problem reporting for broken infrastructure, damaged rooms, IT outages, safety issues, accessibility issues, triage, and routing.",
|
|
||||||
body=(
|
|
||||||
"This repository is currently a platform module seed. It registers the domain boundary, "
|
|
||||||
"permission surface, role templates, and documentation metadata before runtime APIs, "
|
|
||||||
"database models, migrations, and WebUI routes are introduced."
|
|
||||||
),
|
|
||||||
layer="available",
|
|
||||||
documentation_types=("admin",),
|
|
||||||
audience=("operator", "module_admin", "product_owner"),
|
|
||||||
order=100,
|
|
||||||
related_modules=OPTIONAL_DEPENDENCIES,
|
|
||||||
links=(
|
|
||||||
DocumentationLink(
|
|
||||||
label="Repository domain boundary",
|
|
||||||
href="govoplan-issue-reporting/docs/ISSUE_REPORTING_DOMAIN_BOUNDARY.md",
|
|
||||||
kind="repository",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
metadata={
|
|
||||||
"seed": True,
|
|
||||||
"domain_objects": ['issue intake', 'problem category taxonomy', 'triage and routing facts', 'public reporter references', 'handoff targets'],
|
|
||||||
"first_slice": "Define intake submission, triage status, category, location, evidence, and handoff contracts to helpdesk, cases, assets, or facilities.",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
manifest = ModuleManifest(
|
|
||||||
id=MODULE_ID,
|
|
||||||
name=MODULE_NAME,
|
|
||||||
version=MODULE_VERSION,
|
|
||||||
dependencies=("access",),
|
|
||||||
optional_dependencies=OPTIONAL_DEPENDENCIES,
|
|
||||||
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
|
||||||
permissions=PERMISSIONS,
|
|
||||||
role_templates=ROLE_TEMPLATES,
|
|
||||||
documentation=DOCUMENTATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_manifest() -> ModuleManifest:
|
|
||||||
return manifest
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""GovOPlaN Tickets module."""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Backend integration for the GovOPlaN Tickets module."""
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
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 = "tickets"
|
||||||
|
MODULE_NAME = "Tickets"
|
||||||
|
MODULE_VERSION = "0.1.19"
|
||||||
|
READ_SCOPE = "tickets:ticket:read"
|
||||||
|
WRITE_SCOPE = "tickets:ticket:write"
|
||||||
|
ADMIN_SCOPE = "tickets:ticket:admin"
|
||||||
|
OPTIONAL_DEPENDENCIES = (
|
||||||
|
"cases",
|
||||||
|
"projects",
|
||||||
|
"wiki",
|
||||||
|
"assets",
|
||||||
|
"facilities",
|
||||||
|
"forms_runtime",
|
||||||
|
"portal",
|
||||||
|
"files",
|
||||||
|
"workflow_engine",
|
||||||
|
"tasks",
|
||||||
|
"mail",
|
||||||
|
"notifications",
|
||||||
|
"search",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _permission(scope: str, label: str, description: str) -> PermissionDefinition:
|
||||||
|
module_id, resource, action = scope.split(":", 2)
|
||||||
|
return PermissionDefinition(
|
||||||
|
scope=scope,
|
||||||
|
label=label,
|
||||||
|
description=description,
|
||||||
|
category="Tickets",
|
||||||
|
level="tenant",
|
||||||
|
module_id=module_id,
|
||||||
|
resource=resource,
|
||||||
|
action=action,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
PERMISSIONS = (
|
||||||
|
_permission(
|
||||||
|
READ_SCOPE,
|
||||||
|
"View tickets",
|
||||||
|
"Read discoverable tickets, queue state, and resolution context.",
|
||||||
|
),
|
||||||
|
_permission(
|
||||||
|
WRITE_SCOPE,
|
||||||
|
"Manage tickets",
|
||||||
|
"Create, triage, assign, update, resolve, and link tickets.",
|
||||||
|
),
|
||||||
|
_permission(
|
||||||
|
ADMIN_SCOPE,
|
||||||
|
"Administer tickets",
|
||||||
|
"Configure ticket types, queues, service policies, and intake profiles.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ROLE_TEMPLATES = (
|
||||||
|
RoleTemplate(
|
||||||
|
slug="tickets_manager",
|
||||||
|
name="Tickets manager",
|
||||||
|
description="Triage, assign, update, and resolve tickets.",
|
||||||
|
permissions=(READ_SCOPE, WRITE_SCOPE),
|
||||||
|
),
|
||||||
|
RoleTemplate(
|
||||||
|
slug="tickets_viewer",
|
||||||
|
name="Tickets viewer",
|
||||||
|
description="Read discoverable tickets and their resolution context.",
|
||||||
|
permissions=(READ_SCOPE,),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
DOCUMENTATION = (
|
||||||
|
DocumentationTopic(
|
||||||
|
id=f"{MODULE_ID}.module-boundary",
|
||||||
|
title=f"{MODULE_NAME} module boundary",
|
||||||
|
summary=(
|
||||||
|
"Queue-oriented reports, requests, incidents, problems, triage, "
|
||||||
|
"routing, service work, and auditable resolution."
|
||||||
|
),
|
||||||
|
body=(
|
||||||
|
"This repository is currently a platform module seed. It registers the domain boundary, "
|
||||||
|
"permission surface, role templates, and documentation metadata before runtime APIs, "
|
||||||
|
"database models, migrations, and WebUI routes are introduced."
|
||||||
|
),
|
||||||
|
layer="available",
|
||||||
|
documentation_types=("admin", "user"),
|
||||||
|
audience=("user", "operator", "module_admin", "product_owner"),
|
||||||
|
translations={
|
||||||
|
"de": {
|
||||||
|
"title": "Modulgrenze von Tickets",
|
||||||
|
"summary": "Warteschlangenorientierte Meldungen, Anfragen, Störungen, Probleme, Triage, Weiterleitung, Servicearbeit und nachvollziehbare Lösungen.",
|
||||||
|
"body": "Dieses Repository ist derzeit ein Grundgerüst für ein Plattformmodul. Es registriert die Fachgrenze, Berechtigungsoberfläche, Rollenvorlagen und Dokumentationsmetadaten, bevor Laufzeit-APIs, Datenbankmodelle, Migrationen und WebUI-Routen eingeführt werden.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order=100,
|
||||||
|
related_modules=OPTIONAL_DEPENDENCIES,
|
||||||
|
links=(
|
||||||
|
DocumentationLink(
|
||||||
|
label="Repository domain boundary",
|
||||||
|
href="govoplan-tickets/docs/TICKETS_DOMAIN_BOUNDARY.md",
|
||||||
|
kind="repository",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
metadata={
|
||||||
|
"kind": "reference",
|
||||||
|
"seed": True,
|
||||||
|
"consequence_classes": {
|
||||||
|
"seed_boundary": "Declares ownership and permissions only; no runtime workflow is available yet.",
|
||||||
|
},
|
||||||
|
"domain_objects": [
|
||||||
|
"ticket",
|
||||||
|
"ticket type and queue",
|
||||||
|
"triage and routing facts",
|
||||||
|
"reporter and requester references",
|
||||||
|
"assignment and service-level state",
|
||||||
|
"resolution and escalation links",
|
||||||
|
],
|
||||||
|
"first_slice": (
|
||||||
|
"Define ticket identity, intake profiles, queues, triage, "
|
||||||
|
"assignment, resolution, and stable escalation links to cases."
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
manifest = ModuleManifest(
|
||||||
|
id=MODULE_ID,
|
||||||
|
name=MODULE_NAME,
|
||||||
|
version=MODULE_VERSION,
|
||||||
|
dependencies=("access",),
|
||||||
|
optional_dependencies=OPTIONAL_DEPENDENCIES,
|
||||||
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
||||||
|
permissions=PERMISSIONS,
|
||||||
|
role_templates=ROLE_TEMPLATES,
|
||||||
|
documentation=DOCUMENTATION,
|
||||||
|
architecture=declared_module_architecture(
|
||||||
|
layer="human_work_procedure",
|
||||||
|
kind="domain",
|
||||||
|
maturity="scaffold",
|
||||||
|
documentation_ref="docs/TICKETS_DOMAIN_BOUNDARY.md",
|
||||||
|
known_limits=("Ticket persistence, queues, SLA, and external service-desk adapters are not implemented yet.",),
|
||||||
|
owned_concepts=("ticket", "ticket queue", "ticket transition"),
|
||||||
|
non_owned_concepts=("case", "project", "external service-desk record"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_manifest() -> ModuleManifest:
|
||||||
|
return manifest
|
||||||
+21
-4
@@ -2,19 +2,36 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from govoplan_issue_reporting.backend.manifest import ADMIN_SCOPE, READ_SCOPE, WRITE_SCOPE, get_manifest
|
from govoplan_tickets.backend.manifest import (
|
||||||
|
ADMIN_SCOPE,
|
||||||
|
READ_SCOPE,
|
||||||
|
WRITE_SCOPE,
|
||||||
|
get_manifest,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ManifestSeedTests(unittest.TestCase):
|
class ManifestSeedTests(unittest.TestCase):
|
||||||
def test_manifest_registers_seed_contract(self) -> None:
|
def test_manifest_registers_seed_contract(self) -> None:
|
||||||
manifest = get_manifest()
|
manifest = get_manifest()
|
||||||
|
|
||||||
self.assertEqual(manifest.id, "issue-reporting")
|
self.assertEqual(manifest.id, "tickets")
|
||||||
self.assertEqual(manifest.name, "Issue Reporting")
|
self.assertEqual(manifest.name, "Tickets")
|
||||||
self.assertEqual(manifest.dependencies, ("access",))
|
self.assertEqual(manifest.dependencies, ("access",))
|
||||||
self.assertEqual({permission.scope for permission in manifest.permissions}, {READ_SCOPE, WRITE_SCOPE, ADMIN_SCOPE})
|
self.assertEqual({permission.scope for permission in manifest.permissions}, {READ_SCOPE, WRITE_SCOPE, ADMIN_SCOPE})
|
||||||
self.assertEqual({role.slug for role in manifest.role_templates}, {"issue_reporting_manager", "issue_reporting_viewer"})
|
self.assertEqual(
|
||||||
|
{role.slug for role in manifest.role_templates},
|
||||||
|
{"tickets_manager", "tickets_viewer"},
|
||||||
|
)
|
||||||
self.assertTrue(manifest.documentation)
|
self.assertTrue(manifest.documentation)
|
||||||
|
topic = manifest.documentation[0]
|
||||||
|
self.assertEqual("reference", topic.metadata["kind"])
|
||||||
|
self.assertIn("seed_boundary", topic.metadata["consequence_classes"])
|
||||||
|
self.assertTrue(
|
||||||
|
all(
|
||||||
|
topic.translations.get("de", {}).get(field)
|
||||||
|
for field in ("title", "summary", "body")
|
||||||
|
)
|
||||||
|
)
|
||||||
self.assertIsNone(manifest.route_factory)
|
self.assertIsNone(manifest.route_factory)
|
||||||
self.assertIsNone(manifest.migration_spec)
|
self.assertIsNone(manifest.migration_spec)
|
||||||
self.assertIsNone(manifest.frontend)
|
self.assertIsNone(manifest.frontend)
|
||||||
|
|||||||
Reference in New Issue
Block a user