ops(archive): quota, deploy, pdf TASK/REPORT pairs verified+archived
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# REPORT — Quota monitoring + back-pressure + usage mgmt + resource gate (Redmine 490+491)
|
||||
|
||||
- **When**: 2026-08-29 ~05:00-05:50 CST
|
||||
- **Repo**: `projects/meta/MOPAC/harness` @ `fc518c4` (pushed to origin main)
|
||||
- **Spec**: `docs/SPEC-20260829-charles-brief.md` — "It's a marathon" + Roadmap 1
|
||||
- **Gate**: `./dev.sh check` (build + vet + test) clean, all packages `ok`
|
||||
(config, quota, loop, serve, events, intake, llm, models, tools, writeback)
|
||||
|
||||
## 1. Decision: Redis container (not LiteLLM-native)
|
||||
|
||||
**Chosen: one Redis container, spoken to by a stdlib RESP2 mini-client.**
|
||||
|
||||
Rationale:
|
||||
|
||||
1. **LiteLLM tracks dollars, z.ai meters credits.** The thing we must
|
||||
share is the coding-plan 5-hour + weekly CREDIT buckets with peak
|
||||
multipliers (input x6.9 + cached x1.7 + output x24 per 10k tokens;
|
||||
flash 2.3/0.56/8; off-peak 50% off). LiteLLM's budget system is
|
||||
per-virtual-key $ spend against budgets it enforces itself — a lossy
|
||||
double-accounting that breaks on every plan change and cannot express
|
||||
"two harness accounts share one z.ai key".
|
||||
2. **We own the write path anyway.** With no z.ai usage endpoint (see
|
||||
§2), consumption is OUR estimate; a store only we write to is the
|
||||
natural fit. Doing it "in LiteLLM" would mean SQL against another
|
||||
project's schema — an upgrade hazard, not an integration.
|
||||
3. **9 instances, 2 hosts, one account each**: TTL'd rolling-window
|
||||
counters (`INCRBYFLOAT` + `EXPIRE`), snapshot publish/read, atomic
|
||||
cross-instance — this is exactly Redis. Postgres via LiteLLM gives
|
||||
us none of those primitives for free.
|
||||
4. **Charles's constraints honored**: docker container only
|
||||
(`redis:7-alpine`, runbook in README; bind to 192.168.3.78:6390),
|
||||
config in `harness.toml` (`redis_url`), zero host packages, zero Go
|
||||
dependencies (the repo remains stdlib-only; the RESP2 client is
|
||||
~150 lines and fake-server tested).
|
||||
5. **Fail-soft**: redis down = this instance's local estimate, loop
|
||||
keeps running. LiteLLM-native would couple the loop's health to the
|
||||
proxy's DB.
|
||||
|
||||
Keys: `mopac:quota:<account>:{snapshot, est:5h:<window>, est:weekly:<week>}`.
|
||||
LiteLLM $-spend budget keys remain a separate, still-open item (README
|
||||
status table says so honestly).
|
||||
|
||||
## 2. z.ai usage endpoint findings (LIVE VERIFICATION: OPEN)
|
||||
|
||||
Probed 2026-08-29 ~05:05 with the real plan key (from the litellm
|
||||
container env; never logged, never written to disk):
|
||||
|
||||
- `GET /api/coding/paas/v4/{usage,limits,info,quota,credit/usage,...}`
|
||||
-> **404** (JSON 404 from the gateway, so the route does not exist)
|
||||
- `/api/coding/paas/v4` (the live chat endpoint LiteLLM uses) is real
|
||||
and serving — the 404s are route-level, not auth or reachability.
|
||||
- `/api/coding/{user/info,usage}` -> 200 with `{"code":500,"msg":"404
|
||||
NOT_FOUND"}` — gateway routes, inner services absent.
|
||||
- Docs sweep (`docs.z.ai` llms.txt + devpack pages): the plan DOCS the
|
||||
buckets in detail (5h + weekly credits per tier, multipliers,
|
||||
off-peak discount, reset rules) and points humans at the billing web
|
||||
page for consumption — **no public usage REST route is documented.**
|
||||
|
||||
Per the task's fallback: the parser targets the documented bucket model
|
||||
(strict decode; unknown shapes error so an HTML error page can never
|
||||
read as "quota fine"; alias-tolerant field names), is table-driven
|
||||
tested against a fake server (bearer header asserted, key-leak
|
||||
asserted), and `usage_url` in `[quota]` flips it on the day z.ai ships
|
||||
one. Until then the gate runs on locally estimated consumption from the
|
||||
documented credit formula — which is exactly the number the loop
|
||||
controls anyway.
|
||||
|
||||
## 3. Peak window — Charles's guess verified
|
||||
|
||||
z.ai docs: **peak = Mon-Fri 14:00-18:00 Singapore (UTC+8), off-peak
|
||||
50% off.** 14:00 SGT = 01:00 CST (winter, UTC-6) / 00:00 CDT (summer,
|
||||
UTC-5). So "0100 to 0500 CST" is correct for winter and one hour early
|
||||
during US DST — config carries the window + `timezone` + `peak_weekdays_only`
|
||||
(defaults `01:00`-`05:00` America/Chicago, weekdays only), and the
|
||||
report notes the March/November drift. Schedule logic is table-tested
|
||||
across the edges: window boundaries, weekend exclusion, overnight-wrap
|
||||
windows (weekday check on the window's start day), and zone-vs-instant
|
||||
semantics.
|
||||
|
||||
## 4. Config surface (see `harness.toml.example` + README table)
|
||||
|
||||
- `[quota]`: `enabled`, `account`, `plan_5h_credits` (28000),
|
||||
`plan_weekly_credits` (140000), `usage_url`+`key_ref`+`poll_interval_secs`,
|
||||
`defer_at_pct` (85) / `block_at_pct` (95), `peak_start`/`peak_end`/
|
||||
`timezone`/`peak_weekdays_only`, `peak_classes` (study, read),
|
||||
`redis_url`.
|
||||
- `[resources]`: `enabled`, `max_load_avg` (6), `min_mem_available_mb`
|
||||
(2048), `min_disk_free_mb` (5120), `max_io_delay_pct` (90), test seams
|
||||
`proc_root`/`sys_root`.
|
||||
- Both **off by default** (existing configs unchanged — test-asserted).
|
||||
- CLI: `harness quota status | probe | gate`.
|
||||
|
||||
## 5. How the loop behaves at quota exhaustion (the 19:00 wall, replayed)
|
||||
|
||||
`TestLoopQuotaWallDefersAndRecovers` (fake Redmine + fake LLM + fake
|
||||
usage endpoint at 97% weekly):
|
||||
|
||||
1. Scan sees the issue -> gate decides DEFER -> **zero LLM calls**, one
|
||||
stdout line + one `"type":"defer"` JSONL event with the reason
|
||||
("quota: weekly bucket at 97% (>= block 95%): all classes deferred
|
||||
until reset").
|
||||
2. The task is **not consumed** (no dispatch marker, no note) — defer is
|
||||
a throttle, so quota recovery needs no human touch.
|
||||
3. Endpoint flips healthy -> next scan dispatches normally, notes the
|
||||
REPORT, status transitions. The 19:00-class failure no longer
|
||||
exists: pre-wall, the loop runs LLM-lite classes only past
|
||||
`defer_at`; at `block_at` everything pauses, surfaced, until reset.
|
||||
|
||||
Decision order: resources -> block wall -> peak-window class
|
||||
restriction -> soft-quota heavy deferral. Never a hard fail; unknown
|
||||
quota state is permissive.
|
||||
|
||||
## 6. Usage accounting (feeds the Discourse reports)
|
||||
|
||||
Every dispatched turn appends `class`, `prompt/completion/total_tokens`,
|
||||
and estimated `credits` to its `report` event in `loop.jsonl`;
|
||||
`loopState` rebuilds per-class totals from the JSONL at startup, and
|
||||
`harness quota status` renders the table (turns/tokens/credits per
|
||||
class + TOTAL). Also verified live in-container: real `/proc` reads
|
||||
(load 13.43, PSI io 0.3%, disk 44.9GB) and correct TZ evaluation.
|
||||
|
||||
## 7. Test results (TDD, table-driven, fake clock/servers)
|
||||
|
||||
- `internal/quota`: parser (canonical + aliases + hostile bodies),
|
||||
schedule edges (TZ, wrap, weekdays), credit math (peak/off-peak,
|
||||
flash/flagship, unknown-model conservatism), decision levels (healthy
|
||||
/ defer / block / peak interactions), estimate snapshots (redis +
|
||||
local), fake-redis RESP2 server round-trips, dead/nil-state fail-soft,
|
||||
resource fixtures (PSI present/absent, unreadable).
|
||||
- `internal/loop`: wall defer+recover, peak heavy-defer + flash-run +
|
||||
off-peak release, resource-busy defer, usage accounting JSONL +
|
||||
table, gate-off-by-default regression.
|
||||
- `internal/config`: [quota]/[resources] load + 7 validation-error
|
||||
cases; TOML float support.
|
||||
- Docker dev discipline throughout (`./dev.sh` only); host untouched.
|
||||
|
||||
## 8. Resource gate (491) + cgroup runbook
|
||||
|
||||
Read-only monitor (loadavg, MemAvailable, statfs disk free,
|
||||
`/proc/pressure/io` some-avg60 — skipped when PSI absent, read errors
|
||||
never defer). cgroup enforcement documented as deploy-time in the README
|
||||
runbook (`--memory/--cpus/--pids-limit` / systemd slice), per Charles's
|
||||
"docker + cgroups for all work" rule.
|
||||
|
||||
## 9. Open items
|
||||
|
||||
- **LIVE VERIFICATION open**: z.ai usage endpoint (flip `usage_url`
|
||||
when it ships; parser + probe command ready).
|
||||
- Weekly bucket reset is approximated to Monday 00:00 plan-TZ until
|
||||
`reset_at` arrives from a real endpoint.
|
||||
- Turn `cached_tokens` not yet captured from LiteLLM responses (LLM
|
||||
client returns prompt/completion only) — credits currently
|
||||
conservative (cache discount not credited).
|
||||
- Redis container not yet deployed to 192.168.3.78 (runbook written;
|
||||
`enabled = false` defaults mean nothing regresses until then).
|
||||
- LiteLLM $-spend budget keys (separate from credit buckets) still
|
||||
phase 3.
|
||||
|
||||
— PMO worker, MOPAC harness self-host loop
|
||||
@@ -0,0 +1,118 @@
|
||||
# REPORT — Multi-account deploy packaging + runbook (Redmine 494)
|
||||
|
||||
- **When**: 2026-08-29 ~05:45-06:00 CST
|
||||
- **Repo**: `projects/meta/MOPAC/harness` @ `b7799ea` (pushed to origin main)
|
||||
- **Spec**: `docs/SPEC-20260829-charles-brief.md` — account list (9 Linux
|
||||
accounts / 2 hosts), "no root on target accounts" PMO constraint
|
||||
- **Gates**: `./dev.sh check` clean (all 11 Go packages ok, untouched);
|
||||
`make deploy-test` **13/13 ok**; `make release` verified static +
|
||||
end-to-end smoke (install COSWFO in a fake HOME -> `once --dry-run
|
||||
--demo` exit 0 via the actual release binary)
|
||||
|
||||
## 1. What Charles executes (the whole deployment)
|
||||
|
||||
Authority: `deploy/runbook.md` + `deploy/accounts.tsv` in the repo.
|
||||
|
||||
1. **Build once** (workstation, ~2-3 min): `make release` ->
|
||||
`bin/harness-linux-amd64` (digest-pinned docker builder, CGO off,
|
||||
linux/amd64, static + stripped — `file` says "statically linked").
|
||||
2. **Stage per host** (~1 min each): `tar czf /tmp/mopac-deploy.tgz
|
||||
deploy bin/harness-linux-amd64` + one `scp` per host.
|
||||
3. **Install per account** (~30s each, idempotent): ssh in, then either
|
||||
direct (`install-account.sh reachableceo`) or via identity switch
|
||||
(`sudo -u TSGBOD -H sh -c '... && install-account.sh TSGBOD'` — sudo
|
||||
is identity-switch only, nothing system-wide). The installer creates
|
||||
`~/.mopac/{bin,state/loop,state/events,reports,work}`, installs the
|
||||
binary, renders `harness.toml` from the template + TSV row, writes the
|
||||
0600 `~/.mopac/env` secrets template, generates `mopac-start`/`mopac-stop`.
|
||||
4. **Secrets bootstrap** (~1 min/account): fill `~/.mopac/env` from
|
||||
Bitwarden (Redmine key, LiteLLM vkey, 3 webhook secrets, serve vkey).
|
||||
Configs stay secret-free (env: refs only).
|
||||
5. **Verify then start** (~25s/account): see §3.
|
||||
6. **Reboot persistence**: cron `@reboot` line (documented verbatim) or
|
||||
manual re-run of the idempotent `mopac-start`. **No systemd** — that
|
||||
needs root, which the accounts don't have; both options documented.
|
||||
|
||||
## 2. Concurrency guard (why 9 daemons per host can't collide)
|
||||
|
||||
Port scheme from `accounts.tsv` (index is global 0-8, listed order):
|
||||
`events = 4100 + index`, `serve = 8090 + index`.
|
||||
|
||||
| account | host | events | serve | | account | host | events | serve |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| reachableceo | streaming | 4100 | 8090 | | reachableceo-offstage | offstage | 4105 | 8095 |
|
||||
| TSGBOD | streaming | 4101 | 8091 | | COSRCEO-Personal | offstage | 4106 | 8096 |
|
||||
| TSGCOO | streaming | 4102 | 8092 | | COSRCEO-Biz | offstage | 4107 | 8097 |
|
||||
| TSGCTO | streaming | 4103 | 8093 | | COSWFO | offstage | 4108 | 8098 |
|
||||
| TSGCCO | streaming | 4104 | 8094 | | | | | |
|
||||
|
||||
The loop daemon has no port; its state (`state/loop`, `state/events`) is
|
||||
per-account under `~/.mopac/`. The installer hard-asserts the scheme per
|
||||
row (dies if `events != 4100+index`), and `deploy/tests.sh` re-asserts
|
||||
fleet-wide (host,port) uniqueness.
|
||||
|
||||
## 3. Verification steps (per account, runbook step 5+6)
|
||||
|
||||
1. `~/.mopac/bin/harness once --dry-run --demo -config ~/.mopac/harness.toml`
|
||||
— no secrets, no LLM call; must exit 0 printing the PLAN with the
|
||||
account's vertical (this exact path is what the release smoke ran).
|
||||
2. `. ~/.mopac/env && ~/.mopac/bin/harness loop --once --dry-run -config ...`
|
||||
— first real Redmine scan of the account's scope (needs the Redmine
|
||||
key): prints what would dispatch, writes nothing.
|
||||
3. `mopac-start`, then healthz on the account's two ports
|
||||
(`curl http://127.0.0.1:<events>/healthz`, `<serve>/healthz`) and an
|
||||
authenticated `GET /v1/models` through the serve vkey; `tail
|
||||
~/.mopac/state/loop.log` for scan lines.
|
||||
|
||||
## 4. Rollback (per account, ~30s)
|
||||
|
||||
`~/.mopac/bin/mopac-stop` (SIGTERM, clean) -> `rm -rf ~/.mopac` (or `mv`
|
||||
aside to keep evidence) -> drop the cron `@reboot` line if used. Every
|
||||
trace of an instance lives under `~/.mopac/`; Redmine/Gitea data is
|
||||
untouched by removal.
|
||||
|
||||
## 5. Time estimate (the Charles window)
|
||||
|
||||
| | streaming (5 accts) | offstage (4 accts) |
|
||||
|---|---|---|
|
||||
| stage + install | ~3 min | ~2.5 min |
|
||||
| secrets bootstrap | ~5 min (unless prefilled) | ~4 min |
|
||||
| verify + start + healthz | ~2 min | ~1.5 min |
|
||||
| **total** | **~10 min** (mechanical only: ~5) | **~8 min** (mechanical: ~4) |
|
||||
|
||||
Build + Redmine project bootstrap happen before the window.
|
||||
|
||||
## 6. Tests (deploy/tests.sh, 13 assertions, all green)
|
||||
|
||||
TSV: 9 rows, spec-exact account/host sets, unique accounts + (host,port)
|
||||
pairs, `events=4100+idx`/`serve=8090+idx` on every row. Installer: render
|
||||
all 9 accounts into fake HOMEs (no leftover `@PLACEHOLDER@`s, correct
|
||||
vertical/ports/project/absolute paths, env mode 0600, helpers executable
|
||||
with the right ports); idempotent re-run (exit 0, config+env
|
||||
byte-identical); hand-edited `harness.toml` survives re-runs; unknown
|
||||
account exits non-zero; staged-bundle binary lookup works; rendered TOML
|
||||
actually loads (`once --dry-run --demo`, exit 0). Plus the live
|
||||
release-binary smoke described above.
|
||||
|
||||
## 7. Assumptions flagged in the runbook (one-line fixes, no redeploy)
|
||||
|
||||
- **Redmine project identifiers** `mopac-<vertical>` x9 must exist (or
|
||||
edit the TSV column before staging / the `scope_query` after install —
|
||||
installer never overwrites an existing config).
|
||||
- **Quota grouping** assumes one z.ai Max plan per host
|
||||
(`zai-max-1`/`zai-max-2`); `[quota]` ships commented with values
|
||||
pre-filled — confirm grouping, then flip `enabled = true` per account.
|
||||
- `[redmine.status_map]` ships empty (workflow names are per-project;
|
||||
the generated file documents the Released->Done pair to set).
|
||||
- Host short names assumed `ultix-streaming`/`ultix-offstage`; the
|
||||
installer prints an advisory on mismatch, never blocks.
|
||||
|
||||
## 8. Open items
|
||||
|
||||
- Actual ssh/scp execution is Charles's window (PMO runtime cannot
|
||||
ssh/sudo) — everything is scripted, tested locally, and pushed.
|
||||
- Webhook registration in Redmine/Discourse/Gitea per account (URLs +
|
||||
secrets) is post-install config, not packaging; noted in runbook step 0.
|
||||
- LiteLLM virtual keys per account assumed to exist on 192.168.3.78:4001.
|
||||
|
||||
— PMO worker, MOPAC harness self-host loop
|
||||
@@ -0,0 +1,79 @@
|
||||
# REPORT-20260829-1300-pdf — mopac-pdf v0 (Redmine 499)
|
||||
|
||||
Status: DONE. ukrrs/mopac-pdf created (Gitea API/tea), seeded AGPLv3,
|
||||
implemented, tested, smoked, pushed to `main`
|
||||
(https://git.knownelement.com/ukrrs/mopac-pdf). Clone at
|
||||
`~/projects/meta/MOPAC/pdf`.
|
||||
|
||||
## Engine decision: typst (digest-pinned), pandoc+LaTeX rejected
|
||||
|
||||
Full rationale + measurements committed as `docs/ENGINE-DECISION.md` in
|
||||
the repo. Summary, measured 2026-08-29 on identical content (TOC + table +
|
||||
chart figure):
|
||||
|
||||
| | typst 0.15.1 | pandoc 3.5 + xelatex |
|
||||
|---|---|---|
|
||||
| compile | 0.94 s | 7.66 s (~8x slower) |
|
||||
| image | ~200 MB | 758 MB |
|
||||
| templates | typed functions, `#show: report.with(...)` | LaTeX preamble/class surgery, multi-pass TOC |
|
||||
| output | modern typography out of the box | classic LaTeX look |
|
||||
|
||||
typst also natively streams `compile doc.typ -` to stdout, which keeps our
|
||||
engine layer pure bytes-in/bytes-out (no root-owned files, no temp
|
||||
collisions). Supply chain per Charles' clarification: prebuilt Rust binary
|
||||
in a digest-pinned image is tooling; OUR code is Go only.
|
||||
Pin: `ghcr.io/typst/typst@sha256:032e292...9c4c422f` (= 0.15.1,
|
||||
cross-checked), run `--network none` with a private `/work` root.
|
||||
|
||||
## What shipped (v0)
|
||||
|
||||
- CLI `mopac-pdf`: markdown + front-matter (title/subtitle/author/date/
|
||||
classification/template) -> PDF to stdout or `-o`; `-t`/`-T` template
|
||||
pick/override (templates embedded in the binary AND loadable from a
|
||||
dir); `-pages` helper; exit codes 0 ok / 1 usage-input / 2 engine.
|
||||
- Templates: `report` (title page, TOC, numbered headings, running header,
|
||||
page X/Y + classification footer) and `brief` (dense 1-3 pager, compact
|
||||
title block, classification badge, small tables).
|
||||
- Markdown subset the whole stack actually emits: headings, paragraphs,
|
||||
inline bold/italic/code/links, ul/ol, blockquotes, hr, GFM tables with
|
||||
alignment, fenced code, block images.
|
||||
- Charts: fenced ```chart data blocks -> bar charts rendered PURE GO
|
||||
(vendored go-chart, MIT) -> PNG figures. One type proven end to end,
|
||||
per v0 scope.
|
||||
- Dev in docker only: `dev.sh`/`make` route through the family builder
|
||||
digest; deps vendored (hermetic); smoke drives the REAL typst container
|
||||
from the host (11 checks: both templates, stdin/stdout, page counts,
|
||||
exit codes, custom template dir — all green).
|
||||
- Tests: 7 packages ok — front-matter table tests, parser tests, golden
|
||||
.md -> .typ fixtures (drift-catching), chart PNG decode, tiny
|
||||
/Pages /Count parser tests, engine argv contract via stub docker, CLI
|
||||
flag/exit-code matrix.
|
||||
|
||||
## Sample outputs (local, regenerable via ./dev.sh smoke)
|
||||
|
||||
- ~/projects/meta/MOPAC/pdf/out/sample-report.pdf — 4 pages (title, TOC, body + table + chart)
|
||||
- ~/projects/meta/MOPAC/pdf/out/sample-brief.pdf — 1 page (dense exec brief)
|
||||
- ~/projects/meta/MOPAC/pdf/out/stdin-report.pdf — stdin->stdout path
|
||||
- fixtures live in-repo: testdata/sample-{report,brief}.md (+ .typ.golden)
|
||||
|
||||
## How the briefing pipeline will call it
|
||||
|
||||
The COS briefing/harness side already speaks markdown; the contract here
|
||||
is a single exec: `mopac-pdf -o <path>.pdf <brief.md>` (or pipe bytes:
|
||||
`mopac-pdf < brief.md > brief.pdf`). Front-matter carries the identity
|
||||
(title/date/classification/template=brief for the 0630 morning brief,
|
||||
report for long-form), so callers never touch typst. Failure semantics
|
||||
are harness-shaped: 0/1/2, engine stderr surfaced verbatim, no partial
|
||||
output files on failure. Engine image + docker binary overridable by env
|
||||
for canary/migration. No daemon, no state — a pure function, safe to call
|
||||
from any loop turn.
|
||||
|
||||
## Next (proposals, not started)
|
||||
|
||||
- More chart types (line, donut) — renderer is a one-function extension
|
||||
point; needs a data-block convention for series.
|
||||
- More templates: letterhead, invoice, slide-deck-ish landscape.
|
||||
- Nested lists >1 level; footnotes; typst-side escape hardening fuzz.
|
||||
- Optional direct embedding of briefing charts without PNG round-trip
|
||||
(emit typst vector drawing for crisp print) once typst scripting
|
||||
surface stabilizes.
|
||||
@@ -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: 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,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