ops(archive): TASK prompts and REPORT verifications, studies through V1
This commit is contained in:
@@ -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.
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Reference in New Issue
Block a user