1
0
Files
Rihards Gailums 87bab46b0f Workspace INDEX.md + flag §07 DI non-conformance
Two changes:

1. INDEX.md (new, generated). Workspace-level index that lists every
   package grouped by UAPF level (L0/L1/L2/L3/L4) for navigation. Per
   UAPF specification §01-concepts.md, levels are an aggregation and
   governance scope only and MUST NOT be used to imply modeling
   semantics; per §04-folder-structure.md the SHOULD-recommended
   on-disk layout is enterprise/ + domains/ + processes/ regardless of
   level. INDEX.md is therefore a level-grouped *view* over the
   spec-conformant on-disk layout, not an alternative layout. Generated
   by tools/build-index/build_index.py (new), which walks the workspace
   manifests and emits INDEX.md from current state.

2. docs/methodology.md updated to document the §07 auto-layout DI as a
   known deliberate non-conformance. §07-package-format.md requires
   authored DI (from a conforming OMG modeler) and explicitly forbids
   automatic layout generation as a substitute. The DI in the five
   .bpmn files in this workspace is auto-generated by
   tools/register-transcoder/bpmn_di.py and is therefore non-conformant
   under §07. It is kept for the POC so artefacts preview visually;
   the conformant path is to re-author each .bpmn in Camunda Modeler
   or bpmn-js Studio and recommit, which emits authored DI. New
   *Known non-conformances* section in methodology.md cites §07
   verbatim and explains this rationale. The Pass-1 transcoder section
   and Final-validation-pass section are softened to call out that
   the DI is auto-generated.

uapf-cli validate green on all 13 packages (L1 domains/gramatvediba,
L2 fg1-fg6, L3 fg3-2/3/6, L4 fg3-1/4/5).
2026-05-21 11:12:45 +00:00

161 lines
5.7 KiB
Python

#!/usr/bin/env python3
"""
build_index.py — generate INDEX.md from workspace manifests.
Walks the `vk-gramatvediba` workspace tree, reads every `uapf.yaml` and the
top-level `enterprise/enterprise.yaml`, groups packages by their declared
level (0–4), and emits a level-grouped index at the workspace root.
Per UAPF specification §01-concepts.md, levels are an aggregation and
governance scope only — directory structure must not encode them. Per
§04-folder-structure.md the SHOULD-recommended workspace layout is
`enterprise/` + `domains/` + `processes/` regardless of level. INDEX.md is
therefore a level-grouped *view* over a spec-conformant on-disk layout,
not an alternative layout.
Usage: python3 tools/build-index/build_index.py
Dependencies: pyyaml.
"""
import glob
import os
import sys
try:
import yaml
except ImportError:
sys.exit("error: pyyaml is required (pip install pyyaml)")
def find_workspace_root(start):
"""Walk up from `start` until a directory containing `enterprise/` is found."""
d = os.path.abspath(start)
while d != os.path.dirname(d):
if os.path.isdir(os.path.join(d, "enterprise")) and \
os.path.isdir(os.path.join(d, "processes")):
return d
d = os.path.dirname(d)
sys.exit("error: not inside a UAPF workspace (no enterprise/ + processes/)")
def read_manifest(path):
with open(path, encoding="utf-8") as fh:
return yaml.safe_load(fh)
def short(s, n=160):
if not s:
return ""
return s.split("\n")[0][:n]
LEVEL_TITLES = {
0: "L0 — Enterprise process collection index",
1: "L1 — Domain process collections",
2: "L2 — End-to-end business processes",
3: "L3 — Composed subprocesses / variants",
4: "L4 — Atomic executable processes",
}
def collect(ws):
pkgs = {0: [], 1: [], 2: [], 3: [], 4: []}
ent_path = os.path.join(ws, "enterprise", "enterprise.yaml")
if os.path.isfile(ent_path):
m = read_manifest(ent_path)
# L0 enterprise-index nests id/name/description under `enterprise:`
ent = m.get("enterprise") or m
pkgs[0].append({
"id": ent.get("id", "?"),
"name": ent.get("name", ""),
"desc": short(ent.get("description", "")),
"path": "enterprise/enterprise.yaml",
"includes": [p.get("ref", "") for p in (m.get("packages") or [])],
"cornerstones": {},
})
for pat in ("domains/*/uapf.yaml", "processes/*/uapf.yaml"):
for p in sorted(glob.glob(os.path.join(ws, pat))):
m = read_manifest(p)
lvl = m.get("level")
if lvl not in pkgs:
continue
pkgs[lvl].append({
"id": m.get("id", "?"),
"name": m.get("name", ""),
"desc": short(m.get("description", "")),
"path": os.path.relpath(os.path.dirname(p), ws) + "/",
"includes": m.get("includes") or [],
"cornerstones": m.get("cornerstones") or {},
})
return pkgs
def render(pkgs):
L = []
L.append("# `vk-gramatvediba` workspace index")
L.append("")
L.append("This index lists every package in the workspace grouped by UAPF")
L.append("level (L0–L4). Per UAPF specification `§01-concepts.md`, levels")
L.append("are an aggregation and governance scope only; per")
L.append("`§04-folder-structure.md` the SHOULD-recommended workspace layout")
L.append("is `enterprise/`, `domains/`, `processes/` regardless of level.")
L.append("This file is therefore a level-grouped *view* over a")
L.append("spec-conformant on-disk layout, not an alternative layout.")
L.append("")
L.append("Regenerate with `python3 tools/build-index/build_index.py`.")
L.append("")
total = sum(len(v) for v in pkgs.values())
L.append(f"Total packages: **{total}** "
"(L0: {0}, L1: {1}, L2: {2}, L3: {3}, L4: {4}).".format(
*(len(pkgs[i]) for i in range(5))))
L.append("")
for lvl in (0, 1, 2, 3, 4):
L.append(f"## {LEVEL_TITLES[lvl]}")
L.append("")
items = pkgs[lvl]
if not items:
L.append("_(none)_")
L.append("")
continue
if lvl == 0:
L.append("| Package | Path | Description |")
L.append("|---|---|---|")
for e in items:
desc = e["name"] or e["desc"] or ""
L.append(f"| `{e['id']}` | `{e['path']}` | {desc} |")
elif lvl in (1, 2, 3):
L.append("| Package | Path | Includes | Description |")
L.append("|---|---|---|---|")
for e in items:
inc = ", ".join(f"`{i}`" for i in e["includes"]) or "_(none)_"
desc = e["name"] or e["desc"] or ""
L.append(f"| `{e['id']}` | `{e['path']}` | {inc} | {desc} |")
else: # L4
L.append("| Package | Path | Cornerstones | Description |")
L.append("|---|---|---|---|")
for e in items:
cs = e["cornerstones"] or {}
present = [k for k in ("bpmn", "dmn", "cmmn", "resources")
if cs.get(k)]
cs_str = ", ".join(present) or "_(none)_"
desc = e["name"] or e["desc"] or ""
L.append(f"| `{e['id']}` | `{e['path']}` | {cs_str} | {desc} |")
L.append("")
return "\n".join(L) + "\n"
def main():
ws = find_workspace_root(os.path.dirname(os.path.abspath(__file__)))
pkgs = collect(ws)
text = render(pkgs)
out = os.path.join(ws, "INDEX.md")
with open(out, "w", encoding="utf-8") as fh:
fh.write(text)
print(f"wrote {out} ({sum(len(v) for v in pkgs.values())} packages)")
if __name__ == "__main__":
main()