# CM AI Docking Port

## Reception Protocol Spec v1.0 (usklajena specifikacija)

*Nastane 2026-09-29 z združitvijo: Master Development Prompt v0.1 (osnovni koncept), Gemini addon ("Planning Draft – implementacijski koraki"), Grok addon ("Planning Draft – Protocol & First Files"). Ta dokument je zdaj referenčna verzija — v0.1 in oba addona ostanejo kot zgodovina razmisleka, ne kot ločena navodila.*

*Danes nastane samo ta dokument. Prve datoteke (manifest.json, index.html, reception.php) pridejo naslednjič, na dogovorjeni lokaciji (glej §12).*

---

## 1. Namen (nespremenjeno iz v0.1)

CM AI Docking Port je univerzalni vstopni mehanizem, skozi katerega AI agent odkrije CM Ecosystem in z njim komunicira preko standardnega HTTP/JSON protokola — brez CM-specifičnega vtičnika.

Ni nov CM, ni nov poslovni motor, ni centralni marketplace, ni AI agent. CM ostaje edini vir resnice (source of truth). Docking Port je tanek protokolarni/usmerjevalni sloj nad obstoječimi javnimi API-ji (dokaz koncepta: `GET /app/public/api/availability_multi.php`, ki že vrača čist, semantično razumljiv JSON — glej realno implementacijo na `/var/www/html/app/public/api/availability_multi.php`).

---

## 2. Ključno usklajevalno načelo (Grok, potrjuje in izostri v0.1 §6/§14)

To je edina prava **sprememba/izostritev** glede na v0.1 — vse ostalo se le dopolnjuje.

> **AI agent nikoli ne kliče drugih nodov ali obstoječih poslovnih API-jev neposredno. Agent komunicira izključno z Reception svojega vstopnega noda.**

```text
AI Agent
   │
   ▼
CM AI Docking Port (manifest + docs) — odkritje
   │
   ▼
Reception — EDINA točka komunikacije
   │
   ├── local CM (kliče availability_multi.php interno)
   ├── nearby nodes (forward envelope, agent tega ne vidi)
   └── deeper nodes (znotraj max_depth)
   │
   ▼
agregiran odgovor → nazaj do AI Agent
```

Model je "pošta": agent odda zahtevek na Reception, počaka, dobi en sam agregiran odgovor. Agent ni usmerjevalec in ne pozna interne topologije mreže — to ohranja decentralizacijo (v0.1 §9) in hkrati preprečuje, da bi agent postal vektor za zanke, zlorabo ali fragmentacijo protokola.

Vse iz v0.1 §7–§12 (geografski routing, verižna poizvedba, globina, zaščita pred kroženjem) ostaja veljavno — le izvaja ga Reception rekurzivno, agent tega procesa ne vidi in vanj ne posega.

---

## 3. Dve ravni dostopa (nespremenjeno iz v0.1 §5)

- **A. Local CM access** — javno izpostavljene funkcije konkretne instalacije (npr. availability).
- **B. Ecosystem access** — isti Docking Port, a zahtevek gre v skupno Reception, ki ga po potrebi usmeri naprej po mreži.

---

## 4. Geografija kot routing princip (nespremenjeno iz v0.1 §7–§8)

Vstopna točka ≠ cilj iskanja. Agent lahko potrka na CM v Radovljici in vpraša za Ljubljano — Reception usmerja glede na `target.location`, ne glede na lokacijo vstopnega noda.

---

## 5. Request Envelope — usklajena končna shema

Osnova iz v0.1 §13, dopolnjena s konkretnimi vrednostmi iz Grok addona.

```json
{
  "protocol": "CM-AI-Docking-Port",
  "version": "0.1",
  "request_id": "req-2026-08-10-abc123",
  "request_expiry": "2026-08-10T12:00:00Z",
  "origin": "ai-agent",
  "target": {
    "location": { "lat": 46.05, "lon": 14.50, "label": "Ljubljana" },
    "radius_km": 30
  },
  "query": {
    "type": "availability",
    "from": "2026-08-10",
    "to": "2026-08-16",
    "guests": 2,
    "units": null
  },
  "limits": {
    "max_depth": 2,
    "max_distance_km": 40,
    "max_results": 10
  },
  "visited": []
}
```

Pravila:

- `request_id` unikaten, potuje z zahtevkom (v0.1 §12 — zaznava podvajanja).
- `request_expiry` **OBVEZEN** (v0.1 §11/§9). Popravek 2026-09-30 (GPT catch): spec je od v0.1 naprej to polje navajala med obveznimi varovalkami, a ga noben Request Envelope primer (vključno s Korakom 2 kodo) dejansko ni vseboval — neusklajenost je obstajala od začetka, nihče je ni opazil do zdaj. Reception (od tega popravka dalje) preveri **samo prisotnost** tega polja — format/semantika ("kdaj zahtevek zares poteče") **ni definirana in se namenoma ne izmišlja** v Koraku 2; to je odprto vprašanje za forwarding fazo (Korak 3), ko bo `request_expiry` dejansko pomemben za nadzor nad tem, kako dolgo se zahtevek sme širiti po mreži.
- `visited` dopolnjuje **Reception**, ne agent (v0.1 §12 — preprečuje zanke A→B→C→A).
- `target.location` je cilj iskanja, ločen od vstopne točke (§4).
- `query.type` je za MVP omejen na `"availability"` (v0.1 §15, §23).
- Agent ne sme dodajati lastnih hopov ali polj izven te sheme.
- `max_depth`/`max_distance_km`/`max_results` niso strogo obvezni na ravni Reception validacije — imajo smiselne privzete vrednosti iz `limits_default` v manifestu, če jih agent izpusti. To namerno razlikuje njihovo obravnavo od `request_expiry`, za katerega ni smiselnega privzetka, ki bi ga lahko strežnik izmislil namesto agenta.

---

## 6. Response Envelope — nova dopolnitev (Grok; v0.1 tega ni imel eksplicitno)

v0.1 §15 je pokazal samo surov odgovor `availability_multi.php`. Manjkala je ovojnica za agregiran, multi-node odgovor. To je zapolnjena vrzel:

```json
{
  "protocol": "CM-AI-Docking-Port",
  "version": "0.1",
  "request_id": "req-2026-08-10-abc123",
  "ok": true,
  "partial": false,
  "search_meta": {
    "entry_node": "CM-AI-DEMO-001",
    "target_location": { "lat": 46.05, "lon": 14.50, "label": "Ljubljana" },
    "nodes_queried": 1,
    "nodes_reached": 1,
    "max_depth_used": 0,
    "max_depth_limit": 2
  },
  "results": [
    {
      "node_id": "CM-AI-DEMO-001",
      "depth": 0,
      "source": "local_availability_api",
      "freshness": { "generated_at": "2026-09-28T18:18:29+00:00" },
      "data": { "...": "neposreden izhod availability_multi.php, brez reinterpretacije" }
    }
  ],
  "errors": []
}
```

Pravilo (v0.1 §14 potrjeno): Reception **ne reinterpretira** poslovnega odgovora — `data` je surov izhod obstoječega API-ja, samo ovit s provenance (`node_id`, `depth`, `source`, `freshness`). `partial: true` + razlog, kadar kak node ni bil dosegljiv (v0.1 §20 — brez lažnega vtisa popolne pokritosti).

---

## 7. Manifest — usklajena končna shema

Združuje v0.1 §16 (osnovna struktura) z Grokovo dopolnitvijo (capability-level metadata: `side_effect`, `requires_consent` — neposredno iz v0.1 §22 zahteve).

Objavljen na dveh mestih (ena vsebina, ena morda redirect na drugo):
```text
/.well-known/cm-ai-docking-port.json
/app/public/ai/manifest.json
```

```json
{
  "protocol": "CM-AI-Docking-Port",
  "version": "0.1",
  "service": {
    "name": "CM Ecosystem Reception",
    "type": "ai-entry-point",
    "node_id": "CM-AI-DEMO-058",
    "location": { "label": "Lancovo / Radovljica area (Demo 58)", "lat": 46.34, "lon": 14.17 }
  },
  "capabilities": {
    "local": [
      { "id": "availability", "side_effect": false, "requires_consent": false,
        "description": "Check unit availability for a date range" }
    ],
    "ecosystem": [
      { "id": "accommodation-discovery", "side_effect": false, "requires_consent": false,
        "description": "Geographic discovery across participating CM nodes via Reception" }
    ]
  },
  "reception": { "method": "POST", "endpoint": "/app/public/ai/reception.php", "content_type": "application/json" },
  "documentation": {
    "human": "/app/public/ai/index.html",
    "machine": "/app/public/ai/manifest.json",
    "spec": {
      "html": "/app/public/ai/spec.html",
      "markdown": "/app/public/ai/spec.md",
      "note": "Full working design specification. SKLENJENO sections are settled; OSNUTEK sections are open drafts.",
      "spec_synced_at": "<ISO8601, auto-updated on each publish>"
    }
  },
  "limits_default": { "max_depth": 2, "max_distance_km": 50, "max_results": 20 },
  "notes": [
    "AI agents must always communicate with Reception.",
    "Do not call internal business APIs directly.",
    "Entry point and search target are separate concepts."
  ]
}
```

**Dodano 2026-09-30 (Dvojček/Gemini predlog, uporabnik potrdil)**: manifest zdaj eksplicitno referencira ta dokument (`documentation.spec`, obe obliki) in nosi `spec_synced_at` — kdaj je bila javna kopija specifikacije nazadnje osvežena iz lokalnega delovnega izvoda. Objava ni ročna: `~/Dokumenti/Guestbook/publish_ai_docking_spec.sh` v enem koraku redigira LAN podrobnosti, pretvori `.md` v stilizirano `spec.html`, deploya oboje na instanco 58, in posodobi `spec_synced_at` na obeh manifestih (`/app/public/ai/manifest.json` in `/.well-known/cm-ai-docking-port.json`). Poganja se ročno ob vsaki vsebinski spremembi tega dokumenta — ni (še) avtomatiziran cron.

---

## 8. Vloga `availability_multi.php` (nespremenjeno, potrjeno v0.1 §15 + Gemini §Korak2)

```text
Reception (lokalni node)
   │  preveri: je target v dosegu? je depth 0?
   ▼
kliče lastni GET /app/public/api/availability_multi.php?units=...&from=...&to=...&mode=basic
   │
   ▼
vzame JSON odgovor, vstavi v results[] z provenance (§6)
   │
   ▼ (če max_depth > 0 in potrebno)
forwarda envelope sosedom, zbira odgovore
   ▼
vrne Response Envelope agentu
```

Docking Port **ne posega** v `availability_multi.php` ali katerikoli drug obstoječi endpoint. Klic je interni, agent ga ne vidi.

---

## 9. Varovalke in omejitve (nespremenjeno, potrjeno iz vseh treh virov)

Iz v0.1 §11, §12, §19, §21, §22, §25, dodatno eksplicirano v Gemini addonu kot "Opozorilo za razvijalca":

**Prepovedano v prvi fazi (MVP):**

- 🚫 Centralni master-registry strežnik vseh nodov — usmerjanje je P2P / sosedske liste (v0.1 §9).
- 🚫 Poseg v obstoječo poslovno logiko (rezervacije, cene) — Docking Port se samo "priklopi".
- 🚫 Write operacije preko Reception (booking, inquiry submit) — MVP je read/discovery only (v0.1 §22).
- 🚫 Vezava na en AI stack/ponudnik/težke knjižnice — čist HTTP/JSON, nativen PHP skladen z obstoječim CM Free stackom.
- 🚫 Cache kot source of truth — availability ostane avtoritativna iz konkretnega CM (v0.1 §21).
- 🚫 Agent, ki sam skaka med nodi (§2 zgoraj).

**Obvezno:**

- `max_depth`, `max_distance_km`, `max_results`, `request_expiry` na vsakem zahtevku (v0.1 §11).
- `request_id` + `visited` proti kroženju in podvajanju (v0.1 §12).
- Vsaka capability označena z `side_effect` in `requires_consent` (v0.1 §22, §7 zgoraj).
- Izpad noda ne sme prekiniti celotnega iskanja — `partial: true` namesto lažne popolnosti (v0.1 §20).

---

## 10. Human-readable dokumentacija (`/app/public/ai/index.html`) — nespremenjeno iz v0.1 §17

Kratka stran, berljiva ljudem in LLM-jem (jasni `<h1>`/`<code>` bloki, brez težkega JS-a), ki razloži: kaj je Docking Port, kaj je Reception (edina točka za AI), primer Request/Response Envelope, da agent ne kliče notranjih API-jev neposredno, omejitve, in da gre za read/discovery MVP.

---

## 11. MVP koraki — usklajen zaporedni načrt

Združitev v0.1 §23–§25 (postopnost, "ne graditi prezgodaj") + Gemini addon (4 konkretni koraki) + Grok addon (datotečni seznam).

```text
Korak 0 (danes, 2026-09-29): ta usklajena specifikacija ✅

Korak 1: Static Entry
  /.well-known/cm-ai-docking-port.json  ✅ DEPLOYANO 2026-09-29 (uporabnik ročno naredil sudo mkdir
                                            +chown na instanci 58, Claude Code postavil vsebino),
                                            potrjeno: https://ai-cmfree.duckdns.org/.well-known/cm-ai-docking-port.json (HTTP 200)
  /app/public/ai/manifest.json          ✅ DEPLOYANO 2026-09-29 (node_id CM-AI-DEMO-058,
                                            usklajen skozi Claude+Gemini+Grok+GPT review),
                                            potrjeno javno dostopno: https://ai-cmfree.duckdns.org/app/public/ai/manifest.json (HTTP 200)
  /app/public/ai/index.html             ✅ DEPLOYANO 2026-09-29 (Claude Code pripravil vsebino,
                                            uporabnik obrnil vrstni red: tokrat Claude pred skupino),
                                            potrjeno: https://ai-cmfree.duckdns.org/app/public/ai/index.html (HTTP 200)

Korak 1 zaključen v celoti.

Korak 2: Reception (single node, depth 0)          ✅ DEPLOYANO 2026-09-29
  /app/public/ai/reception.php
  - koda: Gemini prvi osnutek → GPT (izčrpen review proti živemu API-ju, 5 popravkov) → Claude
    (preverjeno: curl extenzija na 58 obstaja, Apache ima en sam catch-all vhost torej Host-header
    ni nujen a je neškodljiv, `availability_multi.php` dejansko vrača `generated_at`) → deployano
    z `.tmp` + `php -l` lint + atomski `mv`, brez okna polomljene sintakse v živo
  - popravki proti Geminijevemu prvotnemu osnutku: `units: null` → interni `units=all`; lokalni
    fail = `ok:false` (ne `partial:true`, to je rezervirano za Korak 3); VEDNO Response Envelope
    tudi na 400/405; samo POST (+OPTIONS/CORS); agent ne vidi internega URL-ja/HTTP kode/curl napake
  - Claude dodal: `error_log()` server-side ob curl napaki (samo v log, agent ga ne vidi)
  - testirano 5 scenarijev na živem endpointu: veljaven zahtevek (ok:true, L1 available),
    GET→405+envelope, napačen protocol→400+envelope, manjkajo from/to→200+missing_dates,
    nepodprt query.type→200+unsupported_query_type — vsi po pričakovanjih
  - endpoint: https://ai-cmfree.duckdns.org/app/public/ai/reception.php
  - index.html status badge posodobljen na "live" ŠELE po uspešnem testu (ne hkrati z uploadom)

Naslednje: Korak 3 (neighbour table + geografski forwarding) — podroben načrt v §16, testni scenarij v §13.
```

Šele po potrjenem Koraku 4 razmišljati o širšem discoveryju, kompleksnejšem routingu, cache-u, globalnih indeksih (v0.1 §25 — to velja nespremenjeno).

---

## 12. Lokacija izvedbe — odločeno danes (2026-09-29)

Prve dejanske datoteke (Korak 1–2) gredo na **ai-cmfree demo, instanca 58** (`ai-cmfree.duckdns.org` → GN7 reverse-proxy → 58; glej memorijo `ai_bot_demo_public_access`) — brez pravih podatkov, `robots.txt` že odprt, obstoječa promotion-testing površina. To je edino okolje kjer read/discovery MVP nima tveganja glede realnih gostov/rezervacij.

Realni endpoint `availability_multi.php` (referenca za obliko odgovora, §1/§8 zgoraj) je preverjen na GN7 (`/var/www/html/app/public/api/availability_multi.php`) — instanca 58 ima svojo kopijo istega API-ja, path je treba potrditi ob dejanski implementaciji (ni nujno identičen `/app/public/api/...`).

---

## 13. Testni scenarij (usklajen iz v0.1 §24 + Gemini + Grok)

Tri testne CM/node instalacije na različnih lokacijah (Korak 4). Preveriti:

1. Agent najde manifest, prebere capabilities in reception endpoint.
2. Zahtevek za lokalno availability (depth 0) vrne pravilen Response Envelope.
3. Zahtevek s ciljem na drugi lokaciji pravilno potuje Node A → relevanten sosed in vrne rezultat od tam.
4. `visited` prepreči zanko (A vabi B, B vabi A).
5. `max_depth` ustavi širjenje na pravem koraku.
6. `request_id` prepreči podvojeno obravnavo istega zahtevka.
7. Izpad enega noda (B nedosegljiv) ne prekine iskanja — rezultat vsebuje `partial: true` in jasno pove obseg, ne pretvarja se, da je pregledan cel Ecosystem.
8. Odgovor je agentu semantično jasen brez dodatne razlage.

### Testna infrastruktura — pripravljena 2026-09-29

Tri instalacije za zgornji test:

| Node | node_id | Lokacija (lat/lon) | Kaj je | URL |
|---|---|---|---|---|
| A (pravi) | `CM-AI-DEMO-058` | Lancovo/Radovljica (46.34, 14.17) | Prava CM instanca, prava `availability_multi.php` podatka | `https://ai-cmfree.duckdns.org/app/public/ai/reception.php` |
| B (stub) | `CM-AI-STUB-BLED` | Bled (46.3683, 14.1146) | Test stub na 57, fiksni izmišljeni podatki, ni prave CM logike | `[interno testno omrežje — LAN, ni javno dostopno]` |
| C (stub) | `CM-AI-STUB-LJ` | Ljubljana (46.0569, 14.5058) | Test stub na 57, fiksni izmišljeni podatki, ni prave CM logike | `[interno testno omrežje — LAN, ni javno dostopno]` |

Stub node-a B in C implementirata isti Reception protokol (envelope, POST-only, `invalid_envelope`/`missing_dates`/`unsupported_query_type` obravnava) kot pravi node, a vedno odgovorita lokalno (depth 0, brez lastnega forwardinga) s fiksnimi izmišljenimi podatki — nikoli ne kličeta prave CM logike. Namen: omogočiti resnično testiranje `max_fanout`/sort-po-razdalji/`visited` mehanike na pravem omrežju (HTTP klici med pravima strežnikoma), preden se karkoli od tega postavi na produkcijo.

Preverjeno 2026-09-29: oba stub node-a odgovarjata pravilno lokalno na 57; node A (58) dostopa do obeh preko LAN (interno LAN, IP namerno ni objavljen) — HTTP 200, potrjen doseg. Testna infrastruktura za §13 scenarij je s tem pripravljena; sama forwarding koda (Korak 3) na node A še ni napisana.

---

## 14. §14 — ZAPRTO / SKLENJENO (formalno 2026-09-30, po Gregovem A→B→C okviru, potrjeno Claude+Grok+GPT+Gemini)

Prvotno odprta vprašanja (Grok predlog, Claude dopolnil, Gemini "Dvojček" zaprl — vsi trije se strinjajo):

- **Bootstrap sosedske tabele** — SKLENJENO: ročen, kuriran `nodes.json` ob pridružitvi, out-of-band, brez direktorija/self-registration servisa (skladno s prepovedjo centralnega registra, §9). To je naloga **operaterja Docking Port infrastrukture**, ne CM uporabnika/gostitelja — ti dve vlogi se v specifikaciji izrecno ne smeta mešati (gostitelj v smislu [[cm_customer_onboarding_profile]] nima nobenega ročnega dela s P2P konfiguracijo).
- **Avtentikacija med Reception-i** — SKLENJENO: brez, dokler je vse read-only in javno (skladno z `requires_consent: false`).
- **Rate limiting** — SKLENJENO na nivoju principa: `reception.php` ostane popolnoma brezstatusen (stateless) — noben APCu, noben datotečni counter v sami PHP datoteki, to bi podrlo načelo "tanka vrata". Omejevanje pogostosti se rešuje na infrastrukturnem nivoju (reverse-proxy/Apache `mod_ratelimit` pred `ai-cmfree.duckdns.org`), ne v aplikacijski kodi. Konkreten mehanizem še ni izbran — to ni blokada za načrtovanje Koraka 3, ker gre za sloj pred, ne znotraj `reception.php`.
- **Consent/write model** — SKLENJENO: ostane izven protokola, dokler ne obstaja dejanska write capability. `requires_consent: false` drži za MVP.
- **Pasivne varovalke brez stanja** (Gemini dodatek, sprejeto): tudi v Koraku 3 ostajata dve preverbi popolnoma brez baze — (1) prisilno obrezovanje `max_depth` na lokalno dovoljeni maksimum, ne glede na to, kaj pošlje agent; (2) `visited` array — če je lastni `node_id` že v njem, se zahtevek ne posreduje naprej. Obe se izračunata iz samega zahtevka, brez shranjevanja stanja.

### Nova odprta točka (predlog uporabnika, 2026-09-29, še NI usklajena s celotno skupino)

**Globina (depth) ≠ širina (fanout)** — spec trenutno omejuje samo, kolikokrat se zahtevek sme odbiti naprej (`max_depth`), ne pa, koliko sosedov Reception na enem hopu dejansko pokliče. Brez omejitve širine je promet eksponenten: pri kuriranih 5 sosedih in `max_depth: 2` lahko en agentov zahtevek sproži do 5 + 25 = 30 vzporednih klicev po mreži — to je bolj realno "open relay" tveganje kot tisto, ki ga rešuje rate limit na enem nodu, ker se pomnoži po celotni mreži.

Uporabnikov sklep (po lastnem premisleku je zavrnil agentovo nastavljivo globino IN geografsko spremenljivo globino kot nesmiselni — oboje bi obremenilo enega od udeležencev): edino kar šteje je **globina 0** (lasten strežnik) in **globina 1** (prvi najbližji nodi) — in ključno vprašanje ni "kako globoko", ampak **"s koliko nodi node komunicira vzporedno na enem hopu"**.

Predlog za razpravo (Claude, še ne implementacija):

- Nov parameter, npr. `max_fanout`, **stalen po nodu** (del node-ove lastne konfiguracije, ne nastavljiv s strani agenta, ne geografsko spremenljiv) — privzeto npr. 3.
- Reception pri forwardingu iz `nodes.json` vzame samo `max_fanout` geografsko najbližjih, še-neobiskanih sosedov glede na `target.location` (sort po razdalji + slice) — ostane brezstatusno, brez baze.
- S tem postane zgornja meja prometa izračunljiva vnaprej namesto eksponentno neomejene.

**SKLENJENO 2026-09-29** — Gemini potrdil `max_fanout` predlog in dodatno razložil "jump over" mehaniko (usmerjanje po Haversine razdalji do `target.location`, ne do vstopne točke): node sortira svoje sosede iz `nodes.json` po razdalji do cilja in vzame prvih `max_fanout`, s čimer lahko zahtevek v enem skoku "preskoči" geografsko bližje, a smerno nerelevantne sosede (primer: Piran → Rogla preskoči Portorož/Izolo/Koper, gre direktno proti Ljubljani). Priporočilo za operaterje: `nodes.json` naj poleg 2-3 mikrolokalnih sosedov vsebuje vsaj 1 "regionalni hub" (večje, centralno vozlišče), da jump-over deluje učinkovito na daljših razdaljah. To ni kršitev prepovedi centralnega registra (§9) — hub je navaden node, izbran prostovoljno s strani operaterja, ne vsiljena infrastruktura.

Vsi štirje (Claude, Grok, GPT, Gemini) so s tem §14 v celoti usklajen. Zelena luč za pripravo `nodes.json` strukture in pisanje forwarding logike v Koraku 3 — testna infrastruktura zanjo je pripravljena (glej §13 zgoraj).

### Ujeta ideja za kasnejšo fazo (NI odločeno, NI del MVP) — avtomatiziran bootstrap

Uporabnik je 2026-09-29 predlagal (izrecno kot razmislek, ne odločitev): namesto ročne kuracije `nodes.json`, bi ob podpisu "pogodbe o delitvi namestitev" skript samodejno poiskal 3 najbližje node in jih zapisal kot sosede; node-i bi to sprejeli in označili z zastavico "Povezan input"; vsak node bi imel omejeno število vhodnih/izhodnih "rež" (X input, X output), brez podvajanja povezav.

Zakaj to (zaenkrat) ni šlo v spec kot odločitev:

- Ponovno odpira bootstrap vprašanje, ki je bilo pravkar sklenjeno kot ročno/out-of-band (glej zgoraj) — samodejno iskanje "3 najbližjih" zahteva neki način odkrivanja obstoječih node-ov, kar je blizu centralnemu registru, ki ga v0.1 §9 izrecno prepoveduje (razen če bi šlo za decentraliziran gossip/peer-exchange mehanizem — bistveno kompleksnejše, ni oblikovano).
- Vezano je na poslovni/pravni proces ("pogodba o delitvi namestitev"), ki v tem projektu še ne obstaja in je izven obsega protokola samega.
- Master prompt §25 eksplicitno svari pred prezgodnjo kompleksnostjo — danes obstaja en sam pravi node, ročna kuracija zadostuje.

Vredno vrniti se k temu v v0.2, ko/če bo ročno vzdrževanje `nodes.json` dejansko postalo boleče zaradi realnega števila node-ov. Koncept "X input/X output rež brez podvajanja" je sam po sebi dober vzorec (naravno omeji stopnjo omrežja, ustvari back-pressure) — vredno ga takrat znova pogledati, morda celo neodvisno od vprašanja avtomatizacije odkrivanja.

---

## 15. Deset temeljnih načel (nespremenjeno iz v0.1 §26, ostaja veljavno kot preverba pri vsakem naslednjem koraku)

1. CM ostane source of truth.
2. AI ne dobi neposrednega dostopa do celotnega Ekosistema — samo do Reception (§2, izostreno).
3. Vsaka CM instalacija ima lahko svoja vrata.
4. Reception je skupen protokol, ne nujno centralni strežnik.
5. Geografija vodi discovery.
6. Poizvedba se širi po globini, vedno z omejitvami.
7. Zahtevek nosi svojo zgodovino (`request_id`, `visited`, `depth`).
8. Podatki ostanejo lokalni — v mrežo gre samo rezultat, ki ga node sme izpostaviti.
9. Ni vezave na enega AI ponudnika.
10. Docking Port je razširljiv preko manifesta/protokola, ne novih AI sistemov.

---

## 16. Korak 3 — Načrt pred kodo (SKLENJENO — čaka implementacijo, formalno zaprto 2026-09-30)

Po Gregovem A→B→C okviru (Gemini/Grok/GPT potrdili): preden se napiše ena vrstica forwarding kode v `reception.php`, se uskladimo na treh točkah. To je osnutek za skupni pregled, ne dokončana odločitev.

### 16.1 Shema `nodes.json`

**Posodobljeno 2026-09-30** — samo-opisna oblika (uporabnikov eksperiment), vsebuje tudi `max_fanout` (prejšnji osnutek ni povedal, kam se ta shrani):

```json
{
  "protocol": "CM-AI-Docking-Port",
  "version": "0.1",
  "node_id": "CM-AI-DEMO-058",
  "max_fanout": 2,
  "neighbours": [
    {
      "node_id": "CM-AI-STUB-BLED",
      "label": "Bled (stub test node)",
      "location": { "lat": 46.3683, "lon": 14.1146 },
      "reception_url": "[interno testno omrežje — LAN, ni javno dostopno]"
    }
  ]
}
```

**Realna lokacija (po incidentu in popravku 2026-09-30):** `/home/cmfree/cm-private/ai/nodes.json` na instanci 58 — izven `/var/www` v celoti (ne samo izven `/app/public/`), lastnik `cmfree:www-data`, pravice `640` na datoteki in `750` na mapah. `reception.php` jo bo bral po absolutni poti.

**Drugi popravek, isti dan — dovoljenja v verigi map:** `reception.php` teče kot `www-data` (potrjeno: `ps aux`, `APACHE_RUN_USER=www-data`), ki ni član skupine `cmfree` (`id www-data` → samo skupina `33(www-data)`). `/home/cmfree` je imel `other: ---`, kar bi `www-data` popolnoma blokiralo pri prehodu (`traverse`) v mapo — `reception.php` sploh ne bi mogel priti do datoteke, ne glede na njena lastna dovoljenja. To bi na živem Koraku 3.1 tiho spodletelo (`is_readable() === false`), ne bi pa se pokazalo prej, ker Korak 2 te poti sploh ne uporablja.

Popravek (brez sudo, `cmfree` je lastnik `/home/cmfree`): `chmod o+x /home/cmfree` (samo prehod, brez branja/izpisovanja vsebine za "other") + `chgrp www-data` na `cm-private/` in `cm-private/ai/` (`cmfree` je član skupine `www-data`, zato lahko to naredi sam). Veriga po popravku:

```text
/home/cmfree          drwxr-x--x cmfree:cmfree   (+x za "other" — samo prehod)
/home/cmfree/cm-private     drwxr-x--- cmfree:www-data
/home/cmfree/cm-private/ai  drwxr-x--- cmfree:www-data
.../nodes.json               -rw-r----- cmfree:www-data
```

Empirično preverjeno (ne samo izračunano): začasen PHP test skript na živem endpointu (odstranjen takoj po testu) je potrdil `is_readable() === true`, `129` bajtov, veljaven JSON. Javna izpostavljenost ostaja zaprta (ponovno preverjeno po spremembi dovoljenj).

**Incident in popravek (2026-09-30):** uporabnik je pri eksperimentiranju ročno ustvaril testno `nodes.json` na `/var/www/html/app/config/ai/nodes.json` — znotraj DocumentRoot-a (`/var/www/html`), zato je bila datoteka javno dostopna na `https://.../app/config/ai/nodes.json` (HTTP 200), poleg tega je bil na tej poti vklopljen Apache `Indexes` (javno vidljiv directory listing obeh nadrejenih map). Ujeto s strani zunanjega pregleda (curl), preden je vsebina postala nevarna (`neighbours` je bil prazen). Ista vrsta napake kot [[connector_modules_data_dir_exposure_2026_08_05]] — pot znotraj DocumentRoot-a, ne prave dostopne pravice. Popravek: datoteka premaknjena izven `/var/www` v celoti (`/home/cmfree/cm-private/ai/`, mapa `cmfree:cmfree` drwxr-x---, torej noben Apache config je nikoli ne more streči, ne glede na `Indexes`), prazni mapi v DocumentRoot-u odstranjeni. Preverjeno: `GET /app/config/ai/nodes.json` in `/app/config/` zdaj 404, Reception brez regresije (`ok:true`, L1).
**Pomembno — namerno ni javno dostopna datoteka.** V nasprotju z `manifest.json` (namerno javen, to je discovery površina), `nodes.json` je operativna konfiguracija posameznega noda — seznam, komu node zaupa in kam posreduje promet. Ta projekt ima že realno izkušnjo s tem natančnim razredom napake (`data/` mape connector modulov so bile javno berljive, popravljeno 2026-08-05, ponovilo se je še dvakrat) — `nodes.json` gre zato **izven javnega docroot-a** (ali vsaj eksplicitno blokirana v Apache konfiguraciji), bere jo samo `reception.php` server-side. To ni teoretična previdnost, je ponovitev znane napake, ki se tej ekipi je že zgodila.

### 16.2 Pogoji za forwarding

Realna sprememba glede na Korak 2: trenutna živa koda **vedno** kliče lokalni `availability_multi.php`, ne glede na `target.location`. To je bilo ustrezno za Korak 2 (depth 0, brez konteksta cilja), a za Korak 3 ni več smiselno — če nekdo iz Pirana vpraša za Roglo, Piranov lokalni odgovor ni relevanten rezultat, samo šum v `results[]`.

**SKLENJENO 2026-09-30 — efektivna razdalja (popravek, lokalni klic in izbira sosedov)**: `target.radius_km` (agentova želena relevantnost) in `limits.max_distance_km` (node-ova/sistemska zgornja meja iz `limits_default`, če agent ne pove svoje) sta dve različni stvari, ne sopomenki. Prejšnji osnutek je uporabljal samo `radius_km` — to je bila ista vrsta tihe dvoumnosti kot prej pri `request_expiry`, zdaj eksplicitno razrešena:

```text
effective_distance_km =
  če sta podana oba:  min(target.radius_km, limits.max_distance_km)
  če je podan samo eden: ta eden
  če ni podan noben: limits_default.max_distance_km (iz manifesta)
```

Razlog za `min()`, ne za katero koli polje samo: `radius_km` je agentova poslovna želja ("rezultati do X km od cilja so zame še uporabni"), `max_distance_km` je node-ova/sistemska zgornja meja prometa. Agent z ozkim `radius_km: 5` ne sme dobiti "lokalnega" odgovora 40 km stran samo zato, ker je node-ov `max_distance_km` širši — in obratno, node-ova lastna zgornja meja mora vedno obrezati tudi prevelik agentov `radius_km`, dosledno z že sklenjenim načelom pri `max_depth` (§14 — server vedno obreže, nikoli ne zaupa golemu agentovemu številu).

`effective_distance_km` se uporabi na **dveh** mestih, ne samo enem (dopolnitev, ne v prvotnem predlogu): tako pri odločitvi za lokalni klic KOT pri izbiri sosedov za forwarding — sicer node zavrne lasten odgovor kot "predaleč", nato pa brez težav forwarda k sosedu, ki je še bolj oddaljen, kar je notranje nekonsistentno.

Predlagan sklep (za potrditev):
1. **Lokalni klic je pogojen z razdaljo**: samo če je `haversine(own_location, target.location) <= effective_distance_km`, node pokliče svoj lokalni `availability_multi.php`. Sicer lokalnega klica sploh ne naredi.
2. **Forward pogoj**: `len(visited) < max_depth_limit` (kjer je `max_depth_limit` = min(agentov `limits.max_depth`, node-ova lastna omejitev iz `limits_default`) — server vedno obreže, nikoli ne zaupa golemu agentovemu številu, skladno z že sklenjenim §14).
3. **Izbira sosedov**: iz `nodes.json` izloči node-e, katerih `node_id` je že v `visited` IN katerih razdalja do `target.location` presega `effective_distance_km`; preostale sortiraj po Haversine razdalji do `target.location`; vzemi prvih `max_fanout` (sklenjeno z Gemini).
4. **Pred posredovanjem**: dodaj lastni `node_id` v `visited`, posreduj isti envelope (z dopolnjenim `visited`, nespremenjenim `request_id`/`request_expiry`) vsakemu izbranemu sosedu, vzporedno (ne zaporedno — da se čas ne seštevaj linearno).
5. Če je `len(visited) >= max_depth_limit` ali `nodes.json` po filtriranju (`visited` + `effective_distance_km`) prazen, node ne poskuša forwardati — ne napako, samo prazen forward-del odgovora.

**SKLENJENO 2026-09-30 (uporabnik)**: forward je **fallback-only**, in to ni samo tehnična odločitev za manj prometa — je namerna vrednostna izbira. Node najprej poskusi obdržati gosta pri sebi; šele če lokalni rezultat ni "true" (glej natančen pogoj spodaj), referira naprej v zaupano mrežo. To je digitalni ekvivalent že uveljavljenega vzorca v tem projektu — fizični keycard referral kot prototip CM Community (glej [[guest_trust_and_referral_strategy]]) — prenesen v protokol namesto zgolj v gostov ključ. Mreža je tako po zasnovi **overflow/referral mehanizem**, ne generično "poišči vse v bližini" iskanje.

Natančen pogoj (za implementacijo): lokalni klic šteje za "true rezultat" in **ne forwarda** naprej, če `data.ok === true` IN `data.available_count > 0` (obstaja dejansko prosta enota, ki ustreza povpraševanju). Če je lokalni klic spregledan zaradi razdalje (§16.2 točka 1), ali je vrnil `available_count: 0`, ali je lokalni klic spodletel — node forwarda naprej, znotraj `max_depth`/`max_fanout` omejitev.

**Pomembna ločnica (uporabnik, 2026-09-30)**: "lojalnost do lokalne skupnosti" velja na **kurirni ravni** (kdo sploh pride v `nodes.json` — operater ročno izbere prave, zaupanja vredne sosede po resničnem odnosu, "po svoji volji in interesu", ne po surovi razdalji; to je vloga bodočega Community zemljevida, §14 jump-over razprava). **Ne velja** na ravni algoritma med že-izbranimi sosedi — tam ostaja mehanski Haversine-sort + `max_fanout`, dogovorjen z Gemini (§14). Kurirana izbira je vrednostna, usmerjanje med kuriranimi sosedi je geografsko — ti dve ravni se ne smeta zamešati.

### 16.3 Agregacija in `partial`

1. Vsak klic (lasten lokalni + vsak sosed) ima neodvisen timeout (obstoječ vzorec: `CURLOPT_TIMEOUT`). Napaka/timeout enega soseda **ne** prekine drugih — ta sosed se izpusti iz `results[]`, `partial` postane `true`.
2. Odgovor vsakega soseda je že sam po sebi poln Response Envelope (lahko vsebuje več `results[]` vnosov, če je sosed sam nekaj forwardal naprej). Ta node samo **sploshi** (flatten) vse prejete `results[]` v svoj lastni `results[]` — brez reinterpretacije vsebine (skladno z že sklenjenim pravilom iz §6).
3. `search_meta.nodes_queried`/`nodes_reached` = vsota čez lasten poskus + vse poskuse vseh sosedov (vključno s tem, kar so sosedje sami poročali v svojih `search_meta`).
4. `search_meta.max_depth_used` = največja vrednost, ki jo je poročal kateri koli otrok, +1 za ta hop (ne samo lasten hop).
5. **SKLENJENO 2026-09-30**: `max_results` rez — če je po združitvi `results[]` daljši od `max_results`, obdrži najprej najbližje `target.location` (ne arbitrarno prvih N) — skladno s filozofijo "geografija vodi relevantnost" (§4).

**Status §16 (2026-09-30): načrt v celoti SKLENJEN, nič kode.** Vseh pet elementov potrjenih: fallback-only forward, `effective_distance_km` (min/fallback pravilo, velja za lokalni klic IN izbiro sosedov), ločnica kuracija-vs-routing, `nodes.json` izven docroot, `max_results` rez po bližini. Community/User Map kot kasnejši, user-controlled način polnjenja `nodes.json` je potrjen kot skladen s tem načrtom, a ostaja izrecno po Koraku 4/Fazi C, ne pred njim — ne odpira Koraka 3. Implementacija je ločena, zavestna odločitev ("Piši"), ne sledi avtomatsko iz zaprtega načrta.

---

## 17. Korak 3.1 — Branje konfiguracije (varen prvi podkorak, SKLENJENO 2026-09-30, čaka "Piši")

Namerno ozek, pred-forwarding podkorak: `reception.php` zna prebrati `nodes.json`, a se obnašanje agenta **ne spremeni**, dokler je `neighbours` prazen ali se datoteka ne prebere.

1. `reception.php` prebere `/home/cmfree/cm-private/ai/nodes.json` po absolutni poti (dovoljenja preverjena in popravljena, glej §16.1).
2. **Fail-safe, ne 500**: če datoteka ne obstaja, JSON ni veljaven, ali `node_id` v datoteki ne ujema z `CM-AI-DEMO-058` — `reception.php` **tiho pade nazaj** na obnašanje Koraka 2 (čisto lokalno, `ok:true`, `depth:0`), agent nikoli ne vidi napake konfiguracije.
3. **Dodatek (Claude)**: vsak od treh fail-safe pogojev se zabeleži server-side z `error_log()` (isti vzorec kot pri Koraku 2 za curl napake) — agent tega ne vidi, operater pa lahko razišče, zakaj forwarding ne deluje, ne da bi moral ugibati.
4. **Brez forwardinga v tem podkoraku** — nobenega HTTP/cURL klica proti sosedom, tudi če je `neighbours` napolnjen s testnimi stubi. Živi endpoint ostane ves čas 100% lokalen in stabilen.

**Status: ✅ DEPLOYANO in testirano 2026-09-30.** Koda (Grok "Piši", `load_nodes_config()` + fail-safe + `error_log()`) je na živo, po istem vzorcu kot Korak 2 (`.tmp` + `php -l` + atomski `mv`). Testi na živem endpointu, vsi po pričakovanjih:
1. Normalen zahtevek → odgovor bit-za-bit enak Koraku 2 (`ok:true`, `depth:0`, `L1`).
2. Odgovor ne vsebuje nobene sledi `nodes.json`/`/home/cmfree`/`max_fanout`/`neighbours`/`cm-private`.
3. `/app/config/ai/nodes.json` ostaja 404.
4. Fail-safe, oba scenarija na živem strežniku: preimenovana datoteka → `ok:true` lokalno + `error_log`: "nodes.json not readable"; pokvarjen JSON → `ok:true` lokalno + `error_log`: "nodes.json invalid JSON". Datoteka obnovljena z originalnimi dovoljenji (`cmfree:www-data`, `640`) po testu.
5. GET→405 in napačen protokol→400 nespremenjena (regresija čista).
6. Brez zaostalih začasnih testnih skriptov v docroot-u.

`$nodes_cfg` je prebran in ovrednoten, a se za forwarding še ne uporablja — endpoint ostaja 100% lokalen. Naslednje: Korak 3.2 (Haversine, `effective_distance_km` gating za lokalni klic) — čaka na nov "Piši".

---

## 18. Privaten tracker — ni del protokola, samo operativno orodje (2026-09-30)

Namen: videti, ali objava specifikacije dejansko vodi do zanimanja/uporabe (ne samo teoretičen dokaz koncepta). Namerno **brez** novega javnega endpointa/dashboarda — manj nove površine po incidentu iz §16.1, in ni potreben, ker je edini naslovnik lastnik sam.

- `reception.php` zdaj beleži vsak klic (uspeh in vsak error path, eno mesto v `send()`) v privaten JSONL: `/home/cmfree/cm-private/ai-logs/visits.jsonl`. Zapisuje `ts`, `http_code`, `ok`, `query_type`, `error_code`, `user_agent` — **namerno brez IP naslova**, zanima nas samo *ali/kdo* (user agent) dejansko kliče, ne identiteta.
- Mapa za dnevnik (`ai-logs/`) je namerno ločena od mape s `nodes.json` (`ai/`) — čeprav obe zahtevata pisanje s strani `www-data` procesa, bi skupna mapa pomenila, da lahko `www-data` (prek dovoljenja mape, ne datoteke) tudi izbriše/preimenuje `nodes.json`. Ločitev odpravi to tveganje.
- Privaten CLI report (`php /home/cmfree/cm-private/ai-logs/report.php`, poganja se prek SSH, ni web-dostopen) združi ta dnevnik z Apache `access.log` (ki že sam beleži vsak ogled `index.html`/`spec.html`/`spec.md`/`manifest.json`/`.well-known` — ni bilo treba graditi ločenega beleženja za statične strani).

Prvi zagon (2026-09-30) je že pokazal pravi signal: obiski `spec.md`/`.well-known` z `curl/7.88.1` in `Python-urllib/3.12` — različni od lastnih orodij v tej seji (`curl/8.5.0`) — torej najverjetneje avtonomno branje s strani enega od AI sodelavcev ali njihove infrastrukture.
