ops(redmine): roadmap build-out — versions, categories, fleet children, relations

Versions: V1 Core (closed), Beta 8/31, Production 9/1, Phase 3 9/7.
Categories for all majors; 490-499 versioned+categorized+estimated;
490 blocks 492/493; 15 integration-fleet children (500-514) under 498;
515 ITAR provider routing; 516 session/context quality.
This commit is contained in:
2026-08-29 05:26:50 -05:00
parent 6faefa3f36
commit d13559f9a2
3 changed files with 126 additions and 2 deletions
+124
View File
@@ -0,0 +1,124 @@
import os, json, urllib.request
URL = os.environ["REDMINE_URL"]
KEY = os.environ["REDMINE_API_KEY"]
def api(method, path, body=None):
data = json.dumps(body).encode() if body is not None else None
req = urllib.request.Request(URL + path, data=data,
headers={"X-Redmine-API-Key": KEY, "Content-Type": "application/json"},
method=method)
resp = urllib.request.urlopen(req, timeout=15)
raw = resp.read().decode()
return json.loads(raw) if raw.strip() else {}
# 1. Versions (roadmap milestones)
versions = {}
for name, due, status in [
("V1 Core", "2026-08-28", "closed"),
("Beta", "2026-08-31", "open"),
("Production", "2026-09-01", "open"),
("Phase 3 - Integrations", "2026-09-07", "open"),
]:
try:
v = api("POST", "/projects/MOPAC/versions.json", {"version": {"name": name, "due_date": due, "status": status, "sharing": "descendants"}})
versions[name] = v["version"]["id"]
print("version created:", name, v["version"]["id"])
except Exception as e:
print("version exists/err:", name, getattr(e, "code", e))
if not versions:
for v in api("GET", "/projects/MOPAC/versions.json")["versions"]:
versions[v["name"]] = v["id"]
print("versions:", versions)
# 2. Categories
cats = {}
for name in ["Quota & Backpressure", "Model Selection", "Task Management",
"Deployment", "Briefing", "Secrets", "Integrations",
"Infrastructure", "Documentation"]:
try:
c = api("POST", "/projects/MOPAC/issue_categories.json", {"issue_category": {"name": name}})
cats[name] = c["issue_category"]["id"]
print("cat created:", name)
except Exception:
pass
for c in api("GET", "/projects/MOPAC/issue_categories.json")["issue_categories"]:
cats[c["name"]] = c["id"]
print("cats:", len(cats))
# 3. Update majors: category, version, estimates
updates = [
(490, "Quota & Backpressure", "Beta", 8),
(491, "Quota & Backpressure", "Beta", 3),
(492, "Model Selection", "Production", 5),
(493, "Task Management", "Production", 5),
(494, "Deployment", "Beta", 6),
(495, "Briefing", "Beta", 8),
(496, "Task Management", "Phase 3 - Integrations", 12),
(497, "Secrets", "Beta", 5),
(498, "Integrations", "Phase 3 - Integrations", 40),
(499, "Documentation", "Phase 3 - Integrations", 6),
]
for iid, cat, ver, hrs in updates:
api("PUT", "/issues/%d.json" % iid, {"issue": {
"category_id": cats[cat], "fixed_version_id": versions[ver],
"estimated_hours": hrs}})
print("updated", iid, cat, ver, str(hrs) + "h")
# 4. Relations: quota blocks dispatcher pieces
for to in (492, 493):
try:
api("POST", "/issues/490/relations.json", {"relation": {"issue_to_id": to, "relation_type": "blocks"}})
print("relation 490 blocks", to)
except Exception:
pass
# 5. Children of 498 (integration fleet)
children = [
("Google suite (docs/tasks/keep/gmail) MCP/CLI", "Normal", 8),
("IMAP client integration", "Low", 5),
("Linkwarden integration (tag-driven intake)", "Normal", 5),
("Google Chat integration", "Low", 5),
("Signal integration (QR login)", "Normal", 8),
("Discord integration", "Low", 5),
("Redmine Go CLI (replaces bash/python glue)", "Normal", 8),
("Discourse Go client (tracked in 495, child for roadmap)", "Normal", 4),
("Gitea Go client", "Normal", 5),
("Browser automation (Chrome/Firefox/WebKit)", "Low", 12),
("Linux SSH/MCP fleet (Kali, OpenVAS for security queue)", "Normal", 12),
("Generic REST API driver", "Normal", 5),
("Cloudron API integration", "Normal", 5),
("HomeAssistant integration", "Low", 4),
("Kubernetes integration", "Low", 8),
]
for subject, prio, hrs in children:
prio_id = 4 if prio == "Normal" else 3
body = {
"issue": {
"project_id": "MOPAC", "subject": "[fleet] " + subject,
"priority_id": prio_id,
"category_id": cats["Integrations"],
"fixed_version_id": versions["Phase 3 - Integrations"],
"estimated_hours": hrs,
"parent_issue_id": 498,
"tracker_id": 2,
"description": "Child of umbrella #498 (MCP/CLI outbound integration fleet). One integration = one standalone ukrrs repo; loose coupling, config-driven, exec+JSON contracts. OAuth one-time browser flow preferred; QR for Signal. Language tiers apply (node = last resort + consult Charles). See SPEC-20260829-charles-brief.md.",
}
}
r = api("POST", "/issues.json", body)
print("child:", r["issue"]["id"], subject)
# 6. Two brief items not yet ticketed
extra = [
("High-security provider routing (ITAR/TS-SCI: dedicated LiteLLM + internal models)", "Infrastructure", "Phase 3 - Integrations", 3, 8,
"Spec: Provider routing section. Some orchestrated work is ITAR/TS-SCI/proprietary and must route to internally hosted models via dedicated OpenWebUI instances; likely needs a dedicated high-security LiteLLM instance. Low priority, KEY requirement - design constraint to respect while building everything else."),
("Session/context quality: long-running rapport + auto-compact handling", "Model Selection", "Production", 3, 8,
"Spec: workflow section. Crush+GLM-5.2 rapport: agents working hours-long bodies of work with auto-compaction. Meaty projects need bigger active working memory. Design how MOPAC conductor sessions persist/compact/resume across long work."),
]
for subject, cat, ver, prio, hrs, desc in extra:
r = api("POST", "/issues.json", {"issue": {
"project_id": "MOPAC", "subject": subject, "priority_id": prio,
"category_id": cats[cat], "fixed_version_id": versions[ver],
"estimated_hours": hrs, "tracker_id": 2, "description": desc}})
print("extra:", r["issue"]["id"], subject[:50])