1
0

Architecture-as-Code 0.1.0: tikai dati un definīcijas; jomu katalogs (21 VARAM joma), JSON shēma, B09 datu līgums, pārbaudītājs

- kultūras un valodu tehnoloģiju joma → domains/kultura-valoda, avota PDF → sources/kultura-valoda
- izņemts KISC MCP servera kods, Docker un servera identitātes datnes (paliek KISC projekta repozitorijā)
- domenas.yaml, schemas/arhitektura-0.1.schema.json, contracts/B09-digitalas-parvaldes-arhitekturas.odcs.yaml, tools/validate.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016679RwmHsuTFfxt26wP6rk
This commit is contained in:
2026-10-11 11:52:49 +00:00
parent 8fb6c52e44
commit 3574c8133f
95 changed files with 668 additions and 7880 deletions

8
CHANGELOG.md Normal file
View File

@@ -0,0 +1,8 @@
# Izmaiņu žurnāls
## 0.1.0 — 2026-10-11
- Repozitorijs pārdēvēts: Architekture-as-Code → Architecture-as-Code.
- Repozitorijā tikai dati un definīcijas: kultūras un valodu tehnoloģiju joma pārcelta uz `domains/kultura-valoda`, avota PDF uz `sources/kultura-valoda/`. KISC MCP servera kods, Docker datnes un servera identitātes datnes (DID, JWKS, akreditācijas pieprasījums) izņemtas — tās paliek KISC projekta repozitorijā.
- `domenas.yaml`: visas 21 VARAM arhitektūras jomas (6 horizontālās, 1 apakšjoma, 14 nozaru) ar izstrādes vadītāju iestādi, VARAM apstiprinājumu un dokumentu saitēm.
- Shēma `schemas/arhitektura-0.1.schema.json` (JSON Schema 2020-12), datu līgums `contracts/B09-digitalas-parvaldes-arhitekturas.odcs.yaml`, pārbaudītājs `tools/validate.py`.

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# Digitālās pārvaldes arhitektūras kā kods (Architecture-as-Code)
VARAM digitālās pārvaldes arhitektūras horizontālās un nozaru jomas kā dati: viens jomu katalogs un katrai jomai — funkcijas, pakalpojumi, informācijas resursi, informācijas sistēmas, organizācijas, mērķi un saites starp tiem, visas pēc vienas shēmas.
Autors: PPP Asociācija (PPPA), Valsts Pirmkods (VPK). Shēmas versija 0.1, melnraksts.
> **Koncepcijas demonstrācija.** Šīs datnes nav oficiāls izdevums. Metodiski saistošs ir VARAM publicētais arhitektūras apraksts ([horizontālās jomas](https://www.varam.gov.lv/lv/horizontalo-jomu-arhitekturas), [nozaru jomas](https://www.varam.gov.lv/lv/nozaru-jomu-arhitekturas)).
## Stāvoklis
21 joma katalogā (6 horizontālās, 1 apakšjoma, 14 nozaru un starpnozaru). Kodā pārveidota 1: **Kultūra un valodu tehnoloģijas** (`domains/kultura-valoda`). Pārējās tiks pārveidotas pa vienai; katras jomas stāvoklis ir `domenas.yaml` laukā `as_code.status`.
## Saturs
| Datne / mape | Saturs |
|---|---|
| `domenas.yaml` | Visu jomu katalogs: nosaukums, veids, izstrādes vadītāja iestāde (ar VPK ID), VARAM apstiprinājums, dokumentu saites, stāvoklis kodā |
| `domains/<slug>/manifest.yaml` | Jomas apraksts un tās reģistru atrašanās vieta |
| `domains/<slug>/registers/` | Reģistri (YAML): organizācijas, funkcijas, pakalpojumi, informācijas resursi, sistēmas, mērķi, ceļa karte, riski, saites |
| `domains/<slug>/views/` | Apraksta nodaļas (Markdown), ģenerētas no reģistriem |
| `sources/<slug>/` | Avota dokumenti, no kuriem joma pārveidota |
| `schemas/arhitektura-0.1.schema.json` | Kopīga JSON shēma visām jomām; `x-roles` nosaka, pret kuru tipu pārbauda katru reģistru |
| `contracts/B09-digitalas-parvaldes-arhitekturas.odcs.yaml` | ODCS v3 datu līgums (VDZP datu kopa B09) |
| `tools/validate.py` | Tās pašas pārbaudes, ko veic VDZP savienotājs |
## Identifikatori
Elementa identifikators jomā ir lokāls (`func.01`, `svc.k.03`, `org.kisc`). Pilnais identifikators ir `<jomas slug>:<lokālais ID>`, piemēram `kultura-valoda:func.01`, tāpēc jomas var izmantot vienādus lokālos identifikatorus. Organizācijām var norādīt `vpk_id` (Valsts kancelejas organizāciju reģistrs, VDZP B01).
## Jaunas jomas pievienošana
1. `domenas.yaml`: jomai `as_code.status: in_progress`, vēlāk `transformed` un `path: domains/<slug>`.
2. `domains/<slug>/manifest.yaml` ar `id: domain.<slug>` un reģistru sarakstu (lomas kā `domains/kultura-valoda/manifest.yaml`).
3. Reģistri `domains/<slug>/registers/`; avota dokuments `sources/<slug>/`.
4. `python3 tools/validate.py` — visām pārbaudēm jābūt OK.
5. Commit. VDZP nākamajā rītā (06:30 Rīgas laikā) ielādē jauno jomu automātiski.
## Saistītās datu kopas
- VDZP — Valsts datu un zināšanu platforma (demonstrācija): <https://lakehouse.pppa.lv/>, datu kopa B09.
- Valdības deklarācija, organizāciju reģistrs (VPK ID): [Valdibas-Deklaracija-as-Code](https://processgit.org/Valsts-Pirmkods/Valdibas-Deklaracija-as-Code).
- Valsts budžets: [Budget-as-Code](https://processgit.org/Valsts-Pirmkods/Budget-as-Code).

View File

@@ -0,0 +1,100 @@
# ODCS v3 datu līgums: B09 Digitālās pārvaldes arhitektūras (arhitektūra kā kods)
# Shēma: schemas/arhitektura-0.1.schema.json (JSON Schema 2020-12). Šī līguma schema daļa apraksta to, ko VDZP ielādē;
# pilnā struktūra un pieļaujamās vērtības ir JSON shēmā.
apiVersion: v3.0.2
kind: DataContract
id: urn:pppa:cac:contract:B09
name: Digitālās pārvaldes arhitektūras (arhitektūra kā kods)
version: 0.1.0
status: active
domain: Valsts kā kods (Country as Code)
dataProduct: Digitālās pārvaldes arhitektūras
tenant: PPP Asociācija (PPPA)
description:
purpose: >-
VARAM digitālās pārvaldes arhitektūras horizontālās un nozaru jomas kā dati: viens jomu katalogs (domenas.yaml) ar visām jomām,
to izstrādes vadītāju iestādi, VARAM apstiprinājuma statusu un dokumentu saitēm, un katrai jomai, kas pārveidota kodā, —
funkcijas, pakalpojumi, informācijas resursi, informācijas sistēmas, organizācijas, mērķi un saites starp tiem.
usage: Mašīnlasāma datu apmaiņa; ielāde VDZP katru dienu; AI aģentu vaicājumi caur MCP.
limitations: >-
Koncepcijas demonstrācija, nav oficiāls izdevums. Juridiski un metodiski saistošs ir VARAM publicētais arhitektūras apraksts.
Kodā pārveidotas ne visas jomas — stāvoklis katrai jomai ir domenas.yaml (as_code.status). Identifikatori jomā ir lokāli;
pilnais identifikators ir <jomas slug>:<lokālais ID>.
servers:
- server: processgit
type: custom
format: yaml
description: ProcessGit repozitorijs (Gitea API v1)
customProperties:
- {property: url, value: "https://processgit.org/Valsts-Pirmkods/Architecture-as-Code"}
- {property: ref, value: main}
- {property: catalogue, value: domenas.yaml}
- {property: path, value: "domains/*/manifest.yaml (un manifestā norādītie reģistri)"}
- {property: schema, value: "https://processgit.org/Valsts-Pirmkods/Architecture-as-Code/src/branch/main/schemas/arhitektura-0.1.schema.json"}
schema:
- name: Domain
logicalType: object
physicalType: yaml-object
physicalName: "domenas.yaml#/domains[]"
description: Viena VARAM arhitektūras joma (horizontālā, nozaru vai apakšjoma) un tās stāvoklis kodā.
customProperties: [{property: lakehouseTable, value: cac_bronze.arch_domain}, {property: jsonSchema, value: "#/$defs/CatalogueDomain"}]
properties:
- {name: slug, logicalType: string, required: true, primaryKey: true, unique: true, logicalTypeOptions: {pattern: "^[a-z0-9]+(-[a-z0-9]+)*$"}}
- {name: title, logicalType: string, required: true}
- {name: kind, logicalType: string, required: true, quality: [{type: library, rule: validValues, validValues: [horizontal, sectoral, subdomain]}]}
- {name: parent, logicalType: string, required: false, description: Apakšjomai — jomas slug}
- {name: lead_institution, logicalType: string, required: true, description: "Iestāde, kas vada arhitektūras apraksta izstrādi"}
- {name: lead_vpk_id, logicalType: string, required: false, description: Iestādes VPK ID (B01), logicalTypeOptions: {pattern: "^[0-9]{2}-[0-9]{4}$"}}
- {name: varam_status, logicalType: string, required: true, quality: [{type: library, rule: validValues, validValues: [approved, in_review, in_development, not_started]}]}
- {name: approved, logicalType: date, required: false}
- {name: description_url, logicalType: string, required: false, description: VARAM publicētais arhitektūras apraksts}
- {name: as_code.status, logicalType: string, required: true, quality: [{type: library, rule: validValues, validValues: [transformed, in_progress, not_started]}]}
- {name: as_code.path, logicalType: string, required: false, description: "Jomas mape domains/<slug>"}
- name: Element
logicalType: object
physicalType: yaml-object
physicalName: "domains/<slug>/registers/**#/items[]"
description: Viens arhitektūras elements jomā — organizācija, funkcija, pakalpojums, informācijas resurss, informācijas sistēma, mērķis, ceļa kartes pasākums, mijiedarbība vai risks.
customProperties: [{property: lakehouseTable, value: cac_bronze.arch_register}, {property: jsonSchema, value: "#/$defs/Organization, Function, Component, Goal, Item"}]
properties:
- {name: uid, logicalType: string, required: true, primaryKey: true, unique: true, description: "<jomas slug>:<id>, piemēram kultura-valoda:func.01 (veido savienotājs)"}
- {name: id, logicalType: string, required: true, description: Lokālais identifikators jomā, logicalTypeOptions: {pattern: "^[a-z][a-z_]*(\\.[A-Za-z0-9_-]+)+$"}}
- {name: entity_type, logicalType: string, required: true, quality: [{type: library, rule: validValues, validValues: [organisation, function, service, information_resource, system, goal, roadmap_item, interaction, risk, as_is_component]}]}
- {name: name, logicalType: string, required: true, description: name vai title}
- {name: subdomain, logicalType: string, required: false}
- {name: status, logicalType: string, required: false, description: status vai change_status}
- {name: vpk_id, logicalType: string, required: false, description: Organizācijai — VPK ID (B01)}
- {name: attributes, logicalType: object, required: false, description: Pārējie elementa lauki, kā autorēti}
- name: Edge
logicalType: object
physicalType: yaml-object
physicalName: "domains/<slug>/registers/99-relations/edges.yaml#/edges[]"
description: Saite starp diviem vienas jomas elementiem.
customProperties: [{property: lakehouseTable, value: cac_bronze.arch_edge}, {property: jsonSchema, value: "#/$defs/Edge"}]
properties:
- {name: from, logicalType: string, required: true}
- {name: type, logicalType: string, required: true, description: "has_goal, has_function, has_service, has_information_resource, has_system, performed_by …"}
- {name: to, logicalType: string, required: true}
quality:
- {name: schema_valid, type: custom, engine: json-schema, implementation: schemas/arhitektura-0.1.schema.json,
description: "Katalogs, jomu manifesti un reģistri atbilst JSON shēmai (katra datne — savas lomas tipam, x-roles)", dimension: conformity}
- {name: catalogue_valid, type: custom, engine: cac-lakehouse, implementation: "app.cac_processgit:arch_catalogue_valid",
description: "Katalogā katra joma unikāla; katrai kodā pārveidotajai jomai ir mape ar manifestu; katra mape ir katalogā", dimension: consistency}
- {name: ids_unique, type: custom, engine: cac-lakehouse, implementation: "app.cac_processgit:arch_ids_unique", description: Lokālie identifikatori jomā ir unikāli, dimension: uniqueness}
- {name: edges_resolve, type: custom, engine: cac-lakehouse, implementation: "app.cac_processgit:arch_edges_resolve", description: "Katras saites abi gali ir šīs jomas elementi", dimension: consistency}
- {name: organization_has_vpk_id, type: custom, engine: cac-lakehouse, implementation: "app.cac_processgit:arch_org_vpk",
description: "Organizācija sasaistīta ar VPK ID — norādīts reģistrā (vpk_id) vai atrasts pēc nosaukuma", dimension: completeness}
- {name: lead_has_vpk_id, type: custom, engine: cac-lakehouse, implementation: "app.cac_processgit:arch_lead_vpk",
description: "Jomas izstrādes vadītājai iestādei norādīts VPK ID", dimension: completeness}
slaProperties:
- {property: frequency, value: 1, unit: d, element: "pull ingest katru dienu 06:30 Rīgas laikā"}
- {property: retention, value: "visas versijas (git vēsture)"}
team:
- {role: owner, name: "VARAM (digitālās pārvaldes arhitektūras ietvars); jomu arhitektūru turētāji — jomu vadošās iestādes"}
- {role: data steward, name: PPP Asociācija (PPPA)}
support:
- {channel: issues, url: "https://processgit.org/Valsts-Pirmkods/Architecture-as-Code/issues"}
customProperties:
- {property: sourceId, value: B09}
- {property: identifiers, value: "joma <slug>; elements <slug>:<lokālais ID>"}
- {property: validator, value: tools/validate.py}

240
domenas.yaml Normal file
View File

@@ -0,0 +1,240 @@
# Digitālās pārvaldes arhitektūras jomas (VARAM) un to stāvoklis šajā repozitorijā.
# Avots: VARAM lapas „Horizontālo jomu arhitektūras” un „Nozaru jomu arhitektūras” (pārbaudīts 2026-10-11).
# Tiek glabāta tikai iestāde, kas vada apraksta izstrādi; personu kontaktdati netiek glabāti.
# as_code.status: transformed — joma pārveidota kodā (domains/<slug>); not_started — vēl tikai VARAM publicētais dokuments.
# Shēma: schemas/arhitektura-0.1.schema.json (#/$defs/Catalogue).
version: "0.1.0"
sources:
- {title: "VARAM: Horizontālo jomu arhitektūras", url: "https://www.varam.gov.lv/lv/horizontalo-jomu-arhitekturas", checked: "2026-10-11"}
- {title: "VARAM: Nozaru jomu arhitektūras", url: "https://www.varam.gov.lv/lv/nozaru-jomu-arhitekturas", checked: "2026-10-11"}
- {title: "VARAM: Digitālās pārvaldes arhitektūra", url: "https://www.varam.gov.lv/lv/digitalas-parvaldes-arhitektura", checked: "2026-10-11"}
domains:
# ---------------------------------------------------------------- horizontālās jomas
- slug: valsts-pakalpojumi
title: "Valsts pakalpojumi"
kind: horizontal
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija"
lead_vpk_id: "21-0000"
varam_status: approved
approved: "2026-02-18"
approved_versions: [{version: "1", approved: "2025-06-25"}, {version: "2", approved: "2026-02-18"}]
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51282/download?attachment"
presentation_url: "https://www.varam.gov.lv/media/44522"
as_code: {status: not_started}
- slug: datu-parvaldiba
title: "Datu pārvaldība un koplietošana"
kind: horizontal
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija"
lead_vpk_id: "21-0000"
varam_status: approved
approved: "2024-11-27"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51756/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51717/download?attachment"
as_code: {status: not_started}
- slug: datu-analize
title: "Datu analīze"
kind: horizontal
lead_institution: "Centrālā statistikas pārvalde"
lead_vpk_id: "12-0039"
varam_status: approved
approved: "2025-04-23"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51750/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51753/download?attachment"
as_code: {status: not_started}
- slug: uzticamiba-identifikacija
title: "Uzticamība un personu identifikācija"
kind: horizontal
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija"
lead_vpk_id: "21-0000"
varam_status: approved
approved: "2025-01-22"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51687/download?attachment"
presentation_url: "https://www.varam.gov.lv/media/42702"
as_code: {status: not_started}
- slug: ikt-infrastruktura-kiberdrosiba
title: "IKT infrastruktūra un kiberdrošība"
kind: horizontal
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija"
lead_vpk_id: "21-0000"
varam_status: approved
approved: "2025-03-19"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51128/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51131/download?attachment"
as_code: {status: not_started}
- slug: parvaldes-modernizacija
title: "Valsts pārvaldes modernizācija (t. sk. resursi un dokumenti)"
kind: horizontal
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija"
lead_vpk_id: "21-0000"
varam_status: approved
approved: "2025-08-27"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51071/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51711/download?attachment"
as_code: {status: not_started}
- slug: mi-produktivitatei
title: "Mākslīgais intelekts pārvaldes produktivitātei"
kind: subdomain
parent: parvaldes-modernizacija
lead_institution: "Valsts digitālās attīstības aģentūra"
varam_status: approved
approved: "2025-10-29"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51122/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51119/download?attachment"
as_code: {status: not_started}
# ---------------------------------------------------------------- nozaru un starpnozaru jomas
- slug: kultura-valoda
title: "Kultūra un valodu tehnoloģijas"
kind: sectoral
lead_institution: "Kultūras informācijas sistēmu centrs"
lead_vpk_id: "22-0558"
varam_status: approved
approved: "2025-07-07"
approved_by: IKT forums (rakstiskā procedūra)
description_url: "https://www.varam.gov.lv/lv/media/51729/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51699/download?attachment"
as_code: {status: transformed, path: domains/kultura-valoda, source: "sources/kultura-valoda/"}
- slug: izglitiba-zinatne
title: "Izglītība un zinātne"
kind: sectoral
lead_institution: "Izglītības un zinātnes ministrija"
lead_vpk_id: "15-0000"
varam_status: approved
approved: "2026-02-18"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/49004/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/49007/download?attachment"
as_code: {status: not_started}
- slug: labklajiba
title: "Labklājība"
kind: sectoral
lead_institution: "Labklājības ministrija"
lead_vpk_id: "18-0000"
varam_status: approved
approved: "2025-08-27"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51068/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51089/download?attachment"
as_code: {status: not_started}
- slug: veseliba
title: "Veselība"
kind: sectoral
lead_institution: "Latvijas Digitālās veselības centrs"
varam_status: approved
approved: "2025-09-25"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51765/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51588/download?attachment"
as_code: {status: not_started}
- slug: nodokli
title: "Nodokļi"
kind: sectoral
lead_institution: "Valsts ieņēmumu dienests"
lead_vpk_id: "13-0056"
varam_status: approved
approved: "2025-07-07"
approved_by: IKT forums (rakstiskā procedūra)
description_url: "https://www.varam.gov.lv/lv/media/51732/download?attachment"
presentation_url: "https://www.varam.gov.lv/media/44489"
as_code: {status: not_started}
- slug: muita
title: "Muita"
kind: sectoral
lead_institution: "Valsts ieņēmumu dienests"
lead_vpk_id: "13-0056"
varam_status: approved
approved: "2025-04-23"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51744/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51747/download?attachment"
as_code: {status: not_started}
- slug: transports
title: "Transports"
kind: sectoral
lead_institution: "Satiksmes ministrija"
lead_vpk_id: "17-0000"
varam_status: approved
approved: "2025-07-07"
approved_by: IKT forums (rakstiskā procedūra)
description_url: "https://www.varam.gov.lv/lv/media/51735/download?attachment"
presentation_url: "https://www.varam.gov.lv/media/44495"
as_code: {status: not_started}
- slug: e-lieta
title: "E-lieta"
kind: sectoral
lead_institution: "Tiesu administrācija"
lead_vpk_id: "19-0458"
varam_status: approved
approved: "2025-04-23"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51738/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51741/download?attachment"
as_code: {status: not_started}
- slug: buvnieciba
title: "Būvniecība"
kind: sectoral
lead_institution: "Valsts zemes dienests"
lead_vpk_id: "19-0485"
varam_status: approved
approved: "2025-06-12"
approved_by: IKT forums (rakstiskā procedūra)
description_url: "https://www.varam.gov.lv/lv/media/51720/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51723/download?attachment"
as_code: {status: not_started}
- slug: energetika
title: "Enerģētika"
kind: sectoral
lead_institution: "Būvniecības valsts kontroles birojs"
lead_vpk_id: "12-0686"
varam_status: approved
approved: "2025-08-04"
approved_by: IKT forums (rakstiskā procedūra)
description_url: "https://www.varam.gov.lv/lv/media/51708/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51726/download?attachment"
as_code: {status: not_started}
- slug: vide-regioni
title: "Vide un reģionālā attīstība"
kind: sectoral
lead_institution: "Valsts digitālās attīstības aģentūra"
varam_status: approved
approved: "2025-09-25"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51083/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51086/download?attachment"
as_code: {status: not_started}
- slug: demokratija-lidzdaliba
title: "Demokrātija, līdzdalība un tiesiskā informācija"
kind: sectoral
lead_institution: "Valsts digitālās attīstības aģentūra"
varam_status: approved
approved: "2026-01-21"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51768/download?attachment"
as_code: {status: not_started}
- slug: zemkopiba
title: "Zemkopība"
kind: sectoral
lead_institution: "Zemkopības ministrija"
lead_vpk_id: "16-0000"
varam_status: approved
approved: "2025-08-27"
approved_by: IKT forums
description_url: "https://www.varam.gov.lv/lv/media/51077/download?attachment"
presentation_url: "https://www.varam.gov.lv/lv/media/51080/download?attachment"
as_code: {status: not_started}
- slug: pasvaldibas
title: "Pašvaldības"
kind: sectoral
lead_institution: "Viedās administrācijas un reģionālās attīstības ministrija (līdz kompetences centra izveidei)"
lead_vpk_id: "21-0000"
varam_status: in_review
description_url: "https://www.varam.gov.lv/lv/media/50924/download?attachment"
as_code: {status: not_started}

View File

@@ -1,37 +0,0 @@
# Server
MCP_PORT=8787
MCP_HOST=0.0.0.0
# IMPORTANT: point this to the folder that contains registers/ and views/
# In this repo that's likely the domain folder, e.g. /app/domains/kultura-valoda
REPO_ROOT=/app/domains/kultura-valoda
# Public URL of the server (used in OAuth discovery metadata)
SERVER_PUBLIC_URL=https://llm.kis.gov.lv:8787
# Auth switch
AUTH_REQUIRED=true
# Auth mode: "both" (default) | "jwks" | "static"
# both — accepts either a valid static token OR a valid OAuth JWT
# jwks — only OAuth/OIDC JWT tokens (validated via JWKS)
# static — only the fixed bearer token
AUTH_MODE=both
# ── Static bearer token ──────────────────────────────────────────────
STATIC_BEARER_TOKEN=change-me
# ── Built-in OAuth (Authorization Code + PKCE & client_credentials) ──
# Login credentials for the /authorize page (browser-based flow)
OAUTH_LOGIN_USERNAME=admin
OAUTH_LOGIN_PASSWORD=change-me
# Pre-registered machine-to-machine client (client_credentials grant)
OAUTH_CLIENT_ID=mcp-service-account
OAUTH_CLIENT_SECRET=change-me-to-a-strong-secret
# ── External JWKS / OIDC settings (optional, for external IDP) ──────
# Leave blank to use the built-in OAuth server only.
# OAUTH_JWKS_URL=https://YOUR-IDP/.well-known/jwks.json
# OAUTH_ISSUER=https://YOUR-IDP/
# OAUTH_AUDIENCE=YOUR_API_AUDIENCE

View File

@@ -1 +0,0 @@

View File

@@ -1,30 +0,0 @@
name: QA
on:
push:
pull_request:
jobs:
qa:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install MCP deps
working-directory: mcp
run: npm ci
- name: Generate doc
run: |
cd mcp
REPO_ROOT="$GITHUB_WORKSPACE" DOMAIN_DIR="domains/kultura-valoda" OUT_FILE="KISC-merkarhitektura-apraksts.md" npm run gen:doc
- name: Non-empty required registers
run: tools/qa/check_nonempty_registers.sh
- name: Required headings exist in regenerated doc
run: tools/qa/check_generated_doc_headings.sh KISC-merkarhitektura-apraksts.md
- name: Stakeholder presence sanity check
run: tools/qa/check_stakeholder_count.sh KISC-merkarhitektura-apraksts.md

View File

@@ -1,6 +0,0 @@
node_modules/
dist/
.env
.DS_Store
*.log
site/

View File

@@ -1,560 +0,0 @@
# MCP serveris — Kultūras un valodu tehnoloģiju arhitektūras interfeiss
## Pārskats
Šis dokuments apraksta KISC MCP (Model Context Protocol) serveri, kas nodrošina AI aģentiem un automatizācijas rīkiem strukturētu piekļuvi Latvijas kultūras un valodu tehnoloģiju jomas mērķarhitektūras datiem.
**Servera adrese:** `https://llm.kis.gov.lv/mcp`
**Versija:** 0.3.0
**Operators:** Kultūras informācijas sistēmu centrs (KISC)
**Identitāte:** `did:web:llm.kis.gov.lv`
MCP serveris ir **tikai-lasīšanas interfeiss** — tas nemaina SSOT saturu, bet gan ļauj to meklēt, analizēt un ģenerēt dokumentus no tā.
---
## 1) Kas ir šī arhitektūra un ko MCP serveris dara
Kultūras un valodu tehnoloģiju jomas mērķarhitektūra apraksta Latvijas valsts stratēģiju kultūras mantojuma digitalizācijai un latviešu valodas tehnoloģiju attīstībai. Tā ietver divus apakšdomēnus:
- **Kultūras apakšjoma** (`kultura`) — muzeju, bibliotēku, arhīvu un kultūras pieminekļu digitalizācija, datu pārvaldība, publiskā pieejamība
- **Valodu tehnoloģiju apakšjoma** (`valoda`) — Lielais latviešu valodas modelis (LVM-LV), mašīntulkošana, runas sintēze/atpazīšana, semantiskā meklēšana
Arhitektūra satur šādus elementu tipus:
| Entītiju tips | Piemēri | Skaits |
|---|---|---|
| **Mērķi** (`goal`) | M1 "Kultūras mantojuma saglabāšana", M4 "Latviešu valoda digitālajā laikmetā" | 6 |
| **Organizācijas** (`org`) | KISC, LNB, LU MII, Kultūras ministrija | ~40 |
| **Funkcijas** (`func`) | Mantojuma digitalizācija, valodu platformas uzturēšana | 6 |
| **Pakalpojumi** (`svc`) | Mašīntulkošana, runas sintēze, DOM, statistikas portāls | 30 |
| **Informācijas resursi** (`ir`) | Valodu korpusi, muzeja dati, terminoloģija | 23 |
| **Sistēmas** (`sys`) | LVM-LV, VBK, valodu platformas, DOM | 32 |
| **Ceļa kartes pasākumi** (`rm`) | Modernizācijas iniciatīvas ar laika grafiku | ~10 |
| **Riski** (`risk`) | Datu kvalitāte, kiberdrošība, AI Act atbilstība | ~8 |
| **Dokumenti** (`doc`) | GDPR, AI Act, VDAR, nozares regulējums | ~15 |
| **Principi** (`principle`) | Arhitektūras pamatprincipi | 4 |
---
## 2) MCP rīki — ko AI aģents saņem
Kad AI aģents (piemēram, Claude) pieslēdzas MCP serverim, tam kļūst pieejami **9 rīki**. Šajā sadaļā aprakstīts katrs rīks — tā nolūks, parametri un kas tiek atgriezts.
### 2.1 `identify` — servera identitātes verificēšana
**Nolūks:** Atgriež informāciju par servera operatoru, identitāti un uzticamības pierādījumiem. Ļauj AI aģentam pārliecināties, ka serveris pieder KISC un ir verificēts.
**Parametri:** nav obligātu parametru.
**Atgriež:**
- Servera nosaukums, versija, operators (KISC)
- DID identitāte: `did:web:llm.kis.gov.lv`
- MCPF (MCP Trust Framework) atbilstības informācija
- VeriTrust izdotas verifikācijas akreditācijas URL
- JWKS, DID dokumenta, MCP manifesta un uzticamības reģistra URL
**Kad lietot:** Pirms uzticēšanās servera datiem — lai pārliecinātos, ka datu avots ir leģitīms.
---
### 2.2 `describe_model` — arhitektūras metamodelis
**Nolūks:** Atgriež pilnu arhitektūras datu modeļa aprakstu — kādi entītiju tipi pastāv, kādi atribūti katram tipam, kādi attiecību tipi, kādi apakšdomēni un skati.
**Parametri:** nav.
**Atgriež:**
- 11 entītiju tipu saraksts ar to ID shēmām un atribūtiem
- 16 attiecību tipu saraksts (has_goal, depends_on, owns, u.c.)
- 2 apakšdomēni (kultura, valoda)
- 6 dokumenta skatu saraksts
**Kad lietot:** Kā pirmo soli — lai AI aģents saprastu, kāda veida dati ir pieejami un kā tie ir strukturēti. Šis rīks ir "karte" visiem pārējiem rīkiem.
**Piemērs — ko AI aģents uzzina:**
> "Šajā arhitektūrā ir 6 stratēģiskie mērķi ar ID formātā `goal.m1`–`goal.m6`. Sistēmas ir ar ID `sys.kultura.01` vai `sys.valoda.01`. Attiecības ietver `has_system`, `depends_on`, `owns`. Ir 2 apakšdomēni: kultūra un valoda."
---
### 2.3 `search` — meklēšana pēc atslēgvārdiem
**Nolūks:** Meklēt jebkādu arhitektūras entītiju vai dokumenta fragmentu pēc atslēgvārdiem. Atbalsta gan latviešu, gan angļu valodas vaicājumus.
**Parametri:**
- `query` (obligāts) — meklēšanas frāze, piemēram, "valodu tehnoloģijas", "KISC", "digitālais mantojums", "AI risks"
- `kind` (neobligāts) — filtrēt pēc tipa: goal, org, sys, svc, ir, func, principle u.c.
- `subdomain` (neobligāts) — filtrēt pēc apakšdomēna: "kultura" vai "valoda"
- `limit` (neobligāts) — rezultātu skaits (noklusējums 25)
**Atgriež:** Sakārtots saraksts ar atrastajām entītijām, katrai norādot ID, piemērotības novērtējumu (score), tipu un pilnus datus.
**Kad lietot:** Kad zināms aptuvens jautājums, bet nav precīzs entītijas ID.
**Piemēri:**
```
search(query="mašīntulkošana") → atrod svc.valoda.04, sys.valoda.03
search(query="LNB", kind="org") → atrod org.lnb
search(query="risks", kind="risk") → atrod visus riskus
search(query="korpuss", subdomain="valoda") → atrod ir.valoda.01, ir.valoda.02
```
---
### 2.4 `get_entity` — konkrētas entītijas izgūšana
**Nolūks:** Atgriež pilnu informāciju par vienu konkrētu entītiju pēc tās ID.
**Parametri:**
- `id` (obligāts) — entītijas ID, piemēram: `goal.m4`, `org.kisc`, `sys.valoda.02`, `risk.001`
**Atgriež:** Visu entītijas informāciju — nosaukums, apraksts, statuss, izmaiņu apraksts, VIRSIS ID atsauces un avota faila ceļš.
**Kad lietot:** Kad precīzi zināms, kuru entītiju vajag apskatīt.
**Piemēri:**
```
get_entity(id="goal.m4") → "Latviešu valoda digitālajā laikmetā" — pilns apraksts
get_entity(id="sys.valoda.02") → "Lielais latviešu valodas modelis (LVM-LV)" — statuss, resursi
get_entity(id="org.kisc") → KISC organizācijas pilna informācija
```
---
### 2.5 `list_entities` — entītiju saraksts pēc tipa
**Nolūks:** Atgriež visu entītiju ID sarakstu, iespējams filtrētu pēc tipa un/vai apakšdomēna. Ļauj ātri uzzināt, kas pastāv.
**Parametri:**
- `type` (neobligāts) — entītiju tipa filtrs: "goal", "org", "sys", "svc", "ir", "func", "principle", "rm", "risk", "doc", "int"
- `subdomain` (neobligāts) — "kultura" vai "valoda"
**Atgriež:** ID saraksts ar katras entītijas pamata informāciju (nosaukums, tips).
**Kad lietot:** Kad vajag pārskatu par visām noteikta tipa entītijām.
**Piemēri:**
```
list_entities(type="goal") → 6 mērķi (goal.m1–goal.m6)
list_entities(type="sys", subdomain="valoda") → 11 valodu tehnoloģiju sistēmas
list_entities(type="risk") → visi identificētie riski
list_entities() → pilns visu entītiju saraksts
```
---
### 2.6 `list_relations` — attiecību meklēšana
**Nolūks:** Atrast visas attiecības (šķautnes), kas saistītas ar konkrētu entītiju — ienākošās, izejošās vai abas.
**Parametri:**
- `id` (obligāts) — entītijas ID, piemēram: `domain.kultura-valoda`, `goal.m4`
- `direction` (neobligāts) — "in" (ienākošās), "out" (izejošās), "both" (abas, noklusējums)
**Atgriež:** Saraksts ar šķautnēm, katrai norādot `from`, `type`, `to` un kopējo skaitu.
**Kad lietot:** Ietekmes analīzē ("kuri pakalpojumi ir saistīti ar mērķi M4?"), atkarību kartēšanā, audita nolūkos.
**Piemērs:**
```
list_relations(id="domain.kultura-valoda", direction="out")
→ 95 šķautnes: has_goal→goal.m1..m6, has_system→sys.kultura.01..sys.valoda.11, u.c.
```
> **Piezīme:** Pašreizējā versijā visas šķautnes iziet no `domain.kultura-valoda`. Lai atrastu, piemēram, visas valodu apakšjomas sistēmas, meklējiet `list_relations(id="domain.kultura-valoda")` un filtrējiet pēc tipa `has_system` un `to` prefiksa `sys.valoda.*`.
---
### 2.7 `subgraph` — apakšgrafa izgūšana
**Nolūks:** Sākot no vienas vai vairākām sēklas entītijām, apstaigāt attiecību grafu un atgriezt visas saistītās entītijas līdz noteiktam dziļumam.
**Parametri:**
- `seed_ids` (obligāts) — sākuma entītiju ID masīvs, piemēram: `["goal.m4"]` vai `["org.kisc", "org.lumii"]`
- `depth` (neobligāts) — apstaigāšanas dziļums (noklusējums 1, maks. 3)
**Atgriež:** Pilns mezglu un šķautņu saraksts — katra mezgla pilna informācija un visas savienojošās šķautnes.
**Kad lietot:** Lai iegūtu "apkārtnes karti" — visas entītijas, kas saistītas ar noteiktu komponentu.
**Piemērs:**
```
subgraph(seed_ids=["domain.kultura-valoda"], depth=1)
→ 96 mezgli, 95 šķautnes — viss domēna saturs
```
---
### 2.8 `get_view` — dokumenta skata izgūšana
**Nolūks:** Atgriež viena konkrēta dokumenta skata saturu Markdown formātā. Skati ir cilvēklasāmas dokumenta nodaļas.
**Parametri:**
- `view_id` (obligāts) — skata identifikators. Iespējamās vērtības:
- `01-ievads` — Ievads
- `02-esosas-arhitekturas-novertejums` — Esošās arhitektūras novērtējums
- `03-merki-un-principi` — Mērķi un principi
- `04-merk-arhitektura` — Mērķarhitektūra
- `05-cela-karte` — Ceļa karte
- `06-pielikums-komponentu-katalogs` — Komponentu katalogs
**Atgriež:** Markdown saturs ar virsrakstiem, tekstiem un atsaucēm uz reģistriem.
**Kad lietot:** Lai izlasītu noteiktu dokumenta nodaļu bez pilna dokumenta ģenerēšanas.
**Piemērs:**
```
get_view(view_id="03-merki-un-principi")
→ Markdown ar M1–M6 mērķu aprakstiem un arhitektūras principiem
```
---
### 2.9 `generate_document` — pilna dokumenta ģenerēšana
**Nolūks:** Reģenerēt pilnu cilvēklasāmo arhitektūras dokumentu no YAML reģistriem. Apvieno visus skatus ar inline reģistru paplašinājumiem vienotā Markdown vai JSON dokumentā.
**Parametri:**
- `format` (neobligāts) — "markdown" (noklusējums) vai "json"
- `sections` (neobligāts) — konkrētu sadaļu saraksts. Ja nav norādīts, ģenerē pilnu dokumentu.
- Pieejamās vērtības: `ievads`, `esosa`, `merki`, `arhitektura`, `celakarte`, `katalogs`
**Atgriež:** Pilns Markdown vai JSON dokuments, kas satur visas vai izvēlētās sadaļas ar iekļautiem reģistru datiem.
**Kad lietot:**
- Lai iegūtu aktuālu, pilnu mērķarhitektūras dokumentu
- Lai sagatavotu materiālu prezentācijai vai pārskatam
- Lai pārbaudītu, ka visi reģistri ir korekti un savstarpēji saskanīgi
**Piemēri:**
```
generate_document()
→ Pilns dokuments Markdown formātā (~50+ lappuses)
generate_document(format="json")
→ Strukturēts JSON ar katras sadaļas datiem
generate_document(sections=["merki", "arhitektura"])
→ Tikai mērķi/principi un mērķarhitektūras sadaļas
```
---
## 3) Tipisko jautājumu un atbilžu piemēri
Šajā sadaļā parādīts, kādus jautājumus var uzdot AI aģentam un kādus rīkus tas izmantos, lai atbildētu.
### 3.1 Vispārīgs pārskats
**Jautājums:** "Pastāsti par šo arhitektūru — kas tajā ir un kā tā ir organizēta?"
**AI aģenta darbība:** Izsauc `describe_model`, pēc tam `list_entities` vispārīgam pārskatam.
**Sagaidāmā atbilde:** Skaidrojums, ka tā ir Latvijas kultūras un valodu tehnoloģiju jomas mērķarhitektūra ar 2 apakšdomēniem, 6 mērķiem, ~30 pakalpojumiem, ~30 sistēmām u.c.
---
### 3.2 Konkrēta mērķa izpēte
**Jautājums:** "Kas ir M4 mērķis un kā tas attiecas uz latviešu valodas AI?"
**AI aģenta darbība:** Izsauc `get_entity(id="goal.m4")`.
**Sagaidāmā atbilde:** M4 ir "Latviešu valoda digitālajā laikmetā" — mērķis nodrošināt latviešu valodas klātbūtni MI risinājumos, attīstot valodas resursus, modeļus un pakalpojumus.
---
### 3.3 Sistēmu saraksts valodu jomā
**Jautājums:** "Kādas informācijas sistēmas ir valodu tehnoloģiju apakšjomā?"
**AI aģenta darbība:** Izsauc `list_entities(type="sys", subdomain="valoda")`.
**Sagaidāmā atbilde:** 11 sistēmu saraksts — valodu tehnoloģiju platforma, LVM-LV, mašīntulkošana, runas atpazīšana, runas sintēze, virtuālie asistenti, semantiskā meklēšana, teksta ģenerēšana, vieglās valodas rīki, valodu resursu pārvaldība, jaunu tehnoloģiju izmitināšanas vide.
---
### 3.4 Konkrētas sistēmas detaļas
**Jautājums:** "Pastāsti par Lielo latviešu valodas modeli — kas tas ir, kāds ir tā statuss?"
**AI aģenta darbība:** Izsauc `search(query="latviešu valodas modelis")` vai `get_entity(id="sys.valoda.02")`.
**Sagaidāmā atbilde:** sys.valoda.02 — "Lielais latviešu valodas modelis (LVM-LV)", statuss "Jauns", tiks izveidots un uzturēts kā valsts mēroga valodas infrastruktūras pamatelements.
---
### 3.5 Ietekmes analīze
**Jautājums:** "Kuras sistēmas un pakalpojumi ir saistīti ar kultūras domēnu?"
**AI aģenta darbība:** Izsauc `list_relations(id="domain.kultura-valoda", direction="out")`, filtrē pēc `has_system` un `has_service`.
**Sagaidāmā atbilde:** 21 kultūras sistēma un 16 kultūras pakalpojumi, katrs ar savu ID un saiti uz domēnu.
---
### 3.6 Risku analīze
**Jautājums:** "Kādi riski ir identificēti šajā arhitektūrā?"
**AI aģenta darbība:** Izsauc `list_entities(type="risk")`, pēc tam `get_entity` katram riskam.
**Sagaidāmā atbilde:** Risku saraksts ar aprakstiem, iespējamību, ietekmi un mazināšanas pasākumiem.
---
### 3.7 Pilna dokumenta ģenerēšana
**Jautājums:** "Saģenerē pilnu mērķarhitektūras dokumentu."
**AI aģenta darbība:** Izsauc `generate_document(format="markdown")`.
**Sagaidāmā atbilde:** Pilns Markdown dokuments ar visām 6 sadaļām — ievads, esošā situācija, mērķi un principi, mērķarhitektūra, ceļa karte, komponentu katalogs.
---
### 3.8 Organizāciju meklēšana
**Jautājums:** "Kas ir KISC un kāda ir tā loma?"
**AI aģenta darbība:** Izsauc `search(query="KISC", kind="org")` vai `get_entity(id="org.kisc")`.
**Sagaidāmā atbilde:** Kultūras informācijas sistēmu centrs — domēna īstenotājs, atbildīgs par kultūras IS uzturēšanu un attīstību.
---
### 3.9 Ceļa kartes izpēte
**Jautājums:** "Kādi ir plānotie pasākumi un to laika grafiks?"
**AI aģenta darbība:** Izsauc `get_view(view_id="05-cela-karte")` vai `list_entities(type="rm")`.
**Sagaidāmā atbilde:** Ceļa kartes sadaļa ar pasākumu sarakstu, termiņiem un atbildīgajām organizācijām.
---
### 3.10 Servera uzticamības pārbaude
**Jautājums:** "Vai šis serveris ir uzticams? Kas to pārvalda?"
**AI aģenta darbība:** Izsauc `identify`.
**Sagaidāmā atbilde:** Serveris pieder KISC (Latvijas valsts iestāde), ir verificēts ar VeriTrust akreditāciju, DID identitāte ir `did:web:llm.kis.gov.lv`, atbilst MCPF Layer 1.
---
## 4) Kā AI aģents saprot arhitektūras kontekstu
Kad AI aģents (piemēram, Claude) pieslēdzas šim MCP serverim, tas **automātiski saņem** šādu konteksta informāciju:
1. **Servera apraksts:** "MCP server for Latvian cultural heritage and language technology architecture documentation" — tas uzreiz norāda, ka dati ir par Latvijas kultūras un valodu tehnoloģiju jomu.
2. **Rīku apraksti:** Katrs rīks satur detalizētu `description` lauku, kas palīdz AI aģentam saprast, kad un kā to lietot. Piemēram, `search` rīka aprakstā ir minēts: "Search KISC architecture documentation for Latvian government cultural digitalization. Contains: strategic goals (M1-M6), organizations (KISC, LNB, LUMII, ministries, museums)..." — tas dod aģentam bagātu kontekstu.
3. **Entītiju ID shēmas:** Rīku parametru aprakstos ir norādīti piemēri (`goal.m4`, `org.kisc`, `sys.valoda.01`), kas aģentam palīdz konstruēt pareizus vaicājumus.
4. **Uzticamības metadati:** Inicializācijas laikā serveris atgriež MCPF metadatus ar DID, akreditācijas un verifikācijas URL — AI aģents var novērtēt datu avota uzticamību.
Tādējādi AI aģentam **nav nepieciešama ārēja apmācība** — konteksts tiek saņemts tieši no servera, un aģents var sākt atbildēt uz jautājumiem par arhitektūru nekavējoties.
---
## 5) Dokumenta ģenerēšanas detalizēta procedūra
### 5.1 Ģenerēšanas mehānisms
Dokumenta ģenerēšana apvieno divus avotus:
```
views/ (Markdown struktūra) + registers/ (YAML dati) → Pilns dokuments
```
Process:
1. Tiek nolasīts katrs skats (`01-ievads.md`, `02-esosas-...md`, ..., `06-pielikums-...md`)
2. Skatā atrastās reģistru atsauces (backtick formātā) tiek aizstātas ar attiecīgo YAML reģistru saturu, formatētu kā tabulas vai saraksti
3. Rezultāts tiek apvienots vienotā dokumentā
### 5.2 Ģenerēšana caur komandrindu
```bash
cd mcp
REPO_ROOT="$(pwd)/.." \
DOMAIN_DIR="domains/kultura-valoda" \
OUT_FILE="KISC-merkarhitektura-apraksts.md" \
npm run gen:doc
```
### 5.3 Ģenerēšana caur MCP (AI aģents)
```
# Pilns dokuments (visas sadaļas)
generate_document(format="markdown")
# Tikai ievads un mērķi
generate_document(format="markdown", sections=["ievads", "merki"])
# JSON formāts mašīnapstrādei
generate_document(format="json")
# Tikai mērķarhitektūra un katalogs
generate_document(format="markdown", sections=["arhitektura", "katalogs"])
```
### 5.4 Sadaļu identifikatori
| Sadaļas ID | Skata fails | Saturs |
|---|---|---|
| `ievads` | `01-ievads.md` | Ievads, tvērums, termini, saīsinājumi |
| `esosa` | `02-esosas-arhitekturas-novertejums.md` | Esošās situācijas novērtējums |
| `merki` | `03-merki-un-principi.md` | Mērķi M1–M6 un 4 arhitektūras principi |
| `arhitektura` | `04-merk-arhitektura.md` | Funkcijas, pakalpojumi, IR, sistēmas |
| `celakarte` | `05-cela-karte.md` | Ceļa karte, riski, mijiedarbības |
| `katalogs` | `06-pielikums-komponentu-katalogs.md` | Pilns komponentu katalogs |
### 5.5 Ģenerētā dokumenta struktūra
Pilns dokuments satur aptuveni šādu struktūru:
```
1. Ievads
1.1 Dokumenta nolūks un mērķauditorija
1.2 Domēna arhitektūras tvērums
1.3 Termini un saīsinājumi
1.4 Saistītie dokumenti
2. Esošās arhitektūras novērtējums
2.1 Kultūras apakšjomas esošā situācija
2.2 Valodu tehnoloģiju esošā situācija
2.3 Esošo sistēmu novērtējums
3. Mērķi un principi
3.1 Stratēģiskie mērķi (M1–M6)
3.2 Arhitektūras principi (P1–P4)
4. Mērķarhitektūra
4.1 Funkcijas (6 gab.)
4.2 Pakalpojumi (30 gab. — kultūra + valoda)
4.3 Informācijas resursi (23 gab.)
4.4 Sistēmas (32 gab.)
5. Ceļa karte
5.1 Pasākumu plāns
5.2 Attiecības un atkarības
5.3 Riski
5.4 Mijiedarbības ar citām jomām
6. Pielikums — komponentu katalogs
6.1 Pilns sistēmu saraksts
6.2 Pilns pakalpojumu saraksts
6.3 Pilns IR saraksts
```
---
## 6) MCPF uzticamības ietvars
Serveris implementē **MCPF (MCP Trust Framework) Layer 1** — uzticamības atklāšanu sesijas līmenī.
### 6.1 Inicializācijas metadati
Katras MCP sesijas sākumā serveris atgriež šādus metadatus:
```json
{
"_meta": {
"identity": {
"id": "did:web:llm.kis.gov.lv",
"service": { "mcp": "https://llm.kis.gov.lv/mcp" },
"keys": { "jwks_uri": "https://llm.kis.gov.lv/.well-known/jwks.json" }
},
"mcpf": {
"version": "0.1",
"entrypoint": {
"type": "manifest",
"url": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json"
}
},
"trust": {
"verifications": [{
"verifier": "did:web:veritrust.vc",
"type": ["VerifiableCredential", "MCPServerVerification"],
"covers": "did:web:llm.kis.gov.lv"
}]
}
}
}
```
### 6.2 Verifikācijas galapunkti
| URL | Saturs |
|---|---|
| `https://llm.kis.gov.lv/.well-known/jwks.json` | Publiskā atslēga JWT verifikācijai |
| `https://llm.kis.gov.lv/.well-known/did.json` | DID dokuments |
| `https://llm.kis.gov.lv/.well-known/mcp/manifest.json` | MCP manifests |
| `https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json` | Uzticamības reģistrs |
| `https://veritrust.vc/portal/mcp/credentials/...` | VeriTrust akreditācija |
---
## 7) Tehniskā informācija
### 7.1 Servera versija un konfigurācija
| Parametrs | Vērtība |
|---|---|
| Versija | 0.3.0 |
| Protokols | MCP 2024-11-05 |
| Transports | Streamable HTTP (`/mcp`) |
| Iespējas (capabilities) | `tools`, `resources` |
| Rīku skaits | 9 |
| Vide | Docker (Node.js 20, TypeScript) |
### 7.2 Palaišana lokāli (izstrādei)
```bash
cd mcp
npm install
npm run dev
```
Serveris būs pieejams: `http://localhost:8787/mcp`
### 7.3 Palaišana ar Docker
```bash
cd mcp
docker compose up -d
```
Vai no POC izvietojuma:
```bash
cd /opt/kisc-llm/poc/deploy
docker compose build --no-cache arch-mcp
./scripts/start.sh
```
### 7.4 Veselības pārbaude
```bash
curl https://llm.kis.gov.lv/health
# → {"status":"ok"}
```
### 7.5 MCP inicializācijas tests
```bash
curl -sS https://llm.kis.gov.lv/mcp \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":"2024-11-05",
"capabilities":{},
"clientInfo":{"name":"test","version":"1.0"}
}}'
```
Sagaidāmā atbilde: servera informācija ar versiju 0.3.0, 9 rīkiem un MCPF metadatiem.

View File

@@ -1,409 +0,0 @@
# IKT arhitektūra — Kultūras un valodu tehnoloģiju joma (SSOT)
Šis repozitorijs uztur jomas mērķarhitektūru kā kodu (**"Architecture as Code"**), kur visa informācija tiek glabāta strukturēti, versēti un atkārtojami ģenerējama.
Repozitorijs ir veidots kā **SSOT (Single Source of Truth)** — autoritatīvais saturs ir mašīnlasāmos YAML reģistros, bet cilvēklasāmais dokuments tiek ģenerēts no tiem. AI aģenti un automatizācijas rīki piekļūst šiem datiem caur MCP serveri.
---
## 1) Pamatideja (Architecture as Code)
Šajā repozitorijā arhitektūra tiek uzturēta šādi:
- **Skati (`views/`)** — cilvēklasāms dokuments (Markdown), kas nosaka dokumenta struktūru un stāstījumu
- **Reģistri (`registers/`)** — mašīnlasāmi dati (YAML), kas ir autoritatīvais avots visām entītijām
- **Diagrammas (`diagrams/`)** — Mermaid (šobrīd TODO vietturi)
- **Attiecības (`registers/99-relations/edges.yaml`)** — grafiks starp entītijām (95 šķautnes)
Repozitorijā visas entītijas tiek uzturētas kā **vienumi** (*items*), kuriem ir stabili identifikatori (ID) un atribūti. Piemēram, `sys.valoda.02` ir "Lielais latviešu valodas modelis (LVM-LV)", `goal.m4` ir "Latviešu valoda digitālajā laikmetā".
---
## 2) Repozitorija struktūra
```
.
├── domains/
│ └── kultura-valoda/
│ ├── manifest.yaml # Domēna manifests
│ ├── views/ # Dokumenta skati (Markdown)
│ │ ├── 01-ievads.md
│ │ ├── 02-esosas-arhitekturas-novertejums.md
│ │ ├── 03-merki-un-principi.md
│ │ ├── 04-merk-arhitektura.md
│ │ ├── 05-cela-karte.md
│ │ └── 06-pielikums-komponentu-katalogs.md
│ ├── registers/ # SSOT reģistri (YAML)
│ │ ├── 00-meta/ # Termini, saīsinājumi, saistītie dokumenti
│ │ ├── 02-goals/ # Stratēģiskie mērķi (M1–M6)
│ │ ├── 03-organizations/ # Institūcijas, lomas, atbildības
│ │ ├── 04-functions/ # Jomas funkcijas
│ │ ├── 05-services/ # Pakalpojumi (kultūra + valoda)
│ │ ├── 06-information-resources/ # Informācijas resursi
│ │ ├── 07-systems/ # Informācijas sistēmas
│ │ ├── 08-roadmap/ # Ceļa karte un mijiedarbības
│ │ ├── 09-risks/ # Riski un mazināšanas pasākumi
│ │ └── 99-relations/ # Attiecību grafiks (edges.yaml)
│ └── diagrams/ # Mermaid diagrammas (TODO)
├── mcp/ # MCP serveris (AI aģentu interfeiss)
├── tools/
│ └── qa/ # Kvalitātes pārbaudes skripti
└── docs/
├── MCP.md # MCP servera dokumentācija
├── TRACEABILITY.md
└── REGENERATION.md
```
---
## 3) Skati (views/) — cilvēklasāms dokuments
**Atrašanās vieta:** `domains/kultura-valoda/views/`
Skati satur dokumenta nodaļu struktūru, virsrakstus un paskaidrojošu tekstu. Skatos ir atsauces uz reģistriem (backtick formātā), piemēram:
```md
`registers/03-organizations/organizations.yaml`
```
Svarīgi: skatos nav jāuztur manuālas tabulas ar daudz vienumiem. Skati norāda "ko rādīt", bet saturs tiek ņemts no reģistriem. Dokumenta ģenerēšanas laikā atsauces tiek aizstātas ar reģistru saturu.
Pieejamie skati:
| Skats | Apraksts |
|---|---|
| `01-ievads.md` | Ievads, tvērums, termini |
| `02-esosas-arhitekturas-novertejums.md` | Esošās arhitektūras novērtējums |
| `03-merki-un-principi.md` | Mērķi (M1–M6) un arhitektūras principi |
| `04-merk-arhitektura.md` | Mērķarhitektūra (funkcijas, pakalpojumi, sistēmas, IR) |
| `05-cela-karte.md` | Ceļa karte, riski, mijiedarbības |
| `06-pielikums-komponentu-katalogs.md` | Komponentu katalogs (pilns saraksts) |
## 4) Reģistri (registers/) — mašīnlasāmi dati
**Atrašanās vieta:** `domains/kultura-valoda/registers/`
Reģistri ir YAML faili, kas satur vienumus. Šie reģistri ir SSOT un ir autoritatīvais avots.
| Reģistrs | Saturs |
|---|---|
| `00-meta/abbreviations.yaml` | Saīsinājumi |
| `00-meta/terms.yaml` | Termini un definīcijas |
| `00-meta/related-documents.yaml` | Saistītie dokumenti |
| `00-meta/legal-acts.yaml` | Juridiskais regulējums (grupēts pa tēmām) |
| `02-goals/goal-m1.yaml` … `goal-m6.yaml` | Stratēģiskie mērķi |
| `03-organizations/organizations.yaml` | Institūcijas, lomas, atbildības |
| `04-functions/functions.yaml` | Jomas funkcijas (6 gab.) |
| `05-services/kultura-services.yaml` | Kultūras apakšjomas pakalpojumi (16 gab.) |
| `05-services/valoda-services.yaml` | Valodu tehnoloģiju pakalpojumi (14 gab.) |
| `06-information-resources/kultura-info-resources.yaml` | Kultūras informācijas resursi (15 gab.) |
| `06-information-resources/valoda-info-resources.yaml` | Valodu tehnoloģiju IR (8 gab.) |
| `07-systems/kultura-systems.yaml` | Kultūras IS (21 gab.) |
| `07-systems/valoda-systems.yaml` | Valodu tehnoloģiju IS (11 gab.) |
| `08-roadmap/roadmap.yaml` | Ceļa kartes pasākumi |
| `08-roadmap/interactions.yaml` | Mijiedarbība ar citām jomām |
| `09-risks/risks.yaml` | Riski un mazināšanas pasākumi |
| `99-relations/edges.yaml` | Attiecību grafiks (95 šķautnes) |
## 5) Attiecības (edges.yaml) — grafiks starp vienumiem
**Atrašanās vieta:** `domains/kultura-valoda/registers/99-relations/edges.yaml`
Šis fails satur attiecības starp vienumiem. Katrai attiecībai ir `from`, `type` un `to` lauki.
Pieejamie attiecību tipi:
| Tips | Nozīme |
|---|---|
| `has_goal` | Domēnam ir mērķis |
| `has_function` | Domēnam ir funkcija |
| `has_service` | Domēnam ir pakalpojums |
| `has_system` | Domēnam ir sistēma |
| `has_information_resource` | Domēnam ir informācijas resurss |
| `owns` / `manages` / `operates` | Organizācija ir īpašnieks / pārvaldītājs / operators |
| `depends_on` | Atkarība starp komponentēm |
| `implements` / `supports` / `enables` | Realizācijas un atbalsta saites |
| `regulates` / `complies_with` | Regulējuma saites |
Pašlaik visas 95 šķautnes ir tipa `domain.kultura-valoda → {entītija}`, veidojot zvaigznes topoloģiju. Nākotnē grafiks tiks papildināts ar organizāciju, sistēmu un pakalpojumu savstarpējām saitēm.
## 6) Diagrammas (diagrams/) — Mermaid TODO vietturi
**Atrašanās vieta:** `domains/kultura-valoda/diagrams/`
Diagrammas tiek uzturētas Mermaid formātā. Šobrīd tās ir vietturi (TODO), kas nākotnē tiks aizstātas ar ģenerētām vai manuāli veidotām vizualizācijām.
---
## 7) Kā ģenerēt cilvēklasāmo dokumentu no SSOT
Repozitorijs nodrošina pilna mērķarhitektūras dokumenta automātisku ģenerēšanu no reģistriem un skatiem.
### 7.1 Priekšnosacījumi
- Node.js 18+ (ieteicams Node 20)
- npm
### 7.2 Uzstādīšana
No repozitorija saknes:
```bash
cd mcp
npm install
```
### 7.3 Dokumenta ģenerēšana (komandrinda)
No repozitorija saknes:
```bash
cd mcp
REPO_ROOT="$(pwd)/.." DOMAIN_DIR="domains/kultura-valoda" OUT_FILE="KISC-merkarhitektura-apraksts.md" npm run gen:doc
```
Rezultāts: `KISC-merkarhitektura-apraksts.md` (repo saknē).
### 7.4 Dokumenta ģenerēšana caur MCP serveri (AI aģents)
Ja MCP serveris darbojas, jebkurš AI aģents var ģenerēt dokumentu, izsaucot rīku `generate_document`:
```
Rīks: generate_document
Parametri:
format: "markdown" — Markdown formāts cilvēklasāmam dokumentam
format: "json" — JSON formāts mašīnapstrādei
sections: ["ievads", "arhitektura", "celakarte"] — konkrētas sadaļas (neobligāts)
```
Pilna dokumenta ģenerēšana (visas sadaļas):
```
generate_document(format="markdown")
```
Atsevišķu sadaļu ģenerēšana:
```
generate_document(format="markdown", sections=["ievads", "merki"])
```
Pieejamās sadaļas: `ievads`, `esosa`, `merki`, `arhitektura`, `celakarte`, `katalogs`.
Ģenerēšanas laikā tiek apvienoti visi skati (`views/`) ar tajā atsaucēto reģistru (`registers/`) saturu, veidojot vienotu, pilnu dokumentu.
### 7.5 Svarīgi par ģenerēto dokumentu
- Ģenerētais dokuments ir **build artifact** — tas netiek rediģēts manuāli.
- Izmaiņas vienmēr veic **reģistros** vai **skatos**, pēc tam atkārtoti ģenerē.
- Katru reizi ģenerējot, dokuments atspoguļo aktuālo SSOT stāvokli.
---
## 8) Kvalitātes kontrole (QA)
Repo ietver QA pārbaudes, lai nepieļautu satura zudumu.
No repozitorija saknes:
```bash
tools/qa/check_nonempty_registers.sh
tools/qa/check_generated_doc_headings.sh KISC-merkarhitektura-apraksts.md
tools/qa/check_stakeholder_count.sh KISC-merkarhitektura-apraksts.md
```
Ja QA neiziet — jālabo reģistri vai skati. Nedrīkst "saīsināt" saturu, lai tikai testi izietu.
---
## 9) MCP serveris — AI aģentu interfeiss
MCP (Model Context Protocol) serveris nodrošina AI aģentiem strukturētu piekļuvi arhitektūras SSOT datiem. Serveris darbojas kā tikai-lasīšanas slānis — tas nemaina SSOT saturu.
Detalizēta MCP servera dokumentācija, rīku apraksti, lietošanas scenāriji un piemēri ir pieejami atsevišķā dokumentā:
📖 **[docs/MCP.md](docs/MCP.md)** — pilna MCP servera dokumentācija
### 9.1 Īsumā par iespējām
MCP serveris piedāvā **9 rīkus**, kas ļauj AI aģentiem:
- Meklēt un izgūt jebkuru arhitektūras entītiju (mērķi, sistēmas, pakalpojumus, IR, riskus u.c.)
- Analizēt attiecības starp entītijām (grafu vaicājumi)
- Ģenerēt pilnu mērķarhitektūras dokumentu tieši no SSOT
- Pārlūkot dokumenta skatus un metamodeli
- Verificēt servera identitāti un uzticamību (MCPF trust framework)
### 9.2 Kā pieslēgties
MCP serveris ir pieejams adresē:
```
https://llm.kis.gov.lv/mcp
```
Pieslēgšanās no Claude.ai: **Settings** → **Connectors** → **Add** → ievadiet URL.
---
## 10) MCP autentifikācija
MCP serveris atbalsta **trīs autentifikācijas metodes**, kas var darboties vienlaicīgi (`AUTH_MODE=both`):
1. **Statiskais Bearer tokens** — vienkārša fiksēta atslēga
2. **OAuth 2.1 Authorization Code + PKCE** — interaktīva pieeja (Claude Desktop, pārlūkprogramma)
3. **OAuth 2.0 client_credentials** — mašīna-pret-mašīnu (API, skripti)
### 10.1 Statiskais Bearer tokens (vienkāršā pieeja)
Iestatiet `.env`:
```env
AUTH_REQUIRED=true
AUTH_MODE=static # vai "both", lai darbotos visi veidi
STATIC_BEARER_TOKEN=my-secret-token-here
```
Pieprasījuma piemērs:
```bash
curl -H "Authorization: Bearer my-secret-token-here" \
http://localhost:8787/mcp
```
Šī metode ir piemērota iekšējai testēšanai un vienkāršiem integrācijas gadījumiem.
### 10.2 OAuth 2.1 — Authorization Code + PKCE (Claude Desktop)
Šo plūsmu automātiski izmanto **Claude Desktop** un citi MCP klienti, kas atbalsta OAuth 2.1.
Serveris implementē pilnu MCP autorizācijas specifikāciju:
- **RFC 9728** — Protected Resource Metadata (`/.well-known/oauth-protected-resource`)
- **RFC 8414** — Authorization Server Metadata (`/.well-known/oauth-authorization-server`)
- **RFC 7591** — Dynamic Client Registration (`/register`)
- **PKCE S256** — obligāts drošības mehānisms
#### Konfigurācija (.env)
```env
AUTH_REQUIRED=true
AUTH_MODE=both
SERVER_PUBLIC_URL=https://llm.kis.gov.lv:8787
# Pieteikšanās dati autorizācijas lapā
OAUTH_LOGIN_USERNAME=admin
OAUTH_LOGIN_PASSWORD=change-me
```
#### Pieslēgšanās no Claude Desktop
1. Claude Desktop → **Settings** → **Connectors** → **Add**
2. Ievadiet MCP servera URL: `https://llm.kis.gov.lv:8787/mcp`
3. Claude automātiski atklās OAuth galapunktus un atvērs pieteikšanās lapu pārlūkprogrammā
4. Ievadiet `OAUTH_LOGIN_USERNAME` / `OAUTH_LOGIN_PASSWORD`
5. Pēc autorizācijas Claude saņem JWT tokenu un izmanto to turpmākajos pieprasījumos
#### Plūsmas secība (tehniski)
```
Claude Desktop MCP Server
│ │
├── POST /mcp (bez tokena) ──────────►│
│◄── 401 + WWW-Authenticate ─────────┤
│ │
├── GET /.well-known/ │
│ oauth-protected-resource ───────►│
│◄── { authorization_servers: [...] } │
│ │
├── GET /.well-known/ │
│ oauth-authorization-server ─────►│
│◄── { endpoints, PKCE, ... } │
│ │
├── POST /register ─────────────────►│
│◄── { client_id, client_secret } │
│ │
├── 🌐 Opens browser → /authorize ──►│
│ (user logs in with credentials) │
│◄── 302 redirect with ?code=... ────┤
│ │
├── POST /token (code + PKCE) ──────►│
│◄── { access_token, refresh_token } │
│ │
├── POST /mcp + Bearer token ────────►│
│◄── MCP response ──────────────────┤
```
### 10.3 OAuth 2.0 — client_credentials (mašīna-pret-mašīnu)
Skriptiem un API integrācijām, kas neizmanto pārlūkprogrammu.
#### Konfigurācija (.env)
```env
OAUTH_CLIENT_ID=mcp-service-account
OAUTH_CLIENT_SECRET=change-me-to-a-strong-secret
```
#### Tokena iegūšana
```bash
# 1. Iegūt JWT tokenu
TOKEN=$(curl -s -X POST https://llm.kis.gov.lv:8787/token \
-d grant_type=client_credentials \
-d client_id=mcp-service-account \
-d client_secret=change-me-to-a-strong-secret \
| jq -r .access_token)
# 2. Lietot tokenu MCP pieprasījumos
curl -H "Authorization: Bearer $TOKEN" \
https://llm.kis.gov.lv:8787/mcp
```
Tokens ir derīgs 1 stundu (3600 s). Pēc termiņa beigām iegūstiet jaunu.
### 10.4 Galapunkti
| Galapunkts | Metode | Auth | Apraksts |
|---|---|---|---|
| `/health` | GET | Nē | Veselības pārbaude |
| `/.well-known/oauth-protected-resource` | GET | Nē | RFC 9728 — resursa metadati |
| `/.well-known/oauth-authorization-server` | GET | Nē | RFC 8414 — autorizācijas servera metadati |
| `/.well-known/jwks.json` | GET | Nē | Publiskā atslēga JWT verifikācijai |
| `/register` | POST | Nē | RFC 7591 — klienta dinamiskā reģistrācija |
| `/authorize` | GET/POST | Nē | Autorizācijas lapa (login + consent) |
| `/token` | POST | Nē | Tokenu izsniegšana (auth code, client_credentials, refresh) |
| `/mcp` | POST/GET/DELETE | **Jā** | MCP servera galapunkts |
### 10.5 AUTH_MODE vērtības
| Vērtība | Apraksts |
|---|---|
| `both` (noklusējums) | Pieņem statisko tokenu, OAuth JWT un auth code tokenu |
| `static` | Tikai statiskais Bearer tokens |
| `jwks` | Tikai OAuth/OIDC JWT (validēts ar JWKS) |
### 10.6 Ārējais identitātes nodrošinātājs (neobligāts)
Ja izmantojat ārēju IDP (Auth0, Keycloak, Azure AD) papildus iebūvētajam OAuth:
```env
OAUTH_JWKS_URL=https://your-idp/.well-known/jwks.json
OAUTH_ISSUER=https://your-idp/
OAUTH_AUDIENCE=your-api-audience
```
---
## 11) Darba kārtība (ieteicamais process)
1. Veic izmaiņas reģistros (`registers/`)
2. Ja vajag — koriģē struktūru skatos (`views/`)
3. Ģenerē dokumentu (`npm run gen:doc` vai `generate_document` caur MCP)
4. Palaid QA (`tools/qa/`)
5. Commit
---
## 12) Papildu materiāli
- **[docs/MCP.md](docs/MCP.md)** — MCP servera dokumentācija (rīku apraksti, lietošanas scenāriji, piemēri)
- `docs/TRACEABILITY.md` — izsekojamība starp sākotnējo dokumentu, reģistriem un ģenerēto rezultātu
- `docs/REGENERATION.md` — regenerācijas procedūra un noteikumi

View File

@@ -1,13 +0,0 @@
version: "3.9"
services:
arch-mcp:
build:
context: .
dockerfile: mcp/Dockerfile
ports:
- "${MCP_PORT:-8787}:8787"
env_file:
- .env
environment:
- ARCH_ROOT=/app/domains
- REPO_ROOT=${REPO_ROOT:-/app}

View File

@@ -1,266 +0,0 @@
# MCP Best Practices, Market Reality, and Cached Knowledge Rationale
## Executive Summary
MCP (Model Context Protocol) has rapidly evolved from Anthropic's November 2024 release to become the de-facto standard for AI-to-tool integration, with adoption by OpenAI in March 2025 and donation to the Linux Foundation's Agentic AI Foundation in December 2025. However, its effectiveness for document-centric use cases depends heavily on implementation patterns. This analysis addresses whether MCP with cached/pre-computed knowledge layers makes sense versus alternatives.
---
## 1. Market Best Practices and Real Examples
### 1.1 Production MCP Document Implementations
| Implementation | Approach | Key Features |
|----------------|----------|--------------|
| **Chroma MCP Server** | Vector-native semantic search | Sets the standard for semantic document management using vector search, supporting both ephemeral and persistent storage |
| **Knowledge-Base-MCP (Puran)** | Production-grade RAG | Agent-directed hybrid retrieval with auto mode choosing among dense, hybrid, sparse, and rerank routes; when scores are low, returns abstain so client can decide whether to retry |
| **AWS Bedrock KB + MCP** | Enterprise knowledge bases | Connects to Amazon Bedrock Knowledge Bases for semantic search capabilities, providing unified access pattern regardless of underlying AWS service |
| **Basic Memory** | Local-first knowledge graphs | Local-first knowledge management system that builds a semantic graph from Markdown files, enabling persistent memory across conversations |
| **AtScale MCP** | Semantic layer for BI | Exposes semantic models to any MCP-compatible AI agent with real-time model discovery where new models become queryable instantly after deployment |
### 1.2 Dominant Architecture Patterns
**Pattern A: MCP + Vector Store (Most Common)**
```
Documents → Chunking → Embeddings → Vector DB → MCP Server → LLM Client
```
Prepare the knowledge base by collecting and preprocessing data, chunk into reasonably sized pieces, embed using an embedding model, and load into a vector database like FAISS, Weaviate, or Pinecone
**Pattern B: MCP + Knowledge Graph**
```
Documents → Entity Extraction → Graph DB → MCP Server → LLM Client
```
The MCP server exposes Graphiti's core capabilities including episode management, entity management, search capabilities with semantic and hybrid search for facts and node summaries
**Pattern C: MCP + Hybrid (Best Practice)**
```
Documents → [Vectors + Graph + BM25] → Unified MCP Interface → LLM Client
```
Combines vector search plus BM25 lexical search using RRF, then reranks. Best for complex queries with both conceptual and specific keyword requirements
---
## 2. When MCP Beats Alternatives
### 2.1 MCP vs. Direct Document Attachment
| Scenario | Winner | Rationale |
|----------|--------|-----------|
| **One-off Q&A on small docs** | Direct Attachment | Zero setup, full context visible |
| **Repeated queries on same corpus** | MCP | Avoid re-processing, selective retrieval |
| **Multi-user access** | MCP | AI applications can seamlessly access up-to-date information and context as needed through unified protocol |
| **Agentic workflows** | MCP | Enables "agentic" AI systems that can autonomously interact with multiple systems, retrieve the latest information, and even take actions |
| **Version-controlled knowledge** | MCP | Git-based updates, deterministic ingestion |
### 2.2 MCP vs. Traditional RAG API
| Aspect | Traditional RAG | MCP-wrapped RAG | Advantage |
|--------|-----------------|-----------------|-----------|
| **Standardization** | Custom per-service | Universal protocol | MCP |
| **Tool Discovery** | Manual documentation | Auto-discovery | MCP |
| **Multi-source** | N×M integrations | M+N integrations | MCP flips the N×M problem to an M+N model: tool providers implement one standard MCP server, and AI app developers implement MCP client support once |
| **Agent autonomy** | Fixed pipeline | LLM itself makes contextual decisions about how to interact with the data, determining query strategy and prompt formulation | MCP |
### 2.3 When NOT to Use MCP
1. **Simple, one-time document analysis** - Direct attachment wins
2. **Highly dynamic real-time data** - Direct API calls may be simpler
3. **No multi-client requirement** - Overhead not justified
4. **Prototype/exploratory phase** - Start simple, add MCP later
---
## 3. Cached/Pre-computed Knowledge: The Rationale
### 3.1 What is "Cached Knowledge" in MCP Context?
Three tiers of pre-computation that MCP servers can provide:
| Tier | What's Cached | When Generated | Example |
|------|---------------|----------------|---------|
| **1. Embeddings** | Vector representations | At ingestion | Semantic search index |
| **2. Summaries** | Condensed content | At ingestion or scheduled | "This document describes X" |
| **3. Knowledge Graph** | Entity-relationship extractions | At ingestion | "Goal M1 → implemented by → System X" |
### 3.2 Rationale FOR Cached Knowledge Layers
**A. Token Economics**
Simply stuffing all potentially relevant data into the prompt is inefficient and sometimes impossible. MCP enables dynamically retrieving just-in-time context from external sources as needed instead of front-loading everything
Cost comparison (200-page document):
- Direct attachment: ~100K tokens every query = $0.30-3.00/query
- MCP with embeddings: ~2K tokens retrieved = $0.006-0.06/query
- MCP with summaries: ~500 tokens = $0.0015-0.015/query
**B. Response Quality**
Tune the number of retrieved documents included in the prompt - often 3-5 good snippets are better than 10 - sometimes using too many can overwhelm the model
Pre-computed summaries ensure the LLM gets:
- Condensed, relevant context
- Pre-extracted key facts
- Relationship context from knowledge graphs
**C. Caching Benefits**
Implement caching at multiple levels. Cache the results of common retrieval queries — for example, if many users ask "What is the refund policy?", you can cache the answer or at least the retrieved document so the agent doesn't vector-search the same question repeatedly
Implementing advanced caching (exact, semantic, task-aware) to avoid redundant API calls, tracking and optimizing costs across providers
**D. Offline/Latency Benefits**
Pre-computed knowledge enables:
- Faster response times (no embedding at query time)
- Offline capability (no API calls for embeddings)
- Deterministic behavior (same query = same retrieval)
### 3.3 Implementation: Hierarchical Memory
Provides hierarchical memory storage with three-tier compression (chunks, micro-summaries, meta-summaries)
Example architecture:
```yaml
# Pre-computed knowledge layers
raw_chunks:
- content: "Full text chunk"
- embedding: [0.1, 0.2, ...]
micro_summaries:
- chunk_ids: [1, 2, 3]
- summary: "These chunks describe the cultural heritage preservation goals"
- keywords: ["heritage", "preservation", "M1"]
meta_summaries:
- scope: "02-goals subdomain"
- summary: "Six strategic goals (M1-M6) covering preservation through cybersecurity"
- entity_count: 6
```
### 3.4 Rationale AGAINST Over-caching
| Risk | Description | Mitigation |
|------|-------------|------------|
| **Staleness** | Summaries out of sync with source | Git-triggered regeneration |
| **Loss of nuance** | Summarization loses detail | Keep raw chunks accessible |
| **Hallucination risk** | LLM-generated summaries may be wrong | Human review for critical content |
| **Storage cost** | Multiple representations | Tiered storage, compress cold data |
---
## 4. Recommended Architecture for KISC Use Case
### 4.1 Current State Analysis
Your Architecture-as-Code repo has:
- ✅ Structured YAML entities (good for knowledge graph)
- ✅ Explicit relationships in `edges.yaml`
- ❌ No vector embeddings
- ❌ No pre-computed summaries
- ❌ Primitive substring search (not semantic)
### 4.2 Proposed Enhanced Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ KISC Architecture MCP Server │
├─────────────────────────────────────────────────────────────────┤
│ Layer 1: Raw Data │
│ ├── YAML entities (goals, systems, services, etc.) │
│ └── Markdown documentation │
├─────────────────────────────────────────────────────────────────┤
│ Layer 2: Pre-computed Knowledge (NEW) │
│ ├── embeddings.index (vector search via FAISS/Qdrant) │
│ ├── summaries.yaml (entity-level summaries in Latvian) │
│ ├── knowledge_graph.json (Neo4j-style graph export) │
│ └── glossary.yaml (term definitions for natural language) │
├─────────────────────────────────────────────────────────────────┤
│ Layer 3: MCP Tools (ENHANCED) │
│ ├── semantic_search(query) → vector similarity │
│ ├── get_entity(id) → full entity + related context │
│ ├── explain_concept(term) → natural language explanation │
│ ├── find_relationships(entity) → graph traversal │
│ ├── summarize_domain(domain) → pre-computed summary │
│ └── natural_query(latvian_question) → LLM-friendly response │
└─────────────────────────────────────────────────────────────────┘
```
### 4.3 Pre-computation Pipeline
```bash
# On every git commit to Architecture-as-Code:
1. Load all YAML entities
2. Generate embeddings for each entity (title + description)
3. Generate micro-summaries for each subdomain
4. Build/update knowledge graph from edges.yaml
5. Create glossary from all entity titles/codes
6. Store in /mcp/cache/ directory
```
### 4.4 Query Flow (Enhanced)
**User asks:** "Kādas sistēmas realizē valodas tehnoloģiju mērķi?"
(What systems implement the language technology goal?)
**Current behavior:** Returns `[]` (no match for Latvian natural language)
**Enhanced behavior:**
1. `natural_query` tool receives Latvian question
2. Extracts intent: "systems implementing language technology goal"
3. Maps to goal.m4 (Latviešu valoda digitālajā laikmetā)
4. Traverses knowledge graph: goal.m4 → implemented_by → [sys.valoda.01, sys.valoda.02, ...]
5. Returns pre-computed summary + entity list
---
## 5. Decision Framework: When to Add Cached Knowledge
| Question | If YES | If NO |
|----------|--------|-------|
| Will multiple users query the same corpus? | Add caching | Skip |
| Is query latency critical (<1s)? | Add embeddings | Direct retrieval OK |
| Do users ask in natural language (not IDs)? | Add semantic search | ID-based lookup OK |
| Is the corpus >100 entities? | Add summaries | Full scan OK |
| Do queries require cross-entity reasoning? | Add knowledge graph | Flat search OK |
| Is the corpus updated less than daily? | Pre-compute aggressively | Real-time generation |
---
## 6. Conclusion
### Is MCP with Cached Knowledge Worth It?
**YES, when:**
- You have a stable, structured knowledge base (like Architecture-as-Code)
- Multiple consumers need consistent access
- Natural language queries are required
- Token cost optimization matters
- Cross-entity reasoning is needed
**NO, when:**
- One-time document analysis
- Rapidly changing data (real-time feeds)
- Simple keyword lookup suffices
- No multi-client requirement
### For KISC Specifically:
Your Architecture-as-Code approach is **fundamentally correct** but needs:
1. **Semantic search layer** (embeddings for Latvian content)
2. **Pre-computed summaries** (domain/subdomain level)
3. **Natural language interface** (Latvian query handling)
4. **Enhanced MCP tools** (beyond primitive substring search)
The investment in these layers will pay off as the architecture grows and more stakeholders (internal teams, external auditors, automated agents) need to query it.
---
## References
- Model Context Protocol Specification: https://modelcontextprotocol.io/specification
- MCP Server Registry: https://github.com/modelcontextprotocol/servers
- Agentic RAG + MCP Integration Guide: https://becomingahacker.org/integrating-agentic-rag-with-mcp-servers
- AWS MCP Implementation: https://aws.amazon.com/blogs/machine-learning/unlocking-the-power-of-model-context-protocol-mcp-on-aws/

View File

@@ -1 +0,0 @@
placeholder.git

View File

@@ -1,19 +0,0 @@
FROM node:20-alpine
WORKDIR /app
# Copy MCP server code
COPY mcp/package.json mcp/tsconfig.json /app/mcp/
COPY mcp/src /app/mcp/src
# Copy architecture data (SSOT)
COPY domains /app/domains
WORKDIR /app/mcp
RUN npm ci || npm install
RUN npm run build
ENV ARCH_ROOT=/app/domains
EXPOSE 8787
CMD ["npm", "run", "start"]

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +0,0 @@
{
"name": "kisc-arch-mcp",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/index.js",
"gen:doc": "ts-node src/generateDoc.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"dotenv": "^16.6.1",
"express": "^4.21.2",
"fast-glob": "^3.3.2",
"jose": "^5.10.0",
"js-yaml": "^4.1.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.17.0",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}

View File

@@ -1,285 +0,0 @@
import fs from "node:fs/promises";
import path from "node:path";
import yaml from "js-yaml";
type AnyObj = Record<string, any>;
const REPO_ROOT = process.env.REPO_ROOT || path.resolve(__dirname, "../../");
const DOMAIN_DIR = process.env.DOMAIN_DIR || "domains/kultura-valoda";
const OUT_FILE = process.env.OUT_FILE || "KISC-merkarhitektura-apraksts.md";
async function readText(rel: string): Promise<string> {
const abs = path.join(REPO_ROOT, rel);
return fs.readFile(abs, "utf-8");
}
async function readYaml(rel: string): Promise<AnyObj> {
const abs = path.join(REPO_ROOT, rel);
const raw = await fs.readFile(abs, "utf-8");
const obj = yaml.load(raw);
if (!obj || typeof obj !== "object") return {};
return obj as AnyObj;
}
function mdEscape(s: any): string {
const t = (s ?? "").toString();
return t.replace(/\r?\n/g, " ").replace(/\|/g, "\\|").trim();
}
function mdTable(headers: string[], rows: string[][]): string {
const h = `| ${headers.join(" | ")} |`;
const sep = `| ${headers.map(() => "---").join(" | ")} |`;
const body = rows.map((r) => `| ${r.join(" | ")} |`).join("\n");
return [h, sep, body].filter(Boolean).join("\n");
}
function isRegisterRefLine(line: string): string | null {
// Matches: `registers/..../file.yaml` or `registers/..../file.yml`
const m = line.match(/`(registers\/[^`]+\.(yaml|yml))`/i);
return m?.[1] ?? null;
}
function isDiagramRefLine(line: string): string | null {
const m = line.match(/`(diagrams\/[^`]+\.(mmd|png|svg|pdf))`/i);
return m?.[1] ?? null;
}
function renderAbbreviations(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
const rows = items.map((it: any) => [mdEscape(it.abbr), mdEscape(it.meaning)]);
return mdTable(["Saīsinājums", "Nozīme"], rows);
}
function renderTerms(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
// Accept both {term, meaning} and {name, definition}
const rows = items.map((it: any) => [
mdEscape(it.term ?? it.name ?? it.id ?? ""),
mdEscape(it.meaning ?? it.definition ?? it.description ?? ""),
]);
return mdTable(["Termins", "Skaidrojums"], rows);
}
function renderRelatedDocs(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
const rows = items.map((it: any) => [
mdEscape(it.id),
mdEscape(it.title),
mdEscape(it.date ?? ""),
mdEscape(it.type ?? ""),
mdEscape(it.relevance ?? ""),
]);
return mdTable(["ID", "Nosaukums", "Datums", "Veids", "Saistība / nozīme"], rows);
}
function renderOrganizations(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
const rows = items.map((it: any) => [
mdEscape(it.id),
mdEscape(it.title),
mdEscape(it.role ?? ""),
mdEscape(Array.isArray(it.responsibilities) ? it.responsibilities.join("; ") : ""),
mdEscape(it.status ?? ""),
]);
return mdTable(["ID", "Organizācija", "Loma", "Atbildība (kopsavilkums)", "Statuss"], rows);
}
function renderGenericItemsTable(obj: AnyObj, preferredCols: string[] | null = null): string {
const items = Array.isArray(obj.items) ? obj.items : [];
if (!items.length) return "_(tukšs reģistrs)_";
const allKeys = new Set<string>();
for (const it of items) {
if (it && typeof it === "object") Object.keys(it).forEach((k) => allKeys.add(k));
}
const cols = preferredCols
? preferredCols.filter((c) => allKeys.has(c)).concat([...allKeys].filter((k) => !preferredCols.includes(k)))
: [...allKeys];
const headers = cols.map((c) => c);
const rows = items.map((it: any) =>
cols.map((c) => {
const v = it?.[c];
if (Array.isArray(v)) return mdEscape(v.join(", "));
if (v && typeof v === "object") return mdEscape(JSON.stringify(v));
return mdEscape(v ?? "");
})
);
return mdTable(headers, rows);
}
function renderLegalActs(obj: AnyObj): string {
const groups = Array.isArray(obj.groups) ? obj.groups : [];
if (!groups.length) return "_(tukšs reģistrs)_";
const out: string[] = [];
for (const g of groups) {
out.push(`### ${mdEscape(g.title ?? g.id ?? "")}`.trim());
const items = Array.isArray(g.items) ? g.items : [];
if (!items.length) {
out.push("_(nav ierakstu)_");
out.push("");
continue;
}
for (const it of items) {
const ref = mdEscape(it.ref ?? "");
const notes = mdEscape(it.notes ?? "");
out.push(`- **${ref}** — ${notes}`.trim());
}
out.push("");
}
return out.join("\n").trim();
}
function renderRoadmap(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
if (!items.length) return "_(tukšs reģistrs)_";
const rows = items.map((it: any) => [
mdEscape(it.id),
mdEscape(it.title),
mdEscape(it.timeframe ?? ""),
mdEscape(Array.isArray(it.owner_orgs) ? it.owner_orgs.join(", ") : ""),
mdEscape(Array.isArray(it.depends_on) ? it.depends_on.join(", ") : ""),
mdEscape(Array.isArray(it.outputs) ? it.outputs.join("; ") : ""),
]);
return mdTable(["ID", "Pasākums", "Laika posms", "Atbildīgie", "Atkarības", "Rezultāti"], rows);
}
function renderRisks(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
if (!items.length) return "_(tukšs reģistrs)_";
const rows = items.map((it: any) => [
mdEscape(it.id),
mdEscape(it.risk),
mdEscape(it.likelihood ?? ""),
mdEscape(it.impact ?? ""),
mdEscape(Array.isArray(it.mitigation) ? it.mitigation.join("; ") : it.mitigation ?? ""),
mdEscape(Array.isArray(it.owner_orgs) ? it.owner_orgs.join(", ") : ""),
]);
return mdTable(["ID", "Risks", "Varbūtība", "Ietekme", "Mazināšana", "Atbildīgie"], rows);
}
function renderInteractions(obj: AnyObj): string {
const items = Array.isArray(obj.items) ? obj.items : [];
if (!items.length) return "_(tukšs reģistrs)_";
const out: string[] = [];
for (const it of items) {
out.push(`### ${mdEscape(it.title ?? it.id ?? "")}`.trim());
out.push(`- **ID:** ${mdEscape(it.id)}`);
out.push(`- **Tips:** ${mdEscape(it.interaction_type ?? "")}`);
out.push(`- **Pretējā puse:** ${mdEscape(it.counterpart ?? "")}`);
if (Array.isArray(it.components) && it.components.length) {
out.push(`- **Komponentes:** ${mdEscape(it.components.join(", "))}`);
}
if (it.why_it_matters) {
out.push(`- **Kāpēc svarīgi:** ${mdEscape(it.why_it_matters)}`);
}
if (Array.isArray(it.requirements) && it.requirements.length) {
out.push(`- **Prasības:** ${mdEscape(it.requirements.join("; "))}`);
}
out.push("");
}
return out.join("\n").trim();
}
function renderRegisterByPath(relRegisterPathFromDomain: string, domainRoot: string): Promise<string> {
const fullRel = path.posix.join(domainRoot, relRegisterPathFromDomain);
return (async () => {
const obj = await readYaml(fullRel);
// Render by filename conventions
const file = relRegisterPathFromDomain.replace(/\\/g, "/");
if (file.endsWith("abbreviations.yaml") || file.endsWith("abbreviations.yml")) return renderAbbreviations(obj);
if (file.endsWith("terms.yaml") || file.endsWith("terms.yml")) return renderTerms(obj);
if (file.endsWith("related-documents.yaml") || file.endsWith("related-documents.yml")) return renderRelatedDocs(obj);
if (file.endsWith("organizations.yaml") || file.endsWith("organizations.yml")) return renderOrganizations(obj);
if (file.endsWith("legal-acts.yaml") || file.endsWith("legal-acts.yml")) return renderLegalActs(obj);
if (file.endsWith("roadmap.yaml") || file.endsWith("roadmap.yml")) return renderRoadmap(obj);
if (file.endsWith("risks.yaml") || file.endsWith("risks.yml")) return renderRisks(obj);
if (file.endsWith("interactions.yaml") || file.endsWith("interactions.yml")) return renderInteractions(obj);
// Known item-heavy registers: functions/services/info-resources/systems: use a nicer preferred order
const preferred = (() => {
if (file.includes("/04-functions/")) return ["id", "subdomain", "name", "change_status", "change_description"];
if (file.includes("/05-services/")) return ["id", "subdomain", "name", "change_status", "change_description"];
if (file.includes("/06-information-resources/"))
return ["id", "subdomain", "name", "type", "owner", "change_status", "change_description"];
if (file.includes("/07-systems/")) return ["id", "subdomain", "name", "owner", "change_status", "change_description"];
return null;
})();
return renderGenericItemsTable(obj, preferred);
})();
}
async function build() {
const domainRoot = DOMAIN_DIR.replace(/\\/g, "/");
const manifestRel = path.posix.join(domainRoot, "manifest.yaml");
const manifest = await readYaml(manifestRel);
if (!Array.isArray(manifest.views)) {
throw new Error(`manifest.yaml missing 'views' array: ${manifestRel}`);
}
const pieces: string[] = [];
pieces.push(`# ${manifest.title ?? "Dokuments"}`);
if (manifest.version) pieces.push(`\n_Versija: ${manifest.version}_\n`);
for (const v of manifest.views) {
const viewFile = v?.file;
if (!viewFile) continue;
const viewRel = path.posix.join(domainRoot, viewFile);
let md = await readText(viewRel);
const outLines: string[] = [];
const lines = md.split(/\r?\n/);
for (const line of lines) {
const regRef = isRegisterRefLine(line);
const diagramRef = isDiagramRefLine(line);
if (regRef) {
// keep the original line, then render the register below it
outLines.push(line);
const rendered = await renderRegisterByPath(regRef, domainRoot);
outLines.push("");
outLines.push(rendered);
outLines.push("");
continue;
}
if (diagramRef) {
// keep diagram placeholder as-is (per your rule)
outLines.push(line);
continue;
}
outLines.push(line);
}
pieces.push(outLines.join("\n").trimEnd());
pieces.push(""); // spacing between views
}
const finalMd = pieces.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
const outAbs = path.join(REPO_ROOT, OUT_FILE);
await fs.writeFile(outAbs, finalMd, "utf-8");
// eslint-disable-next-line no-console
console.log(`OK: generated ${OUT_FILE}`);
}
build().catch((e) => {
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
});

View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +0,0 @@
import { loadRepo } from "./load.js";
import { RepoIndex } from "./types.js";
let cached: RepoIndex | null = null;
export function getIndex(archRoot: string): RepoIndex {
if (!cached) cached = loadRepo(archRoot);
return cached;
}
// Optional: reload endpoint support (not exposed yet)
export function reload(archRoot: string): RepoIndex {
cached = loadRepo(archRoot);
return cached;
}

View File

@@ -1,195 +0,0 @@
import fg from "fast-glob";
import fs from "node:fs";
import path from "node:path";
import yaml from "js-yaml";
import { Entity, Edge, RepoIndex } from "./types.js";
function toKindFromId(id: string): Entity["_kind"] {
if (id.startsWith("goal.")) return "goal";
if (id.startsWith("func.")) return "function";
if (
id.startsWith("svc.kultura.") ||
id.startsWith("svc.valoda.") ||
id.startsWith("svc.")
)
return "service";
if (
id.startsWith("ir.kultura.") ||
id.startsWith("ir.valoda.") ||
id.startsWith("ir.")
)
return "information_resource";
if (
id.startsWith("sys.kultura.") ||
id.startsWith("sys.valoda.") ||
id.startsWith("sys.")
)
return "system";
if (id.startsWith("principle.")) return "principle";
if (id.startsWith("org.")) return "org";
if (id.startsWith("domain.")) return "domain";
if (id.startsWith("subdomain.")) return "subdomain";
if (id.startsWith("rm.")) return "roadmap";
if (id.startsWith("risk.")) return "risk";
if (id.startsWith("int.")) return "interaction";
if (id.startsWith("doc.")) return "document";
if (id.startsWith("legal.")) return "legal";
return "unknown";
}
function safeString(x: unknown): string {
if (x === null || x === undefined) return "";
if (typeof x === "string") return x;
if (typeof x === "number" || typeof x === "boolean") return String(x);
try {
return JSON.stringify(x);
} catch {
return "";
}
}
function indexEntity(idx: RepoIndex, e: Entity) {
idx.entitiesById.set(e.id, e);
const hay = [
e.id,
e._kind,
e._domain ?? "",
e._subdomain ?? "",
safeString(e.title),
safeString(e.name),
safeString(e.description),
safeString(e.change_description),
safeString(e.status),
]
.join(" ")
.toLowerCase();
idx.textIndex.push({
id: e.id,
haystack: hay,
kind: e._kind,
domain: e._domain,
subdomain: e._subdomain,
});
}
function readYaml(filePath: string): any {
const raw = fs.readFileSync(filePath, "utf-8");
return yaml.load(raw);
}
export function loadRepo(archRoot: string): RepoIndex {
const idx: RepoIndex = {
entitiesById: new Map(),
edges: [],
textIndex: [],
resources: [],
};
// Register resource URIs for views and manifest and raw registers.
const domainManifests = fg.sync(["**/manifest.yaml"], {
cwd: archRoot,
dot: false,
absolute: true,
});
for (const mf of domainManifests) {
const mfObj: any = readYaml(mf);
const domainFolder = path.dirname(mf);
const domainSlug = path.basename(domainFolder);
idx.resources.push({
uri: `manifest://${domainSlug}`,
title: `Manifest: ${domainSlug}`,
mimeType: "application/yaml",
});
// Views resources
const views = (mfObj?.views ?? []) as Array<{
file: string;
title?: string;
id?: string | number;
}>;
for (const v of views) {
const name = path.basename(v.file, path.extname(v.file));
idx.resources.push({
uri: `views://${domainSlug}/${name}`,
title: `View: ${domainSlug} / ${v.title ?? name}`,
mimeType: "text/markdown",
});
}
// Register raw YAML registers as resources
const regFiles = fg.sync(["registers/**/*.yaml"], {
cwd: domainFolder,
absolute: true,
});
for (const rf of regFiles) {
const rel = path.relative(domainFolder, rf).replaceAll("\\", "/");
idx.resources.push({
uri: `register://${domainSlug}/${rel}`,
title: `Register: ${domainSlug}/${rel}`,
mimeType: "application/yaml",
});
}
// Load domain entity (if present)
const domainYaml = path.join(domainFolder, "registers/00-meta/domain.yaml");
if (fs.existsSync(domainYaml)) {
const d = readYaml(domainYaml);
const ent: Entity = {
...(d ?? {}),
id: d?.id ?? `domain.${domainSlug}`,
_kind: "domain",
_domain: `domain.${domainSlug}`,
_source_path: path.relative(archRoot, domainYaml).replaceAll("\\", "/"),
};
indexEntity(idx, ent);
}
// Load YAML registers with "items"
const regList = fg.sync(["registers/**/*.yaml"], {
cwd: domainFolder,
absolute: true,
});
for (const file of regList) {
const obj = readYaml(file);
// edges file
if (file.endsWith("registers/99-relations/edges.yaml") && obj?.edges) {
const edges = obj.edges as Edge[];
idx.edges.push(...edges);
continue;
}
// Single-entity YAML (has id but no items)
if (obj?.id && !obj?.items) {
const ent: Entity = {
...(obj ?? {}),
_kind: toKindFromId(obj.id),
_domain: `domain.${domainSlug}`,
_subdomain: obj?.subdomain ?? undefined,
_source_path: path.relative(archRoot, file).replaceAll("\\", "/"),
};
indexEntity(idx, ent);
continue;
}
// List YAML (items)
if (Array.isArray(obj?.items)) {
for (const it of obj.items) {
if (!it?.id) continue;
const ent: Entity = {
...(it ?? {}),
_kind: toKindFromId(it.id),
_domain: `domain.${domainSlug}`,
_subdomain: it?.subdomain ?? undefined,
_source_path: path.relative(archRoot, file).replaceAll("\\", "/"),
};
indexEntity(idx, ent);
}
}
}
}
return idx;
}

View File

@@ -1,44 +0,0 @@
export type DomainId = string;
export type Edge = {
from: string;
type: string;
to: string;
};
export type Entity = {
id: string;
_kind:
| "goal"
| "function"
| "service"
| "information_resource"
| "system"
| "principle"
| "org"
| "domain"
| "subdomain"
| "roadmap"
| "risk"
| "interaction"
| "document"
| "legal"
| "unknown";
_domain?: DomainId;
_subdomain?: string;
_source_path?: string;
[k: string]: any;
};
export type RepoIndex = {
entitiesById: Map<string, Entity>;
edges: Edge[];
textIndex: Array<{
id: string;
haystack: string;
kind: string;
domain?: string;
subdomain?: string;
}>;
resources: Array<{ uri: string; title: string; mimeType: string }>;
};

View File

@@ -1,43 +0,0 @@
import fs from "node:fs";
import path from "node:path";
import fg from "fast-glob";
function readFileSafe(filePath: string): string {
return fs.readFileSync(filePath, "utf-8");
}
export function getResource(archRoot: string, uri: string) {
// manifest://<domain>
if (uri.startsWith("manifest://")) {
const domain = uri.replace("manifest://", "");
const mf = path.join(archRoot, domain, "manifest.yaml");
if (!fs.existsSync(mf)) return { ok: false, error: `Not found: ${uri}` };
return { ok: true, uri, mimeType: "application/yaml", content: readFileSafe(mf) };
}
// views://<domain>/<viewfile-without-ext>
if (uri.startsWith("views://")) {
const rest = uri.replace("views://", "");
const [domain, viewName] = rest.split("/", 2);
const domainDir = path.join(archRoot, domain);
const matches = fg.sync([`views/${viewName}.md`], { cwd: domainDir, absolute: true });
if (!matches.length) return { ok: false, error: `Not found: ${uri}` };
return {
ok: true,
uri,
mimeType: "text/markdown",
content: readFileSafe(matches[0]!),
};
}
// register://<domain>/<relative-path>
if (uri.startsWith("register://")) {
const rest = uri.replace("register://", "");
const [domain, rel] = rest.split("/", 2);
const filePath = path.join(archRoot, domain, rel);
if (!fs.existsSync(filePath)) return { ok: false, error: `Not found: ${uri}` };
return { ok: true, uri, mimeType: "application/yaml", content: readFileSafe(filePath) };
}
return { ok: false, error: `Unsupported uri: ${uri}` };
}

View File

@@ -1,8 +0,0 @@
import { RepoIndex } from "../repo/types.js";
export function listResources(idx: RepoIndex) {
return {
ok: true,
resources: idx.resources,
};
}

View File

@@ -1,11 +0,0 @@
import { RepoIndex } from "../repo/types.js";
export function getEntityTool(idx: RepoIndex, args: any) {
const id = String(args?.id ?? "").trim();
if (!id) return { ok: false, error: "Missing args.id" };
const ent = idx.entitiesById.get(id);
if (!ent) return { ok: false, error: `Entity not found: ${id}` };
return { ok: true, entity: ent };
}

View File

@@ -1,16 +0,0 @@
import { RepoIndex } from "../repo/types.js";
export function listRelationsTool(idx: RepoIndex, args: any) {
const id = String(args?.id ?? "").trim();
if (!id) return { ok: false, error: "Missing args.id" };
const direction = String(args?.direction ?? "both"); // in|out|both
const edges = idx.edges.filter((e) => {
if (direction === "out") return e.from === id;
if (direction === "in") return e.to === id;
return e.from === id || e.to === id;
});
return { ok: true, edges };
}

View File

@@ -1,29 +0,0 @@
import { RepoIndex } from "../repo/types.js";
export function searchTool(idx: RepoIndex, args: any) {
const q = String(args?.query ?? "").toLowerCase().trim();
if (!q) return { ok: false, error: "Missing args.query" };
const kind = args?.kind ? String(args.kind) : null;
const subdomain = args?.subdomain ? String(args.subdomain) : null;
const hits: Array<{ id: string; score: number; kind: string; subdomain?: string }> = [];
for (const row of idx.textIndex) {
if (kind && row.kind !== kind) continue;
if (subdomain && row.subdomain !== subdomain) continue;
// Very simple scoring: count substring occurrences
const hay = row.haystack;
let score = 0;
let pos = hay.indexOf(q);
while (pos !== -1) {
score++;
pos = hay.indexOf(q, pos + q.length);
}
if (score > 0) hits.push({ id: row.id, score, kind: row.kind, subdomain: row.subdomain });
}
hits.sort((a, b) => b.score - a.score);
return { ok: true, results: hits.slice(0, 50) };
}

View File

@@ -1,42 +0,0 @@
import { RepoIndex } from "../repo/types.js";
export function subgraphTool(idx: RepoIndex, args: any) {
const seeds = Array.isArray(args?.seed_ids)
? args.seed_ids.map((x: any) => String(x))
: [];
const depth = Number.isFinite(args?.depth) ? Number(args.depth) : 1;
if (!seeds.length) return { ok: false, error: "Missing args.seed_ids[]" };
if (depth < 0 || depth > 5) return { ok: false, error: "depth must be 0..5" };
const nodes = new Set<string>(seeds);
const edgesOut: any[] = [];
let frontier = new Set<string>(seeds);
for (let d = 0; d < depth; d++) {
const next = new Set<string>();
for (const e of idx.edges) {
if (frontier.has(e.from)) {
edgesOut.push(e);
if (!nodes.has(e.to)) next.add(e.to);
nodes.add(e.to);
}
if (frontier.has(e.to)) {
edgesOut.push(e);
if (!nodes.has(e.from)) next.add(e.from);
nodes.add(e.from);
}
}
frontier = next;
if (!frontier.size) break;
}
const nodeObjs = Array.from(nodes).map(
(id) => idx.entitiesById.get(id) ?? { id, _kind: "unknown" }
);
return { ok: true, nodes: nodeObjs, edges: edgesOut };
}

View File

@@ -1,15 +0,0 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}

View File

@@ -1,506 +0,0 @@
# KISC MCP Server - MCPF Integration Deployment Guide
**Project:** MCPF (MCP Trust Framework) Integration for KISC MCP Server
**Target Server:** llm.kis.gov.lv
**DID:** `did:web:llm.kis.gov.lv`
**Date:** 2026-01-30
---
## 📋 Table of Contents
1. [Overview](#overview)
2. [Package Contents](#package-contents)
3. [Prerequisites](#prerequisites)
4. [Part A: Local Implementation](#part-a-local-implementation)
5. [Part B: VeriTrust Integration](#part-b-veritrust-integration)
6. [Validation & Testing](#validation--testing)
7. [Troubleshooting](#troubleshooting)
---
## Overview
This package integrates MCPF (MCP Trust Framework) into the KISC MCP Server, providing:
✅ **Cryptographic Identity** — `did:web:llm.kis.gov.lv` with Ed25519 signing
✅ **Verifiable Credentials** — VeriTrust-issued MCPServerCredential
✅ **Trust Metadata** — Discoverable at `/.well-known/mcp-trust-registry.json`
✅ **Standards Compliance** — W3C DID Core, VC Data Model, MCPF Specification
---
## Package Contents
```
kisc-mcpf-deploy/
├── README.md # This file
├── keypair-SECURE.json # ⚠️ PRIVATE KEY (secure handling!)
├── public-key.json # Public key reference
│
├── wellknown/ # .well-known files for nginx
│ ├── did.json # DID Document
│ ├── jwks.json # JWK Set (public keys)
│ ├── mcp-trust-registry.json # MCPF registry discovery
│ ├── security.txt # RFC 9116 security contact
│ ├── mcp/
│ │ └── manifest.json # MCP server capabilities
│ └── credentials/
│ └── mcp-server.json # VC placeholder (VeriTrust will replace)
│
├── scripts/ # Deployment automation
│ ├── deploy-wellknown.sh # Deploy .well-known to server
│ ├── validate-endpoints.sh # Test all endpoints
│ └── update-env.sh # Add MCPF_PRIVATE_KEY to .env
│
├── nginx/ # nginx configuration
│ └── wellknown.conf # nginx config for .well-known
│
├── docs/ # Documentation
│ ├── DEPLOYMENT.md # Step-by-step deployment
│ ├── INTEGRATION.md # start.sh/status.sh updates
│ └── TESTING.md # Validation procedures
│
└── veritrust/ # VeriTrust submission
├── README-VERITRUST.md # Instructions for VeriTrust
├── mcp-server-request.json # Credential request payload
└── install-credential.sh # Install received VC
```
---
## Prerequisites
Before deployment, ensure:
- [ ] SSH access to `llm.kis.gov.lv` (10.20.30.96)
- [ ] Sudo privileges or ownership of `/opt/kisc-llm/`
- [ ] POC stack running (`/opt/kisc-llm/poc/deploy/`)
- [ ] nginx container (kisc-nginx) operational
- [ ] Let's Encrypt certificates valid
- [ ] Git access to `kisc-gov-lv/MCP-KISC-architecture`
---
## Part A: Local Implementation
### Step 1: Secure Private Key Storage
**⚠️ CRITICAL: Handle `keypair-SECURE.json` securely!**
```bash
# On your local machine (NOT on server yet)
cat keypair-SECURE.json
# Contains: private_key_pem, public_key_pem, multibase, jwk_x
# Verify integrity
sha256sum keypair-SECURE.json
```
**DO NOT:**
- ❌ Commit to Git
- ❌ Send via unencrypted email
- ❌ Store in Slack/Teams
- ❌ Print to logs
**DO:**
- ✅ Transfer via encrypted channel (scp with key auth, 1Password, etc.)
- ✅ Store in `/opt/kisc-llm/poc/deploy/.env` only
- ✅ Backup offline (encrypted USB/vault)
- ✅ Document who has access
---
### Step 2: Deploy .well-known Files to Server
```bash
# On llm.kis.gov.lv server
# 1. Create .well-known directory
sudo mkdir -p /opt/kisc-llm/poc/deploy/.well-known/{mcp,credentials}
sudo chown -R "$USER":"$USER" /opt/kisc-llm/poc/deploy/.well-known
# 2. Copy .well-known files
cd /opt/kisc-llm/poc/deploy
rsync -av /path/to/kisc-mcpf-deploy/wellknown/ .well-known/
# 3. Verify structure
tree .well-known/
# Expected:
# .well-known/
# ├── did.json
# ├── jwks.json
# ├── mcp-trust-registry.json
# ├── security.txt
# ├── mcp/
# │ └── manifest.json
# └── credentials/
# └── mcp-server.json
# 4. Set permissions (read-only for nginx)
chmod -R 644 .well-known/**/*
find .well-known -type d -exec chmod 755 {} \;
```
---
### Step 3: Add Private Key to .env
```bash
# On llm.kis.gov.lv server
cd /opt/kisc-llm/poc/deploy
# Extract private key from keypair-SECURE.json
PRIVATE_KEY_PEM=$(cat /path/to/keypair-SECURE.json | jq -r '.private_key_pem')
# Add to .env (replace newlines with \n)
echo "MCPF_PRIVATE_KEY=\"$PRIVATE_KEY_PEM\"" >> .env
# Verify (should show -----BEGIN PRIVATE KEY-----)
grep MCPF_PRIVATE_KEY .env | head -c 100
# Secure the .env file
chmod 600 .env
```
---
### Step 4: Update nginx Configuration
```bash
# On llm.kis.gov.lv server
cd /opt/kisc-llm/poc/deploy/nginx/conf.d
# Backup existing config
cp default.conf default.conf.backup-$(date +%Y%m%d)
# Add .well-known location block (insert after line 30, before "location /")
cat >> default.conf << 'EOF'
# ==========================================================================
# MCPF .well-known endpoints
# ==========================================================================
location /.well-known/ {
alias /opt/kisc-llm/poc/deploy/.well-known/;
# CORS headers for trust framework discovery
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type" always;
# Cache DID documents for 1 hour (they rarely change)
add_header Cache-Control "public, max-age=3600" always;
# Serve JSON files
location ~ \.(json)$ {
add_header Content-Type application/json;
}
# Serve text files
location ~ \.(txt)$ {
add_header Content-Type text/plain;
}
# No directory listing
autoindex off;
}
EOF
# Validate nginx config
docker exec kisc-nginx nginx -t
# If validation passes, reload
docker exec kisc-nginx nginx -s reload
```
---
### Step 5: Update start.sh Script
Add MCPF integration steps to `/opt/kisc-llm/poc/deploy/scripts/start.sh`:
```bash
# Insert after Step 3 (TLS cert handling), before Step 4 (OpenGateLLM start)
# =============================================================================
# Step 3.5: MCPF .well-known Files
# =============================================================================
log_step "Step 3.5: Checking MCPF .well-known files..."
if [[ ! -f "$DEPLOY_DIR/.well-known/did.json" ]]; then
log_error "MCPF .well-known files not found!"
log_error "Run: rsync -av /path/to/wellknown/ $DEPLOY_DIR/.well-known/"
exit 1
fi
# Verify critical files exist
REQUIRED_FILES=(
".well-known/did.json"
".well-known/jwks.json"
".well-known/mcp-trust-registry.json"
".well-known/mcp/manifest.json"
".well-known/credentials/mcp-server.json"
)
for file in "${REQUIRED_FILES[@]}"; do
if [[ ! -f "$DEPLOY_DIR/$file" ]]; then
log_warn "Missing: $file"
fi
done
log_info "MCPF .well-known files OK"
```
---
### Step 6: Update status.sh Script
Add MCPF health checks to `/opt/kisc-llm/poc/deploy/scripts/status.sh`:
```bash
# Insert at the end, before final completion message
# =============================================================================
# MCPF ENDPOINTS STATUS
# =============================================================================
echo -e "${CYAN}MCPF Endpoints:${NC}"
echo "----------------------------------------"
check_wellknown() {
local endpoint=$1
local name=$2
local response
response=$(curl -sS -k --connect-timeout 2 "https://localhost$endpoint" 2>/dev/null || echo "")
if [[ -n "$response" ]] && echo "$response" | grep -q "@context\|keys\|mcpfVersion"; then
echo -e " ${GREEN}✅${NC} $name"
else
echo -e " ${RED}❌${NC} $name (HTTP error or empty response)"
fi
}
check_wellknown "/.well-known/did.json" "DID Document"
check_wellknown "/.well-known/jwks.json" "JWKS"
check_wellknown "/.well-known/mcp-trust-registry.json" "MCPF Registry Discovery"
check_wellknown "/.well-known/mcp/manifest.json" "MCP Manifest"
check_wellknown "/.well-known/credentials/mcp-server.json" "MCP Credential"
echo ""
```
---
### Step 7: Validate Deployment
```bash
# On llm.kis.gov.lv server
cd /opt/kisc-llm/poc/deploy
# Test .well-known endpoints
./scripts/validate-endpoints.sh
# Expected output:
# ✅ DID Document: https://llm.kis.gov.lv/.well-known/did.json
# ✅ JWKS: https://llm.kis.gov.lv/.well-known/jwks.json
# ✅ MCPF Registry: https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json
# ✅ MCP Manifest: https://llm.kis.gov.lv/.well-known/mcp/manifest.json
# ✅ MCP Credential: https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json
```
---
## Part B: VeriTrust Integration
### Step 8: Submit to VeriTrust for Credential Issuance
**KISC already has a VeriTrust profile!**
Existing KISC DID in VeriTrust:
- `did:key:z6Mkuwv1z6y2yorbBf4LEkNzJCg16ERVfWE3bJEPKXtQm7a9` (Holder DID)
- `did:web:veritrust.vc:portal:company:df0684bd-b54a-4684-b3d6-93a3b1c4bcb9` (Public Alias)
**Action Required:**
1. **Contact VeriTrust** via existing relationship
2. **Request MCPServerCredential** for `did:web:llm.kis.gov.lv`
3. **Provide:**
- DID: `did:web:llm.kis.gov.lv`
- Public Key (multibase): `z6MkjWGNnJsdyvutfbsytFJhkwDwyHkMkfWVL8X1fS1yBm2w`
- MCP Endpoint: `https://llm.kis.gov.lv/mcp`
- Manifest URL: `https://llm.kis.gov.lv/.well-known/mcp/manifest.json`
- Organization: KISC (Kultūras informācijas sistēmu centrs)
- Owner: Kultūras ministrija
- Compliance: GDPR, NIS2, Latvian Data Protection Act
**Submission Payload:** See `veritrust/mcp-server-request.json`
---
### Step 9: Install VeriTrust-Issued Credential
Once VeriTrust issues the credential:
```bash
# On llm.kis.gov.lv server
cd /opt/kisc-llm/poc/deploy
# Backup placeholder
cp .well-known/credentials/mcp-server.json .well-known/credentials/mcp-server.json.placeholder
# Install VeriTrust credential (replace PLACEHOLDER with actual credential)
cat > .well-known/credentials/mcp-server.json << 'EOF'
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://mcpf.dev/credentials/v1"
],
"id": "https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json",
... (VeriTrust-provided credential JSON) ...
}
EOF
# Verify signature (use MCPF-python or manual verification)
# The credential MUST be signed by did:web:veritrust.vc
# Reload nginx to pick up new credential
docker exec kisc-nginx nginx -s reload
```
---
## Validation & Testing
### Local Tests (from server)
```bash
# Test DID Document
curl https://llm.kis.gov.lv/.well-known/did.json | jq
# Test MCPF Registry Discovery
curl https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json | jq
# Test MCP Manifest
curl https://llm.kis.gov.lv/.well-known/mcp/manifest.json | jq
# Test credential (placeholder until VeriTrust issues)
curl https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json | jq
```
### External Tests (from any machine)
```bash
# DID Resolution (W3C standard)
curl https://llm.kis.gov.lv/.well-known/did.json
# Should return:
# {
# "@context": [...],
# "id": "did:web:llm.kis.gov.lv",
# "verificationMethod": [...],
# ...
# }
```
### AI Agent Discovery Test
```python
import requests
# Agent discovers MCPF-enabled MCP server
registry_response = requests.get("https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json")
print(registry_response.json())
# Agent fetches credential for verification
credential_url = registry_response.json()["services"]["mcp"]["credential"]
credential = requests.get(credential_url).json()
# Agent verifies signature against did:web:veritrust.vc
# (Use MCPF-python for full verification)
```
---
## Troubleshooting
### Issue: 404 on .well-known endpoints
**Cause:** nginx not serving .well-known directory
**Fix:**
```bash
# Check nginx volume mount
docker inspect kisc-nginx | grep .well-known
# If missing, update docker-compose.yml:
volumes:
- ./.well-known:/opt/kisc-llm/poc/deploy/.well-known:ro
# Restart
docker restart kisc-nginx
```
---
### Issue: CORS errors when agents try to fetch DID
**Cause:** Missing CORS headers
**Fix:** Ensure nginx config has:
```nginx
add_header Access-Control-Allow-Origin * always;
```
---
### Issue: Private key not found in .env
**Cause:** MCPF_PRIVATE_KEY not set
**Fix:**
```bash
# Check .env
grep MCPF_PRIVATE_KEY /opt/kisc-llm/poc/deploy/.env
# If missing, extract from keypair-SECURE.json and add
```
---
## Security Checklist
Before going to production:
- [ ] `keypair-SECURE.json` deleted from server (only in `.env`)
- [ ] `.env` has permissions `600` (read/write by owner only)
- [ ] `.well-known` files have permissions `644` (world-readable)
- [ ] Private key backed up offline (encrypted)
- [ ] Access control documented (who has private key)
- [ ] VeriTrust credential installed (not placeholder)
- [ ] All endpoints accessible via HTTPS only
- [ ] nginx TLS configured correctly (Let's Encrypt)
---
## Next Steps
1. **Deploy locally** (Part A) — Complete Steps 1-7
2. **Submit to VeriTrust** (Part B) — Step 8
3. **Install credential** — Step 9 (after VeriTrust response)
4. **Test with AI agents** — Validate MCPF discovery workflow
5. **Monitor** — Check logs, status.sh output
6. **Document** — Update KISC internal documentation
---
## Support
- **MCPF Specification:** https://github.com/MCPTrustFramework/MCPF-specification
- **VeriTrust:** https://veritrust.vc
- **Questions:** Contact Rihards (Veritrust relationship) or KISC IT team
---
**Version:** 1.0
**Last Updated:** 2026-01-30
**Status:** Ready for Deployment

View File

@@ -1,138 +0,0 @@
#!/usr/bin/env bash
###############################################################################
# KISC MCPF - Validate .well-known Endpoints
#
# Tests all MCPF endpoints are accessible and return valid JSON
#
# Usage: ./validate-endpoints.sh [domain]
# Default domain: localhost (for local testing)
# Production: ./validate-endpoints.sh llm.kis.gov.lv
###############################################################################
set -euo pipefail
DOMAIN="${1:-localhost}"
BASE_URL="https://$DOMAIN"
# Use -k for localhost self-signed certs
CURL_OPTS="-sS --connect-timeout 5 --max-time 10"
if [[ "$DOMAIN" == "localhost" ]]; then
CURL_OPTS="$CURL_OPTS -k"
fi
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
PASSED=0
FAILED=0
echo "============================================"
echo "MCPF Endpoint Validation"
echo "============================================"
echo "Target: $BASE_URL"
echo ""
test_endpoint() {
local path=$1
local name=$2
local required_field=$3
echo -n "Testing $name... "
local url="$BASE_URL$path"
local response
response=$(curl $CURL_OPTS "$url" 2>/dev/null || echo "")
if [[ -z "$response" ]]; then
echo -e "${RED}❌ FAIL${NC} (No response)"
echo " URL: $url"
((FAILED++))
return 1
fi
# Check if valid JSON
if ! echo "$response" | jq empty 2>/dev/null; then
echo -e "${RED}❌ FAIL${NC} (Invalid JSON)"
echo " URL: $url"
echo " Response: ${response:0:100}..."
((FAILED++))
return 1
fi
# Check for required field
if [[ -n "$required_field" ]]; then
if ! echo "$response" | jq -e "$required_field" >/dev/null 2>&1; then
echo -e "${YELLOW}⚠️ WARN${NC} (Missing field: $required_field)"
echo " URL: $url"
fi
fi
echo -e "${GREEN}✅ PASS${NC}"
echo " URL: $url"
((PASSED++))
}
# =============================================================================
# Test Suite
# =============================================================================
# Test 1: DID Document
test_endpoint "/.well-known/did.json" "DID Document" ".id"
# Test 2: JWKS
test_endpoint "/.well-known/jwks.json" "JWKS" ".keys"
# Test 3: MCPF Registry Discovery
test_endpoint "/.well-known/mcp-trust-registry.json" "MCPF Registry Discovery" ".mcpfVersion"
# Test 4: Security.txt
echo -n "Testing Security.txt... "
response=$(curl $CURL_OPTS "$BASE_URL/.well-known/security.txt" 2>/dev/null || echo "")
if echo "$response" | grep -q "Contact:"; then
echo -e "${GREEN}✅ PASS${NC}"
echo " URL: $BASE_URL/.well-known/security.txt"
((PASSED++))
else
echo -e "${RED}❌ FAIL${NC}"
((FAILED++))
fi
# Test 5: MCP Manifest
test_endpoint "/.well-known/mcp/manifest.json" "MCP Manifest" ".capabilities"
# Test 6: MCP Credential
test_endpoint "/.well-known/credentials/mcp-server.json" "MCP Credential" ".credentialSubject"
# =============================================================================
# Results
# =============================================================================
echo ""
echo "============================================"
echo "Results"
echo "============================================"
echo "Passed: $PASSED"
echo "Failed: $FAILED"
echo ""
if [[ $FAILED -eq 0 ]]; then
echo -e "${GREEN}✅ All tests passed!${NC}"
echo ""
echo "MCPF integration is working correctly."
echo "Next steps:"
echo " 1. Submit to VeriTrust for credential issuance"
echo " 2. Replace placeholder credential in /.well-known/credentials/mcp-server.json"
echo " 3. Test with AI agents (Claude Desktop, ChatGPT, etc.)"
exit 0
else
echo -e "${RED}❌ Some tests failed${NC}"
echo ""
echo "Troubleshooting:"
echo " 1. Check nginx is serving .well-known directory"
echo " 2. Verify .well-known files exist in /opt/kisc-llm/poc/deploy/.well-known/"
echo " 3. Check nginx logs: docker logs kisc-nginx"
echo " 4. Verify TLS certificates are valid"
exit 1
fi

View File

@@ -1,247 +0,0 @@
# VeriTrust MCPF Credential Submission
## Overview
KISC already has a VeriTrust organization profile. This submission requests a **MCPServerCredential** for the new `did:web:llm.kis.gov.lv` identity.
---
## Existing KISC Profile in VeriTrust
**Holder DID:** `did:key:z6Mkuwv1z6y2yorbBf4LEkNzJCg16ERVfWE3bJEPKXtQm7a9`
**Public Alias:** `did:web:veritrust.vc:portal:company:df0684bd-b54a-4684-b3d6-93a3b1c4bcb9`
**Status:** Verified
---
## New Identity for MCP Server
**DID:** `did:web:llm.kis.gov.lv`
**Public Key (multibase):** `z6MkjWGNnJsdyvutfbsytFJhkwDwyHkMkfWVL8X1fS1yBm2w`
**Service Endpoint:** `https://llm.kis.gov.lv/mcp`
**Manifest:** `https://llm.kis.gov.lv/.well-known/mcp/manifest.json`
---
## Submission Process
### Step 1: Verify Local Deployment
Before submitting to VeriTrust, ensure:
```bash
# All .well-known endpoints accessible
curl https://llm.kis.gov.lv/.well-known/did.json
curl https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json
curl https://llm.kis.gov.lv/.well-known/mcp/manifest.json
# MCP server operational
curl https://llm.kis.gov.lv/mcp-health
```
---
### Step 2: Submit Credential Request
**Method 1: Via VeriTrust Portal (Recommended)**
1. Log into VeriTrust portal: https://veritrust.vc/portal
2. Navigate to your KISC organization profile
3. Click "Request Credential" → "MCP Server Credential"
4. Fill in form with data from `mcp-server-request.json`
5. Upload or paste:
- DID: `did:web:llm.kis.gov.lv`
- Public key (multibase): `z6MkjWGNnJsdyvutfbsytFJhkwDwyHkMkfWVL8X1fS1yBm2w`
- Endpoint: `https://llm.kis.gov.lv/mcp`
- Manifest URL: `https://llm.kis.gov.lv/.well-known/mcp/manifest.json`
6. Submit for review
**Method 2: Via API (If Available)**
```bash
# POST to VeriTrust credential issuance API
curl -X POST https://veritrust.vc/api/v1/credentials/issue \
-H "Authorization: Bearer $VERITRUST_API_KEY" \
-H "Content-Type: application/json" \
-d @mcp-server-request.json
```
**Method 3: Email Submission**
Send `mcp-server-request.json` to: credentials@veritrust.vc
Include:
- Subject: "KISC MCP Server Credential Request - did:web:llm.kis.gov.lv"
- Body: Reference existing KISC profile (did:key:z6Mkuwv1z6y2yorbBf4LEkNzJCg16ERVfWE3bJEPKXtQm7a9)
- Attach: mcp-server-request.json
---
### Step 3: Verification by VeriTrust
VeriTrust will verify:
1. ✅ KISC organization profile exists and is verified
2. ✅ `llm.kis.gov.lv` domain is controlled by KISC
3. ✅ DID document accessible at `https://llm.kis.gov.lv/.well-known/did.json`
4. ✅ MCP manifest valid at `https://llm.kis.gov.lv/.well-known/mcp/manifest.json`
5. ✅ Public key matches DID document
6. ✅ Compliance claims are accurate (GDPR, NIS2)
**Timeline:** 1-5 business days (typically 1-2 days for verified organizations)
---
### Step 4: Receive Credential
VeriTrust will provide:
```json
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://mcpf.dev/credentials/v1"
],
"id": "https://veritrust.vc/credentials/[UUID]",
"type": ["VerifiableCredential", "MCPServerCredential"],
"issuer": {
"id": "did:web:veritrust.vc",
"name": "VeriTrust"
},
"issuanceDate": "2026-01-30T10:00:00Z",
"expirationDate": "2027-01-30T10:00:00Z",
"credentialSubject": {
"id": "did:web:llm.kis.gov.lv#mcp-server",
...
},
"credentialStatus": {
"id": "https://veritrust.vc/status/2026#94567",
"type": "StatusList2021Entry",
...
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2026-01-30T10:00:00Z",
"verificationMethod": "did:web:veritrust.vc#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "z5vgK8B..." // VeriTrust's cryptographic signature
}
}
```
---
### Step 5: Install Credential
Use the provided `install-credential.sh` script:
```bash
# On llm.kis.gov.lv server
cd /opt/kisc-llm/poc/deploy
# Save VeriTrust credential to temporary file
cat > /tmp/veritrust-credential.json << 'EOF'
{
... (paste VeriTrust-provided credential JSON) ...
}
EOF
# Run install script
./veritrust/install-credential.sh /tmp/veritrust-credential.json
# Verify installation
curl https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json | jq
```
---
### Step 6: Register in MCPF Registry
VeriTrust will automatically register the MCP server in their MCPF registry at `https://mcp.veritrust.vc`.
Verify registration:
```bash
# Search by country
curl "https://mcp.veritrust.vc/mcp/search?country=LV"
# Get specific server
curl "https://mcp.veritrust.vc/mcp/servers/did:web:llm.kis.gov.lv"
```
Expected response:
```json
{
"did": "did:web:llm.kis.gov.lv",
"endpoint": "https://llm.kis.gov.lv/mcp",
"manifest": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json",
"credentials": [
"https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json"
],
"metadata": {
"organization": "Kultūras informācijas sistēmu centrs",
"country": "LV",
"tags": ["architecture", "government", "latvia", "culture"],
"status": "active"
}
}
```
---
## Troubleshooting
### VeriTrust cannot verify domain ownership
**Solution:** Add DNS TXT record:
```
_veritrust.llm.kis.gov.lv TXT "did=did:web:llm.kis.gov.lv"
```
### VeriTrust cannot fetch DID document
**Solution:** Verify HTTPS and CORS:
```bash
curl -I https://llm.kis.gov.lv/.well-known/did.json
# Should show:
# HTTP/2 200
# access-control-allow-origin: *
# content-type: application/json
```
### Credential issuance delayed
**Solution:** Contact VeriTrust support with:
- Organization: KISC
- Existing DID: did:key:z6Mkuwv1z6y2yorbBf4LEkNzJCg16ERVfWE3bJEPKXtQm7a9
- New DID: did:web:llm.kis.gov.lv
- Request ID: (if provided)
---
## Contact
**VeriTrust Support:**
- Website: https://veritrust.vc
- Email: support@veritrust.vc (or credentials@veritrust.vc)
- Portal: https://veritrust.vc/portal
**KISC Contact:**
- Rihards (VeriTrust relationship)
- KISC IT operations team
---
## Credential Renewal
**Expiration:** 1 year from issuance
**Renewal Process:** 30 days before expiration, VeriTrust will notify KISC via email
**Action Required:** Confirm renewal (usually automatic for verified organizations)
---
**Version:** 1.0
**Last Updated:** 2026-01-30
**Status:** Ready for Submission

View File

@@ -1,206 +0,0 @@
#!/usr/bin/env bash
###############################################################################
# KISC MCPF - Install VeriTrust-Issued Credential
#
# Installs the VeriTrust-signed MCP Server Credential and validates it
#
# Usage: ./install-credential.sh <credential-file.json>
###############################################################################
set -euo pipefail
CREDENTIAL_FILE="${1:-}"
DEPLOY_DIR="/opt/kisc-llm/poc/deploy"
TARGET="$DEPLOY_DIR/.well-known/credentials/mcp-server.json"
# Colors
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# =============================================================================
# Validation
# =============================================================================
if [[ -z "$CREDENTIAL_FILE" ]]; then
log_error "Usage: $0 <credential-file.json>"
echo ""
echo "Example:"
echo " $0 /tmp/veritrust-credential.json"
exit 1
fi
if [[ ! -f "$CREDENTIAL_FILE" ]]; then
log_error "File not found: $CREDENTIAL_FILE"
exit 1
fi
if [[ ! -d "$DEPLOY_DIR" ]]; then
log_error "Deploy directory not found: $DEPLOY_DIR"
exit 1
fi
echo "============================================"
echo "Install VeriTrust Credential"
echo "============================================"
echo "Source: $CREDENTIAL_FILE"
echo "Target: $TARGET"
echo ""
# =============================================================================
# Validate Credential Format
# =============================================================================
log_info "Step 1: Validating credential format..."
# Check if valid JSON
if ! jq empty "$CREDENTIAL_FILE" 2>/dev/null; then
log_error "Invalid JSON in credential file"
exit 1
fi
# Check required fields
REQUIRED_FIELDS=(
"@context"
"type"
"issuer.id"
"credentialSubject.id"
"proof.type"
"proof.proofValue"
)
for field in "${REQUIRED_FIELDS[@]}"; do
if ! jq -e ".$field" "$CREDENTIAL_FILE" >/dev/null 2>&1; then
log_error "Missing required field: $field"
exit 1
fi
done
# Verify issuer is VeriTrust
ISSUER=$(jq -r '.issuer.id' "$CREDENTIAL_FILE")
if [[ "$ISSUER" != "did:web:veritrust.vc" ]]; then
log_error "Invalid issuer: $ISSUER (expected: did:web:veritrust.vc)"
exit 1
fi
# Verify subject is KISC MCP server
SUBJECT=$(jq -r '.credentialSubject.id' "$CREDENTIAL_FILE")
if [[ "$SUBJECT" != "did:web:llm.kis.gov.lv#mcp-server" ]]; then
log_warn "Subject mismatch: $SUBJECT (expected: did:web:llm.kis.gov.lv#mcp-server)"
fi
# Check expiration
EXPIRATION=$(jq -r '.expirationDate' "$CREDENTIAL_FILE")
log_info "Credential expires: $EXPIRATION"
log_info "✅ Credential format valid"
echo ""
# =============================================================================
# Backup Existing Credential
# =============================================================================
log_info "Step 2: Backing up existing credential..."
if [[ -f "$TARGET" ]]; then
BACKUP="$TARGET.backup-$(date +%Y%m%d-%H%M%S)"
cp "$TARGET" "$BACKUP"
log_info "Backup created: $BACKUP"
else
log_warn "No existing credential to backup"
fi
echo ""
# =============================================================================
# Install New Credential
# =============================================================================
log_info "Step 3: Installing new credential..."
# Copy credential to target location
cp "$CREDENTIAL_FILE" "$TARGET"
# Set permissions (world-readable)
chmod 644 "$TARGET"
log_info "✅ Credential installed: $TARGET"
echo ""
# =============================================================================
# Reload nginx
# =============================================================================
log_info "Step 4: Reloading nginx..."
if docker ps --format '{{.Names}}' | grep -q "kisc-nginx"; then
docker exec kisc-nginx nginx -s reload
log_info "✅ nginx reloaded"
else
log_warn "nginx container not found, skipping reload"
fi
echo ""
# =============================================================================
# Verify Installation
# =============================================================================
log_info "Step 5: Verifying installation..."
# Test endpoint
RESPONSE=$(curl -sS -k https://localhost/.well-known/credentials/mcp-server.json 2>/dev/null || echo "")
if [[ -z "$RESPONSE" ]]; then
log_error "Endpoint not accessible"
exit 1
fi
if ! echo "$RESPONSE" | jq empty 2>/dev/null; then
log_error "Endpoint returned invalid JSON"
exit 1
fi
# Check proof value matches
INSTALLED_PROOF=$(echo "$RESPONSE" | jq -r '.proof.proofValue')
SOURCE_PROOF=$(jq -r '.proof.proofValue' "$CREDENTIAL_FILE")
if [[ "$INSTALLED_PROOF" != "$SOURCE_PROOF" ]]; then
log_error "Proof value mismatch! Installation may be corrupted."
exit 1
fi
log_info "✅ Installation verified"
echo ""
# =============================================================================
# Success Summary
# =============================================================================
echo "============================================"
echo "Installation Complete"
echo "============================================"
echo ""
echo "Credential Details:"
echo " Issuer: $(jq -r '.issuer.name' "$TARGET")"
echo " Subject: $(jq -r '.credentialSubject.id' "$TARGET")"
echo " Issued: $(jq -r '.issuanceDate' "$TARGET")"
echo " Expires: $(jq -r '.expirationDate' "$TARGET")"
echo " Status URL: $(jq -r '.credentialStatus.statusListCredential' "$TARGET")"
echo ""
echo "Endpoint:"
echo " https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json"
echo ""
echo "Next Steps:"
echo " 1. Test external access:"
echo " curl https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json | jq"
echo ""
echo " 2. Verify in MCPF Registry:"
echo " curl https://mcp.veritrust.vc/mcp/servers/did:web:llm.kis.gov.lv"
echo ""
echo " 3. Test with AI agent (Claude Desktop, ChatGPT, etc.)"
echo ""

View File

@@ -1,66 +0,0 @@
{
"credentialType": "MCPServerCredential",
"subject": {
"did": "did:web:llm.kis.gov.lv",
"type": "MCPServer",
"endpoint": "https://llm.kis.gov.lv/mcp",
"manifest": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json"
},
"controller": {
"id": "org.kisc",
"name": "Kultūras informācijas sistēmu centrs",
"registrationNumber": "KISC-REG-NUMBER",
"country": "LV",
"website": "https://kis.gov.lv",
"existingDID": "did:key:z6Mkuwv1z6y2yorbBf4LEkNzJCg16ERVfWE3bJEPKXtQm7a9"
},
"owner": {
"id": "org.km",
"name": "Kultūras ministrija",
"country": "LV",
"website": "https://km.gov.lv"
},
"capabilities": [
{
"name": "search",
"description": "Search YAML registers and Markdown documentation",
"riskLevel": "low"
},
{
"name": "get_entity",
"description": "Retrieve entity by canonical ID",
"riskLevel": "low"
}
],
"governance": {
"assuranceLevel": "substantial",
"compliance": [
"GDPR",
"NIS2",
"Latvian-Data-Protection-Act"
],
"dataClassification": "public",
"certifications": [],
"auditTrail": true
},
"publicKey": {
"type": "Ed25519VerificationKey2020",
"multibase": "z6MkjWGNnJsdyvutfbsytFJhkwDwyHkMkfWVL8X1fS1yBm2w",
"jwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "Sw-NGiVKSYj0zsrL7ceP6EMV673IuL2bYzHEypuojvA"
}
},
"validityPeriod": {
"notBefore": "2026-01-30T00:00:00Z",
"notAfter": "2027-01-30T00:00:00Z"
},
"metadata": {
"purpose": "MCPF Trust Framework integration for KISC MCP Server",
"environment": "production",
"poc": "POC-AI-LLM-1",
"technicalContact": "support@kis.gov.lv",
"requestDate": "2026-01-30"
}
}

View File

@@ -1,62 +0,0 @@
{
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://mcpf.dev/credentials/v1"
],
"id": "https://llm.kis.gov.lv/.well-known/credentials/mcp-server.json",
"type": [
"VerifiableCredential",
"MCPServerCredential"
],
"issuer": {
"id": "did:web:veritrust.vc",
"name": "VeriTrust"
},
"issuanceDate": "2026-01-30T00:00:00Z",
"expirationDate": "2027-01-30T00:00:00Z",
"credentialSubject": {
"id": "did:web:llm.kis.gov.lv#mcp-server",
"type": "MCPServer",
"endpoint": "https://llm.kis.gov.lv/mcp",
"manifest": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json",
"controller": {
"id": "org.kisc",
"name": "Kultūras informācijas sistēmu centrs",
"country": "LV"
},
"owner": {
"id": "org.km",
"name": "Kultūras ministrija",
"country": "LV"
},
"capabilities": [
"search",
"get_entity"
],
"governance": {
"assuranceLevel": "substantial",
"compliance": [
"GDPR",
"NIS2",
"Latvian-Data-Protection-Act"
],
"dataClassification": "public",
"certifications": []
}
},
"credentialStatus": {
"id": "https://veritrust.vc/status/2026#PLACEHOLDER",
"type": "StatusList2021Entry",
"statusPurpose": "revocation",
"statusListIndex": "PLACEHOLDER",
"statusListCredential": "https://veritrust.vc/status/2026"
},
"proof": {
"type": "Ed25519Signature2020",
"created": "2026-01-30T00:00:00Z",
"verificationMethod": "did:web:veritrust.vc#key-1",
"proofPurpose": "assertionMethod",
"proofValue": "PLACEHOLDER_WILL_BE_REPLACED_BY_VERITRUST_SIGNATURE"
},
"_comment": "⚠️ PLACEHOLDER: This credential will be replaced by VeriTrust-issued credential. DO NOT use in production until replaced."
}

View File

@@ -1,33 +0,0 @@
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:web:llm.kis.gov.lv",
"controller": "did:web:llm.kis.gov.lv",
"verificationMethod": [
{
"id": "did:web:llm.kis.gov.lv#key-1",
"type": "Ed25519VerificationKey2020",
"controller": "did:web:llm.kis.gov.lv",
"publicKeyMultibase": "z6MkjWGNnJsdyvutfbsytFJhkwDwyHkMkfWVL8X1fS1yBm2w"
}
],
"authentication": [
"did:web:llm.kis.gov.lv#key-1"
],
"assertionMethod": [
"did:web:llm.kis.gov.lv#key-1"
],
"service": [
{
"id": "did:web:llm.kis.gov.lv#mcp-server",
"type": "MCPServer",
"serviceEndpoint": "https://llm.kis.gov.lv/mcp"
}
],
"alsoKnownAs": [
"https://llm.kis.gov.lv",
"urn:kisc:llm-platform"
]
}

View File

@@ -1,12 +0,0 @@
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"x": "Sw-NGiVKSYj0zsrL7ceP6EMV673IuL2bYzHEypuojvA",
"use": "sig",
"kid": "did:web:llm.kis.gov.lv#key-1",
"alg": "EdDSA"
}
]
}

View File

@@ -1,18 +0,0 @@
{
"@context": "https://mcpf.dev/registry/v1",
"id": "did:web:llm.kis.gov.lv#trust-registry",
"type": "MCPTrustRegistry",
"version": "1.0",
"publisher": {
"id": "did:web:llm.kis.gov.lv",
"name": "KISC - Kultūras informācijas sistēmu centrs"
},
"services": {
"mcp": {
"id": "did:web:llm.kis.gov.lv#mcp-server",
"endpoint": "https://llm.kis.gov.lv/mcp",
"manifest": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json",
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json"
}
}
}

View File

@@ -1,135 +0,0 @@
{
"@context": "https://modelcontextprotocol.io/schema/2025-03-26",
"@type": "MCPServer",
"id": "did:web:llm.kis.gov.lv#mcp-server",
"name": "KISC MCP Server (IKT Architecture)",
"version": "1.0.0",
"description": "Target architecture for Latvian Cultural and Language Technology domain (Kultūras un valodas tehnoloģiju apakšjomas mērķarhitektūra)",
"author": {
"name": "Kultūras informācijas sistēmu centrs (KISC)",
"url": "https://kis.gov.lv",
"did": "did:web:llm.kis.gov.lv"
},
"capabilities": {
"tools": true,
"resources": false,
"prompts": false,
"sampling": false
},
"server": {
"endpoint": "https://llm.kis.gov.lv/mcp",
"transport": "sse",
"authentication": {
"required": false,
"methods": []
}
},
"tools": [
{
"name": "search",
"description": "Search YAML registers and Markdown documentation across the target architecture",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query string (searches across all registers and views)"
},
"limit": {
"type": "number",
"description": "Maximum number of results to return",
"default": 25,
"maximum": 100
}
},
"required": ["query"]
}
},
{
"name": "get_entity",
"description": "Retrieve a specific entity by its canonical ID from the architecture registers",
"inputSchema": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Entity canonical ID (e.g., 'goal.m1', 'org.kisc', 'sys.kultura.01')",
"pattern": "^[a-z]+\\.[a-z0-9_-]+$"
}
},
"required": ["id"]
}
}
],
"mcpf": {
"version": "0.1",
"spec": {
"repository": "https://github.com/MCPTrustFramework/MCPF-specification"
},
"entrypoint": {
"type": "manifest",
"url": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json"
},
"artifacts": {
"trust_registry": "https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json",
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json"
}
},
"trust": {
"verifications": [
{
"verifier": "did:web:veritrust.vc",
"type": ["VerifiableCredential", "MCPServerVerification"],
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json",
"covers": "did:web:llm.kis.gov.lv",
"proof_hint": {
"verificationMethod": "did:web:veritrust.vc#key-1",
"created": "2026-01-29T10:12:41Z"
}
}
]
},
"metadata": {
"organization": "Kultūras informācijas sistēmu centrs",
"organizationType": "government",
"country": "LV",
"domain": "kultura-valoda",
"tags": [
"architecture",
"government",
"latvia",
"culture",
"language",
"ikta",
"enterprise-architecture",
"target-architecture"
],
"dataClassification": "public",
"compliance": [
"GDPR",
"NIS2",
"Latvian-Data-Protection-Act"
],
"languages": [
"lv",
"en"
],
"status": "production"
},
"security": {
"tlsRequired": true,
"minTlsVersion": "1.3",
"signedRequestsRequired": false,
"rateLimits": {
"requestsPerMinute": 60,
"requestsPerHour": 1000
}
},
"links": {
"documentation": "https://kis.gov.lv/architecture",
"support": "mailto:support@kis.gov.lv",
"source": "https://github.com/kisc-gov-lv/MCP-KISC-architecture",
"terms": "https://kis.gov.lv/terms",
"privacy": "https://kis.gov.lv/privacy"
}
}

View File

@@ -1,6 +0,0 @@
Contact: mailto:security@kis.gov.lv
Contact: https://kis.gov.lv/security
Expires: 2027-12-31T23:59:59Z
Preferred-Languages: lv, en
Canonical: https://llm.kis.gov.lv/.well-known/security.txt
Policy: https://kis.gov.lv/security-policy

View File

@@ -1 +0,0 @@

View File

@@ -1,192 +0,0 @@
# MCPF Initialize Response - Expected Format
## Request Format
```bash
curl -X POST http://localhost:8787/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "test-client",
"version": "1.0"
}
},
"id": 1
}'
```
## Response Format (SSE Stream)
The server returns Server-Sent Events (SSE) format:
```
event: message
data: {JSON_RESPONSE}
```
## Complete JSON Response Structure
```json
{
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": {
"listChanged": true
}
},
"serverInfo": {
"name": "kisc-arch-kultura-valodu-mcp",
"version": "0.2.0"
},
"_meta": {
"identity": {
"id": "did:web:llm.kis.gov.lv",
"service": {
"mcp": "https://llm.kis.gov.lv/mcp"
},
"keys": {
"jwks_uri": "https://llm.kis.gov.lv/.well-known/jwks.json"
}
},
"mcpf": {
"version": "0.1",
"spec": {
"repository": "https://github.com/MCPTrustFramework/MCPF-specification"
},
"entrypoint": {
"type": "manifest",
"url": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json"
},
"artifacts": {
"trust_registry": "https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json",
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json"
}
},
"trust": {
"verifications": [
{
"verifier": "did:web:veritrust.vc",
"type": [
"VerifiableCredential",
"MCPServerVerification"
],
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json",
"covers": "did:web:llm.kis.gov.lv",
"proof_hint": {
"verificationMethod": "did:web:veritrust.vc#key-1",
"created": "2026-01-29T10:12:41Z"
}
}
]
}
}
},
"jsonrpc": "2.0",
"id": 1
}
```
## MCPF Layer 1: Session-Level Trust Metadata
The `_meta` field contains three key sections:
### 1. Identity
```json
{
"id": "did:web:llm.kis.gov.lv",
"service": {
"mcp": "https://llm.kis.gov.lv/mcp"
},
"keys": {
"jwks_uri": "https://llm.kis.gov.lv/.well-known/jwks.json"
}
}
```
**Purpose:** Establishes the server's decentralized identity (DID) and key material location.
### 2. MCPF Metadata
```json
{
"version": "0.1",
"spec": {
"repository": "https://github.com/MCPTrustFramework/MCPF-specification"
},
"entrypoint": {
"type": "manifest",
"url": "https://llm.kis.gov.lv/.well-known/mcp/manifest.json"
},
"artifacts": {
"trust_registry": "https://llm.kis.gov.lv/.well-known/mcp-trust-registry.json",
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json"
}
}
```
**Purpose:** Links to MCPF specification and trust artifacts for agent discovery.
### 3. Trust Verifications
```json
{
"verifications": [
{
"verifier": "did:web:veritrust.vc",
"type": ["VerifiableCredential", "MCPServerVerification"],
"credential": "https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json",
"covers": "did:web:llm.kis.gov.lv",
"proof_hint": {
"verificationMethod": "did:web:veritrust.vc#key-1",
"created": "2026-01-29T10:12:41Z"
}
}
]
}
```
**Purpose:** Documents third-party verification by VeriTrust credential service.
## Verification Steps
An MCPF-aware agent should:
1. **Extract DID** from `_meta.identity.id`
2. **Resolve DID document** at `https://llm.kis.gov.lv/.well-known/did.json`
3. **Fetch verification credential** from VeriTrust
4. **Verify credential signature** using VeriTrust's public key
5. **Check credential subject** matches server DID
6. **Optionally fetch** trust registry and manifest for additional context
## Test with jq
Extract specific fields:
```bash
# Get the DID
curl ... | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.identity.id'
# Output: did:web:llm.kis.gov.lv
# Get MCPF version
curl ... | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.mcpf.version'
# Output: 0.1
# Get verifier DID
curl ... | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.trust.verifications[0].verifier'
# Output: did:web:veritrust.vc
# Get credential URL
curl ... | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.trust.verifications[0].credential'
# Output: https://veritrust.vc/portal/mcp/credentials/aec9930b-9139-4b33-ac6f-ad3bd3d91da0.json
```
## Notes
- The `_meta` field is **in addition to** standard MCP initialize response fields
- Backward compatible: non-MCPF clients ignore `_meta`
- Layer 1 (session-level) + Layer 2 (per-response attestations) = Dual-layer trust
- Session ID returned in `Mcp-Session-Id` response header (not shown in JSON)

View File

@@ -1,179 +0,0 @@
#!/usr/bin/env python3
"""
MCPF Initialize Response Analyzer
Tests MCPF dual-layer trust integration and displays the response in a structured format.
"""
import json
import requests
import sys
from typing import Dict, Any
def test_mcpf_initialize(endpoint: str = "http://localhost:8787/mcp") -> Dict[str, Any]:
"""
Send initialize request to MCP server and return parsed response.
"""
payload = {
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "mcpf-analyzer",
"version": "1.0"
}
},
"id": 1
}
headers = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream"
}
print(f"🔍 Testing endpoint: {endpoint}")
print(f"📤 Sending initialize request...\n")
try:
response = requests.post(endpoint, json=payload, headers=headers, timeout=5)
response.raise_for_status()
# Parse SSE response (format: "event: message\ndata: {json}\n")
lines = response.text.strip().split('\n')
data_line = None
for line in lines:
if line.startswith('data: '):
data_line = line[6:] # Remove "data: " prefix
break
if not data_line:
print("❌ No data line found in SSE response")
return {}
return json.loads(data_line)
except requests.exceptions.RequestException as e:
print(f"❌ Request failed: {e}")
return {}
except json.JSONDecodeError as e:
print(f"❌ JSON parsing failed: {e}")
return {}
def print_section(title: str, content: str):
"""Print a formatted section."""
print("=" * 80)
print(f" {title}")
print("=" * 80)
print(content)
print()
def analyze_response(response: Dict[str, Any]):
"""Analyze and display MCPF response structure."""
if not response:
print("❌ No response received")
return
# Check for result
result = response.get("result", {})
if not result:
print("❌ No result in response")
print_section("Full Response", json.dumps(response, indent=2))
return
print("✅ Initialize successful\n")
# Display basic server info
server_info = result.get("serverInfo", {})
print_section(
"Server Information",
f"Name: {server_info.get('name', 'Unknown')}\n"
f"Version: {server_info.get('version', 'Unknown')}\n"
f"Protocol: {result.get('protocolVersion', 'Unknown')}"
)
# Display _meta (MCPF Layer 1)
meta = result.get("_meta")
if not meta:
print("⚠️ No _meta field found - MCPF integration not present")
return
print("✅ MCPF Layer 1 (Session-Level Trust Metadata) present\n")
# Identity section
identity = meta.get("identity", {})
if identity:
print_section(
"🆔 Identity (DID & Keys)",
f"DID: {identity.get('id', 'Not found')}\n"
f"MCP Service: {identity.get('service', {}).get('mcp', 'Not found')}\n"
f"JWKS URI: {identity.get('keys', {}).get('jwks_uri', 'Not found')}"
)
# MCPF section
mcpf = meta.get("mcpf", {})
if mcpf:
print_section(
"📋 MCPF Metadata",
f"Version: {mcpf.get('version', 'Not found')}\n"
f"Spec Repository: {mcpf.get('spec', {}).get('repository', 'Not found')}\n"
f"Manifest URL: {mcpf.get('entrypoint', {}).get('url', 'Not found')}\n"
f"Trust Registry: {mcpf.get('artifacts', {}).get('trust_registry', 'Not found')}\n"
f"Credential: {mcpf.get('artifacts', {}).get('credential', 'Not found')}"
)
# Trust verifications section
trust = meta.get("trust", {})
verifications = trust.get("verifications", [])
if verifications:
print_section("🔐 Trust Verifications", "")
for i, verification in enumerate(verifications, 1):
print(f" Verification #{i}:")
print(f" Verifier: {verification.get('verifier', 'Not found')}")
print(f" Type: {', '.join(verification.get('type', []))}")
print(f" Covers: {verification.get('covers', 'Not found')}")
print(f" Credential: {verification.get('credential', 'Not found')}")
proof = verification.get('proof_hint', {})
if proof:
print(f" Proof:")
print(f" Method: {proof.get('verificationMethod', 'Not found')}")
print(f" Created: {proof.get('created', 'Not found')}")
print()
# Summary
print_section(
"✅ MCPF Integration Summary",
f"• Identity DID present: {'✅' if identity else '❌'}\n"
f"• MCPF metadata present: {'✅' if mcpf else '❌'}\n"
f"• Trust verifications: {len(verifications)}\n"
f"• VeriTrust verified: {'✅' if any(v.get('verifier') == 'did:web:veritrust.vc' for v in verifications) else '❌'}\n"
f"\n🎯 This server implements MCPF v{mcpf.get('version', '?')} dual-layer trust"
)
# Full JSON for reference
print_section("📄 Complete _meta JSON", json.dumps(meta, indent=2))
def main():
endpoint = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8787/mcp"
print("""
╔════════════════════════════════════════════════════════════════════════════╗
║ MCPF Initialize Response Analyzer ║
║ Testing Dual-Layer Trust Integration ║
╚════════════════════════════════════════════════════════════════════════════╝
""")
response = test_mcpf_initialize(endpoint)
analyze_response(response)
print("""
╔════════════════════════════════════════════════════════════════════════════╗
║ Next: Test Layer 2 (per-response attestations) by calling a tool ║
║ Example: {"method": "tools/call", "params": {"name": "search", ...}} ║
╚════════════════════════════════════════════════════════════════════════════╝
""")
if __name__ == "__main__":
main()

View File

@@ -1,74 +0,0 @@
#!/bin/bash
# Test MCPF dual-layer trust integration - Initialize stage
# Shows the complete initialize response including _meta
set -e
echo "=========================================="
echo "MCPF Initialize Response Test"
echo "=========================================="
echo ""
# Test against localhost (assuming server is running)
MCP_ENDPOINT="${MCP_ENDPOINT:-http://localhost:8787/mcp}"
echo "Testing endpoint: $MCP_ENDPOINT"
echo ""
# Send initialize request
echo "Sending initialize request..."
RESPONSE=$(curl -s -X POST "$MCP_ENDPOINT" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "mcpf-test-client",
"version": "1.0"
}
},
"id": 1
}' \
--max-time 5)
echo ""
echo "=========================================="
echo "Raw Response (SSE format):"
echo "=========================================="
echo "$RESPONSE"
echo ""
# Extract and pretty-print the JSON from SSE data line
echo "=========================================="
echo "Parsed JSON Response:"
echo "=========================================="
echo "$RESPONSE" | grep '^data:' | sed 's/^data: //' | jq '.'
echo ""
echo "=========================================="
echo "MCPF _meta Section:"
echo "=========================================="
echo "$RESPONSE" | grep '^data:' | sed 's/^data: //' | jq '.result._meta'
echo ""
echo "=========================================="
echo "Layer 1 Trust Metadata Details:"
echo "=========================================="
echo ""
echo "Identity DID:"
echo "$RESPONSE" | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.identity.id'
echo ""
echo "MCPF Version:"
echo "$RESPONSE" | grep '^data:' | sed 's/^data: //' | jq -r '.result._meta.mcpf.version'
echo ""
echo "Trust Verifications:"
echo "$RESPONSE" | grep '^data:' | sed 's/^data: //' | jq '.result._meta.trust.verifications[]'
echo ""
echo "=========================================="
echo "Test Complete"
echo "=========================================="

View File

@@ -1 +0,0 @@

View File

@@ -1,29 +0,0 @@
#!/bin/sh
set -eu
DOC_PATH="${1:-KISC-merkarhitektura-apraksts.md}"
if [ ! -f "$DOC_PATH" ]; then
echo "ERROR: regenerated doc not found: $DOC_PATH"
echo "Hint: pass path as argument, e.g.: tools/qa/check_generated_doc_headings.sh docs/output.md"
exit 1
fi
need_heading() {
h="$1"
if ! grep -Eq "^[#]{1,6}[[:space:]]+$h([[:space:]]|\$)" "$DOC_PATH"; then
echo "ERROR: missing heading: $h"
exit 1
fi
}
# Required headings (core completeness gates)
need_heading "1.2 Iesaistītās puses"
need_heading "1.3 Saīsinājumi"
need_heading "1.4 Saistītie dokumenti"
need_heading "4.1 Juridiskais skats"
need_heading "5.1 Pasākumu plāns"
need_heading "5.2 Mijiedarbība ar citām jomām"
need_heading "5.3 Riski"
echo "OK: required headings exist in $DOC_PATH"

View File

@@ -1,23 +0,0 @@
#!/bin/sh
set -eu
REQ_FILES="
domains/kultura-valoda/registers/08-roadmap/roadmap.yaml
domains/kultura-valoda/registers/08-roadmap/interactions.yaml
domains/kultura-valoda/registers/09-risks/risks.yaml
"
for f in $REQ_FILES; do
if [ ! -f "$f" ]; then
echo "ERROR: missing required register: $f"
exit 1
fi
# fail if file contains only an empty items list
if grep -Eq '^\s*items:\s*\[\s*\]\s*$' "$f"; then
echo "ERROR: register is empty (items: []): $f"
exit 1
fi
done
echo "OK: required registers exist and are non-empty"

View File

@@ -1,21 +0,0 @@
#!/bin/sh
set -eu
DOC_PATH="${1:-KISC-merkarhitektura-apraksts.md}"
if [ ! -f "$DOC_PATH" ]; then
echo "ERROR: regenerated doc not found: $DOC_PATH"
exit 1
fi
# Naive but effective: count organization list lines containing "org." OR common abbreviations.
# Adjust pattern if your doc renders differently.
COUNT="$(grep -Eo '(org\.[a-z0-9_]+)|\b(KM|KISC|LNB|LNA|NKMP|LNKC|NKC|VKKF|VDAA|VARAM|TA|TM|VVC|LVA|LUMII)\b' "$DOC_PATH" | wc -l | tr -d ' ')"
# Require at least 10 hits to ensure it isn't collapsed to just 3 orgs.
if [ "$COUNT" -lt 10 ]; then
echo "ERROR: stakeholder content appears too small (hits=$COUNT). Likely collapsed list."
exit 1
fi
echo "OK: stakeholder presence check passed (hits=$COUNT)"

View File

@@ -1,10 +0,0 @@
# Regenerated document completeness checklist (kultura-valoda)
The regenerated architecture document MUST include:
- [ ] Abbreviations/terms (1.3)
- [ ] Related documents (1.4)
- [ ] Stakeholders/organizations list (1.2 scope)
- [ ] Legal view section "Juridiskais skats" (4.1) sourced from `registers/00-meta/legal-acts.yaml`
If any item is missing, the generator/template must be updated (do not delete content from re

View File

@@ -0,0 +1,166 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://processgit.org/Valsts-Pirmkods/Architecture-as-Code/schemas/arhitektura-0.1.schema.json",
"title": "Digitālās pārvaldes arhitektūras kā kods — shēma v0.1",
"description": "Kopīga shēma visām VARAM digitālās pārvaldes arhitektūras jomām (horizontālajām un nozaru). Jomas reģistri ir YAML datnes; katru datni pārbauda pret vienu no šeit definētajiem tipiem (sk. x-roles). Identifikatori jomā ir lokāli (func.01); pilnais identifikators ir <jomas slug>:<lokālais ID>, piemēram kultura-valoda:func.01. PPPA, Valsts Pirmkods. Melnraksts.",
"x-roles": {
"catalogue": "Catalogue",
"manifest": "Manifest",
"organizations": "OrganizationsFile",
"functions": "FunctionsFile",
"services": "ComponentsFile",
"information_resources": "ComponentsFile",
"systems": "ComponentsFile",
"goals": "Goal",
"roadmap": "ItemsFile",
"interactions": "ItemsFile",
"risks": "ItemsFile",
"as_is_catalog": "ItemsFile",
"relations": "EdgesFile",
"meta": "AnyObject",
"as_is": "AnyObject"
},
"$defs": {
"LocalId": {
"description": "Lokālais identifikators jomā: prefikss un segmenti, piemēram func.01, svc.k.03, org.kisc, goal.m1, domain.kultura-valoda.",
"type": "string",
"pattern": "^[a-z][a-z_]*(\\.[A-Za-z0-9_-]+)+$"
},
"Slug": {"type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$"},
"VpkId": {"description": "VPK ID no Valsts kancelejas organizāciju reģistra (B01): SS-IIII.", "type": "string", "pattern": "^[0-9]{2}-[0-9]{4}$"},
"Text": {"type": "string", "minLength": 1},
"AnyObject": {"type": "object"},
"Catalogue": {
"description": "domenas.yaml — visu VARAM digitālās pārvaldes arhitektūras jomu saraksts un katras jomas statuss šajā repozitorijā.",
"type": "object",
"required": ["version", "sources", "domains"],
"properties": {
"version": {"type": "string"},
"sources": {"type": "array", "items": {"type": "object", "required": ["url", "title"], "properties": {"url": {"type": "string", "format": "uri"}, "title": {"type": "string"}, "checked": {"type": "string", "format": "date"}}}},
"domains": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/CatalogueDomain"}}
}
},
"CatalogueDomain": {
"type": "object",
"required": ["slug", "title", "kind", "lead_institution", "varam_status", "as_code"],
"properties": {
"slug": {"$ref": "#/$defs/Slug"},
"title": {"$ref": "#/$defs/Text"},
"kind": {"enum": ["horizontal", "sectoral", "subdomain"], "description": "horizontal — horizontālā joma; sectoral — nozaru vai starpnozaru joma; subdomain — apakšjoma (parent norāda jomu)"},
"parent": {"$ref": "#/$defs/Slug"},
"lead_institution": {"$ref": "#/$defs/Text", "description": "Iestāde, kuras pārstāvis vada arhitektūras apraksta izstrādi (kā VARAM lapā). Personu kontaktdati netiek glabāti."},
"lead_vpk_id": {"$ref": "#/$defs/VpkId"},
"varam_status": {"enum": ["approved", "in_review", "in_development", "not_started"]},
"approved": {"type": "string", "format": "date"},
"approved_versions": {"type": "array", "items": {"type": "object", "required": ["version", "approved"], "properties": {"version": {"type": "string"}, "approved": {"type": "string", "format": "date"}}}},
"approved_by": {"type": "string"},
"description_url": {"type": "string", "format": "uri"},
"presentation_url": {"type": "string", "format": "uri"},
"as_code": {
"type": "object",
"required": ["status"],
"properties": {
"status": {"enum": ["transformed", "in_progress", "not_started"]},
"path": {"type": "string", "pattern": "^domains/[a-z0-9-]+$"},
"source": {"type": "string", "description": "No kura dokumenta pārveidots (datne mapē sources/)"}
},
"if": {"properties": {"status": {"const": "transformed"}}},
"then": {"required": ["status", "path"]}
}
}
},
"Manifest": {
"description": "domains/<slug>/manifest.yaml — jomas apraksts un tās reģistru atrašanās vieta.",
"type": "object",
"required": ["id", "title", "version", "status", "registers"],
"properties": {
"id": {"type": "string", "pattern": "^domain\\.[a-z0-9]+(-[a-z0-9]+)*$"},
"title": {"$ref": "#/$defs/Text"},
"version": {"type": "string"},
"status": {"enum": ["draft", "approved", "superseded"]},
"template_compliance": {"type": "boolean"},
"views": {"type": "array"},
"registers": {
"type": "object",
"description": "Reģistra loma → datne vai mape (relatīvi pret jomas mapi). Lomas: sk. x-roles.",
"properties": {
"organizations": {"type": "string"}, "functions": {"type": "string"}, "services": {"type": "string"},
"information_resources": {"type": "string"}, "systems": {"type": "string"}, "relations": {"type": "string"}
},
"required": ["organizations", "functions", "relations"],
"additionalProperties": {"type": "string"}
}
}
},
"ItemsFile": {"type": "object", "required": ["items"], "properties": {"items": {"type": "array", "items": {"$ref": "#/$defs/Item"}}}},
"Item": {"type": "object", "required": ["id"], "properties": {"id": {"$ref": "#/$defs/LocalId"}}},
"OrganizationsFile": {"type": "object", "required": ["items"], "properties": {"items": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/Organization"}}}},
"Organization": {
"type": "object",
"required": ["id", "title", "role", "status"],
"properties": {
"id": {"type": "string", "pattern": "^org\\.[A-Za-z0-9_.-]+$"},
"title": {"$ref": "#/$defs/Text"},
"role": {"$ref": "#/$defs/Text"},
"responsibilities": {"type": "array", "items": {"type": "string"}},
"status": {"type": "string"},
"vpk_id": {"$ref": "#/$defs/VpkId", "description": "Iestādes VPK ID (B01). Ja nav norādīts, VDZP to meklē pēc nosaukuma un saiti atzīmē kā izgūtu."}
}
},
"FunctionsFile": {"type": "object", "required": ["items"], "properties": {"items": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/Function"}}}},
"Function": {
"type": "object",
"required": ["id", "name"],
"properties": {
"id": {"type": "string", "pattern": "^func\\.[A-Za-z0-9_.-]+$"},
"subdomain": {"$ref": "#/$defs/LocalId"},
"name": {"$ref": "#/$defs/Text"},
"change_status": {"type": "string"},
"change_description": {"type": "string"},
"performed_by": {"type": "array", "items": {"$ref": "#/$defs/LocalId"}, "description": "Organizācijas (org.*), kas veic funkciju"}
}
},
"ComponentsFile": {"type": "object", "required": ["items"], "properties": {"items": {"type": "array", "items": {"$ref": "#/$defs/Component"}}}},
"Component": {
"description": "Pakalpojums, informācijas resurss vai informācijas sistēma.",
"type": "object",
"required": ["id", "name", "status"],
"properties": {
"id": {"$ref": "#/$defs/LocalId"},
"number": {},
"subdomain": {"$ref": "#/$defs/LocalId"},
"name": {"$ref": "#/$defs/Text"},
"status": {"type": "string"},
"description": {"type": "string"},
"change_description": {"type": "string"},
"resources": {}
}
},
"Goal": {
"type": "object",
"required": ["id", "title"],
"properties": {"id": {"type": "string", "pattern": "^goal\\.[A-Za-z0-9_.-]+$"}, "code": {"type": "string"}, "title": {"$ref": "#/$defs/Text"},
"description": {"type": "string"}, "status": {"type": "string"}}
},
"EdgesFile": {"type": "object", "required": ["edges"], "properties": {"edges": {"type": "array", "items": {"$ref": "#/$defs/Edge"}}}},
"Edge": {
"type": "object",
"required": ["from", "type", "to"],
"properties": {
"from": {"$ref": "#/$defs/LocalId"},
"type": {"type": "string", "pattern": "^[a-z]+(_[a-z]+)*$",
"description": "Saites veids, piemēram has_goal, has_function, has_service, has_information_resource, has_system, performed_by, uses, provides"},
"to": {"$ref": "#/$defs/LocalId"}
},
"additionalProperties": false
}
}
}

112
tools/validate.py Normal file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Pārbauda repozitoriju pret shēmu un datu līguma noteikumiem (tās pašas pārbaudes, ko veic VDZP savienotājs).
python3 tools/validate.py (no repozitorija saknes; pip install pyyaml jsonschema)
Pārbaudes:
catalogue_valid domenas.yaml atbilst #/$defs/Catalogue; slug unikāli; katrai pārveidotajai jomai mape eksistē
schema_valid katrs jomas reģistrs atbilst savas lomas tipam (schemas/arhitektura-0.1.schema.json, x-roles)
ids_unique lokālie identifikatori jomā ir unikāli
edges_resolve katras saites from un to ir šīs jomas elements
"""
import json
import sys
from pathlib import Path
import yaml
from jsonschema import Draft202012Validator
ROOT = Path(__file__).resolve().parents[1]
SCHEMA = json.loads((ROOT / "schemas/arhitektura-0.1.schema.json").read_text(encoding="utf-8"))
ENTITY = {"organizations": "organisation", "functions": "function", "services": "service",
"information_resources": "information_resource", "systems": "system", "goals": "goal",
"roadmap": "roadmap_item", "interactions": "interaction", "risks": "risk", "as_is_catalog": "as_is_component"}
def validator(defname):
return Draft202012Validator({"$schema": SCHEMA["$schema"], "$defs": SCHEMA["$defs"], "$ref": f"#/$defs/{defname}"})
def errors(defname, doc, where):
return [f"{where}: {'/'.join(map(str, e.absolute_path)) or '(sakne)'}: {e.message[:200]}"
for e in sorted(validator(defname).iter_errors(doc), key=lambda e: list(e.absolute_path))][:20]
def files_of(domain_dir, rel):
p = domain_dir / rel
if p.is_dir():
return sorted(p.rglob("*.yaml"))
return [p] if p.exists() else []
def domain(domain_dir):
"""-> (entities, edges, problems) for one domain folder."""
slug = domain_dir.name
problems = {"schema_valid": [], "ids_unique": [], "edges_resolve": []}
man_path = domain_dir / "manifest.yaml"
man = yaml.safe_load(man_path.read_text(encoding="utf-8"))
problems["schema_valid"] += errors("Manifest", man, f"{slug}/manifest.yaml")
if man.get("id") != f"domain.{slug}":
problems["schema_valid"].append(f"{slug}/manifest.yaml: id {man.get('id')} ≠ domain.{slug}")
entities, edges = [], []
for role, rel in (man.get("registers") or {}).items():
defname = SCHEMA["x-roles"].get(role, "AnyObject")
for f in files_of(domain_dir, rel):
doc = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
where = f"{slug}/{f.relative_to(domain_dir)}"
problems["schema_valid"] += errors(defname, doc, where)
if role == "relations":
edges += [dict(e, file=where) for e in doc.get("edges", []) if isinstance(e, dict)]
elif role in ENTITY:
items = [doc] if defname == "Goal" else doc.get("items", []) if isinstance(doc, dict) else []
entities += [dict(it, _type=ENTITY[role], _file=where) for it in items if isinstance(it, dict) and it.get("id")]
# the domain and its subdomains are nodes too (edges start from domain.<slug>)
meta = domain_dir / "registers/00-meta/domain.yaml"
nodes = {f"domain.{slug}"}
if meta.exists():
m = yaml.safe_load(meta.read_text(encoding="utf-8")) or {}
nodes |= {s["id"] for s in m.get("subdomains", []) if isinstance(s, dict) and s.get("id")}
seen = {}
for e in entities:
if e["id"] in seen:
problems["ids_unique"].append(f"{slug}: {e['id']} ({seen[e['id']]} un {e['_file']})")
seen[e["id"]] = e["_file"]
known = set(seen) | nodes
for e in edges:
for k in ("from", "to"):
if e.get(k) not in known:
problems["edges_resolve"].append(f"{e['file']}: {e.get('from')} -{e.get('type')}-> {e.get('to')} ({k} nav jomā)")
return man, entities, edges, problems
def main():
cat = yaml.safe_load((ROOT / "domenas.yaml").read_text(encoding="utf-8"))
bad = errors("Catalogue", cat, "domenas.yaml")
slugs = [d.get("slug") for d in cat.get("domains", [])]
bad += [f"domenas.yaml: slug {s} atkārtojas" for s in set(slugs) if slugs.count(s) > 1]
for d in cat.get("domains", []):
if d.get("as_code", {}).get("status") == "transformed" and not (ROOT / d["as_code"].get("path", "-") / "manifest.yaml").exists():
bad.append(f"domenas.yaml: {d['slug']}: {d['as_code'].get('path')}/manifest.yaml nav")
for p in sorted((ROOT / "domains").glob("*/manifest.yaml")):
if p.parent.name not in slugs:
bad.append(f"domains/{p.parent.name}: jomas nav domenas.yaml")
total = {"catalogue_valid": bad}
for p in sorted((ROOT / "domains").glob("*/manifest.yaml")):
man, ents, edges, prob = domain(p.parent)
print(f"{p.parent.name}: {len(ents)} elementi, {len(edges)} saites")
for k, v in prob.items():
total.setdefault(k, []).extend(v)
ok = True
for k, v in total.items():
print(f"{k}: {'OK' if not v else f'{len(v)} kļūdas'}")
for x in v[:10]:
print(" ", x)
ok &= not v
n = sum(1 for d in cat["domains"] if d["as_code"]["status"] == "transformed")
print(f"Jomas: {len(cat['domains'])}, pārveidotas kodā: {n}")
sys.exit(0 if ok else 1)
if __name__ == "__main__":
main()