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:
8
CHANGELOG.md
Normal file
8
CHANGELOG.md
Normal 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
42
README.md
Normal 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).
|
||||
100
contracts/B09-digitalas-parvaldes-arhitekturas.odcs.yaml
Normal file
100
contracts/B09-digitalas-parvaldes-arhitekturas.odcs.yaml
Normal 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
240
domenas.yaml
Normal 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}
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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
|
||||
@@ -1,6 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
.DS_Store
|
||||
*.log
|
||||
site/
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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}
|
||||
@@ -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/
|
||||
@@ -1 +0,0 @@
|
||||
placeholder.git
|
||||
@@ -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"]
|
||||
2119
ikt-arh-kultura-valodu-tehnologijas/mcp/package-lock.json
generated
2119
ikt-arh-kultura-valodu-tehnologijas/mcp/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 }>;
|
||||
};
|
||||
@@ -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}` };
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { RepoIndex } from "../repo/types.js";
|
||||
|
||||
export function listResources(idx: RepoIndex) {
|
||||
return {
|
||||
ok: true,
|
||||
resources: idx.resources,
|
||||
};
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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) };
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 ""
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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 "=========================================="
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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)"
|
||||
@@ -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
|
||||
166
schemas/arhitektura-0.1.schema.json
Normal file
166
schemas/arhitektura-0.1.schema.json
Normal 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
112
tools/validate.py
Normal 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()
|
||||
Reference in New Issue
Block a user