Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff4380ffef | ||
|
|
5ba42d3d68 | ||
|
|
b8fceff3f4 | ||
|
|
40d6d91c4a | ||
|
|
d3731caf8d | ||
|
|
3f4a25111f | ||
|
|
3662c102c5 | ||
|
|
51f7353953 | ||
|
|
55332fa88c | ||
|
|
129cad410c | ||
|
|
a6db06630c |
@@ -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
|
||||
|
||||
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
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
## Local Workflow
|
||||
|
||||
@@ -1,43 +1,56 @@
|
||||
# GovOPlaN Issue Reporting
|
||||
# GovOPlaN Tickets
|
||||
|
||||
<!-- govoplan-repository-type:start -->
|
||||
**Repository type:** module (domain).
|
||||
<!-- govoplan-repository-type:end -->
|
||||
|
||||
`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-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.
|
||||
|
||||
## Initial Ownership
|
||||
|
||||
- issue intake
|
||||
- problem category taxonomy
|
||||
- triage and routing facts
|
||||
- public reporter references
|
||||
- handoff targets
|
||||
- ticket identity, type, priority, state, and queue
|
||||
- public and internal intake profiles
|
||||
- reporter/requester and affected-object references
|
||||
- triage, routing, assignment, and service-level facts
|
||||
- resolution evidence and escalation links
|
||||
|
||||
## 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
|
||||
- legal case handling
|
||||
- asset lifecycle management
|
||||
Cases are different: `govoplan-cases` owns the authoritative procedural record
|
||||
for an administrative matter, including parties, evidence, decisions,
|
||||
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
|
||||
|
||||
Expected optional integrations:
|
||||
|
||||
- helpdesk
|
||||
- cases
|
||||
- projects
|
||||
- wiki
|
||||
- assets
|
||||
- facilities
|
||||
- forms-runtime
|
||||
- forms_runtime
|
||||
- portal
|
||||
- files
|
||||
- workflow
|
||||
- tasks
|
||||
- mail
|
||||
- notifications
|
||||
- search
|
||||
|
||||
## Development Install
|
||||
|
||||
@@ -45,23 +58,24 @@ From the core checkout:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```bash
|
||||
cd /mnt/DATA/git/govoplan-core
|
||||
/mnt/DATA/git/govoplan/tools/gitea/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.",
|
||||
"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",
|
||||
"color": "1d76db",
|
||||
@@ -143,6 +149,24 @@
|
||||
"description": "GovOPlaN core runner, shared primitives, shell, or extension points.",
|
||||
"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",
|
||||
"color": "c5def5",
|
||||
@@ -151,8 +175,14 @@
|
||||
},
|
||||
{
|
||||
"name": "module/docs",
|
||||
"color": "5319e7",
|
||||
"description": "GovOPlaN documentation layer behavior or integration.",
|
||||
"color": "c5def5",
|
||||
"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
|
||||
},
|
||||
{
|
||||
@@ -161,6 +191,12 @@
|
||||
"description": "GovOPlaN Erp module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/evaluation",
|
||||
"color": "bfdadc",
|
||||
"description": "GovOPlaN Evaluation module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/files",
|
||||
"color": "006b75",
|
||||
@@ -185,6 +221,12 @@
|
||||
"description": "GovOPlaN Identity Trust module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/identity",
|
||||
"color": "bfd4f2",
|
||||
"description": "GovOPlaN Identity module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/idm",
|
||||
"color": "0052cc",
|
||||
@@ -215,30 +257,66 @@
|
||||
"description": "GovOPlaN Ops module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/organizations",
|
||||
"color": "bfdadc",
|
||||
"description": "GovOPlaN Organizations module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/payments",
|
||||
"color": "bfd4f2",
|
||||
"description": "GovOPlaN Payments module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/permits",
|
||||
"color": "fbca04",
|
||||
"description": "GovOPlaN Permits module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/policy",
|
||||
"color": "d93f0b",
|
||||
"description": "GovOPlaN Policy module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/poll",
|
||||
"color": "e4e669",
|
||||
"description": "GovOPlaN Poll module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/portal",
|
||||
"color": "1d76db",
|
||||
"description": "GovOPlaN Portal module behavior or integration.",
|
||||
"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",
|
||||
"color": "c2e0c6",
|
||||
"description": "GovOPlaN Reporting module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/risk-compliance",
|
||||
"color": "b60205",
|
||||
"description": "GovOPlaN Risk Compliance module behavior or integration.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/search",
|
||||
"color": "bfdadc",
|
||||
@@ -270,9 +348,21 @@
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "module/web",
|
||||
"color": "bfd4f2",
|
||||
"description": "GovOPlaN public website, product page, or publication content.",
|
||||
"name": "module/tickets",
|
||||
"color": "0e8a16",
|
||||
"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
|
||||
},
|
||||
{
|
||||
@@ -365,6 +455,12 @@
|
||||
"description": "Versioning, release locks, tags, packaging, or dependency pins.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "area/security",
|
||||
"color": "b60205",
|
||||
"description": "Security posture, static analysis, supply-chain hardening, or vulnerability remediation.",
|
||||
"exclusive": false
|
||||
},
|
||||
{
|
||||
"name": "area/docs",
|
||||
"color": "5319e7",
|
||||
@@ -389,6 +485,48 @@
|
||||
"description": "Imported from markdown backlog, roadmap, plan, or TODO files.",
|
||||
"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",
|
||||
"color": "0e8a16",
|
||||
@@ -400,11 +538,5 @@
|
||||
"color": "f9d0c4",
|
||||
"description": "Needs an explicit human decision before Codex should implement.",
|
||||
"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",
|
||||
"version": "0.1.8",
|
||||
"name": "@govoplan/tickets",
|
||||
"version": "0.1.18",
|
||||
"private": true,
|
||||
"description": "GovOPlaN Issue Reporting platform module seed.",
|
||||
"description": "GovOPlaN Tickets platform module seed.",
|
||||
"type": "module",
|
||||
"peerDependencies": {}
|
||||
}
|
||||
|
||||
+7
-7
@@ -3,23 +3,23 @@ requires = ["setuptools>=69", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "govoplan-issue-reporting"
|
||||
version = "0.1.8"
|
||||
description = "GovOPlaN Issue Reporting platform module seed."
|
||||
name = "govoplan-tickets"
|
||||
version = "0.1.18"
|
||||
description = "GovOPlaN Tickets 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.18",
|
||||
"govoplan-access>=0.1.18",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
govoplan_issue_reporting = ["py.typed"]
|
||||
govoplan_tickets = ["py.typed"]
|
||||
|
||||
[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.8"
|
||||
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,143 @@
|
||||
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.18"
|
||||
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"),
|
||||
order=100,
|
||||
related_modules=OPTIONAL_DEPENDENCIES,
|
||||
links=(
|
||||
DocumentationLink(
|
||||
label="Repository domain boundary",
|
||||
href="govoplan-tickets/docs/TICKETS_DOMAIN_BOUNDARY.md",
|
||||
kind="repository",
|
||||
),
|
||||
),
|
||||
metadata={
|
||||
"seed": True,
|
||||
"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
|
||||
+12
-4
@@ -2,18 +2,26 @@ from __future__ import annotations
|
||||
|
||||
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):
|
||||
def test_manifest_registers_seed_contract(self) -> None:
|
||||
manifest = get_manifest()
|
||||
|
||||
self.assertEqual(manifest.id, "issue-reporting")
|
||||
self.assertEqual(manifest.name, "Issue Reporting")
|
||||
self.assertEqual(manifest.id, "tickets")
|
||||
self.assertEqual(manifest.name, "Tickets")
|
||||
self.assertEqual(manifest.dependencies, ("access",))
|
||||
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.assertIsNone(manifest.route_factory)
|
||||
self.assertIsNone(manifest.migration_spec)
|
||||
|
||||
Reference in New Issue
Block a user