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).
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
# TASK: Quota monitoring + back-pressure + usage mgmt + resource gate (Redmine 490+491)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Charles's spec of record: `docs/SPEC-20260829-charles-brief.md` (READ IT
|
||||||
|
FIRST, esp. "It's a marathon" + Roadmap 1). Last night the z.ai quota wall
|
||||||
|
killed two dispatched turns at 19:00 — this system prevents that class of
|
||||||
|
failure and maximizes the flat-rate plan 24x7 (legacy plan ends 9/22).
|
||||||
|
Redmine tickets: 490 (quota) + 491 (resource monitor).
|
||||||
|
|
||||||
|
## Scope — `internal/quota` + `harness quota` + loop integration
|
||||||
|
1. **z.ai usage polling**: research the z.ai coding-plan usage/limits
|
||||||
|
endpoint (it exists per Charles; find the API surface from public docs
|
||||||
|
+ the z.ai provider behavior we already see). Poll on interval; parse
|
||||||
|
buckets/limits/reset times; expose `QuotaSnapshot` (struct: per-bucket
|
||||||
|
used/limit/window-reset).
|
||||||
|
- Auth via key_ref (never logged, same discipline as keyproxy).
|
||||||
|
- If the endpoint proves unreachable/undocumented: build against a
|
||||||
|
documented interface + fake server, flag LIVE VERIFICATION as open.
|
||||||
|
2. **Central state decision** (Charles leaves it to us): Redis container
|
||||||
|
vs LiteLLM-native (postgres spend tracking already live at
|
||||||
|
192.168.3.78:4001). Decide with rationale in the REPORT; implement the
|
||||||
|
chosen one. Requirements: multiple harness instances (9 accounts, 2
|
||||||
|
hosts) share quota state; redis (if chosen) runs as a docker container
|
||||||
|
on this host, config in harness.toml, no host packages.
|
||||||
|
3. **Back-pressure in the loop** (internal/loop): before dispatching a
|
||||||
|
turn, consult quota snapshot + schedule:
|
||||||
|
- Peak window (config, default 0100-0500 CST — VERIFY empirically if
|
||||||
|
possible; Charles sleeps 2300-0500): restrict to flash-tier classes;
|
||||||
|
defer heavy classes with a logged reason; prefer LLM-lite work.
|
||||||
|
- Quota nearly exhausted: defer + surface status; never hard-fail the
|
||||||
|
loop.
|
||||||
|
- Schedule + thresholds all in `[quota]` harness.toml section (TZ-aware,
|
||||||
|
CST default).
|
||||||
|
4. **Resource gate (ticket 491)**: read-only system monitor (load avg,
|
||||||
|
mem available, disk free, IO delay from /proc + /sys) with
|
||||||
|
configurable busy thresholds; loop defers dispatch when busy. cgroup
|
||||||
|
enforcement is deploy-time — document the runbook section only.
|
||||||
|
5. **Usage accounting**: per vertical/class token+model accounting into
|
||||||
|
the JSONL state (extend loop state) — feeds the eventual
|
||||||
|
per-instance Discourse usage reports.
|
||||||
|
6. TDD red/green: quota parser, schedule logic (peak/offpeak across TZ
|
||||||
|
edges), back-pressure decisions, resource thresholds — table-driven +
|
||||||
|
fake clock. Docker dev discipline. Docs standard (README + config
|
||||||
|
table). Push to origin main.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
`REPORT-20260829-0500-quota.md` in `~/.coordinate/inbox-pmo/`: the Redis
|
||||||
|
vs LiteLLM decision + rationale, endpoint findings, config surface, test
|
||||||
|
results, and how the loop now behaves at quota exhaustion (the 19:00-wall
|
||||||
|
scenario replayed as a test).
|
||||||
|
|
||||||
|
## ADDENDUM (redispatch, ~05:10 CST)
|
||||||
|
- Previous attempt died on a sourcegraph.com timeout — do NOT use sourcegraph; research the z.ai usage endpoint via direct fetch of z.ai docs / the API itself.
|
||||||
|
- BE QUOTA-LEAN (weekly bucket at 93%): minimal exploration, write code in large correct chunks, one test pass, no gold-plating, finish and report.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# TASK: Smart dispatcher — phase-aware model selection + queue selection (Redmine 492+493)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Spec of record: docs/SPEC-20260829-charles-brief.md (Roadmap 2+3). The
|
||||||
|
quota/back-pressure turn (internal/quota) lands in the repo BEFORE this
|
||||||
|
turn starts — build on it, do not re-implement.
|
||||||
|
|
||||||
|
## Part A — Phase-aware model selection (ticket 492)
|
||||||
|
- Routing v1: work phases get different tiers. Convention: a task's phase
|
||||||
|
is derived v1 from explicit fields first, fallbacks second:
|
||||||
|
1. Redmine custom field (configurable field name, e.g. "phase",
|
||||||
|
values: plan/implement/review/grind)
|
||||||
|
2. harness.toml `[models.phases]` map: phase -> class (plan=primary,
|
||||||
|
implement=code, review=review, grind=flash-class default)
|
||||||
|
3. fallback: existing class map (unchanged behavior).
|
||||||
|
- The conductor exposes which phase it ran in the REPORT header (model +
|
||||||
|
phase + class) — telemetry for the eventual auto-classifier (v2, NOT
|
||||||
|
this turn).
|
||||||
|
- Unknown phase value = config error naming valid phases.
|
||||||
|
|
||||||
|
## Part B — Queue selection (ticket 493)
|
||||||
|
- Loop intake query becomes selection-aware:
|
||||||
|
- Redmine sort: priority desc, due_date asc (config keys in
|
||||||
|
`[redmine.selection]`); respect relations (issue blocked while
|
||||||
|
relates/blocks parent open — use /relations or issue relations field;
|
||||||
|
skip blocked, log reason).
|
||||||
|
- Quota gate: consult internal/quota snapshot (from the prior turn) —
|
||||||
|
heavy classes deferred when quota-low or peak-window per its config.
|
||||||
|
- Resource gate: same pattern for the busy-system gate.
|
||||||
|
- Pick order = sort order; one turn at a time (v0 concurrency), scan
|
||||||
|
continues after each.
|
||||||
|
- Selection decisions land in the loop JSONL (task id, chosen/deferred +
|
||||||
|
reason) — auditable.
|
||||||
|
|
||||||
|
## Discipline
|
||||||
|
TDD red/green (fake Redmine server pattern exists; add phase-routing and
|
||||||
|
blocked-relation cases), ALL DEV IN DOCKER, docs standard (README config
|
||||||
|
tables updated), push to origin main, NEVER broad pkill.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
`REPORT-20260829-0700-dispatcher.md` in `~/.coordinate/inbox-pmo/`:
|
||||||
|
phase map surface, selection rules + defer reasons, test results, how a
|
||||||
|
priority-5 due-today ticket beats a priority-3 no-due one (test cited).
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# TASK: Multi-account deploy — packaging + runbook, 9 accounts / 2 hosts (Redmine 494)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Spec: docs/SPEC-20260829-charles-brief.md (account list inside). The PMO
|
||||||
|
runtime CANNOT ssh or sudo — this turn produces everything so that
|
||||||
|
deployment is a ~10-minute Charles window executing one runbook.
|
||||||
|
|
||||||
|
## Accounts
|
||||||
|
- ultix-streaming: reachableceo, TSGBOD, TSGCOO, TSGCTO, TSGCCO
|
||||||
|
- ultix-offstage (ssh reachableceo-offstage, passwordless sudo there):
|
||||||
|
reachableceo-offstage, COSRCEO-Personal, COSRCEO-Biz, COSWFO
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
1. `deploy/` directory in the harness repo:
|
||||||
|
- `deploy/accounts.tsv` — account, host, vertical, redmine project
|
||||||
|
scope, port assignments (events/serve per account: derive a scheme,
|
||||||
|
e.g. base port 4100+ / 8090+ offset by account index).
|
||||||
|
- `deploy/install-account.sh <account>` — idempotent, runs AS the
|
||||||
|
target user (no root needed if binary path is ~/.local/bin): creates
|
||||||
|
~/.mopac/{bin,state,reports}, installs static binary (built in the
|
||||||
|
Docker builder for linux/amd64), writes harness.toml from a template
|
||||||
|
(per-account substitutions), refuses to overwrite existing
|
||||||
|
harness.toml or secrets.
|
||||||
|
- `deploy/runbook.md` — the exact Charles sequence: build once
|
||||||
|
(docker), copy/ssh loop per host+account, run install script, start
|
||||||
|
command per account (nohup or cron @reboot line — NO systemd claims
|
||||||
|
without root; document both options), verify (healthz curl-equivalent
|
||||||
|
+ first `loop --once` dry output), rollback (stop + rm ~/.mopac/bin).
|
||||||
|
- Per-account harness.toml template with vertical/redmine-scope/ports
|
||||||
|
substituted; secrets stay env refs (0600 env files per account,
|
||||||
|
bootstrap instructions).
|
||||||
|
2. Static binary build: Makefile target `release` (docker builder,
|
||||||
|
GOOS=linux GOARCH=amd64, CGO=0) producing `bin/harness-linux-amd64`.
|
||||||
|
3. Concurrency guard: distinct state dirs + ports per account so multiple
|
||||||
|
daemons on one host never collide; document the account-port table.
|
||||||
|
4. Tests where feasible (template substitution, port derivation,
|
||||||
|
idempotence of install script in a fake HOME). Docs standard.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
Commits pushed to ukrrs/MOPAC main (deploy/ + Makefile release target +
|
||||||
|
README deploy section). `REPORT-20260829-0900-deploy.md` in
|
||||||
|
`~/.coordinate/inbox-pmo/`: what Charles executes, per-host time estimate,
|
||||||
|
verification steps, rollback.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# TASK: COS morning briefing — discourse client + `harness brief` (Redmine 495)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Spec: docs/SPEC-20260829-charles-brief.md. Target: first live 3-COS
|
||||||
|
briefing 2026-09-01 06:30 CST (beta 8/31 0600). Briefing = daily digest
|
||||||
|
via Discourse (fallback Redmine note) assembled from: Redmine activity
|
||||||
|
(scope), Gitea PR inbox, quota usage. Exact format TBD by Charles — ship
|
||||||
|
a clean v0 format he can react to.
|
||||||
|
|
||||||
|
## Part A — mopac-discourse-go client (new repo)
|
||||||
|
1. Create repo via Gitea API if absent (PMO pattern: tea token from
|
||||||
|
~/.config/tea/config.yml, POST /api/v1/orgs/ukrrs/repos), clone to
|
||||||
|
~/projects/meta/MOPAC/discourse-go. AGPLv3 LICENSE.
|
||||||
|
2. Stdlib-only Go client: categories (list/create), topics (create/list),
|
||||||
|
posts (create/update), raw JSON passthrough for anything else. API key
|
||||||
|
via key_ref/env (never logged). Fake-server tests. Docs standard.
|
||||||
|
(Current Discourse key 403s on category creation — client supports it,
|
||||||
|
live verification may wait for the admin-scoped key; note in REPORT.)
|
||||||
|
|
||||||
|
## Part B — `harness brief` subcommand
|
||||||
|
1. Digest window: since last briefing marker (state/brief/brief.jsonl).
|
||||||
|
2. Sources (read-only): Redmine issues in scope created/updated/closed in
|
||||||
|
window (reuse intake client); Gitea open PRs across ukrrs repos (API);
|
||||||
|
quota snapshot summary (internal/quota if landed).
|
||||||
|
3. Render: clean markdown — per-vertical sections: shipped (closed),
|
||||||
|
moved (updated w/ status), incoming (new), PRs awaiting review, quota
|
||||||
|
line. Beautiful tables (docs standard).
|
||||||
|
4. Delivery v0: post to Discourse category (configurable id/slug); if
|
||||||
|
Discourse fails (403 etc.), fallback = journal note on a configured
|
||||||
|
Redmine "briefing" issue — NEVER silently drop; log delivery path.
|
||||||
|
5. Scheduling v0: `harness brief` is one-shot (cron/loop calls it at
|
||||||
|
0630); config `[brief]` section (category, redmine fallback issue,
|
||||||
|
window tz CST). Idempotent: one briefing per window (marker).
|
||||||
|
6. TDD (fake servers for both), docker dev, docs, push.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
`REPORT-20260829-1100-briefing.md` in `~/.coordinate/inbox-pmo/`: client
|
||||||
|
surface, the v0 briefing format (render a sample with fake data in the
|
||||||
|
REPORT), delivery verification status, what Charles should tweak.
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
# TASK: mopac-pdf v0 — beautiful PDF generation pipeline (Redmine 499)
|
||||||
|
|
||||||
|
## Context
|
||||||
|
Spec docs/SPEC-20260829-charles-brief.md "Workflows": beautifully
|
||||||
|
formatted PDFs of budgets, travel itineraries, project plans, proposals,
|
||||||
|
consulting reports, multi-year financial plans, business plans,
|
||||||
|
engineering docs — with charts/diagrams. This feeds the COS briefing
|
||||||
|
world (reports) and TSYS client deliverables.
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- OUR code: Go only (no python/node/rust — prebuilt engine BINARIES in
|
||||||
|
digest-pinned docker images are tooling, fine).
|
||||||
|
- ALL DEV IN DOCKER. AGPLv3. Docs standard.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
1. New repo `ukrrs/mopac-pdf` (create via Gitea API if absent — PMO
|
||||||
|
pattern, tea token; clone to ~/projects/meta/MOPAC/pdf). LICENSE
|
||||||
|
AGPLv3 first commit.
|
||||||
|
2. Engine decision (document rationale in REPORT): typst (docker image,
|
||||||
|
typst compile) vs pandoc+latex — evaluate compile speed, chart/table
|
||||||
|
quality, template ergonomics. Typst expected to win on modern output.
|
||||||
|
3. Go package + CLI `mopac-pdf`:
|
||||||
|
- Input: markdown (the composable currency of this whole stack —
|
||||||
|
Redmine notes, Discourse posts, briefing output) + front-matter
|
||||||
|
(title, subtitle, date, classification footer, template name).
|
||||||
|
- Templates dir: 2 shipped v0 — "report" (clean business report:
|
||||||
|
title page, TOC, headers/footers, page numbers) and "brief"
|
||||||
|
(1-3 page exec summary, dense tables).
|
||||||
|
- Charts: pure-Go rendering option (go-chart or similar permissive
|
||||||
|
lib) embedded as images from fenced code blocks with data blocks;
|
||||||
|
keep v0 minimal — one chart type proven end-to-end.
|
||||||
|
- Output: PDF bytes to stdout or -o file; exit codes like harness
|
||||||
|
(0/1/2 usage/engine).
|
||||||
|
4. Docker runner: `mopac-pdf` shells the engine container (digest-
|
||||||
|
pinned); host stays toolchain-free. Makefile/dev.sh per family
|
||||||
|
pattern.
|
||||||
|
5. Tests: template rendering golden tests (fixture .md -> PDF, assert
|
||||||
|
non-empty + page count via a tiny Go PDF header parse or engine
|
||||||
|
stdout), CLI flags, front-matter parsing. NEVER broad pkill.
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
Commits pushed to ukrrs/mopac-pdf main.
|
||||||
|
`REPORT-20260829-1300-pdf.md` in `~/.coordinate/inbox-pmo/`: engine
|
||||||
|
decision + rationale, sample outputs listed (paths), how the briefing
|
||||||
|
pipeline will call it, what's Next (chart types, more templates).
|
||||||
|
|
||||||
|
> CLARIFIED (Charles): supply chain MAY include prebuilt Rust tools (typst
|
||||||
|
> etc.) in digest-pinned images. OUR code: Go/PHP/C/Java only.
|
||||||
Reference in New Issue
Block a user