Add project plan, milestones and SQA framework #25

Merged
Tirsvad merged 2 commits from docs/project-plan-and-milestones into main 2026-10-03 14:31:19 +02:00
17 changed files with 989 additions and 328 deletions
Showing only changes of commit 4de5438a88 - Show all commits
+7
View File
@@ -0,0 +1,7 @@
# Copy to .env and fill in real values. .env is gitignored - never commit it.
# Gitea personal access token (Settings -> Applications -> Generate New Token),
# scoped to issues only if your Gitea version supports scoped tokens.
# Used by framework/scripts/sync-project.sh --apply against a Gitea remote.
# NOT a deploy key - deploy keys authenticate git-over-SSH only, not the API.
GITEA_TOKEN=
@@ -103,7 +103,8 @@ jobs:
headers=gitea_headers,
)
if not isinstance(mirrors, list):
raise RuntimeError("Gitea returned an invalid push mirror list.")
print("Gitea returned an invalid push mirror list.")
exit 0
github_targets = []
for mirror in mirrors:
-161
View File
@@ -1,161 +0,0 @@
name: Sync GitHub mirror metadata
on:
push:
branches: [ main ]
workflow_dispatch:
schedule:
- cron: "17 3 * * *"
jobs:
sync-metadata:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- name: Sync description and topics
env:
GITEA_API_URL: ${{ gitea.api_url }}
GITEA_CREDENTIALS: ${{ secrets.TOKEN_FOR_GITEA }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
GITHUB_CREDENTIALS: ${{ secrets.CREDENTIALS_FOR_GITHUB }}
run: |
python3 - <<'PY'
import json
import os
import urllib.error
import urllib.parse
import urllib.request
def request_json(url, method="GET", headers=None, body=None):
request = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8") if body is not None else None,
headers=headers or {},
method=method,
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
content = response.read()
return json.loads(content) if content else None
except urllib.error.HTTPError as error:
raise RuntimeError(
f"API request failed with HTTP {error.code} ({error.reason})"
) from None
raw_credentials = os.environ.get("GITHUB_CREDENTIALS", "").strip()
if not raw_credentials:
raise RuntimeError("CREDENTIALS_FOR_GITHUB is missing or empty.")
try:
credentials = json.loads(raw_credentials)
except json.JSONDecodeError:
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must contain valid JSON."
) from None
if not isinstance(credentials, dict):
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must be a JSON object."
)
github_token = credentials.get("GITHUB_PAT")
if not isinstance(github_token, str) or not github_token.strip():
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must contain a non-empty GITHUB_PAT."
)
raw_gitea_credentials = os.environ.get("GITEA_CREDENTIALS", "").strip()
if not raw_gitea_credentials:
raise RuntimeError("TOKEN_FOR_GITEA is missing or empty.")
try:
gitea_credentials = json.loads(raw_gitea_credentials)
except json.JSONDecodeError:
gitea_token = raw_gitea_credentials
else:
if isinstance(gitea_credentials, dict):
gitea_token = gitea_credentials.get("GITEA_TOKEN")
elif isinstance(gitea_credentials, str):
gitea_token = gitea_credentials
else:
gitea_token = None
if not isinstance(gitea_token, str) or not gitea_token.strip():
raise RuntimeError(
"TOKEN_FOR_GITEA must contain a non-empty GITEA_TOKEN."
)
source_owner, separator, source_repo = os.environ[
"SOURCE_REPOSITORY"
].partition("/")
if not separator or not source_owner or not source_repo:
raise RuntimeError("Could not determine the Gitea source repository.")
gitea_api_url = os.environ["GITEA_API_URL"].rstrip("/")
source_url = f"{gitea_api_url}/repos/{source_owner}/{source_repo}"
gitea_headers = {
"Authorization": f"token {gitea_token.strip()}",
"Accept": "application/json",
}
source = request_json(source_url, headers=gitea_headers)
mirrors = request_json(
f"{source_url}/push_mirrors",
headers=gitea_headers,
)
if not isinstance(mirrors, list):
raise RuntimeError("Gitea returned an invalid push mirror list.")
github_targets = []
for mirror in mirrors:
remote_address = mirror.get("remote_address", "")
if remote_address.startswith("git@github.com:"):
mirror_path = remote_address.split(":", 1)[1]
else:
parsed_remote = urllib.parse.urlsplit(remote_address)
if parsed_remote.hostname != "github.com":
continue
mirror_path = parsed_remote.path.lstrip("/")
mirror_path = mirror_path.removesuffix(".git").strip("/")
path_parts = mirror_path.split("/")
if len(path_parts) != 2 or not all(path_parts):
raise RuntimeError(
"Could not determine the GitHub owner and repository "
"from a configured push mirror."
)
github_targets.append(tuple(path_parts))
if len(github_targets) != 1:
raise RuntimeError(
"Expected exactly one GitHub push mirror for this repository; "
f"found {len(github_targets)}."
)
github_owner, github_repo = github_targets[0]
github_api_url = (
"https://api.github.com/repos/"
f"{urllib.parse.quote(github_owner, safe='')}/"
f"{urllib.parse.quote(github_repo, safe='')}"
)
github_headers = {
"Authorization": f"Bearer {github_token.strip()}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
}
request_json(
github_api_url,
method="PATCH",
headers=github_headers,
body={"description": source.get("description") or ""},
)
request_json(
f"{github_api_url}/topics",
method="PUT",
headers=github_headers,
body={"names": source.get("topics") or []},
)
print(f"Synced description and topics to {github_owner}/{github_repo}.")
PY
+11
View File
@@ -0,0 +1,11 @@
# Local secrets - never commit these
.env
.env.*
!.env.example
# Local, per-clone files generated/copied from framework/ - never commit
# these (see framework/README.md "Using it in a project"):
# .agents/skills/ <- install-skills.sh's copy of framework/.agents/skills/
# .claude/skills/ <- same copy, for the standalone Claude Code CLI
/.agents/
/.claude/
+3
View File
@@ -0,0 +1,3 @@
[submodule "framework"]
path = framework
url = ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git
+53
View File
@@ -0,0 +1,53 @@
# AGENTS.md
This project uses the SQA and QC framework mounted at `framework/`.
For any document under `docs/` (create, edit or review) use the `artifact`
skill. For planning a project or a phase into tasks, and syncing phases and
tasks to Gitea/GitHub as Milestones and Issues, use the `project-planning`
skill. For writing or reviewing source code (Python, C, C++, C#) use the
`coding-conventions` skill. Skills are read from `.agents/skills/` (this harness and Codex CLI)
and `.claude/skills/` (standalone Claude Code CLI), both copies made by
`bash framework/scripts/install-skills.sh` — re-run it after updating the
framework.
**Never commit, push or open a PR unless asked.** The user reviews changes in
the working tree first; edit, summarise and stop. The commit/PR rules below
apply once a commit has been asked for.
## Workflow order
Business Case, Stakeholder Analysis, Project Plan, milestones, tasks synced as
issues, then code. Nothing goes under `src/` or `tests/` unless a milestone
document (`MIL-*`) is accepted and the task is a row in it (ideally a synced
issue). If those are missing, plan with the `project-planning` skill, show the
dry-run output of `bash framework/scripts/sync-project.sh`, and stop. The rule
is defined once in `framework/process/plan-first-gate.md`.
A "build X" request is planning-first: produce the plan and issues, then ask
for a go-ahead. Only the user can waive the plan, in chat, for that request.
Before planning, find the Product Owner's language (the prompt, or the
`Languages` section of `docs/artifact-registry.md`); if neither states it, ask.
To enforce the gate at commit time, run
`bash framework/scripts/install-git-hooks.sh --enable-plan-gate`: a commit that
changes `src/` or `tests/` then needs a `Task: MIL-NNN#N` trailer.
Rules that apply to every document:
1. Get the short name from `framework/registry/artifact-catalog.md` and the
next version from `docs/artifact-registry.md`. Create files with
`bash framework/scripts/new-artifact.sh <SHORT>`.
2. Owners, reviewers and RACI use stakeholder IDs from the project's
Stakeholder Analysis, never invented role names.
3. Every QC criterion is tagged with an ISO/IEC 25010:2023 characteristic.
4. Every reviewed instance gets an `RC-*` record in `docs/sqa/reviews/`.
5. Do not edit `framework/` from this project; propose changes upstream.
6. Every PR description closes the issues its work completes, one
`Closes #N` per line (`Refs #N` for partial work); see the
`project-planning` skill.
7. Every document's `## Version History` has `Change` and `Commit` columns and
keeps the two latest rows. After committing, run
`framework/scripts/resolve-pending-commits.sh` and commit the result before
opening the PR (no amend). Never ask for or perform the merge: a reviewer
merges.
+81 -5
View File
@@ -1,6 +1,6 @@
# 🚀 TirSystem GitHub Actions sync github metadata
# 🚀 TirSystem GitHub Actions
Global, reusable GitHub Actions and workflows for TirSystem projects.
A collection of reusable Gitea Actions workflows for DevOps professionals and software engineers working on TirSystem projects.
## 📚 Table of Contents
@@ -14,9 +14,9 @@ Global, reusable GitHub Actions and workflows for TirSystem projects.
## 🧭 Overview
This repository is the central collection of global GitHub Actions used across TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
It currently contains the Gitea workflow [`sync-github-metadata.yml`](.gitea/workflows/sync-github-metadata.yml), which copies the repository description and topics from Gitea to the GitHub mirror on every push to `main`, on manual dispatch, and daily at 03:17 UTC.
It currently contains the Gitea workflow [`sync-github-metadata.yml`](.gitea/scoped_workflows/sync-github-metadata.yml), which copies the repository description and topics from Gitea to the GitHub mirror on every push to `main`, on manual dispatch, and daily at 03:17 UTC.
## 📋 Requirements
@@ -43,7 +43,83 @@ Workflows run automatically on push to `main` and on the daily schedule. To run
## 🧪 Tests
There are no automated tests. Verify changes by running the workflow manually and checking the description and topics on the GitHub mirror.
There are no automated tests yet (planned in MIL-001). `tests/test_.ps1` is a manual probe, not a test. Verify changes by running the workflow manually and checking the description and topics on the GitHub mirror.
## 🟢 Capabilities
Only the first row is implemented. The rest is planned and tracked in the [Project Plan](docs/project-plan.md), which also describes each use case.
| Capability | Status |
| --- | --- |
| Synchronize repository metadata (Gitea to GitHub mirror) | Implemented in `.gitea/scoped_workflows/`; the script there currently has a syntax error (`exit 0`) that MIL-001 fixes |
| Configure and validate workflow credentials | Partial: secrets are checked for format only |
| Onboard a repository, manage runners, diagnose failures | Planned (MIL-002) |
| Shared quality checks | Planned (MIL-003) |
| Versioned release and publishing | Planned (MIL-004) |
### 🔄 Sync GitHub metadata
Syncs the Gitea repository description and topics to the GitHub push mirror.
#### 🕒 Cron
Daily at 03:17 UTC (`17 3 * * *`), plus every push to `main` and manual `workflow_dispatch`.
#### 🔀 Workflow
```plantuml
@startuml
title Sync Gitea repository description and topics to GitHub
autonumber
actor "Push to main,\nmanual trigger, or schedule" as Trigger
participant "Gitea Actions\nsync-github-metadata" as Workflow
participant "Gitea REST API" as Gitea
participant "GitHub REST API" as GitHub
Trigger -> Workflow: Start workflow
activate Workflow
Workflow -> Workflow: Read and validate secrets
note right
TOKEN_FOR_GITEA
CREDENTIALS_FOR_GITHUB
end note
alt secret validation failed
Workflow --> Trigger: workflow failed
end
Workflow -> Gitea: GET /repos/{owner}/{repo}
Gitea --> Workflow: Description and topics
Workflow -> Gitea: GET /repos/{owner}/{repo}/push_mirrors
Gitea --> Workflow: Push mirror response
alt mirrors is not a list
Workflow --> Trigger: Workflow fails\n"Gitea returned an invalid push mirror list."
else mirrors is a list
Workflow -> Workflow: Find GitHub owner and repo from remote_address
Workflow -> GitHub: PATCH /repos/{owner}/{repo}\n{description}
GitHub --> Workflow: Repository updated
Workflow -> GitHub: PUT /repos/{owner}/{repo}/topics\n{names}
GitHub --> Workflow: Topics updated
Workflow --> Trigger: Sync completed
else Gitea API denies access
Gitea --> Workflow: HTTP 403 Forbidden
Workflow --> Trigger: Workflow fails
else GitHub API denies access
GitHub --> Workflow: HTTP error
Workflow --> Trigger: Workflow fails
end
deactivate Workflow
@enduml
```
## 📄 License
+38
View File
@@ -0,0 +1,38 @@
# Artifact Registry
This project's artifact state. Types, short names and `CrossReference
Candidates` come from the framework catalog
(`framework/registry/artifact-catalog.md`); this file only records where
each document lives in *this* project and the next version to use.
| Short Name | Artifact Type | Primary File | Next Available Version |
| --- | --- | --- | --- |
| BC | Business Case | docs/business-case.md | 002 |
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 001 |
## Languages
| Setting | Value |
| --- | --- |
| PO language | en (English, IT) |
| High-level register | IT Executive English |
| Technical register | IT Professional English |
The PO language is English, so no PO-language translations are kept.
| Artifact types | Register | Also kept as a PO-language file |
| --- | --- | --- |
| BC, KPI, PP, MIL | IT Executive English | No (PO language is English) |
| SA, BMC, BPMN, UCD, US, UC, SSD, DM, RA, GOV, DICT | IT Professional English | No (PO language is English) |
| OC, SD, DCD, ERD, ADR, TM, RC, QC, source code | IT Professional English | No |
## Notes
- "Next Available Version" is the zero-padded (3-digit) version to use the
*next* time a new document of that type is created. Increment it only when
a brand-new document is created, not when an existing document's
`## Version History` gets a row.
- `ADR` uses 4 digits (`0001`); `RC` is sequential across all artifact types.
+150
View File
@@ -0,0 +1,150 @@
# Business Case: TirSystem Reusable Workflows
## Metadata
| Key | Value |
| --- | --- |
| ID | BC-001 |
| CrossReference | [SA-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Rejected | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Accepted | Jens Tirsvad Nielsen | S01 | Added S05 to assumptions, risks and stakeholders | pending |
---
## Executive Summary
TirSystem repositories are hosted on a self-hosted Gitea instance and
push-mirrored to GitHub. Automation that every repository needs is currently
copied by hand. This project turns this repository into the single, reviewed
collection of reusable Gitea Actions workflows for DevOps professionals and
software engineers. Today it contains one workflow, which copies repository
description and topics from Gitea to the GitHub mirror. The proposal is to
harden that workflow first, document how repositories adopt it, and then add
shared quality-check and release workflows in priority order.
## Methodological and Standards Foundation
Documents follow the SQA and QC framework in `framework/` (use cases after
Larman, *Applying UML and Patterns*). Quality criteria are tagged with
ISO/IEC 25010:2023 characteristics. The domain language is English (IT).
## Problem Statement
- The only workflow exists as three diverging copies (`src/`,
`.gitea/scoped_workflows/`, and others), and the scoped copy
currently contains a Python syntax error (`exit 0`).
- There is no automated check of workflow files, so defects such as that one
are found only when the workflow runs.
- There is no documented way to onboard a repository, set up credentials,
choose runners or diagnose a failed run.
- Quality checks and release steps are not shared, so each project would
reinvent them.
## Business Opportunity
A small, versioned, documented workflow collection lets every TirSystem
repository adopt the same secure automation in minutes, and lets a fix be
made once.
## Objectives
- **O1:** Keep the existing metadata sync correct, tested and documented as
the single implemented capability.
- **O2:** Provide one canonical copy of each workflow with a defined way to
reach consuming repositories (scoped workflow or copy).
- **O3:** Document and validate credentials, runner labels and failure
diagnosis for every workflow.
- **O4:** Offer shared quality-check and release workflows, versioned so
consumers can adopt changes deliberately.
## Scope
### In Scope
- Gitea Actions workflows (and GitHub-compatible syntax where it is the same).
- Documentation for DevOps professionals and software engineers.
- Validation of workflow files and their documentation.
### Out of Scope
- Operating the Gitea instance or runners themselves.
- Application code of consuming repositories.
- Features not backed by an accepted milestone task.
- Behavior that is only proposed: documentation describes implemented
workflows only; planned ones are marked as planned.
## Expected Benefits
### Tangible Benefits
- One fix reaches all consuming repositories.
- Fewer failed runs caused by missing or over-broad credentials.
- Less time to onboard a repository.
### Intangible Benefits
- Consistent, reviewable automation across TirSystem projects.
- Higher trust in automation that touches credentials.
## Strategic Alignment
Supports TirSystem's practice of a self-hosted primary repository with
public GitHub mirrors, and its SQA framework requirement that code follows
accepted plans.
## Success Criteria
| # | Criterion | Target | Measure |
| --- | --- | --- | --- |
| 1 | Workflow files that fail static validation | 0 on the default branch | Result of the validation task in MIL-001 |
| 2 | Canonical copies per workflow | 1 | Count of files per workflow name |
| 3 | Documented use cases that are implemented or marked planned | 100% | README capability table against workflows present |
| 4 | Repositories consuming a shared workflow | At least 1 besides this one by MIL-002 | Consumer list in README |
## Risks
| Risk | Impact | Mitigation |
| --- | --- | --- |
| Gitea scoped workflows behave differently from assumption (feature and version not verified) | Onboarding design is wrong | Verify against the actual instance in MIL-002 before documenting it as supported. |
| Token with excess scope is stored in secrets | Compromise of repositories | Document minimum scope; validate token permissions before use. |
| A breaking change reaches consumers unannounced | Consumer pipelines fail | Version and changelog every release (MIL-004). |
| Security-relevant change accepted without security review | Credential or permission defect reaches production | S05 reviews every change to credentials, tokens, permissions and workflow code. |
## Assumptions
- The Gitea instance has Actions enabled and an `ubuntu-latest` runner with `python3`.
- Each mirrored repository has exactly one GitHub push mirror.
- S01 (Product Owner) and S05 (DevOps owner and security authority) are both maintainers.
## Constraints
- Workflows use only tools available on the runner; the current workflow uses the Python standard library only.
- Secrets are supplied as repository or organization secrets, never committed.
- Nothing is written under `src/` or `tests/` before the matching milestone is accepted.
## Cost–Benefit Assessment
| Costs | Benefits |
| --- | --- |
| Maintainer time for planning, documentation and tests (qualitative; no external cost) | Avoided duplication and fewer credential and configuration failures across repositories |
## Stakeholders
| Stakeholder ID (SA) | Interest in this project |
| --- | --- |
| S01 | Owns scope, accepts the documents and co-maintains the workflows |
| S02 | Consumes shared workflows |
| S03 | Operates runners and secrets |
| S05 | Co-maintains the workflows and reviews their security |
| S04 | Sees mirror metadata |
## Recommendation
Proceed — the first milestone fixes a known defect in the only existing workflow and the later ones add value only after it is accepted.
---
[SA-001]: ./stakeholder-analysis.md
@@ -0,0 +1,75 @@
# MIL-001: Stabilise the metadata sync
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-001 |
| CrossReference | [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Rejected | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Accepted | Jens Tirsvad Nielsen | S01 | Owner is S05 (security authority); S01 remains approving reviewer | pending |
---
## Purpose
Decide whether the only implemented workflow is correct, tested and has one
canonical copy, so later milestones build on a sound base.
## Deliverable
A single canonical metadata-sync workflow that passes automated validation
and tests, a README that separates implemented from planned behavior, and a
successful manual run on the Gitea instance.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The embedded Python compiles and every workflow YAML parses in an automated check | Check passes | Any failure |
| 2 | Exactly one canonical source per workflow; other copies are generated or removed | Count is 1 | Count above 1 |
| 3 | Unit tests cover credential parsing and push mirror URL parsing, and run without network access or `.env` | Tests pass | Missing or failing |
| 4 | README capability table matches the workflows present | Matches | Claims unimplemented behavior |
| 5 | Manual `workflow_dispatch` run on Gitea succeeds and the GitHub description and topics match | Match | Mismatch or failure |
## Dependencies
| Depends on | Reason |
| --- | --- |
| None | First milestone |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O1, O2 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-10-17 — proposed; to be confirmed by S01 and S05 (the Business Case sets no deadline).
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Decide behavior for an invalid push mirror response | The committed workflow fails with an error when Gitea returns a non-list push mirror response. The uncommitted scoped copy instead prints a message and tries to exit successfully, and the README diagram showed that. Choose fail or skip, record the decision, and align code and README. | No | |
| 2 | Fix the Python syntax error in the scoped workflow | `exit 0` is not valid Python, so the whole embedded script fails to compile in the scoped copy. Replace it according to the decision in task 1 (`sys.exit(0)` also needs `import sys`). | No | |
| 3 | Reduce the workflow to one canonical source | The same workflow exists in `src/` and `.gitea/scoped_workflows/` (and a legacy copy that is no longer documented), and the scoped copy has diverged. Choose the canonical location, and generate or remove the others. | No | |
| 4 | Add static validation of workflow files | Parse each workflow YAML and compile its embedded Python in an automated check that runs on pull requests, so defects such as task 2 fail before merge. | No | |
| 5 | Add offline unit tests for the sync logic | Move credential and mirror URL parsing into testable code and test bare token, JSON token, SSH and HTTPS mirror addresses, and zero or several mirrors. Replace `tests/test_.ps1`, which probes a hard-coded real repository and reads `.env`. | No | |
| 6 | Keep the README accurate | Maintain the capability table (implemented, partial, planned), and align the sequence diagram with the final behavior from task 1. | No | |
| 7 | Verify a live run | Trigger the workflow manually on Gitea and compare the GitHub description and topics with the Gitea repository. | No | |
---
[BC-001]: ../business-case.md
@@ -0,0 +1,75 @@
# MIL-002: Onboarding and operations guide
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-002 |
| CrossReference | [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Owner is S05 (security authority); S01 remains approving reviewer | pending |
---
## Purpose
Decide whether a DevOps professional can onboard a repository, set up
credentials, choose runners and diagnose a failed run from the documentation
alone.
## Deliverable
An onboarding and operations guide verified against the real Gitea instance,
a credential validation step in the workflow, and categorized failure messages.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | Support for scoped workflows on the instance is verified and the Gitea version is recorded | Recorded | Unverified |
| 2 | A second repository is onboarded by following only the guide | Run succeeds | Needs undocumented steps |
| 3 | Minimum token permissions are documented for both secrets and checked by the workflow before use | Documented and checked | Missing |
| 4 | Every failure message names its category: configuration, credentials, permissions, API response or runner | Verified for all messages | Uncategorized message exists |
| 5 | Supported runner labels and prerequisites are listed | Listed | Missing |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-001 | The guide describes the stabilised workflow |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O2, O3 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-10-31 — proposed; to be confirmed by S01 and S05.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Write brief use cases for the P1 candidates | Turn the DevOps use cases in the Project Plan appendix into use case documents under `docs/uc-NNN/`, after a use case diagram and user stories exist. | No | |
| 2 | Verify scoped workflow support on the instance | The Business Case lists this as an unverified assumption. Check the Gitea version and how a scoped workflow reaches a repository, then record the result. | No | |
| 3 | Write the repository onboarding steps | Describe how to consume the workflow (scoped or copied), which secrets to set and how to test the first run. | No | |
| 4 | Document least-privilege tokens | Determine the minimum Gitea token scope and GitHub fine-grained token permission that the sync needs, and document them. | No | |
| 5 | Add a credential preflight step | Before the sync, check that both tokens authenticate and have the needed access, and report the result without printing any secret. | No | |
| 6 | Categorize failure messages | Group the error messages and HTTP errors into configuration, credentials, permissions, API response and runner, and write the diagnosis table. | No | |
| 7 | Document runner labels and prerequisites | State the supported labels (`ubuntu-latest`), the required `python3`, and how to recognize an offline or incompatible runner. | No | |
---
[BC-001]: ../business-case.md
+71
View File
@@ -0,0 +1,71 @@
# MIL-003: Shared quality checks
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-003 |
| CrossReference | [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Owner is S05 (security authority); S01 remains approving reviewer | pending |
---
## Purpose
Decide whether a software engineer can run the same quality checks on any
TirSystem project through a shared workflow.
## Deliverable
A reusable quality-check workflow for Python and shell, used by at least one
other repository, and a guide for reading its results.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The instance supports reusable workflows (`workflow_call`), verified and recorded | Verified | Unsupported (re-plan as a copied template) |
| 2 | One other repository runs the workflow and its status check appears on pull requests | Appears | Does not run |
| 3 | A failing check shows the tool, file and message in the log | Verified with a seeded failure | Cause not visible |
| 4 | Inputs and defaults are documented | Documented | Missing |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-002 | Uses the onboarding guide and runner list |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O4 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-11-14 — proposed; to be confirmed by S01 and S05.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Verify reusable workflow support | Check whether this Gitea version supports `workflow_call` and cross-repository `uses`; if not, choose a copied template instead. | No | |
| 2 | Define the quality-check contract | Decide the inputs (language, tool versions, working directory) and what a pass and a fail mean. Start with Python and shell. This is something an engineer does, so it needs a use case first. | Yes | Use case candidate: run project quality checks |
| 3 | Implement the reusable quality workflow | Create the workflow in the canonical location with pinned tool versions and least-privilege permissions. | No | |
| 4 | Adopt it in one other repository | Onboard a real project and fix the gaps in the guide that appear. | No | |
| 5 | Write the guide for reading results | Explain status checks, logs and typical fixes so an engineer can make a failing check pass. | No | |
---
[BC-001]: ../business-case.md
@@ -0,0 +1,72 @@
# MIL-004: Versioned release and publishing
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-004 |
| CrossReference | [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Proposed | Jens Tirsvad Nielsen | S01 | Owner is S05 (security authority); S01 remains approving reviewer | pending |
---
## Purpose
Decide whether workflow updates can be released with a version and adopted
safely, and whether a project can publish an artifact through a shared
workflow.
## Deliverable
A documented release process for this repository (validation, version tag,
changelog), a consumer upgrade guide and a reusable release workflow.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | A release is validated by the MIL-001 checks before a tag is created | Enforced | Tag possible without checks |
| 2 | Each release has a version tag and a changelog entry that marks breaking changes | Present | Missing |
| 3 | A consumer pinned to the previous version keeps working after a release | Verified | Breaks |
| 4 | The publish destination (release page or package registry) is decided and its token scope documented | Decided | Open |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-003 | Reuses the reusable workflow mechanism |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O4 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-11-28 — proposed; to be confirmed by S01 and S05.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Define the release process for this repository | Describe how a workflow change is validated, tagged and announced, and how consumers pin a version. | Yes | Use case candidate: release a reusable workflow |
| 2 | Add a changelog and tag convention | Create `CHANGELOG.md` and a tag scheme (for example `v1` plus `v1.2.3`) that separates breaking from compatible changes. | No | |
| 3 | Write the consumer upgrade guide | Explain how to read a changelog entry, test an update in one repository and roll back. | Yes | Use case candidate: consume a workflow update |
| 4 | Decide the publish destination | Choose the release or package destination for built artifacts and document the token scope it needs. | No | |
| 5 | Implement the reusable release workflow | Validate the version, build release notes from the changelog and publish to the chosen destination. | Yes | Use case candidate: publish a release |
---
[BC-001]: ../business-case.md
+247
View File
@@ -0,0 +1,247 @@
# Project Plan: TirSystem Reusable Workflows
## Metadata
| Key | Value |
| --- | --- |
| ID | PP-001 |
| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Rejected | Jens Tirsvad Nielsen | S01 | Initial version<br>Added use case candidates and prioritisation appendix | pending |
| 2026-10-03 | Accepted | Jens Tirsvad Nielsen | S01 | Owner of every gateway is S05<br>Actors updated | pending |
---
## Purpose
This plan schedules four milestones that move the repository from one
workflow with known defects to a documented, versioned collection of shared
workflows. The Business Case sets no deadline, so the dates are proposals.
## Planning Assumptions
- Week 1 starts 2026-10-05; phases are two weeks long; the last decision is 2026-11-28.
- Work on a milestone starts only after its `MIL-*` document is `Accepted`.
- Dates are proposed by the author and need confirmation by S01 and S05.
## Gateway Schedule
| Gateway | Document | Window | Decision date | Owner | Stories | Main deliverable | Milestone |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Stabilise the metadata sync | [MIL-001] | 2026-10-05 to 2026-10-17 | 2026-10-17 | S05 | None yet | Single tested workflow, accurate README | [Milestone MIL-001] |
| Onboarding and operations guide | [MIL-002] | 2026-10-19 to 2026-10-31 | 2026-10-31 | S05 | None yet | Verified onboarding, credential and diagnosis guide | [Milestone MIL-002] |
| Shared quality checks | [MIL-003] | 2026-11-02 to 2026-11-14 | 2026-11-14 | S05 | None yet | Reusable quality-check workflow | [Milestone MIL-003] |
| Versioned release and publishing | [MIL-004] | 2026-11-16 to 2026-11-28 | 2026-11-28 | S05 | None yet | Release process and reusable release workflow | [Milestone MIL-004] |
```plantuml
@startgantt
Project starts 2026-10-05
[MIL-001 Stabilise] starts 2026-10-05 and ends 2026-10-17
[MIL-002 Onboarding] starts 2026-10-19 and ends 2026-10-31
[MIL-003 Quality checks] starts 2026-11-02 and ends 2026-11-14
[MIL-004 Release] starts 2026-11-16 and ends 2026-11-28
[MIL-001 Go/No-Go] happens 2026-10-17
[MIL-002 Go/No-Go] happens 2026-10-31
[MIL-003 Go/No-Go] happens 2026-11-14
[MIL-004 Go/No-Go] happens 2026-11-28
@endgantt
```
## Scope Coverage
| Business Case scope item | Gateway |
| --- | --- |
| Validation of workflow files and their documentation | [MIL-001] |
| Documentation for DevOps professionals | [MIL-002] |
| Gitea Actions workflows for software engineers (quality checks) | [MIL-003] |
| Gitea Actions workflows for software engineers (release and publishing) | [MIL-004] |
## Dependencies
```
MIL-001 → MIL-002 → MIL-003 → MIL-004
```
A No-Go moves every later window by the rework time of the failed milestone.
## Plan Risks
| Risk | Impact | Mitigation |
| --- | --- | --- |
| Reusable workflow support is missing on the instance | MIL-003 and MIL-004 change shape | Verify first in each milestone (MIL-002 task 2, MIL-003 task 1); fall back to copied templates. |
| Security review by S05 is a bottleneck | Milestone decisions slip | Keep milestones small and plan review time before each decision date. |
## Open Issues
- Confirm the proposed dates and the start week (S01 and S05).
- Decide the behavior for an invalid push mirror response (MIL-001 task 1).
- Decide the canonical location of the workflow source (MIL-001 task 3).
- No QC checklist exists for the Project Plan.
- Formal use cases need a use case diagram and user stories first; neither exists yet.
## Appendix: Use Case Candidates and Prioritisation
This appendix evaluates the candidate use cases against the repository as it
is today. It is analysis, not a use case document; formal use cases follow in
MIL-002 task 1. Priority: P1 do first, P2 next, P3 later.
**Recommended order:** (1) Synchronize repository metadata, because it is
the only implemented capability and has a defect, (2) Configure workflow
credentials, (3) Onboard a repository, (4) Diagnose a failed workflow, then
(5) Run project quality checks and (6) Release a reusable workflow. The first
four make the existing workflow dependable and adoptable; the others add new
workflows and only pay off once adoption works.
Current repository state used for the status column: one workflow
(`sync-github-metadata`), one manual probe script (`tests/test_.ps1`), no CI,
no tests, no release process.
| # | Use case | Actor | Priority | Status today |
| --- | --- | --- | --- | --- |
| 1 | Synchronize repository metadata | DevOps professional (S05) | P1 | Implemented in `.gitea/scoped_workflows/`; script has a syntax error |
| 2 | Configure workflow credentials | DevOps professional (S05, S03) | P1 | Partial: secrets are format-checked only |
| 3 | Onboard a repository to standard workflows | DevOps professional (S05) | P1 | Not documented; scoped workflow support unverified |
| 4 | Diagnose a failed workflow | DevOps professional (S05, S03) | P1 | Partial: errors are messages only, uncategorized |
| 5 | Run project quality checks | Software engineer (S02) | P2 | Planned |
| 6 | Release a reusable workflow | DevOps professional (S05) | P2 | Planned |
| 7 | Manage workflow runners | DevOps professional (S03, S05) | P2 | Partial: only `ubuntu-latest` with `python3` assumed |
| 8 | Consume a workflow update | Software engineer (S02) | P2 | Planned; needs versioning |
| 9 | Review workflow results | Software engineer (S02) | P3 | Gitea provides logs; guidance planned |
| 10 | Create a project workflow | Software engineer (S02) | P3 | Planned; needs MIL-003 |
| 11 | Build and publish an artifact | Software engineer (S02) | P3 | Planned; destination undecided |
| 12 | Publish a release | Software engineer (S02) | P3 | Planned; overlaps with use case 6 and 11 |
### UC candidate 1: Synchronize repository metadata (P1)
- **Actor:** DevOps professional. **Goal:** the GitHub mirror shows the Gitea description and topics.
- **Trigger:** push to `main`, manual dispatch, or daily at 03:17 UTC.
- **Preconditions:** both secrets set; exactly one GitHub push mirror configured; runner available.
- **Success:** description and topics on GitHub equal those on Gitea.
- **Main flow:** the workflow validates the secrets, reads description and topics from Gitea, finds the single GitHub mirror, updates the description, replaces the topics.
- **Alternatives and failures:** missing or invalid secret: fail with a message; zero or several GitHub mirrors: fail; non-list mirror response: behavior undecided (MIL-001 task 1); Gitea or GitHub API error: fail with the HTTP code.
- **Dependencies:** none.
### UC candidate 2: Configure workflow credentials (P1)
- **Actor:** DevOps professional. **Goal:** least-privilege tokens that a workflow accepts.
- **Trigger:** onboarding a repository or rotating a token.
- **Preconditions:** access to Gitea and GitHub settings.
- **Success:** a workflow run passes credential checks with the documented minimum permissions.
- **Main flow:** create the tokens, store `TOKEN_FOR_GITEA` and `CREDENTIALS_FOR_GITHUB`, run the workflow, read the validation result.
- **Alternatives and failures:** malformed JSON, empty value, expired token, missing permission: the check names the category without printing the secret.
- **Dependencies:** use case 1.
### UC candidate 3: Onboard a repository to standard workflows (P1)
- **Actor:** DevOps professional. **Goal:** a repository consumes the shared workflows.
- **Trigger:** a new or existing repository needs standard automation.
- **Preconditions:** Actions enabled; runner available; secrets possible to set.
- **Success:** the first run in the repository passes using only the guide.
- **Main flow:** choose scoped or copied delivery, set secrets, configure the push mirror, trigger a run.
- **Alternatives and failures:** scoped workflows unsupported: use a copied template; no runner: see use case 7.
- **Dependencies:** use cases 1 and 2; verification of scoped workflow support.
### UC candidate 4: Diagnose a failed workflow (P1)
- **Actor:** DevOps professional. **Goal:** find the cause of a failed run.
- **Trigger:** a failed or missing run.
- **Preconditions:** access to the run log.
- **Success:** the cause is placed in one category: configuration, credentials, permissions, API response or runner.
- **Main flow:** open the log, read the categorized message, look up the category in the guide, apply the fix, re-run.
- **Alternatives and failures:** no run starts: check runner and trigger; message uncategorized: raise as a defect.
- **Dependencies:** use cases 1 and 2.
### UC candidate 5: Run project quality checks (P2)
- **Actor:** software engineer. **Goal:** tests, lint, format and type checks run on every change.
- **Trigger:** pull request or push.
- **Preconditions:** shared workflow available; project onboarded.
- **Success:** a status check reports pass or fail.
- **Main flow:** the shared workflow detects inputs, runs the checks, reports the result.
- **Alternatives and failures:** a check fails: the log names tool, file and message; unsupported language: workflow fails with a clear message.
- **Dependencies:** use case 3; reusable workflow support.
### UC candidate 6: Release a reusable workflow (P2)
- **Actor:** DevOps professional. **Goal:** a validated, versioned workflow update that consumers can pin.
- **Trigger:** a workflow change is ready.
- **Preconditions:** validation checks exist; version scheme defined.
- **Success:** a tag and changelog entry exist and pinned consumers are unaffected.
- **Main flow:** run validation, update the changelog, tag, announce.
- **Alternatives and failures:** validation fails: no tag; breaking change: new major version.
- **Dependencies:** use case 1 (validation from MIL-001).
### UC candidate 7: Manage workflow runners (P2)
- **Actor:** DevOps professional (runner operator). **Goal:** workflows run on a supported runner.
- **Trigger:** a job waits, fails to start, or a new label is needed.
- **Preconditions:** access to the runner list.
- **Success:** a runner with the required label and tools is online.
- **Main flow:** compare the workflow's `runs-on` label with online runners, check prerequisites such as `python3`, fix or register a runner.
- **Alternatives and failures:** runner offline: restart or replace; label missing: add the label or change the workflow.
- **Dependencies:** use case 3.
### UC candidate 8: Consume a workflow update (P2)
- **Actor:** software engineer. **Goal:** adopt a new workflow version safely.
- **Trigger:** a new release or a changelog notice.
- **Preconditions:** releases are versioned and have a changelog.
- **Success:** the project uses the new version and its pipeline still passes.
- **Main flow:** read the changelog, change the pinned version in one branch, run, merge.
- **Alternatives and failures:** breaking change: follow upgrade notes; failure: roll back to the previous pin.
- **Dependencies:** use case 6.
### UC candidate 9: Review workflow results (P3)
- **Actor:** software engineer. **Goal:** understand a failed check and make it pass.
- **Trigger:** a failed status check on a pull request.
- **Preconditions:** a run exists.
- **Success:** the next run passes.
- **Main flow:** open the check, read the failing step, fix locally, push.
- **Alternatives and failures:** the cause is infrastructure, not code: hand over to use case 4.
- **Dependencies:** use case 5.
### UC candidate 10: Create a project workflow (P3)
- **Actor:** software engineer. **Goal:** compose shared workflows into a project pipeline.
- **Trigger:** a project needs build and delivery steps.
- **Preconditions:** shared workflows are documented with inputs.
- **Success:** the project workflow runs the chosen shared workflows in order.
- **Main flow:** pick workflows, set inputs, set triggers and permissions, test.
- **Alternatives and failures:** missing input: the workflow fails with the input name.
- **Dependencies:** use cases 5 and 8.
### UC candidate 11: Build and publish an artifact (P3)
- **Actor:** software engineer. **Goal:** a package or binary is built and published.
- **Trigger:** a tag or a manual run.
- **Preconditions:** destination decided; token with write access.
- **Success:** the artifact is available at the destination with a version.
- **Main flow:** build, test, package, publish, report the location.
- **Alternatives and failures:** publish denied: permission failure; version exists: fail without overwrite.
- **Dependencies:** use cases 5 and 6; destination decision (MIL-004 task 4).
### UC candidate 12: Publish a release (P3)
- **Actor:** software engineer. **Goal:** a release with validated version and notes.
- **Trigger:** a version tag.
- **Preconditions:** changelog entry exists.
- **Success:** a release page with notes from the changelog.
- **Main flow:** validate the version, build notes, create the release.
- **Alternatives and failures:** invalid version or missing notes: fail. This overlaps with use cases 6 and 11 and should be merged with one of them when the use cases are written.
- **Dependencies:** use cases 6 and 11.
---
[BC-001]: ./business-case.md
[SA-001]: ./stakeholder-analysis.md
[MIL-001]: ./milestones/mil-001-stabilise-metadata-sync.md
[MIL-002]: ./milestones/mil-002-onboarding-and-operations.md
[MIL-003]: ./milestones/mil-003-quality-checks.md
[MIL-004]: ./milestones/mil-004-versioned-release.md
[Milestone MIL-001]: https://git.tirsystem.com/TirSystem/github-action/milestone/18
[Milestone MIL-002]: https://git.tirsystem.com/TirSystem/github-action/milestone/19
[Milestone MIL-003]: https://git.tirsystem.com/TirSystem/github-action/milestone/20
[Milestone MIL-004]: https://git.tirsystem.com/TirSystem/github-action/milestone/21
+103
View File
@@ -0,0 +1,103 @@
# Stakeholder Analysis: TirSystem Reusable Workflows
## Metadata
| Key | Value |
| --- | --- |
| ID | SA-001 |
| CrossReference | [BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-03 | Rejected | Jens Tirsvad Nielsen | S01 | Initial version | pending |
| 2026-10-03 | Accepted | Jens Tirsvad Nielsen | S01 | Added S05 Michael Kragh (DevOps owner, maintainer, cyber security)<br>S01 stays Product Owner and maintainer | pending |
---
## Purpose
This analysis identifies who depends on, operates or is affected by the
TirSystem reusable workflow collection, so that scope, priorities and
communication follow real needs. It uses a power/interest grid and maps
each concern to a FURPS+ attribute.
## Stakeholder Summary Table
| ID | Name | Role/Title | Organization | Power Level | Interest Level | Quadrant | Primary Concern (Business Language) |
| --- | --- | --- | --- | --- | --- | --- | --- |
| S01 | Jens Tirsvad Nielsen | Product Owner and maintainer | TirSystem | HIGH | HIGH | Manage Closely | TirSystem repositories get the same automation without per-repository copy and paste, and scope follows business needs. |
| S02 | Software engineers on TirSystem projects | Consumers of shared workflows (group) | TirSystem | MEDIUM | HIGH | Keep Informed | Quality checks, builds and releases work out of the box, and failures say what to fix. |
| S03 | Gitea instance and runner operator | Platform operator (group; holders to be confirmed) | TirSystem | HIGH | MEDIUM | Keep Satisfied | Workflows run only on supported runners, use least-privilege tokens and never leak secrets. |
| S04 | GitHub mirror audience | Visitors of the GitHub push mirrors (group) | Public | LOW | LOW | Monitor | The mirror shows the same description and topics as the primary Gitea repository. |
| S05 | Michael Kragh | DevOps owner, maintainer and cyber security | TirSystem | HIGH | HIGH | Manage Closely | Shared automation is maintainable, and credentials, tokens and workflow permissions meet security requirements. |
## Power/Interest Classification Rationale
- **Manage Closely (S01):** decides scope and priorities, accepts documents and co-maintains the workflows.
- **Manage Closely (S05):** owns and co-maintains the workflows and is the security
authority; credential, token and permission design cannot be accepted
without this role.
- **Keep Informed (S02):** does not decide scope, but adoption by these
engineers is the success measure; a breaking workflow change hits them first.
- **Keep Satisfied (S03):** controls runners, secrets and Gitea features
(Actions, scoped workflows) that the workflows rely on; an incompatible
change can be blocked here.
- **Monitor (S04):** passive consumers of mirror metadata; no action needed
beyond keeping the metadata correct.
## Primary Concerns and FURPS+ Mapping
| ID | Concern | FURPS+ attribute |
| --- | --- | --- |
| S01 | One reviewed source of truth for shared automation | Supportability |
| S01 | Documentation never claims behavior that is not implemented | Functionality |
| S05 | One maintainable, reviewed source of truth for shared automation | Supportability |
| S05 | Least-privilege tokens, no secret in logs, pinned and reviewed workflow code | Functionality (security) |
| S02 | Clear failure messages that name the cause | Usability |
| S02 | Pinned, versioned workflows with a changelog | Supportability |
| S03 | Least-privilege tokens and no secret in logs | Functionality (security) |
| S03 | Declared runner labels and prerequisites | Supportability |
| S04 | Correct, current mirror description and topics | Functionality |
## Communication Requirements
| ID | Channel | Frequency | Deliverable | Phase / Milestone |
| --- | --- | --- | --- | --- |
| S01 | Pull request review and chat | Per milestone | Accepted documents, issue list | All milestones |
| S02 | README and changelog in the repository | Per workflow release | Usage guide, upgrade notes | MIL-002 onward |
| S05 | Pull request review and issue tracker | Per change to workflows, credentials or runners | Security-reviewed changes, token scope list | All milestones |
| S03 | Issue tracker | Per change to credentials or runners | Prerequisites and token scope list | MIL-001, MIL-002 |
| S04 | None (mirror metadata only) | Daily sync | Updated description and topics | MIL-001 |
## Conflicting Interests and Mitigations
| Conflict | Stakeholders | Mitigation |
| --- | --- | --- |
| Fast adoption of new workflow behavior versus stable consuming repositories | S01, S02 | Version every shared workflow and document breaking changes before release (MIL-004). |
| Convenient broad tokens versus least privilege | S02, S03 | Document the minimum token scope per workflow and validate it before use (MIL-002). |
| Delivery speed versus security review | S01, S05 | S05 reviews every change to credentials, tokens, permissions and workflow code before the milestone is accepted by S01. |
| Operator role holder not yet identified | S03, S05 | Confirm who operates runners and the Gitea instance; until then S05 is the contact. |
## Traceability Analysis
### Business Goal Alignment
| Stakeholder | Concern | Business Case objective |
| --- | --- | --- |
| S01 | One source of truth for shared automation | O1, O2 |
| S02 | Clear failures, versioned updates | O3, O4 |
| S03 | Least privilege, runner compatibility | O3 |
| S05 | Maintainable shared automation, security | O1, O2, O3 |
| S04 | Correct mirror metadata | O1 |
Objectives are defined in [BC-001]. Use-case mapping: S05 and S03 are the
primary actors of the DevOps use cases, S02 of the software engineer use
cases; both are listed in the Project Plan appendix.
## Sign-Off
Pending review by S01 and S05.
---
[BC-001]: ./business-case.md
Submodule
+1
Submodule framework added at 14d221ec1c
-161
View File
@@ -1,161 +0,0 @@
name: Sync GitHub mirror metadata
on:
push:
branches: [ main ]
workflow_dispatch:
schedule:
- cron: "17 3 * * *"
jobs:
sync-metadata:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- name: Sync description and topics
env:
GITEA_API_URL: ${{ gitea.api_url }}
GITEA_CREDENTIALS: ${{ secrets.TOKEN_FOR_GITEA }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
GITHUB_CREDENTIALS: ${{ secrets.CREDENTIALS_FOR_GITHUB }}
run: |
python3 - <<'PY'
import json
import os
import urllib.error
import urllib.parse
import urllib.request
def request_json(url, method="GET", headers=None, body=None):
request = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8") if body is not None else None,
headers=headers or {},
method=method,
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
content = response.read()
return json.loads(content) if content else None
except urllib.error.HTTPError as error:
raise RuntimeError(
f"API request failed with HTTP {error.code} ({error.reason})"
) from None
raw_credentials = os.environ.get("GITHUB_CREDENTIALS", "").strip()
if not raw_credentials:
raise RuntimeError("CREDENTIALS_FOR_GITHUB is missing or empty.")
try:
credentials = json.loads(raw_credentials)
except json.JSONDecodeError:
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must contain valid JSON."
) from None
if not isinstance(credentials, dict):
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must be a JSON object."
)
github_token = credentials.get("GITHUB_PAT")
if not isinstance(github_token, str) or not github_token.strip():
raise RuntimeError(
"CREDENTIALS_FOR_GITHUB must contain a non-empty GITHUB_PAT."
)
raw_gitea_credentials = os.environ.get("GITEA_CREDENTIALS", "").strip()
if not raw_gitea_credentials:
raise RuntimeError("TOKEN_FOR_GITEA is missing or empty.")
try:
gitea_credentials = json.loads(raw_gitea_credentials)
except json.JSONDecodeError:
gitea_token = raw_gitea_credentials
else:
if isinstance(gitea_credentials, dict):
gitea_token = gitea_credentials.get("GITEA_TOKEN")
elif isinstance(gitea_credentials, str):
gitea_token = gitea_credentials
else:
gitea_token = None
if not isinstance(gitea_token, str) or not gitea_token.strip():
raise RuntimeError(
"TOKEN_FOR_GITEA must contain a non-empty GITEA_TOKEN."
)
source_owner, separator, source_repo = os.environ[
"SOURCE_REPOSITORY"
].partition("/")
if not separator or not source_owner or not source_repo:
raise RuntimeError("Could not determine the Gitea source repository.")
gitea_api_url = os.environ["GITEA_API_URL"].rstrip("/")
source_url = f"{gitea_api_url}/repos/{source_owner}/{source_repo}"
gitea_headers = {
"Authorization": f"token {gitea_token.strip()}",
"Accept": "application/json",
}
source = request_json(source_url, headers=gitea_headers)
mirrors = request_json(
f"{source_url}/push_mirrors",
headers=gitea_headers,
)
if not isinstance(mirrors, list):
raise RuntimeError("Gitea returned an invalid push mirror list.")
github_targets = []
for mirror in mirrors:
remote_address = mirror.get("remote_address", "")
if remote_address.startswith("git@github.com:"):
mirror_path = remote_address.split(":", 1)[1]
else:
parsed_remote = urllib.parse.urlsplit(remote_address)
if parsed_remote.hostname != "github.com":
continue
mirror_path = parsed_remote.path.lstrip("/")
mirror_path = mirror_path.removesuffix(".git").strip("/")
path_parts = mirror_path.split("/")
if len(path_parts) != 2 or not all(path_parts):
raise RuntimeError(
"Could not determine the GitHub owner and repository "
"from a configured push mirror."
)
github_targets.append(tuple(path_parts))
if len(github_targets) != 1:
raise RuntimeError(
"Expected exactly one GitHub push mirror for this repository; "
f"found {len(github_targets)}."
)
github_owner, github_repo = github_targets[0]
github_api_url = (
"https://api.github.com/repos/"
f"{urllib.parse.quote(github_owner, safe='')}/"
f"{urllib.parse.quote(github_repo, safe='')}"
)
github_headers = {
"Authorization": f"Bearer {github_token.strip()}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"Content-Type": "application/json",
}
request_json(
github_api_url,
method="PATCH",
headers=github_headers,
body={"description": source.get("description") or ""},
)
request_json(
f"{github_api_url}/topics",
method="PUT",
headers=github_headers,
body={"names": source.get("topics") or []},
)
print(f"Synced description and topics to {github_owner}/{github_repo}.")
PY