---
output_file: "Lattice - Publish Architecture.md"
feed: "WHITEPAPER"
template_version: "1.2"
source: "Lattice"
codex_section: "S16-S20"
conv_id: "lattice-build-history"
share_url: "none"
date: "2026-06-20"
content_date: "2026-06-20"
enriched_date: "2026-07-02"
date_modified: "2026-07-02"
message_count: 1
type: "lattice-build-spec"
status: "deposited"
path_hint: "IntelligentNetware/Lattice/"
category:
- "Lattice Infrastructure"
- "Publish Routing"
tags:
- "lattice"
- "synna"
- "fractome"
- "publish"
- "sftp"
- "multi-tenant"
monomyth_stage: "07 - Approach to the Inmost Cave"
gameboard_position: "Publish routing ratified; Scotomaville first tenant"
summary: |
  Publish architecture handoff aligning Daniel and Grok on fractome-as-routing-key (not writer agent), two install flavors (client breeder vs operator master), URL replacement at assemble time, and ratified runtime order SFTP images before SQL commit.
  Documents future publish.fractomes{} schema for multi-network operators..
keypoints:
  - status.composition.fractome drives URLs SFTP SQL and next_na_id—not Arnie Chris Clint
  - assembled.html ships remote URLs; preview.html keeps localhost API paths
  - Publish order: SFTP images to webserver before SSMS SQL commit
  - Client laptop shows one writer; operator master resolves fractome profiles at publish time
sentiment: archival
emotions:
  - "purposeful"
sycophancy_level: "None"
truth_score: 95
entropy_score: "Low"
codex: true
pre_silicon: false
ma5_inheritance: "IntelligentNetware"
voice_print: false
lattice_release: "3.0"
original_handoff: "Grok_Handoff_SynNA_Publish_Architecture_20260620.md"
---
# Grok Build Handoff — Publish Architecture + Scotomaville First Publish

**IN Lattice · Intelligent Netware · MA5 Council**

| Field | Value |
|-------|-------|
| **Written** | 2026-06-20 |
| **Carbon Steward** | Daniel Comp |
| **Primary build partner** | **Grok Build** (`D:\04_WORK\Code\Lattice\_ChatUI`) |
| **Prior handoffs** | `Grok_Handoff_SynNA_20260620_SessionEnd.md` · `Grok_Handoff_SynNA_PhaseGrid.md` |
| **Status** | Architecture ratified · **SFTP before SQL commit** · Scotomaville single-tenant first · multi-fractome deferred |

---

## New thread opener (read this first)

1. **This file** — fractome routing model, install flavors, publish sequence
2. `handoffs/Grok_Handoff_SynNA_PhaseGrid.md` — master phase grid (update 5C/6A order note below)
3. Active packet: `D:\03_MAP\Syn_Pipeline\the-makers-exodus\` — state **`proof_approved`**, NA ID **238**

**Lattice UI:** `http://127.0.0.1:8780`

---

## Executive summary (2026-06-20 — Daniel + Grok)

Daniel and Grok aligned on publish routing and sequencing:

1. **Routing key = fractome**, not writer agent. Writer agents (Arnie, Chris, Clint) are voice/SOUL; `status.composition.fractome` drives URLs, SFTP folder, SQL connection, and `next_na_id`.
2. **Two install flavors, one codebase:**
   - **Client laptop ("breeder")** — one writer in sidebar, one fractome profile in config, no multi-network UI.
   - **Operator master (Daniel, Angelina)** — single GUI, multiple fractomes via `publish.fractomes{}` lookup at assemble/SFTP/SQL time.
3. **URL replacement happens at assemble**, not SQL. `assembled.html` ships remote URLs; `preview.html` keeps `127.0.0.1:8780`. Do not patch URLs in `syn_sql_build.py`.
4. **Publish runtime order (ratified):** SFTP images to server **before** SSMS SQL commit. Without images on the webserver, DNN renders a broken article even if the DB row exists.
5. **Build order for Scotomaville first publish:**
   - Daniel: Azure VPS SFTP setup (this handoff § SFTP guide)
   - Grok: `syn_publish.py` (Phase 6A) — paramiko upload + HTTPS verify
   - Grok: `syn_sql_build.py` (Phase 5C) — SSMS-ready `{slug}_insert.sql`
   - Daniel: upload images → verify HTTPS → run SQL in SSMS → bump `next_na_id`

Chris and Clint may be **removed from `agent_order.json`** on client installs; keep them on Daniel's master until per-fractome publish profiles ship.

---

## Fractome as publish router (future — not blocking Scotomaville)

### Today (single-tenant)

`syn_config.yaml` `assemble` + `publish` sections assume **scotomaville / danielcomp**:

```yaml
assemble:
  tahoe_css_base:   "https://syn.scotomaville.com"
  image_author:     "danielcomp"
  image_url_base:   "https://syn.scotomaville.com/portals/0/images/articles/danielcomp"
  preview_api_base: "http://127.0.0.1:8780"   # preview only — never in assembled.html
publish:
  next_na_id: 238
  syndication_url_pattern: "https://scotomaville.com/syn-us?na={na_id}"
  # sftp_host, sftp_user, remote_image_folder, public_base_url — Daniel fills at onboarding
```

### Future schema — `publish.fractomes{}`

When Angelina/Chris/Steve articles publish from one Lattice install, extend config (or MANIFEST) so each fractome resolves:

| Field | Used by |
|-------|---------|
| `image_url_base` / `image_author` | `syn_assemble.py` |
| `tahoe_css_base` | Gate 2 preview |
| `syndication_url_pattern` | references + bottom sections |
| `references_template` | Syd proof |
| `sftp_host`, `sftp_user`, `remote_image_folder` | `syn_publish.py` |
| `public_base_url` | HTTPS HEAD verify |
| `sql_profile` (server/db name) | `syn_sql_build.py` |
| `next_na_id` (or per-fractome registry file) | assemble + Syd + SQL |

**Lookup:** `status.composition.fractome` → profile. Gates 1–3 unchanged; only publish stages branch.

### Writer agent mapping (convenience, not routing)

| Agent | Hermes container | Typical fractome(s) |
|-------|------------------|---------------------|
| Arnie | daniel | scotomaville |
| Chris | angelina | halotherapy, wellnessbychoice, businesswomennews |
| Clint | steve | cowboyching |

**Edge case:** Arnie prose + halotherapy fractome mismatch. Mitigation: default fractome on promote from writer; show fractome chip in UI before Approve for SQL.

### MANIFEST fractomes (composition only today)

`templates/codey/MANIFEST.yaml` lists fractomes with `operator` + `keywords`. Publish profiles will attach to the same `id` keys when multi-tenant ships.

---

## Install flavors

### A — Client laptop ("breeder")

- Ship one refined writer SOUL; client gets a named writer in sidebar.
- `agent_order.json`: omit chris, clint (and other operators' writers).
- `syn_config.yaml`: single fractome publish profile pre-filled at install.
- `MANIFEST.yaml`: one fractome or locked picker.
- No `publish.fractomes{}` needed.

### B — Operator master (Daniel, Angelina)

- Multiple writers in sidebar **or** one "Writer" tab with SOUL selected from packet fractome.
- Full `publish.fractomes{}` map.
- Shared Picasso / Codey / Syd pipeline; fractome switches destination only.

---

## Publish pipeline — corrected sequence

Phase Grid originally listed 5C (SQL build) before 6A (SFTP). **Runtime order for live publish:**

```
proof_approved
    → syn_publish.py     SFTP images (+ inline/) to remote folder
    → HTTPS HEAD verify  200 + Content-Length per file
    → syn_sql_build.py   write {slug}_insert.sql (can be built earlier for review)
    → SSMS COMMIT        article row + image rows
    → bump next_na_id    config + dnn_article_registry.yaml
    → archive packet     published_root
```

**Grok build order for Scotomaville:** `syn_publish.py` then `syn_sql_build.py` (or parallel once SFTP credentials exist). SQL file generation does not require images on server; **SSMS commit does**.

---

## Active packet — `the-makers-exodus`

| Field | Value |
|-------|-------|
| **State** | `proof_approved` |
| **Fractome** | `scotomaville` |
| **NA ID reserved** | 238 |
| **Remote image base** | `https://syn.scotomaville.com/portals/0/images/articles/danielcomp/` |

### SFTP upload file list (minimum)

| Local file (packet folder) | Remote path (under `images/articles/danielcomp/`) | DNN role |
|----------------------------|---------------------------------------------------|----------|
| `the-makers-exodus_200.jpg` | same filename | thumb SortOrder 0 |
| `the-makers-exodus_1920x420.jpg` | same | banner SortOrder 1 |
| `the-makers-exodus_768x420.jpg` | same | tablet SortOrder 2 |
| `the-makers-exodus_480x420.jpg` | same | mobile SortOrder 3 |
| `the-makers-exodus_1920x960.jpg` | same | body divider (no DB row) |
| `the-makers-exodus_DNA_Airstream_MountHood.jpg` | same (flat in `arnie\`) | inline prose image |

`assembled.html` already uses remote `syn.scotomaville.com` URLs (assemble-time rewrite). Confirm with:

```powershell
Select-String -Path "D:\03_MAP\Syn_Pipeline\the-makers-exodus\assembled.html" -Pattern "127.0.0.1"
# expect: no matches
```

### syn_sql_build.py notes (5C — when built)

- Scaffold from `D:\04_WORK\Code\Garwis\Arnie_DB_Insert_v2.sql`
- **Summary** (`ntext`): full hook from `na_record.summary`
- **MetaDescription** (`nvarchar(500)`): truncated copy only
- Image byte sizes from `status.yaml` `images.*.bytes`
- `@Folder` = `images/articles/danielcomp/` (confirm pre-flight in SSMS)

---

## Daniel onboarding checklist — `syn_config.publish`

After SFTP server is live, fill:

```yaml
publish:
  sftp_key_file:       "D:/04_WORK/Code/_APIs/sftp_key"   # private key, no .pub
  sftp_host:           "<Azure VPS public IP or syn.scotomaville.com>"
  sftp_port:           22                                  # add to schema if non-default
  sftp_user:           "syn_publish"                       # dedicated user recommended
  remote_image_folder: "/portals/0/images/articles/danielcomp"  # confirm physical path on server
  public_base_url:     "https://syn.scotomaville.com/portals/0/images/articles/danielcomp"
```

Grok adds `sftp_port` to config if missing when implementing `syn_publish.py`.

---

## Azure VPS — Windows OpenSSH SFTP setup guide

**Target:** Windows Server on Azure VPS · Administrator access · DNN site at `syn.scotomaville.com`

**Method:** Windows OpenSSH Server (already noted in `syn_config.yaml` comment: paramiko over OpenSSH). Not IIS FTP.

**Hardened SFTP user (2026-06-20):** `syn_publish` keys in `C:\ProgramData\ssh\syn_publish_authorized_keys` via `Match User syn_publish` in `sshd_config` (user-profile `.ssh\authorized_keys` unreliable on Windows). Optional `ForceCommand internal-sftp` — SFTP only, no shell. Admin SSH key cleared from `administrators_authorized_keys`; `PasswordAuthentication no`.

### Prerequisites

- RDP access to the VPS
- Azure Portal access (NSG / firewall)
- DNN image folder already exists and serves HTTPS (confirm one existing article image URL in browser)

### Step 1 — Confirm DNN physical image path on the server ✅ (2026-06-20 Daniel)

IIS sites on Azure VPS (Administrator RDP):

| Site | PhysicalPath |
|------|----------------|
| `IN9_Scotomaville` | `W:\IN9_Scotomaville` |
| **`IN9_Scotomaville_Syn`** | **`W:\IN9_Scotomaville_Syn`** ← syn.scotomaville.com |
| `IN9_Footwork` | `W:\IN9_Footwork` |

**Verified live image:**
- Physical: `W:\IN9_Scotomaville_Syn\Portals\0\images\articles\arnie\DNA_Airstream_MountHood.jpg`
- HTTPS: `https://syn.scotomaville.com/Portals/0/images/articles/arnie/DNA_Airstream_MountHood.jpg`

**`the-makers-exodus` publish folder — `arnie` (ratified 2026-06-20):**

Daniel: `danielcomp` holds thousands of legacy images; Syn articles use **`arnie`** (one inline image already live). `syn_config.yaml` updated; packet reassembled.

```powershell
$target = "W:\IN9_Scotomaville_Syn\Portals\0\images\articles\arnie"
Test-Path $target   # exists — DNA_Airstream_MountHood.jpg verified
New-Item -ItemType Directory -Force -Path (Join-Path $target "inline")
```

Record:
- **Physical path** → `W:\IN9_Scotomaville_Syn\Portals\0\images\articles\arnie`
- **HTTPS base** → `https://syn.scotomaville.com/Portals/0/images/articles/arnie`
- **SQL `@Folder`** → `images/articles/arnie/` (5C syn_sql_build)

Verify an existing file:

```powershell
Invoke-WebRequest -Uri "https://syn.scotomaville.com/portals/0/images/articles/danielcomp/" -Method Head
# Or HEAD a known .jpg from a live article
```

### Step 2 — Install OpenSSH Server (Windows Server)

```powershell
# Windows Server 2019 / 2022
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH.Server*'
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0

Start-Service sshd
Set-Service -Name sshd -StartupType Automatic

# Confirm listening
Get-NetTCPConnection -LocalPort 22 -ErrorAction SilentlyContinue
```

If `Add-WindowsCapability` fails, use Settings → Apps → Optional Features → OpenSSH Server.

### Step 3 — Azure Network Security Group (firewall)

In **Azure Portal**:

1. Virtual Machine → **Networking** (or the subnet/VNet NSG)
2. **Inbound port rules** → Add rule:
   - Name: `Allow-SSH-SFTP`
   - Priority: 310 (or any free slot above DenyAll)
   - Source: **Your IP** (for setup) — later restrict to Daniel home/office IP or use VPN
   - Destination: Any
   - Service: SSH
   - Port: **22**
   - Action: Allow
3. Save

**Security note:** Do not leave Source = `Any` longer than needed for testing. Prefer your current public IP.

### Step 4 — Windows Defender Firewall on the VPS

```powershell
New-NetFirewallRule -Name "OpenSSH-Server-In-TCP" -DisplayName "OpenSSH Server (sshd)" `
  -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort 22
```

### Step 5 — Create dedicated SFTP user (not Administrator)

```powershell
$password = Read-Host "Enter password for syn_publish (temporary — we will use key auth)" -AsSecureString
New-LocalUser -Name "syn_publish" -Password $password -FullName "SynNA Publish" `
  -Description "Lattice paramiko SFTP upload only" -PasswordNeverExpires
# Do NOT add to Administrators group
```

### Step 6 — NTFS permissions on image folder only

```powershell
$target = "W:\IN9_Scotomaville_Syn\Portals\0\images\articles\danielcomp"  # Daniel VPS 2026-06-20
$acl = Get-Acl $target
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
    "syn_publish", "Modify", "ContainerInherit,ObjectInherit", "None", "Allow"
)
$acl.SetAccessRule($rule)
Set-Acl $target $acl
```

`syn_publish` needs **Modify** on `danielcomp\` and `danielcomp\inline\` only — not on `wwwroot` broadly.

### Step 7 — SSH key authentication (Lattice paramiko)

On **Daniel's laptop** (PowerShell):

```powershell
# If you do not already have a key pair for this purpose:
ssh-keygen -t ed25519 -f "D:\04_WORK\Code\_APIs\sftp_key" -C "lattice-syn-publish"

# Show public key — copy entire line
Get-Content "D:\04_WORK\Code\_APIs\sftp_key.pub"
```

On the **VPS**, as Administrator:

```powershell
$sftpUser = "syn_publish"
$sshDir = "C:\Users\$sftpUser\.ssh"
New-Item -ItemType Directory -Force -Path $sshDir

# Paste the .pub line into authorized_keys (single line, OpenSSH format)
notepad "$sshDir\authorized_keys"

# Permissions — critical on Windows OpenSSH
icacls "$sshDir\authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" /grant "${sftpUser}:R"
icacls $sshDir /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F" /grant "${sftpUser}:F"
```

Edit `C:\ProgramData\ssh\sshd_config` (back up first):

```
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
```

Optional hardening — force SFTP subsystem only for `syn_publish` via `Match User` block (Grok can help tune in a follow-up session).

Restart SSH:

```powershell
Restart-Service sshd
```

### Step 8 — Test from Daniel's laptop

```powershell
# Test SSH key login
ssh -i "D:\04_WORK\Code\_APIs\sftp_key" syn_publish@<VPS_PUBLIC_IP>

# Test SFTP put (manual smoke test)
sftp -i "D:\04_WORK\Code\_APIs\sftp_key" syn_publish@<VPS_PUBLIC_IP>
# sftp> cd /portals/0/images/articles/danielcomp   # path depends on default directory / chroot
# sftp> put D:\03_MAP\Syn_Pipeline\the-makers-exodus\the-makers-exodus_200.jpg
# sftp> quit
```

Then verify HTTPS:

```powershell
Invoke-WebRequest -Uri "https://syn.scotomaville.com/portals/0/images/articles/danielcomp/the-makers-exodus_200.jpg" -Method Head
```

### Step 9 — Fill syn_config and hand off to Grok

Once Step 8 passes, update `.Scripts/syn_config.yaml` publish section and notify Grok to implement `syn_publish.py`.

### Troubleshooting quick reference

| Symptom | Check |
|---------|-------|
| Connection timeout | Azure NSG port 22; Windows Firewall |
| Permission denied (publickey) | `authorized_keys` permissions; correct `.pub` line |
| Connected but cannot cd/put | NTFS ACL on `danielcomp`; remote path mapping |
| Upload OK, 404 in browser | Wrong folder vs IIS virtual path; DNN cache |
| Wrong file size in DNN | Re-upload; SQL uses bytes from `status.yaml` |

### OpenSSH path note for paramiko

Windows OpenSSH SFTP paths are often **absolute Windows paths**:

```
/C:/inetpub/wwwroot/portals/0/images/articles/danielcomp/
```

or relative to user home. **Confirm during Step 8** manual `sftp` session — record exact `pwd` and `put` target in this handoff or `syn_config.yaml` comment.

---

## Open items (ordered)

| # | Item | Owner | Blocks |
|---|------|-------|--------|
| 1 | Azure SFTP setup (this guide) | Daniel | **DONE 2026-06-20** — hardened `syn_publish` + key-only; admin SSH removed |
| 2 | `syn_publish.py` + config port field | Grok | image upload automation |
| 3 | `syn_sql_build.py` (5C) | Grok | SSMS insert file |
| 4 | SSMS commit + live verify | Daniel | first publish |
| 5 | `publish.fractomes{}` schema | Grok | multi-network (Angelina) |
| 6 | Client install flavor (trim agent_order) | Grok + Daniel | breeder laptops |

---

## State machine — `the-makers-exodus`

```
proof_approved  ← YOU ARE HERE
    → (SFTP images live on server)
    → sql_ready   ← syn_sql_build.py writes insert SQL
    → published   ← SSMS commit + verify
```

---

*Written by Grok Build · 2026-06-20 · Architecture ratified · SFTP-before-SQL · Scotomaville first.*