Compare commits

...

6 Commits

Author SHA1 Message Date
chrisfu
f0d3a85fbc Avoid fact gathering for Windows Ollama playbook 2026-06-01 23:24:11 -07:00
chrisfu
e5fe9dc2a1 Disable Unix become for Windows inventory hosts 2026-06-01 23:08:40 -07:00
chrisfu
3b1014ee4d Disable sudo become for Windows Ollama playbook 2026-06-01 23:03:49 -07:00
chrisfu
5f60ede5d7 Add Windows Ollama model configuration playbook 2026-06-01 22:04:11 -07:00
chrisfu
5aacee46d8 feat(migrations): 001 — prole ekosystem vector schema
- Prole registered as tenant_id=1, uuid=000necda5b3a6tc2 (DNS anchor)
- Project embedding store uuid=000nectf23m7865j (tenant_id=1 in bits)
- Schema p_000nectf23m7865j: chunks, embeddings, embeddings_merged (vector 1024)
- HNSW index on embeddings_merged for cosine similarity search
- Cross-grant issued to tenant 0 (knoe.dev MCP): grant id=000neddhjsr8eadg
- Establishes knoe-db/schema/migrations/ directory (sqitch wiring: tomorrow)

Applied live to pg.prole.org at 2026-05-30 ~02:25 UTC-7.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-30 02:25:17 -07:00
chrisfu
23cf4a8585 fix(ekosystem): register_tenant — RETURN QUERY + smallint cast
INSERT ... RETURNING needs RETURN QUERY in PL/pgSQL RETURNS TABLE functions.
tenant_id column is smallint in knoe.tenants; cast to integer to match
the function's declared return type.

Reproduced on pg.prole.org at 2026-05-30 during Phase 2 canary deploy.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-30 02:18:09 -07:00
4 changed files with 272 additions and 6 deletions

View File

@ -82,6 +82,9 @@ zinfandel.prole.org ansible_user=chrisfu ansible_ssh_private_key_file=~/.ssh/id_
morgoth.prole.org ansible_user=chrisfu ansible_connection=winrm ansible_winrm_transport=ntlm ansible_winrm_server_cert_validation=ignore
fairyland.prole.org ansible_user=chrisfu ansible_connection=winrm ansible_winrm_transport=ntlm ansible_winrm_server_cert_validation=ignore
[workstations_windows:vars]
ansible_become=false
ansible_become_method=runas
[workstations:children]
workstations_windows

View File

@ -0,0 +1,165 @@
---
# windows_ollama_models.yml — Manage local Ollama model variants on Windows GPU hosts.
#
# This is the small, Ansible-like control surface for the two Windows NVIDIA
# machines that run Ollama for Prole embedding workloads.
#
# Usage:
# cd infrastructure
# ansible-playbook playbooks/windows_ollama_models.yml --ask-pass
#
# Single host:
# ansible-playbook playbooks/windows_ollama_models.yml --limit morgoth.prole.org --ask-pass
#
# Dry-run intent only:
# ansible-playbook playbooks/windows_ollama_models.yml --check --diff --ask-pass
#
# Inventory target:
# [workstations_windows]
# morgoth.prole.org
# fairyland.prole.org
#
# What this deploys:
# ollama create mxbai-embed-large-2k FROM mxbai-embed-large PARAMETER num_ctx 2048
#
# The desired Modelfile is written to C:\ProgramData\Prole\Ollama\Models and a
# SHA-256 marker is recorded after successful creation. Re-running the playbook
# is safe: it recreates the model only when the target model is missing or the
# managed Modelfile content changes.
- name: Configure Prole Ollama models on Windows GPU hosts
hosts: workstations_windows
gather_facts: false
become: false
vars:
ansible_become: false
ansible_become_method: runas
prole_ollama_models_dir: 'C:\ProgramData\Prole\Ollama\Models'
prole_ollama_models:
- name: mxbai-embed-large-2k
base: mxbai-embed-large
parameters:
num_ctx: 2048
# Override per-host if WinRM cannot see ollama.exe in PATH.
# Common installs include:
# C:\Program Files\Ollama\ollama.exe
# C:\Users\<user>\AppData\Local\Programs\Ollama\ollama.exe
ollama_windows_exe: ''
tasks:
- name: Resolve ollama.exe path
ansible.windows.win_shell: |
$ErrorActionPreference = 'Stop'
$candidates = @()
if ('{{ ollama_windows_exe }}') {
$candidates += '{{ ollama_windows_exe }}'
}
$cmd = Get-Command ollama.exe -ErrorAction SilentlyContinue
if ($cmd) {
$candidates += $cmd.Source
}
$candidates += @(
'C:\Program Files\Ollama\ollama.exe',
'C:\Users\{{ ansible_user }}\AppData\Local\Programs\Ollama\ollama.exe'
)
foreach ($candidate in $candidates | Select-Object -Unique) {
if ($candidate -and (Test-Path -LiteralPath $candidate)) {
Write-Output $candidate
exit 0
}
}
throw 'ollama.exe was not found. Set ollama_windows_exe for this host or install Ollama first.'
args:
executable: powershell.exe
register: ollama_exe_result
changed_when: false
- name: Set resolved Ollama executable fact
ansible.builtin.set_fact:
ollama_resolved_exe: "{{ ollama_exe_result.stdout_lines[0] }}"
- name: Ensure Prole Ollama model state directory exists
ansible.windows.win_file:
path: "{{ prole_ollama_models_dir }}"
state: directory
- name: Show planned Ollama model configuration in check mode
ansible.builtin.debug:
msg: >-
Would ensure {{ item.name }} exists from {{ item.base }} with num_ctx={{ item.parameters.num_ctx }}
using {{ ollama_resolved_exe }}.
loop: "{{ prole_ollama_models }}"
when: ansible_check_mode
- name: Create or update managed Ollama model variants
ansible.windows.win_shell: |
$ErrorActionPreference = 'Stop'
$ollama = '{{ ollama_resolved_exe }}'
$modelName = '{{ item.name }}'
$baseModel = '{{ item.base }}'
$modelDir = '{{ prole_ollama_models_dir }}'
$modelfilePath = Join-Path $modelDir "$modelName.Modelfile"
$markerPath = Join-Path $modelDir "$modelName.Modelfile.sha256"
$modelfile = @"
FROM {{ item.base }}
PARAMETER num_ctx {{ item.parameters.num_ctx }}
"@.TrimStart()
Set-Content -LiteralPath $modelfilePath -Value $modelfile -Encoding ascii -NoNewline
$desiredHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $modelfilePath).Hash
$recordedHash = if (Test-Path -LiteralPath $markerPath) {
(Get-Content -LiteralPath $markerPath -Raw).Trim()
} else {
''
}
& $ollama show $modelName *> $null
$modelExists = ($LASTEXITCODE -eq 0)
if ($modelExists -and ($recordedHash -eq $desiredHash)) {
Write-Output "unchanged: $modelName already matches managed Modelfile"
exit 0
}
& $ollama pull $baseModel
if ($LASTEXITCODE -ne 0) {
throw "ollama pull failed for $baseModel"
}
& $ollama create $modelName -f $modelfilePath
if ($LASTEXITCODE -ne 0) {
throw "ollama create failed for $modelName"
}
Set-Content -LiteralPath $markerPath -Value $desiredHash -Encoding ascii -NoNewline
Write-Output "changed: created or updated $modelName from $baseModel"
args:
executable: powershell.exe
loop: "{{ prole_ollama_models }}"
register: ollama_model_results
changed_when: "'changed:' in ollama_model_results.stdout"
when: not ansible_check_mode
- name: Verify managed Ollama model variants
ansible.windows.win_shell: |
$ErrorActionPreference = 'Stop'
& '{{ ollama_resolved_exe }}' list
args:
executable: powershell.exe
register: ollama_list_result
changed_when: false
when: not ansible_check_mode
- name: Show Ollama model list
ansible.builtin.debug:
var: ollama_list_result.stdout_lines
when: not ansible_check_mode

View File

@ -179,11 +179,10 @@ BEGIN
v_uuid := knoe.ekosystem_id(0, 0); -- root authority mints the UUID
INSERT INTO knoe.tenants (tenant_id, uuid, name)
VALUES (v_tenant_id, v_uuid, p_name)
RETURNING tenants.tenant_id, tenants.uuid, tenants.name;
RETURN NEXT;
RETURN QUERY
INSERT INTO knoe.tenants (tenant_id, uuid, name)
VALUES (v_tenant_id::smallint, v_uuid, p_name)
RETURNING tenants.tenant_id::integer, tenants.uuid, tenants.name;
END;
$$;

View File

@ -0,0 +1,99 @@
-- Migration 001 — prole ekosystem vector schema
-- Tenant: prole (tenant_id=1, uuid=000necda5b3a6tc2)
-- Project embedding store UUID: 000nectf23m7865j (minted with tenant_id=1)
-- Embedding model: mxbai-embed-large-v1 (dim=1024)
--
-- Run from ~/dev/prole/knoe-db:
-- psql "$DSN" -v ON_ERROR_STOP=1 -f schema/migrations/001_prole_vector_schema.sql
BEGIN;
-- Drop placeholder schema created before project UUID was minted
DROP SCHEMA IF EXISTS "p_000necda5b3a6tc2" CASCADE;
DROP ROLE IF EXISTS "u_000necda5b3a6tc2";
-- Service account
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'u_000nectf23m7865j') THEN
CREATE ROLE "u_000nectf23m7865j"
LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT;
END IF;
END $$;
-- Project namespace
CREATE SCHEMA IF NOT EXISTS "p_000nectf23m7865j" AUTHORIZATION knoe;
GRANT USAGE ON SCHEMA "p_000nectf23m7865j" TO "u_000nectf23m7865j";
-- Chunks: raw indexed text from repositories
CREATE TABLE IF NOT EXISTS "p_000nectf23m7865j".chunks (
ekosystem_uuid text NOT NULL DEFAULT '000nectf23m7865j',
id text NOT NULL,
repo text NOT NULL,
path text NOT NULL,
lang text,
line_start integer,
line_end integer,
content text NOT NULL,
header text NOT NULL DEFAULT '',
contributor_id text NOT NULL DEFAULT 'system',
indexed_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (id)
);
CREATE INDEX IF NOT EXISTS idx_chunks_repo
ON "p_000nectf23m7865j".chunks (repo, path);
CREATE INDEX IF NOT EXISTS idx_chunks_contributor
ON "p_000nectf23m7865j".chunks (contributor_id);
-- Per-contributor embeddings
CREATE TABLE IF NOT EXISTS "p_000nectf23m7865j".embeddings (
ekosystem_uuid text NOT NULL DEFAULT '000nectf23m7865j',
id bigserial PRIMARY KEY,
chunk_id text NOT NULL
REFERENCES "p_000nectf23m7865j".chunks(id) ON DELETE CASCADE,
contributor_id text NOT NULL DEFAULT 'system',
embedding vector(1024) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
-- Merged canonical embeddings — searched by MCP
CREATE TABLE IF NOT EXISTS "p_000nectf23m7865j".embeddings_merged (
ekosystem_uuid text NOT NULL DEFAULT '000nectf23m7865j',
id bigserial PRIMARY KEY,
chunk_id text NOT NULL
REFERENCES "p_000nectf23m7865j".chunks(id) ON DELETE CASCADE,
embedding vector(1024) NOT NULL,
source_contributor_id text NOT NULL DEFAULT 'system',
merged_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_merged_hnsw
ON "p_000nectf23m7865j".embeddings_merged
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- Grants
GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA "p_000nectf23m7865j" TO knoe;
GRANT USAGE, SELECT
ON ALL SEQUENCES IN SCHEMA "p_000nectf23m7865j" TO knoe;
-- Cross-ekosystem grant: tenant 0 (knoe.dev MCP) can search this store
SELECT knoe.grant_object(
'000nectf23m7865j',
'embedding',
0::smallint,
'{SELECT}'::text[],
NULL
);
COMMIT;
-- Verify
SELECT (d).tenant_id AS owner_tenant, '000nectf23m7865j' AS project_uuid
FROM (SELECT knoe.ekosystem_decode('000nectf23m7865j') d) x;
SELECT direction, object_uuid, object_kind, privileges
FROM knoe.my_grants(1::smallint);
SELECT schemaname, tablename
FROM pg_tables WHERE schemaname = 'p_000nectf23m7865j'
ORDER BY tablename;