ops(archive): TASK prompts and REPORT verifications, studies through V1

This commit is contained in:
2026-08-29 05:22:43 -05:00
parent bd0cea1757
commit b2c4b74d76
37 changed files with 2049 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
Founder directive: a fifth crossfeed dir is LIVE: /home/_crossfeed/founder
(owner reachableceo). Add it to your turn-start peer reads; the founder PMO publishes
STATUS.md there. Same ambient-only rules. Archive this notice after reading.
+101
View File
@@ -0,0 +1,101 @@
# REPORT — keyproxy v0 finish (commit + push of the self-killed turn)
Date: 2026-08-28 21:30
Repo: https://git.knownelement.com/ukrrs/mopac-keyproxy (branch `main`, pushed through `47343d5`)
Task: verify the keyproxy-v0 working tree left uncommitted by the previous
turn (it self-SIGTERM'd via a broad pkill before committing), re-test in
the Docker builder, commit in logical chunks, push, report.
## What shipped (v0, per the TASK spec)
- **Two shapes, one resolver**: `keyproxy serve` (localhost HTTP resolve
hop) + `keyproxy get REF` (exec-style CLI, bare value, no newline).
- **Endpoints** (`internal/server/server.go`):
- `POST /v1/resolve` — bearer-token auth (constant-time compare,
bootstrapped at startup from the file backend), body
`{"ref":"mpk-<name>"}``{"value":"..."}`.
- `GET /healthz` — unauthenticated liveness.
- Status codes: 200 / 401 / 400 (malformed body or non-`mpk-` ref,
never echoed) / 404 / 405 / 413 (4 KiB cap) / 500 (recovered panic,
detail suppressed) / 501 (stub backends) / 502. Failure bodies carry
ref + backend + fixed reason enum only.
- **Backends** (`internal/backend/`, one `Backend` interface):
- `file` — 0600 KEY=VALUE env files, parsed in pure Go (never
sourced), `~` expansion, permission mask enforced before read
(default 0600, stricter passes, looser refused).
- `env` — process-environment indirection (source IS the var name).
- `bitwarden`, `vault`**explicit not-implemented stubs** returning
501 `not_implemented` behind the same interface; phase-3 drop-ins.
- **Config** (`internal/config/`): strict stdlib-only TOML subset parser;
unknown keys and backend-specific misconfigurations fail loudly at
startup; `keyproxy.toml.example` tracked, real `keyproxy.toml`
gitignored (holds the ref MAP — locations only, never material).
- **Redaction, no persistence, no admin UI**: memory-only material; every
log line naming a ref masks it `<ref>=***`; auth failures log remote
address only; parse errors carry line numbers, never contents; panic
values discarded. Enforced by tests + smoke log scan.
- **Build discipline**: all dev in Docker via digest-pinned
`golang:1.26-bookworm`
(`golang@sha256:e8c859f5...e5514`); `dev.sh build|vet|test|check|smoke`,
`make` is a thin front door. Host runs no Go toolchain.
## Verification (re-run this turn, all in the Docker builder)
- `./dev.sh check` (= build + vet + test): PASS —
`ok internal/backend`, `ok internal/config`, `ok internal/server`,
`go vet` clean, binary built.
- `./dev.sh smoke` (end-to-end, containerized): **10/10 PASS**
healthz; 401 no-token; 401 wrong-token; 200 file resolve;
200 env resolve; 404 unknown ref; 400 invalid ref without echo;
501 bitwarden stub; 501 vault stub; 405 GET resolve.
Server log verified redacted: no material strings, refs masked
`ref=mpk-smoke=***`. Smoke container torn down by exact name
(`docker rm -f keyproxy-smoke`); no pkill used this turn.
## Commits pushed to origin/main
Previous turn left everything uncommitted. Committed in 5 logical chunks
(hashes post-rebase, see note below):
| Commit | Content |
|---|---|
| `b43bc55` | backend interface + file/env backends + bitwarden/vault stubs + env-file parser, with tests |
| `7bec2a1` | config loader + stdlib TOML subset parser, with tests |
| `82861b6` | HTTP resolve hop (auth, redaction, reason enums), with tests |
| `24a57c1` | CLI (`serve`, `get`, `help`; exit codes 0/1/2) |
| `47343d5` | dev.sh/Makefile Docker tooling, smoke/, keyproxy.toml.example, .gitignore, README |
Note: first push was rejected — origin/main had been re-seeded
(`6c88e65` + `325a36c`), an unrelated history whose README/LICENSE are
byte-identical to our never-pushed local seed. Resolved by rebasing the 5
implementation commits onto `325a36c` (dropping the redundant local seed;
resulting tree verified identical to the pre-rebase tree). Push:
`325a36c..47343d5 main -> main`, working tree clean.
## How to start it
See README "Quickstart" (repo root, verified verbatim on 2026-08-28):
`./dev.sh check` → create 0600 env files + `cp keyproxy.toml.example
keyproxy.toml``./bin/keyproxy serve` (binds 127.0.0.1:8082) →
`POST /v1/resolve` with bearer token, or `./bin/keyproxy get mpk-example`.
## Left for phase 3
- `bitwarden` backend: replace stub with Bitwarden Secrets Manager REST
(machine accounts, stdlib-first — official SDK license is
AGPL-incompatible).
- `vault` backend: replace stub with HashiCorp Vault KV v2 + AppRole
(official Go api pkg, MPL-2.0, vendored).
- Optional: short-TTL memory cache (per README "Today vs planned").
Both stubs already fail loudly (501 `not_implemented`) naming ref +
backend; connectors slot into the single `backend.Backend` interface and
`newRegistry()` in `cmd/keyproxy/main.go`.
## Hygiene
- No broad pkill this turn; the only process handling was
`docker rm -f keyproxy-smoke` (exact container name) in the smoke
script's cleanup trap.
- Nothing seeded beyond the repo; real `keyproxy.toml`, `*.env`,
`bin/`, `.smoke/` all gitignored; working tree clean after push.
@@ -0,0 +1,108 @@
# REPORT — self-host core: `harness loop` replaces the bash stack — 2026-08-28 21:31
Deliverable for "get to be self hosting asap — smallest core that can
bootstrap itself (redmine/gitea interaction fine, discourse later)".
Pushed to ukrrs/MOPAC main as
`86c39c8` (module rename) → `c869ac1` (keyproxy/config) → `ecc0ee8`
(loop daemon) → `2901bb8` (docs + dev.sh loop runner).
## What runs now
The MOPAC harness (Go, stdlib only, built in the digest-pinned Docker
builder — host stays toolchain-free) drives ITSELF. Redmine is the SoR,
the conductor is the worker. Per issue in the released scope whose
(id, updated_on) has not been processed:
1. one bounded turn (same conductor `harness once` uses; sequential,
one at a time — v0, no concurrency knob yet);
2. REPORT file lands in `reports/` (as before);
3. REPORT body is POSTed back onto the issue as a Redmine journal note
(the previously-stubbed note writeback is now implemented);
4. status transitions per `[redmine.status_map]` (default shipped:
`"In Progress" = "Done"`; names resolved via `/issue_statuses.json`);
5. optional Gitea REPORT commit (`[gitea] commit_reports`, OFF by
default);
6. dedup marker refreshed to the post-writeback `updated_on`, so the
loop's own note never re-triggers itself (self-retrigger hazard
handled + test-asserted).
State: append-only `state/loop/loop.jsonl` — one JSON line per action
(dispatch / report / note / status / commit / refresh / error), dedup
index rebuilt at startup, restarts keep exactly-once-reaction semantics.
One line per action goes to stdout as well. A FAILED turn is recorded,
NOT retried (update the issue to re-release it) — a down proxy cannot
hot-loop the poll.
Secrets: `key_ref = "mpk:mpk-redmine"` style refs resolve through
mopac-keyproxy `POST /v1/resolve` (bearer from `[keyproxy] token_ref`,
60s in-memory cache). `env:`/`file:`/`literal:` refs keep working, so
the loop runs with or without keyproxy up. No hosts hardcoded — all in
harness.toml. Also folded in: module renamed
`git.knownelement.com/reachableceo/MOPAC/harness``ukrrs.com/mopac/harness`
(the doc-rot finding), verified build+vet+test in the builder.
Tests: loop end-to-end against a fake Redmine (mutable: notes bump
updated_on like the real SoR) + fake OpenAI server — dispatch/note/
status happy path, dedup on rescan, re-dispatch on issue update, failed
turn without hot-loop, dry-run zero-trace, restart dedup survival,
status-map miss, gitea create/update, keyproxy 401/404/cache. Full
`./dev.sh check` (build + vet + test) clean on go1.26.
## Exact command to start the loop
From `/home/reachableceo/projects/meta/MOPAC/harness` (harness.toml is
already in place; it is gitignored):
./dev.sh build
HARNESS_REDMINE_KEY=<key> HARNESS_LITELLM_KEY=<vertical virtual key> \
./dev.sh loop
That runs the daemon in the Docker builder container with the repo
bind-mounted (REPORTs + state land in the repo). Verify first with a
zero-cost scan:
HARNESS_REDMINE_KEY=<key> HARNESS_LITELLM_KEY=<key> \
./dev.sh loop --dry-run # prints "would dispatch <id>" per new issue, no turns, no state
./dev.sh loop --once # single scan (cron-able), exit 2 if the intake itself fails
Stop: Ctrl-C (SIGINT) in the foreground, or `docker stop mopac-loop`.
Interval default 120s (`[loop] poll_interval_secs`), override with
`-interval 60s`. With keyproxy up instead of env keys:
`[keyproxy] url/token_ref` in harness.toml + `mpk:` refs (see
harness.toml.example, all commented).
## What it replaces — the die list (NOT deleted yet, pending your sign-off)
1. `/home/_crossfeed/tooling/agent-stack/semaphore.sh` + slot-file
concurrency cap → replaced by the loop's in-process discipline
(v0: one turn at a time; concurrency knob later if load justifies).
2. `~/.coordinate/scripts/queue-next.sh`, `queue-after.sh`,
`pmo-heartbeat.sh` → replaced by the loop + its own wake discipline
(poll interval; `--once` for cron). The heartbeat screen
(`pmo-heartbeat`, PID 2924085) can be killed whenever you say so.
3. `dispatch-turn.sh` doorbell `screen -S reachableceo-PMO -X stuff`
note: ALREADY dead. The screen is named `RCEO-PMO`, so the message
was silently dropped all along. Mentioned for the record; deletion
after your sign-off.
Nothing in `/home/_crossfeed` or `~/.coordinate/scripts` was touched
this turn. `harness once` (manual/chained path) also unchanged in
behavior — file-only writeback, no notes.
## Rollback
Just stop the loop (`Ctrl-C` / `docker stop mopac-loop`). The bash stack
is fully present and untouched — relaunch it exactly as before. The only
new artifacts are `state/loop/loop.jsonl` (delete to forget all dedup
history) and REPORT files. No config the old stack reads was modified.
## Suggested verification (PMO)
1. `./dev.sh loop --dry-run` with the real Redmine — should list the
released-scope issues it would dispatch, zero LLM calls.
2. Pick one low-stakes issue, drop it to "In Progress" in scope, run
`./dev.sh loop --once` with keys: expect turn → REPORT file →
journal note on the issue → status → Done.
3. Check `state/loop/loop.jsonl` for the one-line-per-action trail.
— Work stack, 2026-08-28 ~22:30
@@ -0,0 +1,172 @@
# REPORT — mopac-bitwarden-go v0: Bitwarden Secrets Manager REST client (fake-server tested)
- Date: 2026-08-28 23:30 (work session 2026-08-28 late)
- Repo: https://git.knownelement.com/ukrrs/mopac-bitwarden-go (main, pushed:
`643de0f..9b730af`, 6 commits after the spec seed)
- Local: ~/projects/meta/MOPAC/bitwarden-go
- Status: v0 COMPLETE and green. Stdlib-only Go (zero third-party
modules), ALL dev in Docker (digest-pinned golang:1.26-bookworm, same
image as keyproxy), built and tested entirely against a fake Secrets
Manager — no live vault access exists on this account yet; live creds
attach later with zero code change (config only).
## 1. What was built
A plain-REST Bitwarden Secrets Manager client for machine accounts +
thin CLI. No official SDK imported (AGPL-incompatible); the wire protocol
was reconstructed from Bitwarden's public API behavior and pinned to
published SDK test vectors.
Wire protocol implemented (all verified against the fake server):
| Step | Wire |
|---|---|
| login | POST /identity/connect/token — grant_type=client_credentials, scope=api.secrets, machine client_id/client_secret |
| org key | response's `encrypted_payload`: type-2 EncString sealed for the credential's embedded 16-byte key (HKDF-SHA256, salt `bitwarden-accesstoken`, info `sm-access-token`); unwraps to the organization key |
| refresh | grant_type=refresh_token, fired automatically 30s before expiry (memory-only) |
| secrets | GET /api/accounts/{id}/secrets, GET /api/secrets/{id}, GET /api/projects/{id}/secrets; names/values are EncStrings decrypted in memory with the org key |
| projects | GET /api/accounts/{id}/projects |
Machine credential format handled: `0.<uuid>.<secret>:<b64-16B-key>` as
printed by Secrets Manager (uuid -> client_id; secret -> client_secret;
key -> payload-unwrap derivation). Split BW_CLIENTID/BW_CLIENTSECRET also
accepted (plaintext-mode servers only).
## 2. Library surface (the exact keyproxy integration contract)
```go
import bw "git.knownelement.com/ukrrs/mopac-bitwarden-go"
tok, err := bw.Authenticate(ctx, bw.Credentials{BaseURL: url, AccessToken: cred})
defer tok.Zero()
value, err := bw.GetSecret(ctx, tok, "redmine-api-key") // secret NAME or uuid
secrets, err := bw.ListSecrets(ctx, tok) // metadata only, no values
projects, err := bw.ListProjects(ctx, tok)
```
- Errors are `errors.Is`-friendly sentinels: ErrAuthFailed,
ErrTokenExpired, ErrSecretNotFound, ErrMalformedResponse, ErrDecrypt,
ErrServer, ErrUnreachable, ErrInvalidCredentials. Error strings carry
fixed reason enums + uuids/names ONLY — never values, tokens,
ciphertext, or response bodies.
- Token: memory-only, auto-refresh-before-expiry, `Zero()` wipes key
material, `String()` is log-safe (expiry + ids only).
- Decryption fails closed: MAC verified before decrypt; tampering is a
typed error, never passthrough of ciphertext.
## 3. CLI usage
```
bitwarden-go login # credential check; prints summary only
bitwarden-go projects # "<id> <name>" per line
bitwarden-go secrets list # "<id> <name>" per line
bitwarden-go get <name-or-uuid> # bare value to stdout, no newline
```
Config: BW_* env vars or 0600 env file ($BITWARDENGO_CONFIG, default
~/.config/bitwarden-go/env). NEVER flags/args. Keys: BW_SERVER_URL
(default https://vault.bitwarden.com), BW_ACCESS_TOKEN (full credential)
or BW_CLIENTID+BW_CLIENTSECRET. Exit codes 0/1/2 mirror keyproxy
(ok / usage-config / auth-or-resolution).
## 4. Fake-server test results
`./dev.sh check` (in the Docker builder): build + vet + gofmt clean,
**53 tests pass, 0 fail** across 3 packages (38 top-level + 15 subtests).
Coverage areas:
- Crypto pinned to published SDK vectors: credential parsing, HKDF
shareable-key derivation (3 vectors), type-2 EncString AE vector,
round-trips, tamper detection (MAC + ciphertext).
- Auth table vs fake server: happy path, wrong secret, server 500,
unreachable, malformed token body, split-creds-on-encrypted-server
(fails loudly), plaintext server mode.
- Token lifecycle: refresh-before-expiry (short-TTL), refresh rejection
surfacing, expiry without refresh token, omitted-value list fallback to
by-id fetch.
- Reads: by name, by uuid, lists with decrypted names, missing secret
(name + uuid), malformed list body, tampered value MAC, rejected
bearer.
- Config: 0600 enforcement (0644/0666 refused BEFORE read; 0400 passes),
env-over-file precedence, pure-Go KEY=VALUE discipline, missing-file
behavior, no-credentials error.
- CLI (end-to-end via real 0600 env file): login/get/lists/failure
paths/usage.
- Redaction sweeps: every error path and every CLI run asserted free of
client secret, credential key, access/refresh tokens, secret values;
stdout carries material only in `get` (pinned to the exact value).
Smoke (`./dev.sh smoke`): fake Secrets Manager booted in a container on
127.0.0.1:8600 (full crypto chain, random org key per boot); real binary
driven from host: login/projects/secrets list/get by name/get by
uuid/missing-secret/bad-credential + redaction sweep. Result: `smoke: OK`.
Exact container IDs cleaned up; no broad kills.
## 5. Open questions from PORTING-NOTES-secrets.md §3
Answered by code (v0 decision, reversible):
1. Substrate: direct REST in Go, zero Node/bw. The client implements the
machine-account flow including client-side decryption, so shelling to
`bw` is unnecessary for the harness's read needs.
3. Ref syntax (read side): `bw:<name>` maps to GetSecret(name) = Secrets
Manager secret by NAME (uuid also accepted). `#field`/`#totp` are
vault-item concepts; Secrets Manager secrets have no fields — code
answer: not needed for the SM path.
4. Session lifetime: memory-only for the process lifetime; a long-lived
keyproxy keeps its Token and auto-refreshes before expiry (30s skew).
No disk persistence exists in the library at all.
Still needing Charles:
2. Machine account: dedicated service account for keyproxy/harness (not
coo@turnsys.com), with access scoped to a harness project; the vault
side must exist before phase 3 flips on.
5. Secret inventory: exact secret names/uuids for Redmine + LiteLLM keys
(current fake names redmine-api-key/litellm-key are placeholders).
6. Scope: strictly the SM read path consumed via keyproxy, or also absorb
KNELCredsManager's ~/.creds/*.env contract.
7. Subprocess injection: does anything need secrets as child-process env
(LiteLLM), or HTTP-header-only as today?
+ NEW — module wiring: keyproxy must consume this module from the private
gitea; recommend `go mod vendor` (matches "one auditable vendored
module tree") or GOPRIVATE=git.knownelement.com. Founder's call.
+ NEW — live verification checklist (first day with real creds): run
login/get against Vaultwarden pwvault.turnsys.com and against Bitwarden
cloud to confirm the accounts-path list endpoint and encrypted_payload
shapes match the fake exactly (protocol reconstructed from public
behavior + SDK vectors; end-to-end vs a live server is the one thing
tests cannot prove).
## 6. Exact keyproxy integration point
`keyproxy/internal/backend/bitwarden.go` (currently the 501 stub,
`Resolve` at bitwarden.go:20). Phase 3 replacement sketch:
```go
// keyproxy: internal/backend/bitwarden.go
func (b *Bitwarden) Resolve(ctx context.Context, ref Ref) (string, error) {
cfg, err := config.Load(ref.Source) // ref.Source = 0600 env file path (BW_* keys)
if err != nil { return "", Err(ref, b.Name(), ReasonUnreadableSource) }
tok, err := bw.Authenticate(ctx, bw.Credentials{
BaseURL: cfg.ServerURL, AccessToken: cfg.AccessToken,
ClientID: cfg.ClientID, ClientSecret: cfg.ClientSecret,
})
if err != nil { return "", Err(ref, b.Name(), ReasonUnreadableSource) } // auth -> typed reason
defer tok.Zero()
return bw.GetSecret(ctx, tok, ref.Key) // ref.Key = secret name or uuid
}
```
keyproxy.toml ref shape (existing stub semantics): backend `bitwarden`,
source = path to the 0600 env file, key = secret name/uuid. The library's
typed errors map 1:1 onto keyproxy's Reason enums. Wiring requires adding
the module dependency (see open question: vendor vs GOPRIVATE).
## 7. Reproduce
```
cd ~/projects/meta/MOPAC/bitwarden-go
./dev.sh check # build + vet + test (Docker builder)
./dev.sh smoke # containerized fake server + real CLI end-to-end
```
+114
View File
@@ -0,0 +1,114 @@
# REPORT: docs standards pass — READMEs and docs/ across ukrrs repos
Date: 2026-08-28
Operator: docs-standards turn (TASK-20260828-2100-docs-standards)
Status: COMPLETE — 4 repos committed and pushed; verification-first throughout
## Method
Read code/help/config before writing a word. Ran every documented command:
build/vet/test inside the Docker builder (golang:1.26-bookworm, pulled,
pre-authorized), `./bin/harness help`, `./bin/harness once --dry-run --demo`,
and the no-key live-demo error path (exit 1, clean message). Gitea anchor
slugs for the DESIGN.md TOC were taken from the rendered page, not guessed.
## Per-repo summary
### ukrrs/MOPAC (harness) — commit c6da05f, pushed to main
- README.md: full rewrite from the 16-line skeleton. H1 + purpose paragraph +
Status line; verified quickstart (Docker builder, cp harness.toml.example,
dry-run with real output, live-demo bar incl. verified no-key failure
output); mermaid conductor flowchart (INTAKE -> ROUTING -> bounded TURN ->
REPORT, chaining by re-invocation) + prose; reference tables for CLI
(subcommands, flags, exit codes 0/1/2/4 from actual help), config (every
harness.toml section + defaults from internal/config), model routing
(tier table + class -> tier -> model table); Status table (Works/Stubbed/
Next) sourced from REPORT.md; docs/link section; AGPLv3. Stale
`reachableceo/MOPAC` path replaced with git.knownelement.com/ukrrs/MOPAC.
- docs/: all three PORTING-NOTES-*.md got Status front-matter lines
(crush/maki: complete, drift possible; secrets: complete, 7 open questions
pending Charles, feeds mopac-bitwarden-go). PORTING-NOTES-secrets.md was
untracked in the working tree (pre-existing) — now committed.
- DESIGN.md: Contents section (19 anchor links, verified against Gitea's
rendered ids) after the intro. No sections rewritten, reordered, or added
beyond the TOC.
### ukrrs/mopac-keyproxy — commit 325a36c, pushed to master
- Cloned fresh from Gitea. README upgraded, purpose prose kept: Status line
(spec seed, no code yet); Scope (mpk_ placeholders, BW Secrets Manager
REST + Vault KV v2/AppRole backends, HTTP hop + `keyproxy get` CLI);
Non-goals (no admin UI ever, not a secrets manager, no disk persistence,
no org assumptions); Today-vs-planned table; Design references linking
into ukrrs/MOPAC DESIGN.md key-proxy + FLOSS-repos sections (anchors
verified) and the sibling repos; AGPLv3.
### ukrrs/mopac-bitwarden-go — commit d8c781d, pushed to main
- Cloned fresh from Gitea. README upgraded, purpose prose kept: Status line
(spec seed, reference study complete, open questions pending Charles);
Scope (stdlib REST, no SDK, machine-account auth, porcelain/plumbing,
memory-only session handling); Non-goals (no admin UI, not a vault server,
no disk cache of values); Today-vs-planned table naming the
KNELSecretsManager ADR-002 wrapper as the production interim; Design
references into PORTING-NOTES-secrets.md (incl. its 7 open questions),
DESIGN.md toolchain-policy + key-proxy anchors, keyproxy sibling; AGPLv3.
### ukrrs/KNEL-AIMiddleware — commit 6476304, pushed to main
- Audit-only pass (working clone: projects/meta/MOPAC/siblings/
KNEL-AIMiddleware; the task's ~/projects/siblings path does not exist).
Fixed only wrong/stale facts:
- License badge + footer said MIT; LICENSE is AGPLv3. Corrected both.
- Production numbers said 31/42 (MCP 27/38); PRODUCTION-READY.md says
33/42 (MCP 29/38). Badge + summary table corrected.
- Clone URL was github.com/KNEL/...; corrected to
git.knownelement.com/ukrrs/KNEL-AIMiddleware.git.
- Project structure tree predated tooling-cli/ (redmine, discourse, dns,
KNELCredsManager), the 40+ mcp-/lsp-wrapper scripts, run.sh,
maintenance.sh, PRODUCTION-READY.md, LSP_SETUP.md — all added.
- scripts/ section missing validate-mcp.sh / validate-all.sh — added.
- Status front-matter line added (org standard) without restructuring.
## Checklist applied
- Front matter (H1, purpose paragraph, Status line): all 4 repos.
- Quickstart verified by running: MOPAC (build/vet/test in Docker builder,
help, dry-run, no-key live path; with-key path proven live 2026-08-28
19:29 per log + reports/REPORT-latest.md). Seeds have no runnable code —
quickstarts intentionally absent until commands exist (standard: never
document an unverified command).
- Architecture mermaid + prose: MOPAC (conductor loop).
- Reference tables (CLI/flags/exit codes, config sections, routing map):
MOPAC.
- Status table from REPORT.md: MOPAC.
- Terminology (harness/conductor/turn/REPORT), fenced code with language
tags, no heading-level skips, no ownerless TODOs: all 4.
- Links checked: relative files exist; Gitea anchors verified against
rendered HTML; no dead links introduced.
- Correct repo URLs (git.knownelement.com/ukrrs/...): all 4.
## Doc rot found, NOT fixed in this pass (follow-up list)
1. **go.mod module path stale**: `git.knownelement.com/reachableceo/MOPAC/
harness` (and all internal import paths) predate the ukrrs transfer.
Source change — excluded from a docs-only pass. Fix in a code turn,
then drop the parenthetical note in the harness README quickstart.
2. **DESIGN.md line ~154** still lists mopac-bitwarden-go as "later" — it
is created and seeded now. DESIGN edits were out of scope (TOC only);
fold into the next DESIGN append turn.
3. **Duplicate KNEL-AIMiddleware clone**: ~/projects/KNEL-AIMiddleware
(older location) is now one commit behind the siblings/ clone I pushed.
Someone should `git pull` it or remove it (MIGRATION-projects.md
territory).
4. **KNEL-AIMiddleware STATUS.md / PRODUCTION-READY.md / LSP_SETUP.md /
JOURNAL.md**: not audited this pass (scope was the README); README now
trusts PRODUCTION-READY.md's 33/42, which matched its own internal
tables. A deeper docs pass could reconcile STATUS.md the same way.
5. **golang:1.26-alpine builder breaks the harness test suite** (no bash in
the image; internal/tools tests fail). Bookworm image works and is what
the README documents. Worth pinning digest + documenting image choice
when the builder container is formalized (DESIGN all-dev-in-docker rule).
6. **Harness bin/ is gitignored but README quickstart builds into it**
correct as designed; noting only so nobody "fixes" it away.
+117
View File
@@ -0,0 +1,117 @@
# REPORT: mopac events — webhook receiver (build phase 2b, V1 scope)
Date: 2026-08-28
Operator: phase-2b events turn (DESIGN "Events are V1 scope" section)
Status: COMPLETE — 5 commits on main, pushed to ukrrs/MOPAC; build/vet/test
clean in the digest-pinned Docker builder; live smoke proven on LAN port
4100 (401 without secret, 200 + JSONL record with).
## What shipped
`harness events` — Go stdlib net/http receiver (no frameworks, zero new
deps) that lets Redmine/Discourse/Gitea punch the harness:
- Routes: `POST /hooks/{redmine,discourse,gitea}`, `GET /healthz`; GET on a
hook = 405, unknown path = 404, body > 1 MiB = 413.
- Verification, deny-first: gitea = hex HMAC-SHA256 of the raw body in
`X-Gitea-Signature` (constant-time hmac.Equal); redmine/discourse =
shared-secret header, constant-time compare, header names configurable
(defaults `X-Redmine-Webhook-Secret` / `X-Discourse-Webhook-Secret`).
Unsigned/unverified = 401 with ONE generic error body; rejection logs
name the failure class only.
- Normalization: every provider payload becomes one internal Event
(source, kind, actor, canonical subject id — `redmine:issue:42`,
`discourse:topic:7`, `gitea:pr:ukrrs/MOPAC#5` — title, repo, provider
event id, sha256 payload digest, received-at). Tolerant across known
payload variants (redmine_webhooks + flat shapes, Discourse headers or
payload, Gitea action/pull_request/issue incl. closed+merged →
pr_merged).
- Action mapping per DESIGN: redmine issue update/note/journal →
dispatch_turn; discourse post reply → respond_turn; gitea PR
approved/merged → pipeline_step; rest stored-but-ignored.
- Persistence: append-only `state/events/events.jsonl` (file 0600, dir
0700), dedup by provider event id (`X-Gitea-Delivery`,
`X-Discourse-Event-Id`, `X-Redmine-Delivery`; payload-digest fallback);
dedup index rebuilt from the file at startup (torn tail skipped) so
replays across restarts still dedup — at-least-once delivery,
exactly-once reaction.
- Config: `[events]` listen (default `:4100`), state_dir, per-source
secret refs (`env:NAME` / `file:` / `literal:`), resolved at startup;
refuses to start with zero secrets; ref values never echoed.
- Secrets posture: never logged, never persisted; audit lines carry
normalized fields + truncated digest only. Test-asserted (log + JSONL
scanned for secret material).
## What is stubbed
- Event → turn dispatch: stored actionable events reach
`Conductor.DispatchEvent` (internal/loop/loop.go), which today prints
the would-be action. Real wiring (dispatch/redirect turn for Redmine,
context update + response for Discourse, pipeline step for Gitea) is
the next chunk, per plan.
- Cross-host forwarding/replication of the event log (T1 later); JSONL
is local-only today.
- Discourse HMAC signature mode (their `X-Discourse-Event-Signature`) is
not implemented; the spec asked for the secret-header mode, which is
what shipped.
## Dev-in-docker compliance
Every compile/vet/test path routes through `dev.sh`, which pins the
builder by digest: `golang@sha256:e8c859f5632...` (= golang:1.26-bookworm).
Note: golang:1.26-alpine was tried first per the task text but its image
has no bash, and the phase-2 exec tool's tests exec bash — bookworm keeps
the pin and the suite green. Host ran only docker + python3 stdlib urllib
(curl is banned on host). Exact commands used:
- Gate: `./dev.sh check`
`docker run --rm -v "$PWD:/h" -w /h -u "$(id -u):$(id -g)" -e HOME=/tmp
-e GOFLAGS=-buildvcs=false golang@sha256:e8c859f... sh -c 'go build -o
bin/harness ./cmd/harness && go vet ./... && go test ./...'`
- Targeted runs: `./dev.sh test -run TestNormalize -v` (same container).
- Smoke: `./dev.sh smoke` → builds via the same builder, then
`docker run -d --name mopac-events-smoke -p 4100:4100 -v "$PWD:/h" -w /h
-u "$(id -u):$(id -g)" -e HOME=/tmp golang@sha256:e8c859f...
/h/bin/harness events -config /h/.smoke/harness.toml -listen ":4100"`,
probed from the host with `BASE_URL=http://127.0.0.1:4100 python3
smoke/probe.py`, torn down with `docker rm -f`.
## Smoke results (port 4100, all PASS)
- healthz answers; GET hook = 405.
- gitea unsigned → 401 `{"error":"unverified webhook"}`; bad signature →
401; valid HMAC → 200
`{"action":"pipeline_step","id":"gitea:smoke-delivery-1","status":"stored"}`;
replay → 200 `status:duplicate`.
- redmine shared secret → 200 stored, `dispatch_turn`; wrong secret → 401.
- discourse shared secret → 200 stored, `respond_turn`.
- JSONL: 3 lines (gitea/redmine/discourse), correct fields, no secret
material; container log shows one audit line per delivery + the stub
dispatch firing exactly once per stored actionable event.
## Tests
Table-driven, stdlib only: verification (7 HMAC + 5 shared-secret cases),
normalization (13 shape cases + 12-row action map + digest stability),
store (dedup, restart persistence, torn tail, 0600 perms), end-to-end
httptest server (401/400/200/405/413 paths, replay dedup,
ignore-not-dispatched, secret-leak scans of responses/log/JSONL), config
(events defaults, parse, bad-ref validation). `go build ./...`, `go vet
./...`, `go test ./...` all clean; every commit builds standalone.
## Commits (main, pushed c6da05f..f89b694)
1. `164b145` events: config surface for the webhook receiver
2. `05ec1a4` events: verify, normalize, append-only store with
provider-id dedup
3. `043e03b` events: HTTP receiver, CLI wiring, conductor dispatch stub
4. `88e7b7b` dev.sh + smoke: docker-routed dev loop and webhook smoke test
5. `f89b694` docs: events receiver in README, phase 2b status in REPORT
## Open for Charles
- Confirm the Cloudron apps can be configured with the three secret
headers (Redmine needs a webhook plugin — redmine_webhooks works with
its secret setting; header name is configurable if the plugin differs).
- Port choice 4100 is a default, not a decision — override via
`[events] listen` or `-listen` / `docker -p` when the real stacks land.
+126
View File
@@ -0,0 +1,126 @@
# REPORT — `harness serve`: OpenAI-compatible front door (OWUI is the client)
Date: 2026-08-29 ~01:30 | Repo: ukrrs/MOPAC (harness/) | Status: DONE, live on LAN port 8090, pushed to origin main (2901bb8..704f905)
## What it is
`harness serve` = the OWUI front door from DESIGN (hermes killed; OpenWebUI =
interactive surface with Cloudron SSO + admin RBAC we do not build). OWUI
adds MOPAC as an OpenAI connection; each task class registers as a "model".
OWUI chat and webhooks (`harness events`, port 4100) are now two doors into
the same conductor loop. v0: stateless, non-streaming, tools off.
## Endpoints (port 8090, own port — coexists with events on 4100)
| Route | Auth | Behavior |
|---|---|---|
| `POST /v1/chat/completions` | Bearer vkey | ONE bounded stateless conductor turn over the full history OWUI sends; reply = single assistant message + usage. `temperature`/`max_tokens` forwarded. `stream:true` = explicit 400. |
| `GET /v1/models` | Bearer vkey | the servable catalog (below) |
| `GET /healthz` | none | liveness |
Auth = constant-time SHA-256 digest compare of the Bearer vkey; missing /
wrong / malformed keys all get one byte-identical generic 401. vkey is a
config ref (`[serve] vkey_ref = "env:HARNESS_SERVE_VKEY"`), never logged.
Unknown model = 400 naming the valid ones. Upstream failures = terse 502.
## Model catalog (as configured in harness.toml right now)
Every `[models.classes]` class is exposed as model `mopac-<class>`, routed
through the existing `[models]` tier table to the concrete proxy model:
| OWUI model name | Tier | Concrete model (sent to LiteLLM) |
|---|---|---|
| `mopac-primary` | mopac-primary | glm-5.3 (default / flagship+) |
| `mopac-code` | mopac-code | glm-5.2 (flagship) |
| `mopac-architecture` | mopac-code | glm-5.2 |
| `mopac-review` | mopac-review | glm-5-turbo (mid) |
| `mopac-summarize` | mopac-review | glm-5-turbo |
| `mopac-writeback` | mopac-review | glm-5-turbo |
| `mopac-study` | mopac-study | glm-4.7-flash (flash) |
| `mopac-read` | mopac-study | glm-4.7-flash |
| `mopac-vision` | mopac-vision | glm-4.6v |
`[serve] enabled_models` can narrow this to a subset if you want fewer
buttons in OWUI (validated at config load).
## OWUI connection settings (plug these in)
- **Base URL**: `http://192.168.3.78:8090/v1`
- **API key (vkey)**: `mopac-owui-f2fa1879c71b8d7c83098c7838f8859d3b85d3bf`
(canonical copy: `~/.coordinate/secrets/mopac-serve-vkey.env`, 0600 —
same value as env var `HARNESS_SERVE_VKEY`; revoke = mint a new one +
restart)
- In OWUI: Admin Settings -> Connections -> add OpenAI API connection with
the above, then pull the model list (the 9 `mopac-*` models appear);
per-user/group model access is OWUI's RBAC, not ours.
- OWUI must NOT request streaming for this connection (v0 returns an
explicit 400 on `stream:true`; OWUI tolerates non-streaming providers —
toggle in the connection settings if a client defaults to streaming).
## How to run / restart
```sh
cd ~/projects/meta/MOPAC/harness
set -a; . ~/.coordinate/secrets/mopac-harness-vkey.env # HARNESS_LITELLM_KEY
. ~/.coordinate/secrets/mopac-serve-vkey.env # HARNESS_SERVE_VKEY
set +a
./dev.sh serve # container mopac-serve, port 8090 on the LAN
# stop: docker rm -f mopac-serve (exact container, never pkill)
```
It is RUNNING now (container `mopac-serve`, up as of this report).
## Implementation notes
- Turn machinery REUSED, not copied: `loop.turn` refactored into a shared
bounded-turn core (`runTurn`) that takes arbitrary message history, an
optional tool palette, and forwarded max_tokens/temperature. `once`/`loop`
behavior unchanged (all prior tests pass untouched); the serve path calls
the same core with tools hard-off (a hallucinated tool call is refused as
a tool result, never executed).
- History assembly: OWUI's own leading system message is preserved verbatim
(OWUI personas win); only when none is sent does the harness prepend a
minimal vertical-identity prompt.
- Commits (logical chunks, all Docker-built via digest-pinned builder):
8614827 loop refactor, c54a5a4 [serve] config, c9e86ee serve + CLI +
dev.sh runner, 704f905 docs. Pushed to origin main.
## Test results
`./dev.sh check` (build + vet + test, golang:1.26 builder): ALL GREEN —
```
ok internal/config ok internal/events ok internal/intake
ok internal/llm ok internal/loop ok internal/models
ok internal/serve ok internal/tools ok internal/writeback
```
- Serve tests: scripted fake OpenAI upstream against the REAL server — auth
matrix (missing/malformed/wrong key 401, generic body, no vkey leak),
catalog + enabled_models subset, end-to-end chat (concrete model on the
wire, no tools, OpenAI response shape, usage), history assembly both
ways, multi-round usage accounting (refused tool call fed back, sums
40/30/70), temperature/max_tokens forwarding, unknown-model 400 naming
valid ones, stream 400, empty/bad-role/malformed 400s, upstream 502,
fail-fast construction.
- Loop tests: ServeTurn system-prompt prepend/preserve, tools-off refusal
(allow-listed `pwd` NOT executed), knob forwarding, Router accessor.
- Live LAN proof (python urllib, curl banned on host): healthz 200; wrong
key 401; catalog 200 (all 9 models); unknown model 400 listing them;
stream 400; REAL turns through LiteLLM — `mopac-primary` -> glm-5.3
("MOPAC demo door" reply, 255 tokens, finish stop) and `mopac-study` ->
glm-4.7-flash (166 tokens). Audit lines log routing + counters only.
## Next (per TASK: streaming, tools; plus the obvious follow-ons)
1. **Streaming**: SSE `stream:true` path on the serve door (needs the llm
client's streaming + truncation-resume work, already on the phase 3
list).
2. **Tools in serve turns**: per-vertical gated tool palette inside serve
turns (the machinery already supports a palette; it is deliberately nil
for v0).
3. Per-connection vkeys (LiteLLM-vkey-style minting via keyproxy) instead
of one vkey per harness instance.
4. Each onstage/offstage stack gets its own `harness serve` instance with
its own vertical name + class map — that is how "each stack registers
as a model in OWUI" scales past one box.
+83
View File
@@ -0,0 +1,83 @@
# REPORT — harness skeleton (build phase 2) — 2026-08-28
## Works
- **Config**: `harness.toml` via a stdlib-only TOML-subset parser (tables,
bare keys incl. hyphens, strings/ints/bools, single- and multi-line
arrays; trailing commas tolerated; anything richer fails loudly with
line numbers). Defaults + validation; secrets are refs only
(`env:NAME` / `file:PATH` / `literal:VALUE`; `bw:` reserved) and redacted
from every error path. `harness.toml.example` is tracked and load-tested
so it can never rot; real configs are gitignored.
- **Model routing v0**: `[models]` tier map + `[models.classes]` class map;
requests go out with the CONCRETE model resolved from the map
(mopac-study→glm-4.7-flash, mopac-code→glm-5.2, mopac-review→glm-5-turbo,
mopac-primary→glm-5.3, + mopac-vision→glm-4.6v). No heuristic code;
unknown class = hard error naming the config section.
- **Conductor single-shot**: `harness once` = intake → routing → bounded
turn → REPORT, then exit 0. Chainable, no daemon. `--dry-run` = intake +
plan (resolved model, tool bounds) with ZERO LLM calls (test-asserted).
`--task-id` filter for chaining. Exit codes: 0 ok/no-tasks, 1
config/usage, 2 intake, 4 llm/turn.
- **Intake**: Redmine `/issues.json` scope query (raw filter params or
saved query id), task class from a configurable custom field with
default-class fallback; `--demo` builds the issue from `[demo]` (no
Redmine needed).
- **Bounded turn**: OpenAI-compatible chat via LiteLLM (stdlib http;
base_url ± `/v1` normalized; Bearer auth; retry/backoff on 429/5xx/
transport; usage accounting), tool-calling loop capped at `max_rounds`;
gate denials feed back to the model as tool results and are counted, not
fatal. On mid-turn LLM failure after content exists, a partial REPORT is
still written with the error as stop reason.
- **Exec tool**: allow-listed bash. maki-derived scope semantics without
tree-sitter: compound commands split segment-by-segment (`&&`/`||`/`;`/
`|`, quote-aware), `cmd *` word-boundary, `pfx*` raw prefix, `pfx/**`
path prefix, `*` universal; deny beats allow; `$()`/backticks/subshells
always denied (headless has no prompt channel). Per-command timeout with
process-group cleanup on unix; output truncation.
- **Writeback**: `REPORT-<vertical>-<task>-<ts>.md` + `REPORT-latest.md`
(atomic tmp+rename) with model/tier/class/tokens/rounds/denied/stop
telemetry per the DESIGN auditability bar.
- **Tests**: table-driven, stdlib `testing` only — TOML subset (valid doc
+ 9 error cases), config defaults/validation/key refs (incl. leak check),
routing decisions, scope matcher + gate (14 cases) + exec (timeout,
truncation, deny), writeback, Redmine intake (httptest), LLM client
(auth/model/retry/4xx/empty-choices), and loop end-to-end against a
scripted fake OpenAI server (demo turn, dry-run zero-call, tool
round-trip with message-shape assertions, denial counting, round limit,
error-class mapping). `go build ./...`, `go vet ./...`, `go test ./...`
all clean on go1.26.7; every intermediate commit builds standalone.
## MVP demo bar status
`harness once --demo` is wired end-to-end: prompt "tell me about yourself"
→ LiteLLM (glm-5.3 via mopac-primary) → GLM self-description → REPORT.
Proven against a scripted fake in tests. The live proxy
(http://192.168.3.78:4001) is reachable from this host and answers 401
without a key, so the live run needs exactly one thing:
export HARNESS_LITELLM_KEY=<vertical virtual key>
./bin/harness once --demo
## Stubbed / known gaps
- Redmine issue-note writeback (SoR note after the REPORT) — file only.
- Budget/semaphore GATE (LiteLLM spend APIs, class-aware slots) and cost
in REPORT (tokens only today).
- `bw:` key refs error until the bitwarden wrapper (phase 3).
- Streaming + turn resume on truncation — retry is request-level today.
- Session persistence + read-time repair (crush notes §2) — not started.
- Write confinement to declared roots + symlink-aware canonicalization;
redirects currently match by segment text (phase 3 permission layer).
- Task selection is first-in-scope; no P1-first ordering or status
transitions, so chained `once` re-picks the same issue (use --task-id).
- Local inbox intake (DESIGN core-loop step 1, second half) — not started.
## Next chunk (phase 3)
1. bitwarden-go wrapper + `bw:` refs (unblocks secret posture).
2. Full permission layer: tree-sitter bash scopes, write roots, symlink
checks, per-vertical allow/deny presets.
3. Redmine note writeback + status transition so chaining advances scope.
4. Budget gate via LiteLLM spend APIs; cost line in REPORT.
5. Streaming with truncation retry + turn resume.
@@ -0,0 +1,67 @@
# REPORT: KNEL-AIMiddleware consolidated into _crossfeed/tooling
Date: 2026-08-28
Operator: reachableceo agent
Status: COMPLETE — all deliverables verified
## Method
1. Verified source and target are on the same filesystem (both device 2049, /dev/sda1).
2. Verified the stale target had no unique work before deletion: no extra branches
(main only), no stashes, no commits not on origin/main. Its only uncommitted
change was a deleted `.env` (see Skipped).
3. `rm -rf` stale copy at /home/_crossfeed/tooling/knel-aimiddleware (2.4M).
4. Atomic same-filesystem `mv` of /home/reachableceo/projects/KNEL-AIMiddleware
(512M, incl. vendor/ and .git) into the target path. No data copied; inode
rename only, so no disk-space impact (45G free confirmed before move).
5. Created symlink /home/reachableceo/projects/KNEL-AIMiddleware ->
/home/_crossfeed/tooling/knel-aimiddleware so existing path references keep working.
## File counts (before / after)
| Item | Files | Dirs | Size |
|-------------------------------|--------|-------|------|
| Source before move | 21,590 | 4,240 | 512M |
| Stale target before removal | 283 | 171 | 2.4M |
| Target after move | 21,590 | 4,240 | 512M |
Counts match exactly; nothing lost in transit.
## Git (verified at final path, via both real and symlink paths)
- `git status --porcelain`: clean (empty). Branch main, up to date with origin/main.
- HEAD unchanged: 2440188 2026-08-10 "feat(tooling): containerize dns-cli in tooling-cli/dns"
- Remote intact: origin ssh://git@git.knownelement.com:29418/KNEL/KNEL-AIMiddleware.git (fetch + push)
- History preserved (full .git moved with the tree).
## Symlink
/home/reachableceo/projects/KNEL-AIMiddleware -> /home/_crossfeed/tooling/knel-aimiddleware
Resolves: yes (spot-checked file read through old path).
## Permissions applied
- Top level: reachableceo:users, mode 775.
- Recursive on all reachableceo-owned entries: chgrp users; dirs g+rx; files g+r
(incl. .git, so group members can run read-only git commands).
- Audit: 21,126 entries now group `users`; 0 owned entries missing required group bits.
## Skipped / exceptions
1. 4,753 entries under vendor/discourse-mcp/.pnpm-store are root:root (created by
root during containerized builds). chgrp/chmod impossible without sudo. All are
mode 755 (dirs) / 644 (files), i.e. world-readable, so group `users` read access
is unaffected. Cosmetic only.
2. Stale copy's one uncommitted change (deletion of .env) was discarded with it.
The surviving copy contains .env intact, so nothing of value was lost.
3. Two dangling symlinks in vendor test fixtures (bash-language-server,
terraform-ls testdata) could not be chgrp'd via dereference; `chgrp -h` applied
to the link inodes instead. They were dangling in the source already.
4. Minor: .git/index gets rewritten by git commands and may drift back to group
`reachableceo` over time; harmless for read access (mode stays group/world
readable). Re-run `chgrp -R users` if strict group ownership is ever required.
## Result
/home/_crossfeed/tooling/knel-aimiddleware is now the single current shared copy
(full working tree + vendor/ + .git + remote). Old path is a working symlink.
+100
View File
@@ -0,0 +1,100 @@
# REPORT: LiteLLM proxy deployed + RCEO pilot wired
Date: 2026-08-28 (turn TASK-20260828-1716-litellm)
Operator: RCEO-Work
Status: COMPLETE (one deviation, pre-approved — see "DB deviation")
## Containers
| name | image | status | port |
|---|---|---|---|
| litellm | ghcr.io/berriai/litellm-database:main-stable | Up, restart unless-stopped | **192.168.3.78:4001** -> 4000 |
| litellm-db | postgres:alpine | Up, restart unless-stopped | none (docker-internal only) |
Port note: host 4000 is taken (kneldevstack-supportstack-demo-homepage) -> used
4001 per task instruction. No existing container was stopped/restarted/touched.
## DB deviation (pre-approved fallback)
v2.2 specified "embedded SQLite". Upstream fact: LiteLLM has NEVER supported
SQLite for virtual keys/spend — Prisma schema is postgresql-pinned in every
release tag I checked (v1.53.7 Apr 2025, v1.63.14, v1.67.0, main); current
main-stable fails fast: "database features ... require PostgreSQL". No sqlite
code path exists in the installed package. Deployed the fallback PMO
pre-approved in STATE-SNAPSHOT-20260828-1720.md: "dedicated litellm-db postgres
container (standard minimal deploy, not overbuild)". Everything else per spec.
Full recipe was validated with throwaway containers first, then deployed.
## Virtual keys (8) — NAMES only, values are secrets
Values: /home/_crossfeed/tooling/credentials/litellm-virtual-keys.md (mode 600,
gitignored). Budgets are USD per 30d window, enforced at the proxy.
| key_alias | budget | scope |
|---|---|---|
| rceo | $50 / 30d | onstage pilot (this host, crush) — ACTIVE |
| bod | $30 / 30d | onstage BOD |
| cco | $30 / 30d | onstage CCO |
| cto | $30 / 30d | onstage CTO |
| coo | $30 / 30d | onstage COO |
| offstage-1 | $15 / 30d | offstage (capped lower per v2.2) |
| offstage-2 | $15 / 30d | offstage |
| offstage-3 | $15 / 30d | offstage |
All keys allow all 12 z.ai models (glm-5.3, 5.2, 5.1, 5-turbo, 5, 4.7,
4.7-flash, 4.6, 4.6v, 4.5, 4.5-air, 4.5v). Global hard cap: 3 concurrent
(config.yaml max_parallel_requests). Offstage hosts reach the proxy at
http://192.168.3.78:4001/v1 (OpenAI-compatible).
## Config / secrets layout
- /home/_crossfeed/tooling/litellm/config.yaml — 12 z.ai models via
https://api.z.ai/api/coding/paas/v4 (OpenAI-compatible), master_key from env,
per-token USD pricing mirrored from the crush catalog, drop_params on.
- /home/_crossfeed/tooling/credentials/litellm-secrets.env (600, gitignored) —
LITELLM_MASTER_KEY, ZAI_API_KEY (real key; only place outside crush state),
DATABASE_URL. Passed via --env-file, never in config or logs.
- /home/_crossfeed/tooling/credentials/litellm-db.env (600, gitignored) —
postgres creds for litellm-db.
- /home/_crossfeed/tooling/litellm/data/pg — postgres data (persists across
container recreation).
## RCEO pilot wiring
- Backups: /home/reachableceo/.local/share/crush/providers.json.pre-litellm.bak
(task-required) and crush.json.pre-litellm.bak (companion; holds the direct
z.ai key override).
- Actual wiring lives in /home/reachableceo/.config/crush/crush.json (600):
custom provider "litellm" -> base_url http://192.168.3.78:4001/v1 with the
rceo virtual key; models large=glm-5.2, small=glm-5-turbo.
- WHY not providers.json: crush auto-regenerates providers.json from its
bundled catalog on startup (verified — my endpoint edit there was silently
reverted, which caused a transient 401 during wiring). ~/.local/share/crush/
is machine state; the durable override point is the config file.
## Verification (all redacted, no key values)
1. Headless crush via proxy: `crush run --quiet -m litellm/glm-5.2 "Reply with
exactly: PROXY-OK..."` -> **PROXY-OK**; flagless default run -> "READY";
proxy access log shows the corresponding `POST /v1/chat/completions 200`.
2. Usage/key-info API sample (GET /key/info, master auth):
`{"key_alias":"rceo","spend_usd":2.035e-05,"max_budget_usd":50.0,
"budget_duration":"30d","budget_reset_at":"2026-09-01T00:00:00+00:00"}`
GET /global/spend/logs: `[{"date":"2026-08-28","spend":2.035e-05}]`
-> per-key attribution + dollar spend recording confirmed end-to-end.
## Emergency fallback (restore direct z.ai)
1. cp -p ~/.local/share/crush/providers.json.pre-litellm.bak ~/.local/share/crush/providers.json
2. rm ~/.config/crush/crush.json (removes proxy wiring; model reverts to zai)
(crush.json.pre-litellm.bak also restorable if state was touched)
3. Optional teardown: docker rm -f litellm litellm-db
Direct z.ai key remains intact in crush state the whole time.
## Notes / caveats
- glm-5.2 is priced 0/0 in the crush catalog (coding-plan included), so its
proxy spend tracks $0 by design; other models track real USD.
- Test artifacts from validation (throwaway containers/network/envs) fully
removed; no residue. Images kept locally for fast redeploys.
- credentials/.gitignore extended with the three litellm secret filenames.
+102
View File
@@ -0,0 +1,102 @@
# REPORT — LSP stack in docker, wired into crush (MOPAC dev env)
Date: 2026-08-28 20:15 | Home: ~/projects/meta/MOPAC/lsp/ | Status: DONE (4.5/5 servers with diagnostics; bash wired but server emits none upstream)
## What runs
Five language servers, all containerized, built from `MOPAC/lsp/docker-compose.yml`
(`docker compose --profile build build`), on the dedicated bridge network
`mopac-lsp` (isolated from other stacks; outbound HTTPS allowed because gopls
fetches Go modules via proxy.golang.org at analysis time). stdio servers: one
`docker run -i --rm` container per crush session via wrapper scripts
(KNEL-AIMiddleware pattern, PID-suffixed names for concurrent sessions).
No ports published (stdio over pipes). No host mounts beyond the workspace:
gopls mounts the go.mod project root at its host path (URI parity) + named
cache volumes `mopac-lsp-gomodcache` / `mopac-lsp-gocache`; marksman mounts
cwd read-only; node servers mount nothing.
| Server | Version | Base (digest-pinned) | Diagnostics verified |
|---|---|---|---|
| gopls | v0.23.0 | golang:1.26-alpine sha256:28d89ee9... | YES — semantic (see proof) |
| yaml-language-server | 1.24.0 | node:22-alpine sha256:c610fcdf... | YES — "Flow sequence ... must end with ]" on bad yaml |
| dockerfile-language-server | 0.15.0 | node:22-alpine sha256:c610fcdf... | YES — "Unknown instruction: FRM" on bad Dockerfile |
| marksman | 2026-02-08 | alpine:3.22 sha256:14358309... | YES — lsp_symbols on .md (server emits few diagnostics by design) |
| bash-language-server | 5.6.0 | node:22-alpine sha256:c610fcdf... | WIRED — container starts, initialize+publishDiagnostics flow; **v5.6 upstream does not publish tree-sitter parse errors as diagnostics** (same behavior in the KNEL stack). Hover/symbols/completion work. |
marksman correction to the TASK text: it is an F# project, not a Go binary.
No Go module exists to build; used the official musl release binary, pinned
by release tag + build-time sha256 verification, DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=1
(no ICU package needed).
## Crush wiring (how)
`lsp` block merged into `~/.config/crush/crush.json` (global config; backup at
`~/.config/crush/crush.json.bak-mopaclsp`). Canonical snippet kept in sync at
`MOPAC/lsp/crush-lsp.json`. Entries: gopls (filetypes go/mod/sum/work,
root_markers go.mod|go.work, timeout 120 — cold start may download modules),
bash-language-server, yaml-language-server, dockerfile-language-server
(root_markers Dockerfile*), markdown (= marksman). **No per-project setup is
needed**: crush auto-starts the matching server when a matching file is
opened/viewed/edited in a matching root. Version bumps require only editing
the Dockerfile ARG + compose arg + wrapper image tag, then rebuild.
Wiring gotchas (empirical, crush v0.87.0):
- marksman entry must be keyed `markdown` (KNEL-style); keyed `marksman` it
never matches .md files and no container ever spawns.
- An LSP config entry keyed by catalog name inherits powernap defaults;
explicit filetypes/root_markers in the entry are respected.
- Fixed a real blocker in harness: go.mod was a module-less stub ("go 1.24"
only) — gopls returns "no module path" and zero semantic diagnostics.
Added `module git.knownelement.com/reachableceo/MOPAC/harness` (absorbed
into harness commit 591d345 by the parallel phase-2 session).
## Proof (headless crush turns, 2026-08-28)
1. gopls on real harness code (`cmd/harness/main.go` with a temp injected
error at line 113, restored after):
`113:25 [gopls compiler][UnusedVar] declared and not used: x`
`113:37 [gopls compiler][UndeclaredName] undefined: yy`
(Earlier, pre-fix: `undefined: undefindedVariable` Error 7:14 on the
scratch file — full pipeline incl. container spawn confirmed by docker ps
polling during the turn.)
2. yaml: `line 3:1 Flow sequence in block collection must be sufficiently
indented and end with a ]` (yaml-language-server).
3. dockerfile: 2 errors on `FRM alpine:3.22` — `No source image provided
with FROM` + `Unknown instruction: FRM` (dockerfile-language-server).
4. markdown: `lsp_symbols` returned `String Title (line 1)` for the test .md.
5. bash: container spawns on .sh access; raw probe shows the server parses
and logs `syntax error` via window/logMessage but publishes an empty
diagnostics array (upstream behavior, not a wiring failure).
Scratch files for re-verification kept in `MOPAC/lsp/verify-scratch/`;
raw probes: `lsp-handshake-test.sh`, `lsp-diagnostics-probe.sh`.
## Node-exception list (supply-chain ledger)
Sanctioned node-based exceptions — containerized only, never installed on
host; base node:22-alpine digest-pinned (sha256:c610fcdfb1d5b4740dd70c284
ed3cb16bb857e0f7166196e36a5501df7a3aa32); npm pins installed at build time:
- bash-language-server@5.6.0 (npm integrity sha512-DCuV+/BZAAozsp5...)
- yaml-language-server@1.24.0 (sha512-+HGcwu4M7IC...)
- dockerfile-language-server-nodejs@0.15.0 (sha512-iS3GUUAn1bC3s...)
## Failures / deviations
- marksman mislabeled "Go binary" in TASK — it's F#; handled via pinned
prebuilt musl binary (sha256-verified) instead of building from source.
- gopls v0.23.0 requires go >= 1.26; initial golang:1.25-alpine base failed
(`requires go >= 1.26.0`), switched to golang:1.26-alpine (digest recorded).
- marksman musl binary needs libgcc/libstdc++ on alpine (added) and .NET ICU
check bypassed via invariant-globalization env var.
- Compose parses unquoted `2026-02-08` args as timestamps ("2026-02-08
00:00:00 +0000 UTC") — quote date-like build args (fixed in compose).
- bash-language-server publishes no parse diagnostics (upstream); noted,
options.bashIde.sourceErrorDiagnostics=true set anyway for future-proofing.
- MOPAC/lsp is not yet under version control (MOPAC root has no git repo;
harness/.git is separate). Not committed — awaiting founder instruction
on which repo should own dev-env tooling.
## Ports
None. All servers are stdio (docker run -i, no published ports). Network
`mopac-lsp` exists for isolation + gopls egress only.
+8
View File
@@ -0,0 +1,8 @@
# REPORT — PMO ops batch (2026-08-28 ~19:27, RCEO-Work)
1. DESIGN.md: 6 sections appended (keyproxy/vault placeholders, FLOSS repo coupling, dev-in-docker BIG RULE, events V1 webhook receiver, OWUI front door / Hermes killed, budget+quota). Commit ab29d8a pushed to ukrrs/MOPAC main.
2. Redmine: issue 480 (study-crush) + 481 (study-maki) -> status_id 3 (closed) with journal notes citing deliverables. Env sourced in bash, passed via env to python3/urllib; no exec() of env file.
3. REPORT-study-crush.md + REPORT-study-maki.md written to inbox-pmo; deliverables verified in git (a606810, a1623e1) + pushed.
4. log.md: PMO line appended (19:27-05:00).
ALL DONE.
+5
View File
@@ -0,0 +1,5 @@
REPORT — reachableceo-Work online (2026-08-28)
- Read INTRO-WORK.md and PROTOCOL.md; checklist run (BOARD, inbox-work, log tail).
- inbox-work/ empty — no TASKs yet.
- Awaiting first TASK dispatch.
+7
View File
@@ -0,0 +1,7 @@
# REPORT — study-crush (PMO verification, 2026-08-28)
- Deliverable: /home/reachableceo/projects/meta/MOPAC/harness/docs/PORTING-NOTES-crush.md
- Covers: crush internals — MCP via go-sdk, session model, skip-list, exposure-minimization protocol (env/VAR indirection, redaction, canonical-source regeneration).
- PMO-verified: file exists; committed (a606810) and pushed to ukrrs/MOPAC on main.
- Redmine issue 480 (Study crush internals) set to status_id 3 (closed) with journal note.
- STATUS: DONE.
+7
View File
@@ -0,0 +1,7 @@
# REPORT — study-maki (PMO verification, 2026-08-28)
- Deliverable: /home/reachableceo/projects/meta/MOPAC/harness/docs/PORTING-NOTES-maki.md
- Covers: maki internals — tree-sitter usage, bash permission scopes, token reduction, subagent tiers.
- PMO-verified: file exists; committed (a1623e1) and pushed to ukrrs/MOPAC on main.
- Redmine issue 481 (Study maki internals) set to status_id 3 (closed) with journal note.
- STATUS: DONE.
+118
View File
@@ -0,0 +1,118 @@
# Porting notes: KNELSecretsManager (reference/KNELSecretsManager)
Scope for MOPAC harness: Bitwarden secret retrieval for `bw:` key refs
(reserved in `internal/config/keys.go:34-35`, errors until "phase 3").
DESIGN.md: Go `bitwarden-go` wrapper replaces KNELSecretsManager; secrets
never in logs. Line refs from study 2026-08-28, drift possible.
## 1. Current surface
Two generations ship in the repo; ADR-002 (`docs/ADR-002-ContainerBW.md`)
supersedes the standalone script with a containerized `bw` wrapper.
**Legacy `secrets-manager.sh` (v2.0)** — single bash script, CLI only:
`install | get <name> | list | test`, `-c/--config FILE`. Every invocation
redoes the full lifecycle: source plaintext config (BW_SERVER_URL,
BW_CLIENTID, BW_CLIENTSECRET, BW_PASSWORD) -> `bw config server` ->
`bw logout` -> `bw login --apikey` -> `bw unlock --raw` -> `bw get password
<name>` (secrets-manager.sh:119-159). Exit codes 10/20/30/40/50/60/70
(config/bw-missing/install/server/session/secret/login). Logs to
`/tmp/secrets-manager.sh.log` (names, not values). Designed to be vendored
into shell frameworks; Makefile lint/test/vendor-test targets.
**Current container wrapper (production since 2026-08-13, ADR-002)**:
- `bin/bw-install.sh` — downloads pre-compiled bw `2026.7.0` (inside an
alpine container; no host Node/wget), builds `reachableceo-bw-native:
2026.7.0` (debian:bookworm-slim + ca-certificates, `docker/bw-native/
Dockerfile`), installs wrapper to `~/.local/bin/bw` + entrypoint to
`~/.local/share/bw/entrypoint.sh`.
- `bin/bw-cli.sh` (host) — loads `~/.config/bw/env` (single-quoted values,
mode 600; the only secrets-on-disk allowed org-wide), then `docker run`
with named volume `tsys-bw-cli-state` mounted at `/root/.config/Bitwarden
CLI` so login state persists across calls.
- `bin/bw-entrypoint.sh` (container) — idempotent: `bw config server` ->
`bw login --apikey` (no TOTP; API key minted from an authenticated web
session) -> `bw unlock --passwordfile` (password briefly in /tmp) ->
`bw sync` -> `exec bw "$@"` with BW_SESSION.
- Interface is transparent `bw`: `status | list items/collections | get
password|totp|item <name> | generate`. Lookup is by item **name**; the
default secret is the item's password field (e.g. `APIKEY-pushover`).
- Backend: Vaultwarden at `https://pwvault.turnsys.com`, machine account
`coo@turnsys.com`, API-key auth + master-password unlock.
**Consumers**: nothing in the MOPAC harness consumes it yet — sole planned
integration is the `bw:` key-ref prefix (`internal/config/keys.go`,
asserted by `config_test.go:126,130`; `harness.toml.example:5`). First
real secrets needed: Redmine API key + LiteLLM key (`[redmine] key_ref`,
`[litellm] key_ref`). Siblings still on Bitwarden: KNELCredsManager
(`~/.creds/*.env` feeder) and `mcp-bitwarden-wrapper.sh` in
KNEL-AIMiddleware.
**Weaknesses to fix in the port**: plaintext master password + API secret
on disk; full login/unlock/sync per call (slow, racy logout in legacy);
secret values echoed to stdout and captured by callers into env; docker
required per host; "native" bw binary is secretly a Node SEA (ADR-002
caveat); legacy script `bw logout`s any concurrent session.
## 2. Replacement design sketch (Go)
**Shape**: `internal/secrets` package + thin `MOPAC/tools/bitwarden-go`
CLI (per DESIGN.md:88) so both harness and humans share one resolver.
Config gains `ResolveKeyRef` case for `bw:REF` (keys.go already reserves
it; keep `redact()` semantics).
**bw execution**: exec `bw` from PATH (the ADR-002 wrapper makes it
transparent whether bw is native or containerized). Treat as opaque
subprocess: `bw get password <name> --session <tok> --nointeraction`,
JSON mode where useful (`bw get item <name>`), 30s timeout, capture
stdout into a `[]byte` not a logged string.
**Machine credentials** (bootstrapping only): prefer env
`BW_CLIENTID/BW_CLIENTSECRET/BW_PASSWORD`; fallback `~/.config/bw/env`
parsed by hand (single-quoted values, require mode 0600). Never in
harness.toml, never in logs. Server URL from env `BW_SERVER` with
Vaultwarden default.
**Unlock strategy**: once per process — config server (idempotent),
`bw login --apikey` (skip if `bw status` says logged in), `bw unlock
--passwordenv BW_PASSWORD --raw` -> session key held **in memory only**;
single `bw sync` after login. On session-expired error mid-run: re-unlock
once, then fail. Never `bw logout` (breaks concurrent clients).
**Lookup by key**: `bw:<item-name>` -> password field (matches current
usage). Optional `bw:<name>#<field>` and `bw:<name>#totp` if non-password
fields are needed. Not-found => typed `ErrSecretNotFound`; auth failures
=> `ErrUnlockFailed` (mirrors exit codes 60/50/70 for callers).
**Caching**: per-run in-memory `map[ref]string` + mutex (multi-vertical
workers may resolve concurrently); negative results cached too. Session
token cached for process lifetime, zeroed on exit. No disk cache of
values; optional disk persistence of BW_SESSION is an open question.
**Never-log rules** (extends keys.go:70-78): log refs only as
`bw:<name>****`; error strings never embed values; subprocess stderr from
bw is scrubbed/dropped (bw can echo fragments); secrets never written to
REPORT.md, spans, or crash dumps; `go test` uses fake bw stub, never the
real vault.
## 3. Open questions for Charles
1. **Substrate**: keep shelling out to `bw` (containerized per ADR-002,
Docker on every harness host) or implement the Vaultwarden REST API
directly in Go (truly zero Node/bw, but reimplements crypto + sync)?
DESIGN.md:84 says "replaces Node bw CLI dependency" — which side of
that line do you want?
2. **Machine account**: dedicated service account for the harness instead
of `coo@turnsys.com`, with a vault collection scoped to only the
secrets the harness needs?
3. **Ref syntax**: is `bw:<item-name>` (password field) sufficient for
v1, or do you want `#field` / `#totp` / collection-scoped refs now?
4. **Session lifetime**: unlock per `harness once` run only, or persist
BW_SESSION (0600, TTL) across runs to avoid unlock cost each start?
5. **Secret inventory + names**: confirm exact vault item names for
Redmine and LiteLLM keys so `key_ref` values can be pinned in
harness.toml.example.
6. **Scope of port**: absorb KNELCredsManager's `~/.creds/*.env`
contract too, or strictly the `bw:` refs used by the harness?
7. **Subprocess injection**: any consumers needing secrets as env vars
for child processes (LiteLLM?), vs HTTP-header-only as today?
@@ -0,0 +1,23 @@
# TASK: Consolidate KNEL-AIMiddleware into _crossfeed/tooling
## Objective
Make /home/_crossfeed/tooling/knel-aimiddleware the single current shared copy of KNEL-AIMiddleware, replacing the stale Aug-10 copy.
## Context
- Source (current): /home/reachableceo/projects/KNEL-AIMiddleware — 512M, 21,590 files, git repo, remote ssh://git@git.knownelement.com:29418/KNEL/KNEL-AIMiddleware.git, vendor/ = 508M.
- Target (stale): /home/_crossfeed/tooling/knel-aimiddleware — 2.4M, 283 files, git repo, no vendor/, contains since-removed JOURNAL.md. Dated 2026-08-10.
- Target parent is reachableceo-writable; no sudo needed.
- Charles is splitting work from all-reachableceo to multiple accounts; other agent accounts (group `users`) need read access.
## Constraints
- No sudo.
- Preserve git history + remote in the surviving copy.
- Do not break path references: leave symlink /home/reachableceo/projects/KNEL-AIMiddleware -> /home/_crossfeed/tooling/knel-aimiddleware after the move.
- Keep vendor/ (disk has 45G free; move is same-filesystem rename).
- Verify target git status clean after move.
## Deliverables
1. _crossfeed/tooling/knel-aimiddleware holds the current working tree incl vendor/ and .git.
2. Symlink from old path in place and resolves.
3. Perms: group users, dirs g+rx, files g+r (top-level chgrp users).
4. REPORT to inbox-pmo with: method used, file counts before/after, git status, remote intact, perms applied, anything skipped.
@@ -0,0 +1,19 @@
# TASK: Amend agent-stack GOVERNANCE.md release policy (mechanical gate)
## Objective
Replace the per-release founder gate in agent-stack GOVERNANCE.md with a mechanical release gate modeled on TSYSGroupAIOS.
## Context
- Current GOVERNANCE.md (~L33): "reachableceo: capture anywhere, anytime. Approve releases (founder gate)."
- TSYSGroupAIOS (~/projects/TSYSGroupAIOS, per Charles 2026-08-28) establishes the model: policy in AGENTS.md prose, enforcement via git hooks + scripts/check-rules.sh audit; audit-clean = shippable.
- Proposed policy: agent-stack tooling releases are PRE-APPROVED when the repo's mechanical checks pass (bash -n on scripts, git commit hooks, no changes outside tooling/). Founder gate is RETAINED for: systemd units, relaunchers/stack-touching scripts, multiuser/perms changes, credentials/.
## Constraints
- Edit only /home/_crossfeed/tooling/agent-stack/GOVERNANCE.md; commit to that repo; no other files.
- Wording must stay plain-language (Charles reads this doc).
- Do not rename or touch /home/_crossfeed/founder/ references (separate pending decision).
## Deliverables
1. GOVERNANCE.md amended with the mechanical-gate release policy.
2. Commit with clear message.
3. REPORT to inbox-pmo with the diff.
+20
View File
@@ -0,0 +1,20 @@
# TASK: Deploy LiteLLM proxy + wire RCEO pilot stack
## Objective
One LiteLLM container fronting the shared z.ai key on this host; virtual keys issued; RCEO crush pointed at it; usage API verified. Implements DESIGN-24x7-execution.md §v2.2.
## Context
- z.ai provider + key currently live in /home/reachableceo/.local/share/crush/providers.json (NEVER print key values in REPORT/logs).
- reachableceo is in the docker group — containers allowed, no sudo needed.
- This host already runs docker (25 containers). Offstage (other machine) must reach the proxy later, so bind to the LAN interface, not just loopback.
## Deliverables
1. Config: /home/_crossfeed/tooling/litellm/config.yaml — z.ai models via OpenAI-compatible endpoint (api.z.ai), master_key from credentials/, embedded SQLite. Secrets file mounted 600.
2. Container: name `litellm`, restart unless-stopped, port 4000 on LAN interface (4001+ if busy, note it).
3. Virtual keys (8): rceo, bod, cco, cto, coo, offstage-1, offstage-2, offstage-3. Names + budgets documented; key VALUES only in /home/_crossfeed/tooling/credentials/litellm-virtual-keys.md (mode 600).
4. RCEO pilot wiring: backup providers.json -> providers.json.pre-litellm.bak; point base_url at http://<lan-ip>:4000 with the rceo virtual key.
5. Verify: one headless crush call through the proxy succeeds; hit the LiteLLM usage/key-info endpoint and capture (redacted) output.
6. REPORT to inbox-pmo: container name/health, port, virtual key NAMES (not values), backup path, redacted usage sample, emergency fallback = restore providers.json.pre-litellm.bak.
## Constraints
- No sudo. No secrets in any REPORT/log. Do not touch TSG account homes. Do not stop or restart any existing container. If any step fails, BLOCKED report, do not improvise credentials handling.
+13
View File
@@ -0,0 +1,13 @@
# TASK: Reference study — crush internals (PORTING-NOTES-crush.md)
## Objective
Study /home/reachableceo/projects/meta/MOPAC/reference/crush. Produce
/home/reachableceo/projects/meta/MOPAC/harness/docs/PORTING-NOTES-crush.md.
## Deliverables (notes file, <=180 lines, dense, no fluff)
1. MCP plumbing: config format, stdio/http transports, tool registration — what to port to Go.
2. Session/persistence model: session files, --continue, resume semantics.
3. Provider layer: how providers.json/crush.json merge, model routing, retries/stream handling (note where stream-truncation errors would surface).
4. Headless mode: how crush run/--print works, exit codes.
5. OAuth flows: where they live (deferred for v1 but map the code path).
6. Top 10 packages/files worth reading in detail during build; top 5 gotchas.
+12
View File
@@ -0,0 +1,12 @@
# TASK: Reference study — maki internals (PORTING-NOTES-maki.md)
## Objective
Study /home/reachableceo/projects/meta/MOPAC/reference/maki. Produce
/home/reachableceo/projects/meta/MOPAC/harness/docs/PORTING-NOTES-maki.md.
## Deliverables (notes file, <=180 lines, dense)
1. Tree-sitter bash permission parsing: how commands/subshells/pipes are decomposed -> allow/deny rules. Go equivalents (github.com/tree-sitter/tree-sitter-bash).
2. Token reduction: index/skeleton reads, MCP tool_search hiding, sandboxed code_execution — what's portable to a headless Go harness, what to skip.
3. Subagent model (weak/medium/strong, read-only vs full tools) — map to our TASK/dispatch pattern.
4. Headless/--print mode + session parallelism.
5. Top 10 files to re-read during build; top 5 gotchas.
@@ -0,0 +1,10 @@
# TASK: Reference study — KNELSecretsManager (PORTING-NOTES-secrets.md)
## Objective
Study /home/reachableceo/projects/meta/MOPAC/reference/KNELSecretsManager. Produce
/home/reachableceo/projects/meta/MOPAC/harness/docs/PORTING-NOTES-secrets.md.
## Deliverables (notes file, <=120 lines)
1. Current surface: what it stores/serves, interface (CLI? API?), consumers.
2. Replacement design sketch (Go): Bitwarden CLI (bw) wrapper — unlock strategy, machine credentials, secret lookup by key, caching, never-log-secrets rules.
3. Open questions list for Charles (be specific).
@@ -0,0 +1,26 @@
# TASK: Harness skeleton (build phase 2)
## Objective
Initial Go implementation in /home/reachableceo/projects/meta/MOPAC/harness per
its DESIGN.md (READ IT FIRST, esp. "MVP demo bar" + "Model selection layer"
sections, appended 2026-08-28 ~19:00): config load, conductor loop (single-shot
mode), redmine-pull intake (list issues in scope), bounded turn via LiteLLM
(OpenAI-compatible, base_url+key from config), REPORT file writeback, exec tool
(bash, allow-listed).
## Constraints
- Read docs/PORTING-NOTES-*.md first if present.
- `go build ./...` and `go vet ./...` clean; no external deps beyond stdlib +
tree-sitter/gomft if genuinely needed (prefer stdlib for v1).
- Config: harness.toml (scope query, litellm url/key-ref, paths) + example file.
- Single-shot: `harness once` runs ONE iteration then exits (chainable, no daemon).
- Dry-run mode: `harness once --dry-run` = intake+plan only, no LLM call.
- MODEL ROUTING v0: harness.toml `[models]` tier map (aliases `mopac-study`
-> glm-4.7-flash, `mopac-code` -> glm-5.2, `mopac-review` -> glm-5-turbo,
`mopac-primary` -> glm-5.3) + per-TASK `class` field; requests go out with
the concrete model name resolved from the map. No heuristic code in v0.
- MVP DEMO (the acceptance bar): `harness once` on a demo issue whose prompt
is "tell me about yourself" through LiteLLM; the GLM self-description comes
back and lands as the REPORT. Wire this as an example/smoke path.
- Tests for loop/gate logic (table-driven, stdlib testing).
- Commit incrementally; REPORT lists what works, what's stubbed, next chunk.
+26
View File
@@ -0,0 +1,26 @@
# TASK: LSP stack in docker, wired into crush (MOPAC dev env)
## Objective
Language servers containerized + wired into crush config so agent/human Go
dev on MOPAC gets diagnostics. Home: /home/reachableceo/projects/meta/MOPAC/lsp/
(wrappers + compose + README).
## Deliverables
1. Containers (compose file, isolated network, no host mounts beyond workspace):
- gopls (Go toolchain image; the critical one for MOPAC/harness)
- dockerfile-language-server (node-based: sanctioned exception, containerized)
- marksman (markdown; Go binary)
- yaml-language-server + bash-language-server (node-based: same exception)
2. Wrapper scripts per KNEL-AIMiddleware lsp-*wrapper.sh pattern (see
../siblings/KNEL-AIMiddleware/lsp-bash-wrapper.sh) adapted to MOPAC/lsp/.
3. Crush wiring: add servers to crush config (study how crush declares LSP:
reference/crush docs/code) for gopls at minimum; others best-effort.
4. Verify: open/edit a .go file in a MOPAC project via a headless crush turn ->
diagnostics actually flow (capture proof in REPORT).
5. REPORT to inbox-pmo: what runs, ports, how crush is wired, node-exception
list (for supply-chain ledger), failures if any.
## Constraints
No secrets involved. Reuse golang official images, pin digests. If crush LSP
config needs per-project setup, document exactly where. Do not touch live
screens. Node-based servers stay containerized and unpinned-to-host.
+117
View File
@@ -0,0 +1,117 @@
# TASK: PMO ops batch (traffic-cop delegation, fresh session)
You are RCEO-Work executing a PMO ops batch. Do ALL steps, terse REPORT at end
to /home/reachableceo/.coordinate/inbox-pmo/REPORT-pmo-ops.md.
1. APPEND to /home/reachableceo/projects/meta/MOPAC/harness/DESIGN.md:
## Key proxy: placeholders only, material never leaves the vault (Charles, 2026-08-28 evening)
- PROBLEM with existing gateways: they HOLD real keys in a webui/api and
serve placeholders. We invert that: key material lives ONLY in Bitwarden
(Secrets Manager) + HashiCorp Vault. A Go proxy (MOPAC/tools/keyproxy or
bitwarden-go evolution) resolves placeholder -> real at the wire, holds
material in memory only (fetch-on-demand, short TTL, never persisted,
never in any admin UI; there is no admin surface — config only).
- FRONTEND: consumers (harness, CLIs, containers) receive placeholder keys
(mpk_... opaque refs). If one leaks, it's a placeholder: revoke/re-map in
vault, zero upstream exposure. Defense in depth.
- BACKEND connectors: Bitwarden Secrets Manager REST (machine accounts;
AVOID the official SDK — source-available license conflicts with AGPL;
plain REST via stdlib keeps AGPLv3 clean) + HashiCorp Vault KV v2 +
AppRole (official Go api pkg is MPL-2.0, AGPL-compatible, vendor it).
License: AGPLv3; MIT fallback only if a conflict still arises.
- ADOPT crush's exposure-minimization protocol everywhere (per
docs/PORTING-NOTES-crush.md + house practice): keys via os.environ/VAR
indirection in configs (never literals), 0600 on secret files, redaction
of key material in all logs/output, canonical-source regeneration
(crush.json -> providers.json pattern), never exec env files inside
scripting languages, session scrubbing.
- Also feeds: study-secrets TASK (KNELSecretsManager notes) in flight.
LiteLLM stays a sanctioned service exception (its z.ai key can later be
injected at container start from the vault; not tonight).
## Tooling = standalone public FLOSS repos, loosely coupled (Charles, 2026-08-28 evening)
- Dedicated CLIs/services each get their OWN public Gitea repo under the
UKRRS org (org for ALL LLM work; AGPLv3), composable by anyone:
ukrrs/mopac-keyproxy (created, seeded), later mopac-redmine-cli,
mopac-discourse-cli, mopac-bitwarden-go, ... ukrrs/MOPAC (transferred
2026-08-28) is the harness core. MOPAC references them; it does not
contain them.
- LOOSE COUPLING RULES (FLOSS without support burden): config-driven —
no org-specific hosts/paths/defaults baked into code; contracts are
exec + JSON in/out (Unix plumbing); each tool standalone-usable by a
human; TSYS-specific policy/policy repos (agent-stack) live OUTSIDE the
tools. We build for our needs first; genericness comes from clean
contracts, not from supporting foreign requirements.
## ALL dev work in Docker (Charles, 2026-08-28 evening) — BIG RULE
- No dev toolchains on the host: compiling, vetting, testing, hacking all
happen inside containers (e.g. digest-pinned golang:1.26 builder with the
repo bind-mounted, `go build/vet/test` inside, artifacts out via the
mount). Host runs containers + screens; nothing else.
- docker pull of anything needed is PRE-AUTHORIZED, no asking.
- Exception cleanup: the harness-skeleton turn installed go1.26.7 to
host ~/.local before this rule landed — remove it (follow-up TASK) once
that turn completes; all future builds go through the builder container.
- Rationale: reproducible, supply-chain-clean (CMMC posture), host stays
pristine; same pattern as the LSP stack (digest-pinned containers).
## Events are V1 scope: webhook receiver now (Charles, 2026-08-28 evening)
- NOT deferred. `mopac events` (Go HTTP receiver, in ukrrs/MOPAC) ships with
the first build. The interaction layer (Redmine/Discourse/Gitea) must be
able to PUNCH the harness: when Charles replies on Discourse, updates a
Redmine ticket, or approves a Gitea PR, the harness knows and acts.
- Trigger -> action semantics (dedup + replay, at-least-once):
Redmine issue update/note on an active stack's issue -> dispatch/redirect
turn for that stack; Discourse post reply in a stack's category/topic ->
context update + response turn; Gitea PR approved/merged -> next pipeline
step (build/test/deploy per repo config).
- Signatures: Gitea HMAC-SHA256 secret header; Discourse webhook secret
header; Redmine shared-secret header (verify, reject unsigned). Normalize
to internal event record -> Redmine issue (SoR) -> conductor picks up.
- Runs containerized (dev-in-docker rule), port published on host LAN;
Cloudron apps get the webhook URL configured per-app.
## Hermes killed; OpenWebUI is the interactive front door (Charles, 2026-08-28 evening)
- Hermes was Node — removed (dashboard/gateway/searxng, 2026-08-28 ~19:10).
No Node middleware anywhere in MOPAC's path.
- OWUI (Cloudron + SSO) becomes the ONLY interactive surface: MOPAC serves
an OpenAI-compatible /v1/chat/completions endpoint (Go stdlib HTTP, in
this repo, e.g. `mopac serve`). OWUI adds it as a connection; each stack/
vertical registers as a "model". OWUI admin UI + Cloudron SSO = RBAC we
do not build. Keys minted per OWUI connection (LiteLLM vkeys pattern).
- Event path unchanged: Redmine/Discourse/Gitea webhooks -> `mopac events`
(Go T1 receiver). OWUI chat and webhooks are two doors into the same loop.
## Budget + quota reality (Charles, 2026-08-28 evening)
- Until 2026-09-22: legacy z.ai coding plan = FLAT RATE. Real budget = plan
quota, not USD. Smaller/flash models stretch quota ~3x. Routing policy:
grind/study/summarize -> flash tier (glm-5.3-flash now live on proxy,
13 models); flagship (glm-5.3/5.2) only when the work needs it. Maximize
usage of what we already pay for.
- After 2026-09-22: org-wide AI cap $200/mo across all 8 stacks:
2x $80 z.ai max plans + $40 OpenRouter. Design all budgets/vkeys to fit.
Then: cd harness && git add DESIGN.md && git commit -m "DESIGN: budget +
quota reality (flat-rate now, $200/mo after 09-22; flash-stretch routing)"
&& git push origin main
2. REDMINE SoR mirror (https://projects.knownelement.com, project MOPAC):
source ~/.creds/redmine.env in bash, pass vars to python3 via env —
NEVER exec() the env file inside python (key-leak pattern). Use urllib:
GET /issues.json?project_id=MOPAC to find issue ids by subject
(study-crush, study-maki). Set each to status_id for Done via
PUT /issues/<id>.json {"issue":{"status_id":3}} with X-Redmine-API-Key.
Add a short journal note (notes param) on each: deliverable =
harness/docs/PORTING-NOTES-<name>.md, committed+pushed.
3. WRITE two brief verification REPORTs in
/home/reachableceo/.coordinate/inbox-pmo/:
REPORT-study-crush.md + REPORT-study-maki.md — 5 lines each: deliverable
path, what it covers, PMO-verified (files exist, in git).
4. APPEND to /home/reachableceo/.coordinate/log.md (one line, format
"2026-08-28T<hh:mm>-05:00 | PMO | ..."): litellm restarted cleanly,
glm-5.3-flash live (13 models); chain halt at study-secrets = known z.ai
flake, redispatched; skeleton turn + queue running; Charles directive:
PMO = traffic cop only (write TASKs, farm to crush -p screens), PMO
context must stay tiny — no meaty work in PMO session.
@@ -0,0 +1,31 @@
# TASK: mopac events — webhook receiver (build phase 2b, V1 scope)
## Objective
Implement `mopac events` in /home/reachableceo/projects/meta/MOPAC/harness
per DESIGN.md ("Events are V1 scope" section — READ FIRST). A Go HTTP
webhook receiver that makes Redmine/Discourse/Gitea able to punch the
harness: replies, ticket updates, PR approvals -> harness knows and acts.
## Constraints
- ALL DEV IN DOCKER (hard rule): no host toolchains. Pull digest-pinned
golang:1.26(-alpine) builder; bind-mount the repo; build/vet/test inside
the container. If a Makefile/dev.sh wrapper helps, add it, but every
compile/test path routes through docker run.
- Go stdlib net/http only (no frameworks); no new deps without need.
- Receivers: /hooks/redmine, /hooks/discourse, /hooks/gitea.
- Verify secrets: Gitea HMAC-SHA256 header; Discourse secret header;
Redmine shared-secret header; reject unsigned/unverified with 401.
- Normalize each into one internal Event struct (source, kind, actor,
subject/topic/PR id, payload digest, received-at).
- Persist events (append-only JSONL + dedup by provider event id) under
a state dir from config; NEVER log secret/header values.
- Event -> action mapping per DESIGN (turn dispatch is a stub call into
the conductor interface for now; wiring comes after skeleton lands).
- Config: webhook secrets via env refs (os.environ/VAR pattern), port +
state dir in harness.toml; sensible defaults.
- Table-driven tests for signature verification + normalization + dedup.
- Run it: brief docker run smoke on a high LAN port (e.g. 4100) with a
curl-equivalent POST (python urllib — curl is banned on host) using a
test secret; show 401 without secret, 200 + JSONL record with.
- Commit incrementally to main; push. REPORT to inbox-pmo lists what
works, what's stubbed, exact container commands used.
@@ -0,0 +1,60 @@
# TASK: Docs standards pass — READMEs and docs/ across ukrrs repos
## Context
Charles decreed (2026-08-28 ~21:00): ALL project docs must be informative,
well-formatted, beautiful, and useful. Current MOPAC harness README is 16
skeleton lines with a stale Gitea path (says reachableceo/MOPAC; org moved to
ukrrs/MOPAC). This standard applies to every repo we own from now on.
## Scope (in priority order)
1. `~/projects/meta/MOPAC/harness/` — README.md full rewrite; docs/ polish.
2. `~/projects/meta/MOPAC/harness/` siblings cloned from Gitea: clone
`https://git.knownelement.com/ukrrs/mopac-keyproxy` and
`https://git.knownelement.com/ukrrs/mopac-bitwarden-go` under
`~/projects/meta/MOPAC/` and upgrade their READMEs (they are spec seeds).
3. `~/projects/siblings/KNEL-AIMiddleware` (if present) — README audit:
fix only what is wrong/stale; do not rewrite working docs wholesale.
## The doc standard (apply to every README; checklist)
- **Front matter**: H1 title, one-paragraph purpose (what/why/who-for, no
throat-clearing), then a `Status:` line — date + one-word state
(e.g. `Status: 2026-08-28 — skeleton, MVP demo path live`).
- **Quickstart**: copy-paste commands that actually work, each block followed
by a short expected-output snippet. Verify every command by RUNNING it
(e.g. `./bin/harness once --dry-run --demo`, `./bin/harness help`).
Never document an unverified command.
- **Architecture**: a mermaid flowchart of the conductor loop
(intake -> routing -> bounded turn -> REPORT) plus one paragraph of prose.
- **Reference tables** (not prose walls):
- CLI: subcommands, flags, exit codes (0/1/2/4) — from actual help output.
- Config: every `harness.toml` section with one-line meaning (from
harness.toml.example + internal/config).
- Model routing: the `[models]` tier map as a table (class -> model).
- **Status table**: Works / Stubbed / Next — sourced from REPORT.md, kept
honest.
- **Formatting**: fenced code with language tags; headings never skip levels;
consistent terminology (harness, conductor, turn, REPORT capitalized as
shown); no dead links; no TODO markers without an owner; correct repo
URLs (git.knownelement.com/ukrrs/...).
- For keyproxy/bitwarden-go seeds: purpose, scope, non-goals (no admin UI,
memory-only), license (AGPLv3), status line, and what exists today vs
planned — the spec READMEs already carry the design; make them navigable.
## docs/ polish (MOPAC only)
- Each PORTING-NOTES-*.md gets the Status front-matter line.
- DESIGN.md gets a short table of contents after its intro (section list,
anchor links). Do NOT rewrite or reorder existing sections.
## Constraints
- DOCS ONLY. No source code changes. No new sections in DESIGN.md.
- This turn runs AFTER pmo-ops and events-receiver complete (dispatch is
gated); the repo working tree will be clean and pushed when you start.
- Verify-then-write: read the code/help/config you are documenting.
- Commit per repo with clear messages; push to ukrrs remotes.
## Deliverables
- Commits + pushes: MOPAC (README + docs/), mopac-keyproxy, mopac-bitwarden-go,
(KNEL-AIMiddleware if touched).
- `REPORT-20260828-docs-standards.md` in `~/.coordinate/inbox-pmo/`: per-repo
summary of changes, the checklist items applied, any doc rot found that you
could NOT fix in this pass (list for follow-up).
+49
View File
@@ -0,0 +1,49 @@
# TASK: keyproxy v0 — file backend (mpk_ placeholder interface, live)
## Context
Charles (2026-08-28 ~21:30): the credential gateway is key infrastructure.
This account has NO Bitwarden access, so v0 runs on a FILE backend over
~/.creds-style env files. The `mpk_...` placeholder interface ships NOW;
Bitwarden REST + Vault KV v2/AppRole backends swap in later behind the same
interface (phase 3, already specced in the repo README — read it first).
Consumers only ever see `mpk-<name>` refs; real material crosses the wire
at resolve time, memory-only, never persisted, never logged.
## Scope
1. Clone `https://git.knownelement.com/ukrrs/mopac-keyproxy` to
`~/projects/meta/MOPAC/keyproxy` (spec README + LICENSE already there).
2. Implement v0 in Go (stdlib only):
- `keyproxy serve` — localhost HTTP (default :8082, configurable):
`POST /v1/resolve` body `{"ref":"mpk-redmine"}``{"value":"..."}`
(mask value in any log line as `mpk-redmine=***`).
- Backends behind one interface:
- `file:` — env-file format (KEY=VALUE lines, 0600) at a configured
path; ref maps to file+key. Sources tonight: `~/.creds/redmine.env`,
`/home/_crossfeed/tooling/credentials/litellm-secrets.env`,
`~/.coordinate/secrets/mopac-harness-vkey.env`.
- `env:` — process env indirection.
- `bitwarden:` / `vault:` — stubs returning explicit "not implemented
in v0" errors (interface + tests only, so phase 3 is a drop-in).
- Auth: bearer token for /v1/resolve, bootstrapped from the file backend
itself (a `mpk-keyproxy-self` entry), 0600 file, never logged.
- Config: `keyproxy.toml` — port + ref map (ref → backend/source/key).
`keyproxy.toml.example` tracked; real config gitignored.
3. Rules (non-negotiable, from DESIGN.md keyproxy section):
- No admin UI. No persistence. No key material in logs, errors, or
crash paths (assert redaction in tests).
- Never exec() env files; parse KEY=VALUE in Go.
- Resolution failures name the ref and backend, never the value.
## Build discipline
- ALL DEV IN DOCKER: digest-pinned golang:1.26 builder container, repo
bind-mounted; Makefile targets (build/vet/test) route through docker run.
No host Go toolchain use.
## Deliverables
- Commits pushed to ukrrs/mopac-keyproxy main.
- Docs per standard (Charles's decree): README quickstart with verified
copy-paste curl-equivalent (python3 urllib) resolve example, config
reference table, status table.
- Table-driven tests incl. redaction + fake-HTTP end-to-end.
- `REPORT-20260828-2130-keyproxy-v0.md` in `~/.coordinate/inbox-pmo/`:
what runs, how to start it, the ref map seeded, stub status.
@@ -0,0 +1,54 @@
# TASK: MOPAC self-host core — `harness loop` replaces the bash stack
## Context
Charles (2026-08-28 ~21:30): "get to be self hosting asap — smallest core
that can bootstrap itself (redmine/gitea interaction fine, discourse later)"
and "moving away from the brittle _crossfeed setup and doorbell PMO<->work
channel". The MVP bar is already met (`harness once --demo` live, REPORT in
reports/). This turn makes the harness drive ITSELF: Redmine is the SoR,
the conductor is the worker, no bash middle layer.
## What dies (list explicitly in REPORT, do not delete yet)
- `/home/_crossfeed/tooling/agent-stack/semaphore.sh` + slot-file cap
(replaced by in-process concurrency limit)
- `~/.coordinate/scripts/queue-next.sh`, `queue-after.sh`, `pmo-heartbeat.sh`
(replaced by the loop + its own wake discipline)
- dispatch-turn.sh doorbell `screen -S reachableceo-PMO -X stuff` — note:
this doorbell is ALREADY dead (screen is named RCEO-PMO, message silently
dropped). Mention in REPORT; deletion happens after Charles signs off.
## Scope — smallest self-bootstrapping core
1. `harness loop` subcommand (Go, stdlib only, chainable with `once`):
- Poll Redmine intake (existing `/issues.json` query) on interval
(config, default 120s). `--once` flag = single scan (cron-able).
- For each new/updated task issue not yet processed: run the bounded
turn (existing conductor), then write REPORT back (existing Redmine
note writeback — currently stubbed; implement the note POST) and
update issue status per config map (e.g. In Progress -> Done).
- State: append-only JSONL (one event per dispatch/report), dedup by
issue id + updated_on. No _crossfeed paths, no slot files.
- v0 = SEQUENTIAL turns (one at a time). Concurrency knob can come later.
- Gitea: optional REPORT commit step behind config flag (off by default).
Discourse: out of scope.
2. Credentials: harness.toml `key_ref` gains `mpk:` scheme → POST to
keyproxy `/v1/resolve` (see ukrrs/mopac-keyproxy, built tonight).
`env:` / `file:` refs keep working as fallback, so the loop runs with
or without keyproxy up. Do not hardcode hosts — all in harness.toml.
3. `harness loop` supervises ITS OWN turns; PMO (me) verifies REPORTs and
archives. The loop logs one line per action to stdout + JSONL.
## Build discipline
- ALL DEV IN DOCKER: digest-pinned golang:1.26 builder, bind-mounted repo,
Makefile build/vet/test through docker run. No host Go toolchain.
- Docs standard applies (README status table + config reference update).
- Tests: loop end-to-end against a fake Redmine HTTP server (pattern
exists in internal/loop tests) — dispatch, dedup, writeback, status map.
## Deliverables
- Commits + push to ukrrs/MOPAC main.
- `REPORT-20260828-2131-selfhost-core.md` in `~/.coordinate/inbox-pmo/`:
what runs, exact command to start the loop, what it replaces (the die
list), how to roll back (just stop the loop, bash stack still present).
## Added by PMO (21:30, post docs-turn rot finding)
- Rename Go module path `reachableceo/...` -> `ukrrs.com/mopac/...` or similar (go.mod module line + all internal imports). Mechanical, verify with build+vet+test in the Docker builder. Cited as doc rot in docs-standards REPORT; folding here since it is a source change.
@@ -0,0 +1,31 @@
# TASK: keyproxy-v0 finish — commit, push, report (turn was self-killed)
## Context
The keyproxy-v0 turn completed the implementation but self-terminated
(SIGTERM via its own broad pkill while restarting the smoke server) right
before committing. Everything sits UNCOMMITTED in the working tree of
`~/projects/meta/MOPAC/keyproxy` (README.md modified; cmd/ internal/
Makefile dev.sh go.mod keyproxy.toml.example .gitignore untracked).
Its log (per PMO read): tests were green, docker smoke 10/10 PASS,
live 401/200/400 verified, README quickstart verified verbatim.
## Steps
1. Read the working tree (code + README + example config). Confirm it
matches the v0 TASK spec (file/env backends, bitwarden/vault stubs,
auth, redaction, no persistence, no admin UI).
2. Re-verify: `make test` (or equivalent) inside the digest-pinned
golang:1.26 Docker builder — ALL DEV IN DOCKER, no host toolchain.
Fix anything that fails; keep changes minimal.
3. Commit in logical chunks (backend interface + parsers, config, server,
cmd/CLI, build tooling + docs) with clear messages.
4. Push to `origin main` (ukrrs/mopac-keyproxy; default branch is main —
clone is already on it).
5. NEVER use a broad pkill. If you must stop a server process, kill the
exact PID you spawned (save `$!`), never pattern-match.
6. Seed nothing else; real config/keyproxy.toml stays gitignored.
## Deliverable
`REPORT-20260828-2130-keyproxy-v0.md` in `~/.coordinate/inbox-pmo/`:
what shipped (endpoints, backends, ref map), test results, the commit
hashes, how to start it (README quickstart already written — just cite),
and anything left for phase 3 (bitwarden/vault stubs status).
@@ -0,0 +1,49 @@
# TASK: mopac-bitwarden-go v0 — Secrets Manager REST client (fake-server tested)
## Context
keyproxy v0 is LIVE (ukrrs/mopac-keyproxy: file/env backends, `bitwarden:`
backend is a 501 stub awaiting this library). Charles: the credential
gateway is key infrastructure; Bitwarden backend is phase 3. This account
has NO Bitwarden access today — so v0 is built and tested entirely against
a FAKE Bitwarden Secrets Manager REST server in tests; live creds attach
later with zero code change.
## Inputs to read first
- `~/projects/meta/MOPAC/harness/docs/PORTING-NOTES-secrets.md` — design
sketch + 7 open questions; answer what code can answer, list the rest.
- The spec README already seeded in the repo.
- Public Bitwarden Secrets Manager API docs (machine accounts: client_id +
client_secret -> access token via /identity/connect/token,
client_credentials grant; then /api/accounts/{id}/secrets etc.).
Plain REST only — the official SDK is source-available and
AGPL-incompatible (DESIGN.md), do NOT import it.
## Scope
1. Clone `https://git.knownelement.com/ukrrs/mopac-bitwarden-go` to
`~/projects/meta/MOPAC/bitwarden-go` (default branch main).
2. Go package + thin CLI (`bitwarden-go`):
- `login` — exchange machine credentials (from env refs or 0600 file,
NEVER flags/args) for an access token; memory-only, refresh before
expiry; never logged.
- `projects` / `secrets list` / `get <key>` — read paths keyproxy needs.
- Library surface: `Authenticate(ctx, creds) (Token, error)`,
`GetSecret(ctx, token, key) (string, error)` — keyproxy's `bitwarden:`
backend will call exactly this.
3. Fake server in tests: table-driven, covers auth failure, expiry,
missing secret, malformed payloads, redaction assertions (token/secret
values never in logs or errors).
4. Config via env-file (0600) — same KEY=VALUE discipline as keyproxy.
No admin UI, no persistence of tokens to disk. EVER.
5. Docs per the standard: README (purpose, status line, verified
quickstart against the fake server, config table).
## Build discipline
- ALL DEV IN DOCKER: digest-pinned golang:1.26 builder, repo bind-mounted;
Makefile/dev.sh pattern copied from ukrrs/mopac-keyproxy.
- NEVER broad pkill — kill exact PIDs you spawned.
- Commits in logical chunks, push to origin main.
## Deliverable
`REPORT-20260828-2330-bitwarden-go.md` in `~/.coordinate/inbox-pmo/`:
library surface, CLI usage, fake-server test results, the open questions
still needing Charles, and the exact keyproxy integration point.
+44
View File
@@ -0,0 +1,44 @@
# TASK: `harness serve` — OpenAI-compatible front door (OWUI is the client)
## Context
DESIGN.md "OWUI front door" section (hermes killed): OpenWebUI = interactive
front door with Cloudron SSO + admin RBAC we don't build. MOPAC serves
OpenAI-compatible `/v1/chat/completions`; each stack/vertical registers as
a "model" in OWUI. OWUI chat and webhooks (`harness events`, already live)
are two doors into the same conductor loop.
## Scope
1. `harness serve` subcommand (Go stdlib net/http, same style as events):
- `POST /v1/chat/completions` — OpenAI request/response shapes
(model, messages, temperature, max_tokens; reply = single assistant
message + usage). NON-streaming v0; return an explicit error if
`stream:true` is requested (OWUI tolerates non-streaming providers).
- `GET /v1/models` — lists servable "models" from config (the class ->
tier map is the catalog: each class is exposed as model
`mopac-<class>`).
- Bearer auth (vkey), constant-time; unauthenticated = 401 generic.
- v0 STATELESS: OWUI sends full conversation history each call — run
one bounded conductor turn over the assembled history, return the
final text. No session storage.
- Model routing: request.model (e.g. `mopac-primary`) maps through the
existing `[models]` tier map; unknown model = 400 naming valid ones.
2. Config: `[serve]` section (port, default 8090; `enabled_models`
optional subset). Coexists with `harness events` on its own port.
3. Reuse the conductor turn machinery `once` uses — no copy-paste; if the
turn package needs a small refactor to accept history, do it cleanly.
4. Tool use inside serve turns: v0 = tools OFF (pure chat path), noted in
README as Next.
## Build discipline
- ALL DEV IN DOCKER (digest-pinned builder, dev.sh gets a `serve` runner).
- Tests: fake OpenAI client against the real server — auth, model map,
history assembly, usage accounting, unknown-model 400.
- Docs standard: README section + config table rows + status table update.
- NEVER broad pkill; kill exact PIDs.
- Commits in logical chunks, push to origin main.
## Deliverable
`REPORT-20260829-0000-serve.md` in `~/.coordinate/inbox-pmo/`: endpoints,
the model catalog as configured, test results, exact OWUI connection
settings (base URL + vkey) for Charles to plug in, and what's Next
(streaming, tools).