Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d952222478 | ||
|
|
c8a9bcf946 | ||
|
|
58b2bbb3a5 | ||
|
|
95d4991d20 | ||
|
|
966ee31ea8 | ||
|
|
696ea3a3af | ||
|
|
0ce22dde65 | ||
|
|
36056a5dd9 | ||
|
|
8bb5b3c73c | ||
|
|
6927628b99 | ||
|
|
cd87d5305f | ||
|
|
77a7df8949 | ||
|
|
61af103679 | ||
|
|
eb7240dc85 | ||
|
|
9903a5d6f6 | ||
|
|
4577e46459 | ||
|
|
5e77cdd095 | ||
|
|
5b1ae8d6f4 | ||
|
|
ef119377f8 | ||
|
|
1edf4acfd1 | ||
|
|
13c1b3529f | ||
|
|
380b9810dc | ||
|
|
b2f2071a0a | ||
|
|
49433b4dd3 | ||
|
|
da55da6244 | ||
|
|
1efcaf981b | ||
|
|
9878703d3a | ||
|
|
56cfaf468e | ||
|
|
a41cfd491e | ||
|
|
7fe32d7226 | ||
|
|
06f507dbc0 | ||
|
|
9a50f082f5 |
No files matched your search
@@ -5566,3 +5566,19 @@ claim. Receipts and limitations in agents/lena/work/queue-296/BUILD.md and run-r
|
||||
2026-10-24 | Fethry | Row328 sage4205 stress stopped at required no-reproduction boundary. M1-50 passed50/50; fallbackM1-1 passed50/50, zero library-sync-busy assertions, no skips/cancels. Fixed50 and whole-file NOT RUN. Absolute19 initial priority then host drift recorded. All100 fresh shell gXXX TMPDIRs29bytes; both mutants restore exact test hash and empty diff/stat+porcelain. No test/assertion/product changes. Receipts/nice/load/plain-text BUILD pushed; slot returned fethry0043. Await Sage/Mos ruling; no in-review/acceptance claim.
|
||||
|
||||
2026-10-24 | Fethry | Row328 sage4214/Mos1599 option A GREEN on06c57a7db:8x50 M1-50 produced374 passes/26 expected library-sync-busy assertion failures, exact restore/full diff+porcelain clean, same8x50 fixed400/400, whole library-credential6/6, no skips/cancels or unexpected errors/reruns. Node nice16/19 observed after absolute19; loads and801 receipts retained. Old test staysapproved+4/-3, assertions/product unchanged. Own process sweep empty. Evidence push/slot return and owner review move next; Boyd/Dewey then Sage full bus/scripts gate pending.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 257 (#1762, Q198 S1 pressure parser and rules evaluator, Morgana, Filbert) LANDED 73692e1bb, pushed 17:41:58Z 10-24. Before: refactor at 487500a16. After: Morgana's four commits cherry-picked onto 487500a16 with unchanged patches; the 9 packages/supervisor paths match Filbert's round 2 manifest b176a88f. Filbert r1 c30188 (N1/N2 fixed in r2), r2 APPROVE c30190 rev 3037, request c30189 posted by Sage. Slot result on 1261fa2d8: three files whole 15/0, 29/0, 1/0, supervisor 45/0, M1-M8 killed by assertion. Sage gate: first run RED 17:20:42Z, webui 441/1 on the old row278 sign-out test (timeout at load about 8.5, the row touches no webui path); the webui-only rerun 442/0 was not counted; the full rerun was GREEN 17:41:11Z: bus 998/0, business 283/0, webui 442/0, supervisor 45/0, cli 989 plus the allowed FED R7 skip, all 12 scripts rc 0. Correction: the row278 timing red is an old test, so any change to it goes to Mos (mos-1272). Follow-up: Morgana's carry (sage-3003, CLEAR_HELD entry and a clear-path hold test) binds the row that wires a Q198 hold into a launch refusal. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 266 (#1771, C2-10c Console room view, Fethry on Codex, Boyd and Dewey) LANDED 3e5041b43, pushed 18:00:49Z 10-24. Before: refactor at 73692e1bb. After: Fethry's four commits cherry-picked, every patch-id equal to the seat branch; tree is manifest 5f816042 with no other change. Boyd APPROVE c31609 rev 3983, Dewey APPROVE c31610 rev 3985 (N1-N3 non-blocking), request c31608 posted by Sage. Sage gate GREEN first run 18:00:08Z: bus 1000/0, business 283/0, webui 458/0, supervisor 45/0, cli 989 plus the allowed FED R7 skip, all 12 scripts rc 0. Dewey's N2, the old T9 library-sync-busy race, went to Mos and became row 328. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 224 (#1725, C2-05b, Della on Codex, Dewey) LANDED 5e77cdd09, pushed 19:18Z 10-24. Before: refactor at 7fe32d722. After: Della's 13 commits cherry-picked onto 7fe32d722. Sage gate GREEN 19:16:37Z on 27624aa66 (base 06f507dbc): bus 1011/0, business 283/0, harness 176/0, cli 990/0 plus the allowed FED R7 skip, webui 458/0, all scripts rc 0. Drift between the gated base and 7fe32d722 is only docs/plans/2026-10-24_model-proxy-rows.md, which no test reads (mos-1601 condition); manifest-r9 952482b4 matches the landed tree. Dewey APPROVE c31619 rev 3999. #1725 closed c31623; note rev 4015, done rev 4016. Correction: the first done move passed --evidence and was refused (rc 2, the round closes on its recorded verdicts); rerun without it. Follow-up: Dewey's N3, the old ask helper can't serve a library business, goes to a later row. Not re-pinned.
|
||||
|
||||
2026-10-24T19:44:50Z | Sage | Row 328 (#1826, Vikunja 622, old T9 timer race, Fethry on Codex, Boyd and Dewey) LANDED 8bb5b3c73, pushed 19:40Z 10-24. Before: refactor at 5e77cdd09. After: Fethry's 8 commits cherry-picked onto 5e77cdd09 (same patches); T9's second start passes intervalMs 2147483647 (mos-1594), test-only. Under 8x50 concurrent loops M1 (50 ms back) gave 26 expected failures and the fix 400/400. Boyd APPROVE c31621 rev 4002, Dewey APPROVE c31622 rev 4003. Sage gate GREEN 19:38:54Z on 8bb5b3c73 itself, no drift: bus 1011/0, business 283/0, harness 176/0, cli 990/0 plus the allowed FED R7 skip, webui 458/0, every scripts/test-*.sh rc 0 (Dewey N1). Manifest 1e010468. #1826 closed c31625, Vikunja 622 done (comment 1071), note rev 4019, done rev 4020. Not re-pinned.
|
||||
|
||||
## 2026-10-24 — row332 P3 proxy harness configuration: investigation/test placement
|
||||
|
||||
Fethry at c8a9bcf94, per Sage sage-4292 and mos-1616 pi-route amendment. Before: every managed session receives instance-wide provider credentials; harnesses have no transient per-session proxy context; pi preflight checks only its binary. Intended after: proxy-mode isolation, Claude proxy env, pi private run-local route config using environment references only, credential preflight, and business proxy account settings. Design/test plan: agents/fethry/work/queue-332/BUILD.md. Initial tests placed before production changes. No tests run: Sage CLEAR is required and has not arrived. No success/acceptance claim, live change, or dependency landing. Existing proxy issue number requested from Sage; not yet received.
|
||||
|
||||
## 2026-10-24 — row332 crash stop, documentation-only checkpoint
|
||||
|
||||
Fethry stopped under Mos mos-1630/Jason's T3 crash instruction. No production implementation, tests, probes, mutants or acceptance runs occurred; Sage CLEAR never arrived. Explicit route metadata per sage-4293 is recorded in agents/fethry/work/queue-332/BUILD.md; full state and resumption boundaries are in HANDOFF.md there. Safe crash checkpoint commits documentation/historical plain-text import manifest only; four unrun tests and the import-audit script remain uncommitted. No long jobs restarted, protected branch changes, merges, live changes or further thread messages.
|
||||
@@ -0,0 +1,742 @@
|
||||
# Row 224 — C2-05b broker lookups and typed tools (#1725)
|
||||
|
||||
Owner: Della; reviewer: Dewey. Assignment sage-2795; scope extension sage-2804.
|
||||
Base: `4f3fbc25a` (Researcher C2-05a). Branch: `della/row224`.
|
||||
Brief: C2-05b in `docs/plans/2026-10-20_case2-rows.md` at `aca16404c`
|
||||
and `51cc0445c`; design section 3, 5.3 M3 and 7.2; C2-05a BUILD rulings.
|
||||
|
||||
## Execution boundary and progress
|
||||
|
||||
Code/docs and local Git only while row 253 holds the gate. No Node (including
|
||||
syntax checks), HTTPS Git, queue claim, test or push until Sage's CLEAR.
|
||||
Tests are written first; their RED/GREEN runs are deferred, not claimed.
|
||||
Queue next is deferred too: its executable uses Node. Sage assigned this row
|
||||
explicitly. After 253 and 216 land, rebase under a new branch name.
|
||||
|
||||
- Pre-flight: query.mjs consumes `{repo,commit,mapping}`; broker cannot import
|
||||
Git/filesystem library code because it is in the sandbox import closure.
|
||||
Runtime injects the host lookup adapter; existing human file/status/sync stay.
|
||||
- Pre-flight: resolved has no project list and a library is never a checkout.
|
||||
Bundle needs an explicit library flag from launcher business configuration.
|
||||
- M3 reading: exact creative base is five lookups plus send_message; retain
|
||||
explicitly granted broker web_search/web_fetch for research/companion under
|
||||
landed C2-08 and sage-2804. No generic spec, task, evidence or decision tools. Residual (mos-1308): those
|
||||
reads lack broker action checks; their typed-tool absence is the only fence
|
||||
for creative roles. M1/M2 deny bash; broker-side refusal is outside this row.
|
||||
Mos confirmed this interpretation in mos-1308; design wording fix belongs to Gosalyn. Other definitions retain their current tools.
|
||||
- Multi-library businesses use an optional project ID; omit it only with one
|
||||
configured library. Universe is required for all five tools, including get.
|
||||
- Missing library uses the brief's `library-not-configured`; argument errors
|
||||
reuse `invalid-request` and C2-05a query codes. No new action or authority.
|
||||
|
||||
## Manifest (draft code, unrun)
|
||||
|
||||
- bus/src/broker.mjs: five lookup verbs, strict args, holder/business rechecks.
|
||||
- bus/src/runtime.mjs: host lookup adapter, stale-before-read sync, served pin.
|
||||
- harness/src/tools.mjs: five schemas; creative exact-set filtering.
|
||||
- harness/src/bundle.mjs: library flag forwarded to typedTools.
|
||||
- cli/src/launcher.mjs: one flag derived from trusted business projects.
|
||||
- bus/tests/library-lookups.test.mjs: real query/broker/socket/runtime cases.
|
||||
- harness/tests/library-tools.test.mjs: schemas, role snapshots, bundle parity.
|
||||
- cli/tests/thread-launch.test.mjs: flag reaches a real launched bundle with no library checkout.
|
||||
- agents/della/work/queue-224/BUILD.md: manifest, rulings and deferred verification ledger.
|
||||
|
||||
## Verification ledger
|
||||
|
||||
No test or syntax-check run yet. Acceptance requires full bus/cli/harness suites
|
||||
and every scripts/test-*.sh on Sage's gate; focused files and affected CLI/
|
||||
seat import-closure tests precede that only in an authorized CLEAR.
|
||||
|
||||
## Coordination
|
||||
|
||||
Huey272 reports no runtime/bundle/launcher overlap. Rocko219 changes launcher
|
||||
THREAD_DEFINITIONS checks only, separate from our buildBundle flag argument.
|
||||
The launcher argument is required: bundle cannot derive library presence from
|
||||
checkout IDs (a library has no agent clone). No role, authority or config schema
|
||||
changes; library project entries are already validated and protected by M9.
|
||||
|
||||
## Mount and pin impact (inspection only)
|
||||
|
||||
No new imports in broker.mjs or tools.mjs; no RUNNER_FILES or sandbox mount
|
||||
addition. runtime.mjs is host-only and already imports library index.mjs.
|
||||
Bundle forwards only a boolean, never a root/mapping/slot/credential. The
|
||||
existing import-closure tests are bus/tests/project-runtime.test.mjs and
|
||||
seat/tests/sandbox.test.mjs. Grep found no literal sha256 pin for runtime.mjs
|
||||
or bundle.mjs; bundle tests derive current source digests through SOURCES.
|
||||
Library sources remain in the existing whole-directory release copy.
|
||||
|
||||
## Runs to schedule only after CLEAR
|
||||
|
||||
Set TMPDIR=/mnt/storage/scratch/tmp. Focused new bus/harness files first,
|
||||
then existing library.test.mjs, library-sync.test.mjs, project-runtime.test.mjs,
|
||||
harness creative/tools/spec-tools/bundle files and affected CLI launch tests.
|
||||
Follow with the required whole bus/cli/harness suites and Sage's full shell gate.
|
||||
No timing claim yet: use the repository/Sage per-file limits and record every
|
||||
command, outcome, elapsed time and timeout before acceptance. No focused skip
|
||||
or pattern run substitutes for whole-file gate evidence.
|
||||
|
||||
## Read-through findings and residuals
|
||||
|
||||
- Library get requires universe although the older design table omitted it;
|
||||
section 3 isolation rule and C2-05a's accepted API are binding.
|
||||
- Query type enum matches mapping.TYPES (five values); manuscript is a distinct
|
||||
lookup and not a find type. Unknown host-path/commit/business/accept fields
|
||||
are refused; explicit project is restricted to this business's library list.
|
||||
- Async reads recheck the live holder and current project revision. A stale
|
||||
read attempts sync, then takes the new served snapshot; a returned fetch
|
||||
failure/divergence keeps serving the old pin. Busy sync uses the current pin.
|
||||
Unreadable state, no served commit or unsafe configuration refuse. Query
|
||||
and sync never hold a SQLite transaction over await.
|
||||
- Processing halt refuses new agent lookup work and rechecks before returning
|
||||
async results. Existing human library file/status/sync behavior is unchanged.
|
||||
- Timer sync and reads still use C2-03a's 60-second contract. No timer tuning or
|
||||
resource-limit schema is introduced by this row. Q222 creative reply settings
|
||||
are in C2-06; no reply guard or writing capability is implemented here.
|
||||
- The generic tool socket's existing 60-second client timeout can precede the
|
||||
server's 120-second Git timeout. This row does not change the generic timeout;
|
||||
a timed-out lookup is read-only and retryable, never a write/outcome claim.
|
||||
- All tests are authored but unrun. No RED, GREEN, syntax, runtime performance,
|
||||
sandbox, broker/harness suite, shell gate, claim or push evidence exists yet.
|
||||
|
||||
## Candidate review checklist (read-through only)
|
||||
|
||||
- [x] Five verbs mapped to real C2-05a methods, all require universe.
|
||||
- [x] Default sole library or explicit owned project; no caller repo/commit.
|
||||
- [x] Human-only file/status/sync gates kept; lookup holder rechecked.
|
||||
- [x] Async socket dispatch includes lookup verbs through LIBRARY_VERBS.
|
||||
- [x] Stale/unknown fetch syncs first; results use current immutable served pin.
|
||||
- [x] Six/eight creative names per mos-1308; other roles retain their tools.
|
||||
- [x] Launcher -> bundle -> typedTools passes presence without a library clone.
|
||||
- [x] No imports added to sandbox source closure; no mount change.
|
||||
- [ ] Execute new tests, RED characterization, full files/suites after CLEAR.
|
||||
- [ ] Rebase after rows 253/216 land, under a new branch name.
|
||||
- [ ] Claim/push only in Sage-authorized gap; Dewey review and Sage gate.
|
||||
|
||||
Candidate preservation commits are explicitly unrun under sage-2795; they are
|
||||
not completion, green-suite or landing claims. No dependencies installed.
|
||||
|
||||
Correction: the first committed-range diff --check found a blank line at EOF
|
||||
in the new library-tools test, missed while it was untracked. Removed in a
|
||||
follow-up local commit; the complete range must be checked again. No tests ran.
|
||||
|
||||
## Shared-log receipts for Sage (sage-2815)
|
||||
|
||||
The shared logs are unchanged from the base in this row. Sage appends these
|
||||
receipts in the landing sweep; they are preserved here, not in the manifest.
|
||||
|
||||
For `BUILD-LOG.md`:
|
||||
|
||||
```text
|
||||
2026-10-22T16:36:30Z | Della row224 C2-05b #1725 preparation | Before: C2-05a queries exist at 4f3fbc25a but no broker lookup verbs or typed tools. Scope: broker/tools plus Sage-authorized runtime/bundle integration, tests and docs. Existing human-only library reads retained; five agent queries require a live holder and trusted configured source. Code/docs only under row253 gate; no Node/test/push/claim. Runtime ownership coordinated with Rocko219/Huey272.
|
||||
```
|
||||
|
||||
For `docs/SESSIONS.md`:
|
||||
|
||||
```text
|
||||
2026-10-22T16:36:30Z | Della (sage-2795 / sage-2804, Q220) | Row 224 C2-05b #1725, della/row224 base 4f3fbc25a | Read both pinned briefs and C2-05a source; preparing broker/typed tools and authorized host runtime/bundle seams. No queue command, Node, HTTPS Git, claim, test or push during Gander gate. Dewey reviewer; Rocko/Huey runtime coordination sent. Tests and integration verification deferred to CLEAR.
|
||||
```
|
||||
|
||||
Local-only draft pins 19c0a7adf/7cf39c390 are superseded by the
|
||||
log-free candidate. Saved as local branch della/row224-draft-with-logs before
|
||||
rebuilding the unpublished candidate commit; no remote history changed.
|
||||
|
||||
## Dewey dewey-0852 and Sage sage-2819 — focused corrections
|
||||
|
||||
B1: inline freshness uses max(lastFetch, lastError.at), not lastFetch alone.
|
||||
An error within 60 seconds counts as a recent attempt, so later lookups serve
|
||||
the old served pin without another fetch. Keep inline sync: the brief requires
|
||||
it and businesses without timed libraries have no sync timer. The extended
|
||||
failed-fetch test makes reads at +1, +59,999 and +60,000 ms (one attempt total),
|
||||
then +60,001 ms (second attempt). All reads must return the old commit.
|
||||
|
||||
Required mutant M-B1: remove lastError.at from recentAttempt. The repeated
|
||||
reads must fail the attempted-count assertion. This mutant has NOT been run.
|
||||
|
||||
Residual (Sage will defer): the first inline fetch in each outage window can
|
||||
still exceed the tool timeout (60 seconds, or the runner broker client’s
|
||||
30 seconds). Bounding that fetch is out of scope for 224. Subsequent reads
|
||||
are paced by persisted lastError.at. No claim of first-read outage latency.
|
||||
|
||||
N1: whole bus, cli and harness suites are required, plus the shell gate.
|
||||
If the new thread-launch integration test skips due to sandboxProblem(),
|
||||
name the skip; a skip is not launcher integration proof.
|
||||
N2: cli/tests/thread-launch.test.mjs now depends on library/tests/helpers.mjs
|
||||
mappingEntry() and library/tests/fixtures/mapping.json. Changes to that fixture
|
||||
must rerun the CLI launch file.
|
||||
N3: harness/tests/library-tools.test.mjs compares the type enum to mapping.mjs
|
||||
TYPES, so adding a mapped type cannot silently leave the typed tool behind.
|
||||
|
||||
These edits are local code/docs only. No Node, RED/GREEN run, mutant, claim,
|
||||
HTTPS Git or push. New local pin goes to Dewey/Mos/Sage for focused review.
|
||||
|
||||
## sage-4163 — row224-r2 static rebase, 2026-10-24
|
||||
|
||||
New isolated worktree: `/mnt/storage/scratch/della-row224-r2`.
|
||||
Branch `della/row224-r2`, base refactor `487500a16f4ab5902c318509fdec6acc56a543ca`.
|
||||
Only the two row-owned commits were cherry-picked, in order:
|
||||
`38e322ea0` -> `327073400`; `23816cf28` -> `e138b2c55`.
|
||||
No old prerequisite commits were copied into this series. Rows 216 and 217
|
||||
are now upstream. Row 266 remains a landing dependency: rebase on its landing
|
||||
when Sage announces it. No broker hunk touches room.read or its hydration.
|
||||
|
||||
One content conflict: the footer of cli/tests/thread-launch.test.mjs. Kept
|
||||
both upstream C2-09a test bodies and every assertion, followed by our unchanged
|
||||
C2-05b launched-bundle test. No test was removed or weakened. Range-diff shows
|
||||
only that context relocation and the upstream sandbox workspace expression
|
||||
beside launcher's added library flag. The B1 commit is patch-identical.
|
||||
|
||||
B1 answer: e138b2c55 retains max(lastFetch, lastError.at), the repeated-read
|
||||
regression and M-B1 specification. Dewey already checked the original fix at
|
||||
23816cf28 in dewey-0853; it still needs executed mutant and gate evidence.
|
||||
N2 dependency also includes harness/tests/library-tools.test.mjs importing
|
||||
library/src/mapping.mjs TYPES; changes there must rerun that harness file.
|
||||
|
||||
Static checks: complete-range git diff --check clean; nine manifest paths.
|
||||
Scanned packages/*/tests for every changed basename alongside literal 64-hex
|
||||
hashes (three lines on either side): no matching hash pins. Broker and tools
|
||||
add no imports; no RUNNER_FILES or sandbox mount changes.
|
||||
No Node, syntax check, test, mutant, install or build was run. The historical
|
||||
verification ledger above records the original preparation, not current gate
|
||||
permission. Under sage-4163 the required gate is Dewey's manifest approval,
|
||||
whole bus and harness suites, plus every scripts/test-*.sh on Sage's gate;
|
||||
record every skip. Tests and mutants wait for Sage's explicit CLEAR. Static
|
||||
Git and pushing this new branch are authorized now. Shared logs remain
|
||||
unchanged; Sage owns their append-only landing receipts.
|
||||
|
||||
## sage-4181 — row266 landing rebase, 2026-10-24
|
||||
|
||||
Rebased the complete r2 series onto refactor
|
||||
`3e5041b43b36d7f1c5ba7ce2f14b3634fe0955db` under new branch
|
||||
`della/row224-r3`; r2 is preserved, no force-push. No conflicting files and
|
||||
no manual conflict resolutions. Range-diff: all three prior commits equal
|
||||
(212aef616, 82d6cc62c, c509dcb6e). B1 remains patch-identical.
|
||||
Complete-range diff --check is clean. No test, syntax or mutant run.
|
||||
|
||||
Own touched test files, each to run whole in Sage's slot:
|
||||
- packages/bus/tests/library-lookups.test.mjs
|
||||
- packages/cli/tests/thread-launch.test.mjs
|
||||
- packages/harness/tests/library-tools.test.mjs
|
||||
|
||||
Tests changed by the new upstream base, also named for the rebase-rule gate:
|
||||
- packages/bus/tests/room-check.test.mjs
|
||||
- packages/plugins/tests/room-channel.test.mjs
|
||||
- packages/supervisor/tests/pressure.test.mjs
|
||||
- packages/supervisor/tests/purity.test.mjs
|
||||
- packages/supervisor/tests/rules.test.mjs
|
||||
- packages/webui/tests/room-browser.test.mjs
|
||||
- packages/webui/tests/room-http.test.mjs
|
||||
|
||||
None of those seven upstream test files has a row224-owned diff. Full bus and
|
||||
harness suites plus every scripts/test-*.sh remain the row gate; the listed
|
||||
CLI and upstream files require whole-file runs under sage-4181 too. Dewey's
|
||||
delta review follows Sage's run. No CLEAR is inferred from the rebase.
|
||||
|
||||
## sage-4192 CLEAR — run receipt and old-test stop, 2026-10-24
|
||||
|
||||
Candidate named by CLEAR: a1a7357457a31d219b99effa9b312ec78d06f060.
|
||||
Fresh mktemp TMPDIR: /mnt/storage/scratch/tmp/della-224-4192.r3L7cj.
|
||||
Each command used nice -n 19 and timeout -k 10 300 node --test FILE.
|
||||
Inherited OS niceness was -4, so nice initially yielded 15; corrected the
|
||||
current runner tree to absolute 19 during the CLI run. Future runs must set
|
||||
absolute 19 before spawning. No other seat's processes were changed.
|
||||
|
||||
First whole bus/tests/library-lookups.test.mjs run: 11 tests, 8 pass, 3 fail,
|
||||
0 skipped, duration 1.173 s (exit 1). All three are this row's new tests.
|
||||
Root causes: Broker clones businesses, so mutating the original fixture did
|
||||
not publish the second project or the delisting; runtime requires the
|
||||
selected harness to be enabled. Fixed only new fixture setup: reloadBusiness
|
||||
after each of the two changes and plugins.harness ['pi'] in runtime fixture.
|
||||
All assertions and production code are unchanged. Whole-file rerun: 11 pass,
|
||||
0 fail/cancelled/skipped, 1.322 s (exit 0). B1 regression included and passed;
|
||||
M-B1 has not been executed.
|
||||
|
||||
Next whole cli/tests/thread-launch.test.mjs reported OLD-test reds at :167,
|
||||
:216 and :241. On observing them stopped only this run's process tree; no
|
||||
rerun or next file was started. Interrupted-file summary: 0 pass, 3 fail,
|
||||
1 cancelled, 0 skipped, 62.988 s. Returned slot to Sage in della-0041 under
|
||||
mos-1592. No old test changed. No in-review move: the third own file, all
|
||||
seven new-base files, both whole suites and mutant evidence remain pending.
|
||||
No shell/full landing gate was run.
|
||||
|
||||
Exact completed run receipts:
|
||||
|
||||
```text
|
||||
{"name": "packages/bus/tests/library-lookups.test.mjs", "command": ["nice", "-n", "19", "timeout", "-k", "10", "300", "node", "--test", "packages/bus/tests/library-lookups.test.mjs"], "exit": 1, "seconds": 1.173, "TMPDIR": "/mnt/storage/scratch/tmp/della-224-4192.r3L7cj"}
|
||||
{"name": "packages/bus/tests/library-lookups.test.mjs", "command": ["nice", "-n", "19", "timeout", "-k", "10", "300", "node", "--test", "packages/bus/tests/library-lookups.test.mjs"], "exit": 0, "seconds": 1.322, "TMPDIR": "/mnt/storage/scratch/tmp/della-224-4192.r3L7cj"}
|
||||
```
|
||||
|
||||
Old-test failure output (interrupted whole file, not a whole-file pass):
|
||||
|
||||
```text
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it (20120.78425ms)
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (185.485131ms)
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it (30082.743341ms)
|
||||
|
||||
Interrupted while running:
|
||||
|
||||
⚠ packages/cli/tests/thread-launch.test.mjs (packages/cli/tests/thread-launch.test.mjs:1:1)
|
||||
✖ packages/cli/tests/thread-launch.test.mjs (62982.9717ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 3
|
||||
ℹ cancelled 1
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 62987.534861
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:167:1
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it (20120.78425ms)
|
||||
Error: timed out waiting
|
||||
at until (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:48:9)
|
||||
at async ask (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:153:17)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:179:17)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:216:1
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (185.485131ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'harness-binary-missing'
|
||||
- 'capacity-full'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:227:10)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'harness-binary-missing',
|
||||
expected: 'capacity-full',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:241:1
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it (30082.743341ms)
|
||||
Error: timed out waiting
|
||||
at until (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:48:9)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:251:3)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:1:1
|
||||
✖ packages/cli/tests/thread-launch.test.mjs (62982.9717ms)
|
||||
'Promise resolution is still pending but the event loop has already resolved'
|
||||
```
|
||||
|
||||
Receipt correction: copied test output contained two whitespace-only lines;
|
||||
complete-range diff-check caught them. Trimmed those log lines in this
|
||||
follow-up; no test/code or failure text changed, and no test was rerun.
|
||||
|
||||
## sage-4195 / mos-1595 — pre-install evidence
|
||||
|
||||
No canonical node_modules link made; that suggestion was refused under
|
||||
mos-1575. Before installing anything in this worktree, ran:
|
||||
|
||||
```text
|
||||
$ ls -l node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js
|
||||
ls: cannot access 'node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js': No such file or directory
|
||||
rc=2
|
||||
```
|
||||
|
||||
The file is missing, so the authorized local npm ci follows. This is an
|
||||
environment finding; prior existing-test reds do not establish a row-code
|
||||
regression. No old-test edit or rerun preceded this CLEAR.
|
||||
|
||||
## sage-4195 — install and single-run STOP receipt
|
||||
|
||||
```text
|
||||
$ [ -L node_modules ] && rm node_modules; timeout -k 10 900 npm ci --ignore-scripts --no-audit --no-fund
|
||||
npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
added 128 packages in 2s
|
||||
npm_ci_rc=0
|
||||
$ readlink -f node_modules/.bin/pi
|
||||
/mnt/storage/scratch/della-row224-r2/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js
|
||||
```
|
||||
|
||||
One authorized whole-file invocation, no retry or test edit. Absolute runner
|
||||
and timeout niceness 19 before launch; observed Node parent/worker niceness
|
||||
16 during execution (host ananicy rule), recorded without stopping for nice.
|
||||
Fresh TMPDIR and command in receipt below. Stopped test process tree upon
|
||||
observing existing-test reds. Interrupted summary: 0 pass, 2 fail, 1 cancelled,
|
||||
0 skipped; exit 1, 40.056 s. This is not a whole-file pass. The capacity test
|
||||
now receives launch-failed, rather than harness-binary-missing. Root cause of
|
||||
that launch failure is not established. No assertion/production changes.
|
||||
Slot returned to Sage in della-0044 for escalation to Mos; all other files,
|
||||
suites and M-B1 remain held pending their own CLEAR.
|
||||
|
||||
```text
|
||||
{
|
||||
"command": [
|
||||
"timeout",
|
||||
"-k",
|
||||
"10",
|
||||
"900",
|
||||
"node",
|
||||
"--test",
|
||||
"packages/cli/tests/thread-launch.test.mjs"
|
||||
],
|
||||
"TMPDIR": "/mnt/storage/scratch/tmp/della-224-4195._yq176su",
|
||||
"exit": 1,
|
||||
"seconds": 40.056,
|
||||
"start_runner_nice": 19,
|
||||
"observed_timeout_nice": 19
|
||||
}
|
||||
```
|
||||
|
||||
Failure output:
|
||||
|
||||
```text
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it (20109.08171ms)
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (330.738356ms)
|
||||
|
||||
Interrupted while running:
|
||||
|
||||
⚠ packages/cli/tests/thread-launch.test.mjs (packages/cli/tests/thread-launch.test.mjs:1:1)
|
||||
✖ packages/cli/tests/thread-launch.test.mjs (40023.3188ms)
|
||||
ℹ tests 3
|
||||
ℹ suites 0
|
||||
ℹ pass 0
|
||||
ℹ fail 2
|
||||
ℹ cancelled 1
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 40027.912798
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:167:1
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 REQ-MSG-1 a thread message starts the thread's session, which stops when idle; its history survives the stop and a restart and no other thread sees it (20109.08171ms)
|
||||
Error: timed out waiting
|
||||
at until (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:48:9)
|
||||
at runNextTicks (node:internal/process/task_queues:65:5)
|
||||
at process.processTimers (node:internal/timers:615:9)
|
||||
at async ask (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:153:17)
|
||||
at async TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:179:17)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3)
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:216:1
|
||||
✖ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (330.738356ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
+ actual - expected
|
||||
|
||||
+ 'launch-failed'
|
||||
- 'capacity-full'
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:227:10)
|
||||
at runNextTicks (node:internal/process/task_queues:65:5)
|
||||
at process.processTimers (node:internal/timers:615:9)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 'launch-failed',
|
||||
expected: 'capacity-full',
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:1:1
|
||||
✖ packages/cli/tests/thread-launch.test.mjs (40023.3188ms)
|
||||
'Promise resolution is still pending but the event loop has already resolved'
|
||||
```
|
||||
|
||||
## sage-4204 — chief credential fixture correction (unrun)
|
||||
|
||||
Sage's control pair with a 29-byte TMPDIR: base 3e5041b43 passes 14/14;
|
||||
ed985c044 passes all 14 existing tests but this row's new CLI fixture refuses
|
||||
chief-library-gitea-required before launch. Added only chief.credentials.gitea
|
||||
as a copy of the fixture coder's reference in the C2-05b edit callback, matching
|
||||
cli/tests/launcher.test.mjs:35 and trackers-boot.test.mjs:17. This is a reference
|
||||
to synthetic fixture credentials; no credential value was read or introduced.
|
||||
All assertions, old tests and production code unchanged. No run performed.
|
||||
|
||||
The earlier 48-byte TMPDIR explains the launch-failed reds through the
|
||||
107-byte tools.sock limit, as confirmed by Sage's control. Future named CLEAR
|
||||
uses shell TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/gXXX), 29 bytes with
|
||||
alphanumeric suffix, not Python mkdtemp or a canonical node_modules link.
|
||||
Next authorized run order: whole thread-launch first, other eight files, whole
|
||||
bus/harness suites, M-B1; all await next CLEAR after Fethry/Gyro slots.
|
||||
|
||||
## sage-4208 — first-file red, sequence stopped
|
||||
|
||||
Named candidate 6160b4981. Whole thread-launch.test.mjs invoked once.
|
||||
Shell-created TMPDIR /mnt/storage/scratch/tmp/gCXV, 29 bytes; absolute runner
|
||||
and timeout niceness 19 at start, Node observed 16 afterwards (host policy).
|
||||
15 tests: all 14 existing tests pass, the one new C2-05b fails; no skips or
|
||||
cancellations. Exit 1 in 18.740 s. Stopped sequence and returned slot to Sage
|
||||
in della-0047 before any edit or rerun. No other file, suite or mutant ran.
|
||||
The original r3 list has nine files beyond thread-launch (two row-owned plus
|
||||
seven upstream), rather than eight; none started in this CLEAR.
|
||||
|
||||
New-test failure is call(...).find is not a function in existing ask helper
|
||||
:153, called by new C2-05b :550. Root cause not yet established; no assertion,
|
||||
fixture or production change made. Await Sage's own-new-test ruling.
|
||||
|
||||
Exact run receipt:
|
||||
|
||||
```text
|
||||
{"number": 1, "name": "packages/cli/tests/thread-launch.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/cli/tests/thread-launch.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gCXV", "start_nice": 19, "observed_timeout_nice": 19, "exit": 1, "seconds": 18.74}
|
||||
```
|
||||
|
||||
Failure output:
|
||||
|
||||
```text
|
||||
ℹ tests 15
|
||||
ℹ suites 0
|
||||
ℹ pass 14
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 18707.936662
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/cli/tests/thread-launch.test.mjs:540:1
|
||||
✖ C2-05b a real launched bundle gets lookups for a library project without a library checkout (139.729167ms)
|
||||
TypeError: call(...).find is not a function
|
||||
at file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:153:71
|
||||
at until (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:44:21)
|
||||
at ask (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:153:23)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/cli/tests/thread-launch.test.mjs:550:9)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7)
|
||||
```
|
||||
|
||||
## sage-4210 — new-test async citation fixture fix
|
||||
|
||||
Only C2-05b now inlines send/wait, awaits thread.messages before .find, and
|
||||
keeps the from_role assistant / to_role human assertions previously provided
|
||||
by ask. Comment names broker.mjs:4854 async citation hydration. Existing ask,
|
||||
old tests and production unchanged. This is Sage's own-new-test ruling under
|
||||
mos-1590; Dewey re-reviews the delta. Whole-file rerun once authorized, then
|
||||
the nine other files, whole bus/harness, M-B1 and clean-tree check on green.
|
||||
Each gets a fresh shell gXXX TMPDIR and absolute nice 19; any red stops.
|
||||
|
||||
## sage-4210 — resumed sequence, harness new-test red
|
||||
|
||||
On 8f1b045f4: whole CLI 15/15 pass, no skips (19.164 s); whole bus lookup
|
||||
11/11 pass, no skips (1.309 s). Own new harness file 1 pass, 3 fail, no skips
|
||||
(0.129 s); stopped sequence, returned slot in della-0050 before edits/reruns.
|
||||
Each invocation used fresh shell gXXX TMPDIR, 29 bytes. Runner was set to
|
||||
absolute nice 19 before first spawn; host subsequently reniced runner too,
|
||||
so timeout process observations were 19, -4, -4. Correct future driver to set
|
||||
absolute 19 in each child before exec, as well as parent. Priority-only change
|
||||
did not stop these runs. No claim that later timeout processes retained 19.
|
||||
|
||||
All three new harness failures are fixture validation: roles.bean.credentials
|
||||
must reference gitea (got none). Shared role() helper in this row creates bean
|
||||
without its required credential reference. No fixture/assertion/production edit
|
||||
or rerun yet; awaiting Sage's ruling. Seven upstream files, whole bus/harness
|
||||
suites and M-B1 not run. No full landing gate or in-review move.
|
||||
|
||||
Exact receipts:
|
||||
|
||||
```text
|
||||
{"number": 1, "name": "packages/cli/tests/thread-launch.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/cli/tests/thread-launch.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gBj5", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 19.164}
|
||||
{"number": 2, "name": "packages/bus/tests/library-lookups.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/bus/tests/library-lookups.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gD8X", "start_nice": 19, "observed_timeout_nice": -4, "exit": 0, "seconds": 1.309}
|
||||
{"number": 3, "name": "packages/harness/tests/library-tools.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/harness/tests/library-tools.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gt7o", "start_nice": 19, "observed_timeout_nice": -4, "exit": 1, "seconds": 0.129}
|
||||
```
|
||||
|
||||
Harness failure output:
|
||||
|
||||
```text
|
||||
✖ M3: per-role snapshots contain six names, or eight with the C2-08 web grant (6.427541ms)
|
||||
✔ every noncreative role gets lookups when a library is configured, not otherwise (6.837154ms)
|
||||
✖ lookup schemas require a universe even for get, include paging, and cannot select repo or commit (1.52147ms)
|
||||
✖ bundle flag reaches both harness tool manifests and creative prompt without generic tools (2.019053ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 3
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 99.111266
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/harness/tests/library-tools.test.mjs:19:1
|
||||
✖ M3: per-role snapshots contain six names, or eight with the C2-08 web grant (6.427541ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gt7o/mosaic-harness-biz-CZEENl/businesses/acme.json roles.bean.credentials must reference exactly the services its role definition needs (gitea; got none)
|
||||
at refuse (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/errors.mjs:12:9)
|
||||
at checkCredentialMap (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:51:5)
|
||||
at checkRoles (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:162:20)
|
||||
at validateBusinessDocument (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:251:34)
|
||||
at resolvedFor (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/helpers.mjs:46:20)
|
||||
at role (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:13:49)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:21:42)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/library-tools.test.mjs:38:1
|
||||
✖ lookup schemas require a universe even for get, include paging, and cannot select repo or commit (1.52147ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gt7o/mosaic-harness-biz-iAwXhO/businesses/acme.json roles.bean.credentials must reference exactly the services its role definition needs (gitea; got none)
|
||||
at refuse (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/errors.mjs:12:9)
|
||||
at checkCredentialMap (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:51:5)
|
||||
at checkRoles (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:162:20)
|
||||
at validateBusinessDocument (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:251:34)
|
||||
at resolvedFor (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/helpers.mjs:46:20)
|
||||
at role (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:13:49)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:39:40)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18) {
|
||||
exitCode: 2
|
||||
}
|
||||
|
||||
test at packages/harness/tests/library-tools.test.mjs:57:1
|
||||
✖ bundle flag reaches both harness tool manifests and creative prompt without generic tools (2.019053ms)
|
||||
Error [BusinessError]: /mnt/storage/scratch/tmp/gt7o/mosaic-harness-biz-c2N3Bv/businesses/acme.json roles.bean.credentials must reference exactly the services its role definition needs (gitea; got none)
|
||||
at refuse (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/errors.mjs:12:9)
|
||||
at checkCredentialMap (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:51:5)
|
||||
at checkRoles (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:162:20)
|
||||
at validateBusinessDocument (file:///mnt/storage/scratch/della-row224-r2/packages/business/src/business.mjs:251:34)
|
||||
at resolvedFor (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/helpers.mjs:46:20)
|
||||
at role (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:13:49)
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/harness/tests/library-tools.test.mjs:60:65)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18) {
|
||||
exitCode: 2
|
||||
}
|
||||
```
|
||||
|
||||
## sage-4213 — creative fixture credential reference
|
||||
|
||||
Only the new library-tools.test.mjs role() helper now gives bean its required
|
||||
gitea reference copied from coder, matching creative.test.mjs:27. No assertion,
|
||||
shared helper, old test or production change. One whole harness-file retry
|
||||
then seven upstream files, whole bus/harness, M-B1 and clean-tree check are
|
||||
cleared; no CLI or lookup rerun needed (both passed, unchanged). Each child
|
||||
sets absolute niceness 19 before exec, with fresh shell gXXX TMPDIR.
|
||||
|
||||
## sage-4213 — GREEN verification and M-B1 receipt
|
||||
|
||||
Verified candidate e4c0c8931; prior CLI/lookup passes on 8f1b045f4 remain
|
||||
applicable under sage-4213 (only harness fixture and receipts changed).
|
||||
Every run below used a fresh shell mktemp -d /mnt/storage/scratch/tmp/gXXX,
|
||||
29 bytes. Each child set absolute niceness 19 immediately before exec; timeout
|
||||
process observations were 19 for all ten runs and M-B1. Bus-suite Node later
|
||||
observed at nice 16, consistent with host ananicy policy; no stop for nice.
|
||||
No unexpected red, cancellation or skip in this cleared sequence.
|
||||
|
||||
Whole-file results:
|
||||
|
||||
| File or suite | Pass | Fail | Skip | Seconds |
|
||||
| --- | ---: | ---: | ---: | ---: |
|
||||
| packages/harness/tests/library-tools.test.mjs | 4 | 0 | 0 | 0.213 |
|
||||
| packages/bus/tests/room-check.test.mjs | 2 | 0 | 0 | 0.21 |
|
||||
| packages/plugins/tests/room-channel.test.mjs | 1 | 0 | 0 | 0.102 |
|
||||
| packages/supervisor/tests/pressure.test.mjs | 15 | 0 | 0 | 0.101 |
|
||||
| packages/supervisor/tests/purity.test.mjs | 1 | 0 | 0 | 0.102 |
|
||||
| packages/supervisor/tests/rules.test.mjs | 29 | 0 | 0 | 0.102 |
|
||||
| packages/webui/tests/room-browser.test.mjs | 9 | 0 | 0 | 6.801 |
|
||||
| packages/webui/tests/room-http.test.mjs | 7 | 0 | 0 | 0.183 |
|
||||
| bus-whole | 1011 | 0 | 0 | 30.46 |
|
||||
| harness-whole | 176 | 0 | 0 | 13.346 |
|
||||
|
||||
Earlier cleared whole CLI 15/15 (19.164 s), bus lookup 11/11 (1.309 s),
|
||||
zero skips. Whole bus includes lookup again; whole harness includes fixture
|
||||
file again. No full shell/landing gate run: every scripts/test-*.sh belongs
|
||||
to Sage's landing gate, not this CLEAR.
|
||||
|
||||
M-B1: temporarily replaced only lastError.at freshness term with -Infinity.
|
||||
Whole lookup file: 10 pass, 1 expected fail, no skips; killed at :178 attempted
|
||||
assertion (actual 2, expected 1, recent lastError prevents another sync).
|
||||
Restored exact original runtime bytes in finally. Afterwards git diff --stat
|
||||
and git status --porcelain both empty against committed e4c0c8931, before
|
||||
adding this receipt. Compared with named 6160b4981 the only later package
|
||||
changes are Sage-authorized async CLI fixture and harness credential fixture;
|
||||
no mutant or production change remains. No extra post-restore rerun.
|
||||
|
||||
Exact commands, TMPDIRs, exit codes and elapsed receipts:
|
||||
|
||||
```text
|
||||
{"number": 1, "name": "packages/harness/tests/library-tools.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/harness/tests/library-tools.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gct3", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.213}
|
||||
{"number": 2, "name": "packages/bus/tests/room-check.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/bus/tests/room-check.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/g0ki", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.21}
|
||||
{"number": 3, "name": "packages/plugins/tests/room-channel.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/plugins/tests/room-channel.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gyTq", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.102}
|
||||
{"number": 4, "name": "packages/supervisor/tests/pressure.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/supervisor/tests/pressure.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gK1n", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.101}
|
||||
{"number": 5, "name": "packages/supervisor/tests/purity.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/supervisor/tests/purity.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gFne", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.102}
|
||||
{"number": 6, "name": "packages/supervisor/tests/rules.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/supervisor/tests/rules.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gs9Y", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.102}
|
||||
{"number": 7, "name": "packages/webui/tests/room-browser.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/webui/tests/room-browser.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gP9P", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 6.801}
|
||||
{"number": 8, "name": "packages/webui/tests/room-http.test.mjs", "command": ["timeout", "-k", "10", "900", "node", "--test", "packages/webui/tests/room-http.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/gP9G", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 0.183}
|
||||
{"number": 9, "name": "bus-whole", "command": ["timeout", "-k", "10", "1800", "node", "--test", "packages/bus/tests/access-revoke.test.mjs", "packages/bus/tests/account-migration.test.mjs", "packages/bus/tests/address.test.mjs", "packages/bus/tests/agents.test.mjs", "packages/bus/tests/approval-binding.test.mjs", "packages/bus/tests/broker.test.mjs", "packages/bus/tests/business.test.mjs", "packages/bus/tests/channel-proof.test.mjs", "packages/bus/tests/chief-of-staff.test.mjs", "packages/bus/tests/citation-availability.test.mjs", "packages/bus/tests/citation-guard.test.mjs", "packages/bus/tests/citation-runtime.test.mjs", "packages/bus/tests/comment-cursors.test.mjs", "packages/bus/tests/comment-migration.test.mjs", "packages/bus/tests/console-decide.test.mjs", "packages/bus/tests/creative.test.mjs", "packages/bus/tests/credential-claim.test.mjs", "packages/bus/tests/credential-status.test.mjs", "packages/bus/tests/credential-store.test.mjs", "packages/bus/tests/credential-verbs.test.mjs", "packages/bus/tests/credentials.test.mjs", "packages/bus/tests/csuite-messaging.test.mjs", "packages/bus/tests/daily-quota-runtime.test.mjs", "packages/bus/tests/decision-close.test.mjs", "packages/bus/tests/decision-route.test.mjs", "packages/bus/tests/decisions.test.mjs", "packages/bus/tests/end-launch.test.mjs", "packages/bus/tests/fire-revokes-grants.test.mjs", "packages/bus/tests/gitea-grant-docker.test.mjs", "packages/bus/tests/gitea-grant.test.mjs", "packages/bus/tests/gitea-migration.test.mjs", "packages/bus/tests/grant-enroll.test.mjs", "packages/bus/tests/grant-executor.test.mjs", "packages/bus/tests/grant-host.test.mjs", "packages/bus/tests/grant-migration.test.mjs", "packages/bus/tests/grant-scope-runtime.test.mjs", "packages/bus/tests/grant-scope.test.mjs", "packages/bus/tests/hard-waits.test.mjs", "packages/bus/tests/host-run.test.mjs", "packages/bus/tests/host-tools.test.mjs", "packages/bus/tests/human.test.mjs", "packages/bus/tests/instances-verb.test.mjs", "packages/bus/tests/interpreter.test.mjs", "packages/bus/tests/land.test.mjs", "packages/bus/tests/launch-notice.test.mjs", "packages/bus/tests/launcher-released.test.mjs", "packages/bus/tests/library-credential.test.mjs", "packages/bus/tests/library-lookups.test.mjs", "packages/bus/tests/library-sync.test.mjs", "packages/bus/tests/library.test.mjs", "packages/bus/tests/lifecycle-revoke.test.mjs", "packages/bus/tests/lifecycle-verbs.test.mjs", "packages/bus/tests/limit-checks.test.mjs", "packages/bus/tests/limit-stop.test.mjs", "packages/bus/tests/limits-runtime.test.mjs", "packages/bus/tests/limits.test.mjs", "packages/bus/tests/message-citation.test.mjs", "packages/bus/tests/message-filter.test.mjs", "packages/bus/tests/messages.test.mjs", "packages/bus/tests/messaging.test.mjs", "packages/bus/tests/name-migration.test.mjs", "packages/bus/tests/names.test.mjs", "packages/bus/tests/peer-transport.test.mjs", "packages/bus/tests/peercred.test.mjs", "packages/bus/tests/plugins-verb.test.mjs", "packages/bus/tests/principal.test.mjs", "packages/bus/tests/process.test.mjs", "packages/bus/tests/processing-runtime.test.mjs", "packages/bus/tests/processing-timeout.test.mjs", "packages/bus/tests/processing-web.test.mjs", "packages/bus/tests/processing.test.mjs", "packages/bus/tests/project-await.test.mjs", "packages/bus/tests/project-files.test.mjs", "packages/bus/tests/project-runtime.test.mjs", "packages/bus/tests/publish.test.mjs", "packages/bus/tests/reload.test.mjs", "packages/bus/tests/replies.test.mjs", "packages/bus/tests/requirements.test.mjs", "packages/bus/tests/review.test.mjs", "packages/bus/tests/revoke-window.test.mjs", "packages/bus/tests/room-check.test.mjs", "packages/bus/tests/rooms.test.mjs", "packages/bus/tests/routes-verb.test.mjs", "packages/bus/tests/schema.test.mjs", "packages/bus/tests/session-clear.test.mjs", "packages/bus/tests/session-compactions.test.mjs", "packages/bus/tests/sessions.test.mjs", "packages/bus/tests/single-use.test.mjs", "packages/bus/tests/spec-migration.test.mjs", "packages/bus/tests/spec-review.test.mjs", "packages/bus/tests/spec-socket.test.mjs", "packages/bus/tests/spec.test.mjs", "packages/bus/tests/staff-request.test.mjs", "packages/bus/tests/state.test.mjs", "packages/bus/tests/store.test.mjs", "packages/bus/tests/task-page.test.mjs", "packages/bus/tests/task-read.test.mjs", "packages/bus/tests/tasks.test.mjs", "packages/bus/tests/tester-threads.test.mjs", "packages/bus/tests/tester-writes.test.mjs", "packages/bus/tests/tester.test.mjs", "packages/bus/tests/thread-claims.test.mjs", "packages/bus/tests/thread-migration.test.mjs", "packages/bus/tests/thread-shares.test.mjs", "packages/bus/tests/threads.test.mjs", "packages/bus/tests/transport.test.mjs", "packages/bus/tests/usage-broker.test.mjs", "packages/bus/tests/usage-migration.test.mjs", "packages/bus/tests/usage-runtime.test.mjs", "packages/bus/tests/usage.test.mjs", "packages/bus/tests/user-principal.test.mjs", "packages/bus/tests/viewer.test.mjs", "paLine truncated
|
||||
{"number": 10, "name": "harness-whole", "command": ["timeout", "-k", "10", "1800", "node", "--test", "packages/harness/tests/bundle.test.mjs", "packages/harness/tests/citation-runner.test.mjs", "packages/harness/tests/claude-gate.test.mjs", "packages/harness/tests/claude-session.test.mjs", "packages/harness/tests/creative.test.mjs", "packages/harness/tests/gate.test.mjs", "packages/harness/tests/git-tools.test.mjs", "packages/harness/tests/library-tools.test.mjs", "packages/harness/tests/mcp-server.test.mjs", "packages/harness/tests/pi-session.test.mjs", "packages/harness/tests/raise-domain.test.mjs", "packages/harness/tests/runner.test.mjs", "packages/harness/tests/skills.test.mjs", "packages/harness/tests/spec-tools.test.mjs", "packages/harness/tests/tools.test.mjs"], "TMPDIR": "/mnt/storage/scratch/tmp/g8Yn", "start_nice": 19, "observed_timeout_nice": 19, "exit": 0, "seconds": 13.346}
|
||||
{
|
||||
"mutant": "M-B1",
|
||||
"change": "drop lastError.at term from recentAttempt",
|
||||
"command": [
|
||||
"timeout",
|
||||
"-k",
|
||||
"10",
|
||||
"900",
|
||||
"node",
|
||||
"--test",
|
||||
"packages/bus/tests/library-lookups.test.mjs"
|
||||
],
|
||||
"TMPDIR": "/mnt/storage/scratch/tmp/g8my",
|
||||
"start_nice": 19,
|
||||
"observed_timeout_nice": 19,
|
||||
"exit": 1,
|
||||
"seconds": 1.359
|
||||
}
|
||||
$ git diff --stat
|
||||
(empty)
|
||||
$ git status --porcelain
|
||||
(empty)
|
||||
HEAD=e4c0c8931 (authorized fixture fix after 6160b4981)
|
||||
```
|
||||
|
||||
M-B1 output:
|
||||
|
||||
```text
|
||||
✔ all five agent verbs read the configured served commit through real queries (130.428728ms)
|
||||
✔ each verb refuses missing universe and unknown keys before host work (78.900515ms)
|
||||
✔ no library, missing host adapter and undeclared project refuse library-not-configured (149.899073ms)
|
||||
✔ reads need the current holder on each call; human/tester/reader are not agents (90.636537ms)
|
||||
✔ releasing a claim during an async lookup fences the response (60.938885ms)
|
||||
✔ a business library removed during an async read cannot leak its result (51.783488ms)
|
||||
✖ freshness boundary, unknown last fetch and failed fetch still use immutable served state (123.453393ms)
|
||||
✔ an unreadable sync state fails closed; a busy sync keeps serving the existing pin (89.763849ms)
|
||||
✔ a stale lookup sees the commit published by its sync, not the prior served commit (108.213675ms)
|
||||
✔ real socket awaits lookup results and rechecks rejected query arguments (152.413684ms)
|
||||
✔ runtime actually injects the lookup adapter, without exposing human library.file (157.701866ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 1
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1327.433364
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at packages/bus/tests/library-lookups.test.mjs:153:1
|
||||
✖ freshness boundary, unknown last fetch and failed fetch still use immutable served state (123.453393ms)
|
||||
AssertionError [ERR_ASSERTION]: recent lastError prevents another sync even though lastFetch is null
|
||||
|
||||
2 !== 1
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/della-row224-r2/packages/bus/tests/library-lookups.test.mjs:178:12)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 2,
|
||||
expected: 1,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
# Row 332 — P3 harness config from the model proxy
|
||||
|
||||
## Assignment and boundaries
|
||||
|
||||
2026-10-24: Fethry, thread 65673bd9-3bfa-412b-85d5-9b364187d8b6, branch fethry/row332, baseline c8a9bcf94. Sage assignment sage-4292; accepted brief plus full pi-routes amendment c8a9bcf94 (mos-1616). Gladstone first review, Dewey landing review; landing waits for P1/329 and P2/331, rebase on both and Sage's gate. No tests before Sage's CLEAR. No live proxy, host credential directories, real proxy data, management key, cutover, policy edits, or Codex harness changes.
|
||||
|
||||
Queue next in canonical checkout returned `nothing` during assignment. Starting row with op fethry-332-start met Sage's queue update lock; retry through the queue only. Root npm ci --ignore-scripts --no-audit --no-fund completed in this worktree (128 packages); no canonical node_modules use.
|
||||
|
||||
## Design note (line numbers at c8a9bcf94)
|
||||
|
||||
- packages/seat/src/session.mjs:242-247 defines ENV_ALLOW; keep it unchanged. sessionEnv:256-262 gains optional proxy context and drops CLAUDE_CODE_OAUTH_TOKEN, every passed ANTHROPIC_* variable, OPENAI_API_KEY, OPENROUTER_API_KEY and ZAI_API_KEY before adding harness env. Non-proxy results retain current behavior exactly.
|
||||
- packages/plugins/harness/claude-code/index.mjs:56-67 preflight accepts the session proxy credential; env:119-126 sets ANTHROPIC_BASE_URL and ANTHROPIC_AUTH_TOKEN from that context.
|
||||
- packages/plugins/harness/pi/index.mjs:33-36 preflight requires its own provider credential or proxy key, never a Claude OAuth token. env:63-70 sets only the session's proxy key as ANTHROPIC_API_KEY and selects the generated provider. Fresh run-local home/.pi/agent/models.json is private (0600; parent directories 0700). Every apiKey is the literal reference $ANTHROPIC_API_KEY; recursively forbid values beginning with !. Nothing stores the key in models.json or a manifest.
|
||||
- packages/harness/src/bundle.mjs:313 buildBundle accepts transient proxy context; :379 harness.env call needs it plus run location and launch.models. Context must never be added to session metadata, source digests, or manifest. The run-local file regenerates on each launch.
|
||||
- packages/cli/src/launcher.mjs:391,400 preflight, :412 account checks, :529 bundle creation and :592 child env must agree on the same session credential. Coordinate its acquisition with P2; build/test now against the specified { baseUrl, key } stand-in, not a management client or a key-store implementation.
|
||||
|
||||
Exact business setting keys remain launch.models.<selection>.provider, launch.models.<selection>.model, launch.account.name and launch.account.credential (launch.mjs:12-24,81-89). Provider already accepts proxy. Proposed account credential sentinel is the literal proxy (add alongside existing uppercase environment-set syntax), with launch.account.name a human-readable id such as proxy. No new fields. Existing provider anthropic selects the Anthropic route, zai selects GLM, and proxy selects the stack-owned GPT route; send this interpretation to Sage before implementation because the brief doesn't specify route encoding for provider proxy.
|
||||
|
||||
## Build-time API choice and pinned binary
|
||||
|
||||
P1's committed magica/row329 has CLIProxyAPI 8.0.42 in packages/model-proxy/PIN. Use that commit's fetchPinned({dataRoot:<fresh temporary root>}) implementation, copied from Git objects to this seat's temp tooling directory with its relative dependencies and PIN, until P1 lands. Never inspect another seat's worktree/scratch. It fetches only pinned GitHub release assets and checks archive, executable and panel hashes. No production instance config is needed and nothing under a real dataRoot/proxy is read.
|
||||
|
||||
After CLEAR, try actual pinned pi requests with both openai-responses and openai-completions through that binary under unshare -rn, against stand-in upstreams with made-up credentials under 24 characters. Record version, API path, safe request fields, response status and turn result. Choose one shape once from those results and hard-code it; no runtime fallback. Real Codex OAuth cannot be simulated as a real authenticated turn: first real Codex turn remains row209 phase A step7. Distinguish the offline compatibility evidence from that deferred real-auth proof.
|
||||
|
||||
The separate offline GLM acceptance script (outside scripts/test-*.sh) uses pinned pi and proxy, checks the upstream sees the model id from /v1/models, and records whether thinkingFormat/zaiToolStream-derived fields survive translation. All configs/auth placeholders belong to its fresh temp root.
|
||||
|
||||
## Implementation and test sequence
|
||||
|
||||
1. Add tests first for env isolation, Claude proxy env and preflight, pi credential refusal and each generated route/file invariant, and business account acceptance. Do not run until CLEAR; first new-test red is reported afterward, old-test red or second red is reported before changing anything.
|
||||
2. Implement harness/bundle/env handling against P2's transient interface. Add launcher boundary integration once its exact P2 acquisition seam is agreed. Keep secrets out of session.json and bundle manifests.
|
||||
3. With CLEAR, run the offline API-shape probe and GLM turn, then whole touched test files. Before each whole-file run report the list derived by parsing imports and traversing local dependencies; include harness plug-in dynamic loading explicitly. packages/cli/tests/case1-accept.test.mjs remains excluded without Mos approval.
|
||||
4. Kill named mutants; restore production source and verify. Record exact output and nice. Use a fresh shell mktemp -d /mnt/storage/scratch/tmp/XXXXXX TMPDIR for each run, absolute nice 19, and timeout -k 10 for each scripts/test-*.sh. No hidden skips; Sage owns the package/script landing gate.
|
||||
5. After dependency landings, rebase, complete review and gate stages, update queue only through its CLI, and append session/build receipts. Push only fethry/* when ready for authorized review; no force-push or attribution trailers.
|
||||
|
||||
Planned tests:
|
||||
- Each forbidden environment variable is absent in proxy mode; all existing allowed env is identical without a proxy; two launches receive distinct own keys and no other agent's key.
|
||||
- Claude env has exactly the supplied proxy base URL/auth token; proxy credential satisfies preflight without host upstream credentials.
|
||||
- pi refuses no-session-credential for no key or Claude OAuth alone, accepts an own provider key and a proxy key.
|
||||
- models.json covers Anthropic anthropic-messages at root URL, GLM openai-completions at /v1, and a stack-owned GPT provider at /v1 with the chosen API; routes use configured model ids.
|
||||
- Every apiKey is $ANTHROPIC_API_KEY; no key value occurs in file; recursively no value starts with !; mode 0600 and private parents; next launch uses changed port/models/key reference with no stale content.
|
||||
- Business accepts provider proxy and account credential proxy while preserving existing valid and invalid account behavior.
|
||||
- Launcher/bundle integration keeps context transient and passes only the launch's own key.
|
||||
- Every new refusal code gets a separate test.
|
||||
- Offline GLM pinned-pi turn and z.ai compat observation; both GPT API-shape requests recorded with limitations.
|
||||
|
||||
Named gate mutants:
|
||||
1. Proxy mode still passes CLAUDE_CODE_OAUTH_TOKEN (original brief).
|
||||
2. models.json receives the key value instead of $ANTHROPIC_API_KEY (amendment).
|
||||
|
||||
No tests have run. API-shape choice is pending evidence, not claimed.
|
||||
|
||||
## Progress 22:27Z
|
||||
|
||||
Placed initial RED tests (not executed): packages/seat/tests/proxy-session-env.test.mjs and packages/plugins/tests/proxy-harness.test.mjs. These exercise real sessionEnv and loader-loaded harness exports. Production source remains untouched pending the first authorized RED run. Removed no assertions and ran no tests. Correct baseline bundle harness.env line is 376 (not 379 in the initial design note).
|
||||
|
||||
Sage reporting attempts fethry-0001 (including same-key retry) and fethry-0002 all returned `T3 GET /api/orchestration/threads/<sage> timed out after 30s`. Delivery is unconfirmed. Queue move retry also met a Sage queue update lock (sage-333-reviewers-1024a); no bypass.
|
||||
|
||||
Additional test-first placement: packages/business/tests/proxy-launch.test.mjs covers existing proxy model acceptance, proposed proxy account credential literal, preserved environment account forms, and rejection of mixed/literal credential strings. Pending Sage's route/account interpretation and CLEAR; no production edits or tests run.
|
||||
|
||||
## Import audit and queue status
|
||||
|
||||
The non-evaluating import audit uses Node vm.SourceTextModule.dependencySpecifiers, not a text-path grep, plus explicit computed-import edges from the plug-in loader to both harness modules. Source: import-run-list.mjs; output: IMPORT-RUN-LIST.txt (211 entries, including the explicitly EXCLUDED case1-accept file). This is a candidate impact list for the planned source changes, not an executed test list. Before a whole-file run, report the actual authorized subset to Sage. Parsing executes no repository modules and runs no tests. The first audit invocation had a path-depth setup error (ENOENT); corrected the audit's own repository root and regenerated successfully.
|
||||
|
||||
At 22:28:19Z the canonical start operation returned `queue: only the owner (sage) may start row 332`. Per assignment, wait at least two minutes before repeating this same op. No queue state was edited manually.
|
||||
|
||||
## Design correction after D9 re-read
|
||||
|
||||
The initial route-encoding proposal (provider proxy for GPT only) is not sufficient: design D9 explicitly defines provider proxy as any model through the instance proxy. Do not implement the earlier proposal. Two viable encodings need resolving before route-dependent tests/production: exact model-id lookup in pinned pi's provider catalogs (no new business fields, but refuses proxy-only aliases/unknown models), or explicit route metadata in launch.models (additive schema, needs Sage to confirm it is within the approved brief). Recommend exact catalog membership if all intended configured model ids are in pinned pi; otherwise ask Sage to define/approve an explicit route field. Never guess by model prefix or silently choose a wire API. Independent env/preflight tests already placed remain valid.
|
||||
|
||||
Reporting recovered: fethry-0004 was accepted by t3_send_message at approximately 22:29:51Z. It delivered the D9 route correction, route-encoding/P2-seam question, import-manifest location and no-tests status to Sage. Earlier failed-message deliveries remain unconfirmed.
|
||||
|
||||
22:32:18Z: The start retry issued at 22:30:40Z (over two minutes after first owner refusal) also exited 2: `queue: only the owner (sage) may start row 332`. This is an ownership boundary; row was not started and no code/test execution proceeds through it. Sage's assignment must take effect before the same op can succeed. The full design report fethry-0005 again timed out; fethry-0004 is the confirmed delivered report. git diff --check passed; no test, probe, commit or push has run.
|
||||
|
||||
## Route ruling accepted — sage-4293, 22:37Z
|
||||
|
||||
Sage settled explicit metadata, superseding both initial encoding proposals and the catalog proposal. Exact new setting: launch.models.<selection>.route; permitted values anthropic, glm, codex, required only when provider is proxy. Nonproxy entries still allow provider/model only. Missing route refuses proxy-route-missing; present unknown/nonstring route refuses proxy-route-unknown. Both get independent code assertions; each valid value gets a test using a proxy-only model id absent from any catalog. The GPT route's generated pi provider will be mosaic-proxy-codex, never openai-codex. Its wire API remains a build-time choice after the pinned-binary probe. P2 passes only {baseUrl,key}; no route belongs to that seam.
|
||||
|
||||
A key persistence hazard found during source inspection: launcher.mjs:571 writes bundle.env as session.harnessEnv. Adding the key to harness.env must not retain this behavior for credentials. P3 will keep generated key env transient for spawnSession and omit those credential values from serialized harnessEnv. runner.mjs:390 already inherits the spawn environment before applying nonsecret harnessEnv. Test the on-disk session/bundle boundary, not only models.json.
|
||||
|
||||
The old launch-settings.test.mjs already uses provider proxy fixtures without route. Update those fixtures to explicit anthropic routes before any full-file run; preserve their existing authorization assertions. This is the new approved validation shape, not permission to loosen tests. No tests have run and no production edits exist.
|
||||
|
||||
Additional test-first placement after route ruling: packages/harness/tests/proxy-bundle.test.mjs exercises real buildBundle with explicit provider proxy entries and proxy-only model ids, three generated providers, run-local file mode, environment references, recursive no-! values, no key in bundle files/manifest, fresh model/port/key on next launch. The exact GPT API assertion must be added from the authorized pinned-binary probe before this test file's first run; it deliberately makes no unverified wire-shape claim now. No skip is introduced.
|
||||
|
||||
Canonical queue show returned owner sage and after=331 at 22:40Z; it also warned `view unknown` for the rendered table. No repair or queue bypass attempted; wait for Sage's announced batch. Queue metadata identifies shared proxy Gitea issue #1830.
|
||||
|
||||
## STOPPED — mos-1630, 2026-10-24T22:48Z
|
||||
|
||||
Jason's relayed crash instruction supersedes active work: no long-job restart, commit/push safe documentation only, handoff in this worktree, no other thread messages, then stop. See HANDOFF.md for complete state, next steps, uncommitted tests/audit script, and reruns needed. No tests or probes ever ran; no production implementation exists. Latest design is explicit launch.models route metadata per sage-4293; earlier catalog/GPT-only proposals are superseded. The original 211-entry import manifest predates the fourth new test file and is historical, not run authorization. Full inline resends still hit T3 timeouts. Crash commit will exclude every unrun test and the audit script.
|
||||
@@ -0,0 +1,51 @@
|
||||
# STOPPED — row 332 (P3), T3 crash handoff
|
||||
|
||||
Stopped on 2026-10-24 under Mos mos-1630, relaying Jason's instruction: do not restart long jobs; commit/push safe work, leave a handoff, then stop. No further thread messages, tests, probes, queue operations, or implementation will run in this session.
|
||||
|
||||
## Branch and completed work
|
||||
|
||||
- Worktree: /home/jwoltje/.t3/worktrees/mosaic-stack/fethry-row332
|
||||
- Branch: fethry/row332, based on refactor c8a9bcf94. No implementation commits, rebases, merges or production source edits.
|
||||
- Read P3 brief, rules, full pi amendment, gate template, design and named source files. Detailed notes in BUILD.md beside this handoff.
|
||||
- Root npm ci --ignore-scripts --no-audit --no-fund completed in this worktree only (128 packages). No canonical node_modules used.
|
||||
- Initial test files written before implementation; none executed. Sage has not sent CLEAR. No proxy fetch, binary probe, offline turn, mutant, or suite has run.
|
||||
- No real proxy, management key, host auth directory or live instance was accessed. No production service was started/stopped.
|
||||
- Canonical queue next returned nothing during the assignment. Queue start op fethry-332-start refused on locks, then owner sage, including a retry after two minutes. Latest completed queue show still had owner sage, state briefed, after 331; it also warned that the rendered table was unknown. Did not repair or bypass it. Sage sage-4293 says an assign/after=none batch is running. Queue metadata identifies shared Gitea issue #1830.
|
||||
|
||||
## Latest accepted design
|
||||
|
||||
Sage sage-4293 supersedes all earlier route proposals: explicit route metadata, never catalog membership or model-prefix inference. Selected spelling: launch.models.<selection>.route = anthropic | glm | codex, required only with provider proxy. Missing route: proxy-route-missing; unknown/nonstring route: proxy-route-unknown. Other providers keep provider/model-only entries. Proposed launch.account.credential literal proxy; existing name/credential field names retained. GPT generated provider: mosaic-proxy-codex, never openai-codex. GPT wire API is still undecided pending the authorized pinned-binary requests. P2's seam stays exactly { baseUrl, key }.
|
||||
|
||||
Important implementation finding: launcher.mjs:571 serializes bundle.env into session.harnessEnv. Harness env will contain the session key in proxy mode, so credentials must instead reach spawnSession through transient environment variables and be omitted from serialized harnessEnv. runner.mjs:390 already inherits the spawn environment. Preserve ENV_ALLOW; filter instance provider variables in proxy mode only.
|
||||
|
||||
## Uncommitted work, intentionally left for the next session
|
||||
|
||||
These are unrun, incomplete tests/audit code, excluded from the documentation-only crash commit:
|
||||
|
||||
- packages/business/tests/proxy-launch.test.mjs — explicit routes and refusal codes, proxy account credential, existing nonproxy/account behavior.
|
||||
- packages/plugins/tests/proxy-harness.test.mjs — Claude env/preflight, own-key isolation, pi preflight and OAuth-only refusal.
|
||||
- packages/seat/tests/proxy-session-env.test.mjs — instance credential filtering and unchanged nonproxy env.
|
||||
- packages/harness/tests/proxy-bundle.test.mjs — real bundle generation, all three providers, key reference/no-key/no-! invariants, file modes and launch freshness. Exact GPT API assertion deliberately pending probe evidence; add before its first run.
|
||||
- agents/fethry/work/queue-332/import-run-list.mjs — non-evaluating static import audit using vm.SourceTextModule plus explicit computed loader edges. It ran successfully after correcting its own root-depth setup error. It is not a test runner.
|
||||
|
||||
IMPORT-RUN-LIST.txt is a historical 211-entry candidate impact list, generated before proxy-bundle.test.mjs was added. It includes case1-accept as explicitly EXCLUDED (mos-1612). Regenerate the list after resuming and report the actual authorized run list inline before any whole-file run. Do not treat the historical manifest as a current run authorization.
|
||||
|
||||
## Reporting status
|
||||
|
||||
T3 reporting repeatedly failed during GET with a 30-second timeout. fethry-0004 was confirmed delivered. Some later short numeric keys returned reused:true unexpectedly; do not rely on them as delivery evidence. A row-scoped detailed design resend and the inline 211-line import manifest also timed out, including the manifest's same-key retry. Sage may not have those messages. This handoff and BUILD.md are being pushed so they can be read with git show; no new thread message is sent under mos-1630.
|
||||
|
||||
## Next session, after restart authorization
|
||||
|
||||
1. Read this handoff/BUILD.md and recover queue state from canonical checkout. Wait for owner fethry and no after, then retry the same start op. Never bypass a refusal.
|
||||
2. Recover Sage's slot status. Still NO tests or probes until Sage sends CLEAR. No job needs restarting merely to preserve current state.
|
||||
3. Review the four uncommitted test files. Add the pinned-proxy API probe and exact GPT assertion before the first relevant run. Initial RED has not happened; production implementation must follow the authorized first RED.
|
||||
4. Rebuild the import-derived run list and send it inline or in a reviewed pushed commit before runs. case1-accept remains off without Mos approval. Existing launch-settings.test.mjs proxy fixtures need explicit route metadata with their authorization assertions preserved; no old-test red has occurred.
|
||||
5. Use P1's committed fetchPinned code under an owned fresh temporary root (8.0.42 at the inspected magica/row329 revision), never live proxy assets or another seat's scratch. After CLEAR, try pi's responses and completions requests under unshare -rn against stand-in upstream, record evidence, choose one GPT API at build time. Separately prove offline GLM and record z.ai compat survival. Real Codex auth proof remains row209 phase A step7.
|
||||
6. Implement transient P2 context, model validation/route propagation, harness config, preflight, spawn env/serialization separation and tests. Named mutants: OAuth token leak; literal key in models.json. No policy or real credentials involved.
|
||||
7. Land only after 329 and 331 land, rebasing on both, Gladstone first review, Dewey landing review and Sage's required gate. No force-push or merge authority is added by the crash instruction.
|
||||
|
||||
All future tests use a fresh shell-created short TMPDIR, absolute nice 19, and timeout -k 10 for scripts/test-*.sh. First new-test red is reportable afterward; old-test red or a second red goes to Sage before changes/reruns. No test outcomes are claimed here.
|
||||
|
||||
## Crash commit
|
||||
|
||||
Only documentation and the historical plain-text import manifest are committed. The final STOPPED reply records the exact pushed commit. The uncommitted files above remain deliberately available in this worktree because they have never run and test-slot authorization has not arrived.
|
||||
@@ -0,0 +1,211 @@
|
||||
packages/bus/tests/access-revoke.test.mjs
|
||||
packages/bus/tests/broker.test.mjs
|
||||
packages/bus/tests/channel-proof.test.mjs
|
||||
packages/bus/tests/chief-of-staff.test.mjs
|
||||
packages/bus/tests/citation-guard.test.mjs
|
||||
packages/bus/tests/citation-runtime.test.mjs
|
||||
packages/bus/tests/creative.test.mjs
|
||||
packages/bus/tests/credential-verbs.test.mjs
|
||||
packages/bus/tests/grant-scope-runtime.test.mjs
|
||||
packages/bus/tests/host-run.test.mjs
|
||||
packages/bus/tests/land.test.mjs
|
||||
packages/bus/tests/library-credential.test.mjs
|
||||
packages/bus/tests/library-lookups.test.mjs
|
||||
packages/bus/tests/library-sync.test.mjs
|
||||
packages/bus/tests/library.test.mjs
|
||||
packages/bus/tests/lifecycle-revoke.test.mjs
|
||||
packages/bus/tests/limit-checks.test.mjs
|
||||
packages/bus/tests/messages.test.mjs
|
||||
packages/bus/tests/processing-runtime.test.mjs
|
||||
packages/bus/tests/project-runtime.test.mjs
|
||||
packages/bus/tests/reload.test.mjs
|
||||
packages/bus/tests/tasks.test.mjs
|
||||
packages/bus/tests/tester-writes.test.mjs
|
||||
packages/bus/tests/tester.test.mjs
|
||||
packages/bus/tests/usage-runtime.test.mjs
|
||||
packages/bus/tests/usage.test.mjs
|
||||
packages/bus/tests/workflow-roles.test.mjs
|
||||
packages/business/tests/access-levels.test.mjs
|
||||
packages/business/tests/access.test.mjs
|
||||
packages/business/tests/agent-identity.test.mjs
|
||||
packages/business/tests/assistant.test.mjs
|
||||
packages/business/tests/business.test.mjs
|
||||
packages/business/tests/case1-business-file.test.mjs
|
||||
packages/business/tests/chief-library-credentials.test.mjs
|
||||
packages/business/tests/chief-of-staff.test.mjs
|
||||
packages/business/tests/cli.test.mjs
|
||||
packages/business/tests/credentials.test.mjs
|
||||
packages/business/tests/csuite.test.mjs
|
||||
packages/business/tests/grant-scope.test.mjs
|
||||
packages/business/tests/hard-waits.test.mjs
|
||||
packages/business/tests/hosts.test.mjs
|
||||
packages/business/tests/instance-credential-transitions.test.mjs
|
||||
packages/business/tests/instance-settings.test.mjs
|
||||
packages/business/tests/launch-settings.test.mjs
|
||||
packages/business/tests/library-projects-protected.test.mjs
|
||||
packages/business/tests/library-seats.test.mjs
|
||||
packages/business/tests/lifecycle-settings.test.mjs
|
||||
packages/business/tests/optional-tracker.test.mjs
|
||||
packages/business/tests/plugin-settings.test.mjs
|
||||
packages/business/tests/project-repo.test.mjs
|
||||
packages/business/tests/project.test.mjs
|
||||
packages/business/tests/proxy-launch.test.mjs
|
||||
packages/business/tests/record-grants.test.mjs
|
||||
packages/business/tests/reply-limits.test.mjs
|
||||
packages/business/tests/resolve.test.mjs
|
||||
packages/business/tests/role.test.mjs
|
||||
packages/business/tests/token-limit-settings.test.mjs
|
||||
packages/business/tests/vars.test.mjs
|
||||
packages/business/tests/workflow-roles.test.mjs
|
||||
packages/cli/tests/access-boot.test.mjs
|
||||
packages/cli/tests/access.test.mjs
|
||||
packages/cli/tests/app.test.mjs
|
||||
packages/cli/tests/boot-status.test.mjs
|
||||
EXCLUDED (mos-1612) packages/cli/tests/case1-accept.test.mjs
|
||||
packages/cli/tests/case1-hostcheck.test.mjs
|
||||
packages/cli/tests/case2-install.test.mjs
|
||||
packages/cli/tests/case2-persona-import.test.mjs
|
||||
packages/cli/tests/case2-unit.test.mjs
|
||||
packages/cli/tests/channel-cli.test.mjs
|
||||
packages/cli/tests/channel-recovery.test.mjs
|
||||
packages/cli/tests/cli.test.mjs
|
||||
packages/cli/tests/compactions-launch.test.mjs
|
||||
packages/cli/tests/config.test.mjs
|
||||
packages/cli/tests/credentials.test.mjs
|
||||
packages/cli/tests/federation-acceptance.test.mjs
|
||||
packages/cli/tests/follow.test.mjs
|
||||
packages/cli/tests/git-socket.test.mjs
|
||||
packages/cli/tests/grants-scope.test.mjs
|
||||
packages/cli/tests/grants.test.mjs
|
||||
packages/cli/tests/host.test.mjs
|
||||
packages/cli/tests/hosts.test.mjs
|
||||
packages/cli/tests/idle-stop-race.test.mjs
|
||||
packages/cli/tests/instances.test.mjs
|
||||
packages/cli/tests/launch-account.test.mjs
|
||||
packages/cli/tests/launch-limit-checks.test.mjs
|
||||
packages/cli/tests/launch-limits.test.mjs
|
||||
packages/cli/tests/launch-routes.test.mjs
|
||||
packages/cli/tests/launcher.test.mjs
|
||||
packages/cli/tests/lifecycle.test.mjs
|
||||
packages/cli/tests/limits-tokens.test.mjs
|
||||
packages/cli/tests/limits.test.mjs
|
||||
packages/cli/tests/mark-read.test.mjs
|
||||
packages/cli/tests/names.test.mjs
|
||||
packages/cli/tests/notifier.test.mjs
|
||||
packages/cli/tests/optional-tracker.test.mjs
|
||||
packages/cli/tests/other-tree.test.mjs
|
||||
packages/cli/tests/processing-cli.test.mjs
|
||||
packages/cli/tests/project-name-repo.test.mjs
|
||||
packages/cli/tests/project.test.mjs
|
||||
packages/cli/tests/publish-socket.test.mjs
|
||||
packages/cli/tests/reply-limits.test.mjs
|
||||
packages/cli/tests/revoke-window.test.mjs
|
||||
packages/cli/tests/session-clear.test.mjs
|
||||
packages/cli/tests/session-cli.test.mjs
|
||||
packages/cli/tests/session-launch.test.mjs
|
||||
packages/cli/tests/stack-copy.test.mjs
|
||||
packages/cli/tests/stack-instances.test.mjs
|
||||
packages/cli/tests/tester-reads.test.mjs
|
||||
packages/cli/tests/tester-threads.test.mjs
|
||||
packages/cli/tests/tester.test.mjs
|
||||
packages/cli/tests/thread-cli.test.mjs
|
||||
packages/cli/tests/thread-launch.test.mjs
|
||||
packages/cli/tests/thread-revoke.test.mjs
|
||||
packages/cli/tests/trackers-boot.test.mjs
|
||||
packages/cli/tests/transport.test.mjs
|
||||
packages/cli/tests/usage.test.mjs
|
||||
packages/cli/tests/verbs.test.mjs
|
||||
packages/control-board/tests/creative-hidden.test.mjs
|
||||
packages/control-board/tests/discord.test.mjs
|
||||
packages/control-board/tests/serve.test.mjs
|
||||
packages/conversation/tests/flows.test.mjs
|
||||
packages/harness/tests/bundle.test.mjs
|
||||
packages/harness/tests/claude-gate.test.mjs
|
||||
packages/harness/tests/claude-session.test.mjs
|
||||
packages/harness/tests/creative.test.mjs
|
||||
packages/harness/tests/gate.test.mjs
|
||||
packages/harness/tests/git-tools.test.mjs
|
||||
packages/harness/tests/library-tools.test.mjs
|
||||
packages/harness/tests/mcp-server.test.mjs
|
||||
packages/harness/tests/pi-session.test.mjs
|
||||
packages/harness/tests/raise-domain.test.mjs
|
||||
packages/harness/tests/runner.test.mjs
|
||||
packages/harness/tests/skills.test.mjs
|
||||
packages/harness/tests/spec-tools.test.mjs
|
||||
packages/harness/tests/tools.test.mjs
|
||||
packages/plugins/tests/channel.test.mjs
|
||||
packages/plugins/tests/harness-compactions.test.mjs
|
||||
packages/plugins/tests/harness.test.mjs
|
||||
packages/plugins/tests/loader.test.mjs
|
||||
packages/plugins/tests/proxy-harness.test.mjs
|
||||
packages/plugins/tests/searxng.test.mjs
|
||||
packages/processing-control/tests/task.test.mjs
|
||||
packages/seat/tests/compactions-budget.test.mjs
|
||||
packages/seat/tests/compactions-ledger.test.mjs
|
||||
packages/seat/tests/compactions.test.mjs
|
||||
packages/seat/tests/proxy-session-env.test.mjs
|
||||
packages/seat/tests/sandbox.test.mjs
|
||||
packages/seat/tests/session-workspace.test.mjs
|
||||
packages/seat/tests/session.test.mjs
|
||||
packages/tasks/tests/fields.test.mjs
|
||||
packages/webui/tests/agents-browser.test.mjs
|
||||
packages/webui/tests/agents.test.mjs
|
||||
packages/webui/tests/assistant.test.mjs
|
||||
packages/webui/tests/board-held.test.mjs
|
||||
packages/webui/tests/browser-edge.test.mjs
|
||||
packages/webui/tests/browser.test.mjs
|
||||
packages/webui/tests/bus-browser.test.mjs
|
||||
packages/webui/tests/bus.test.mjs
|
||||
packages/webui/tests/channel-chat.test.mjs
|
||||
packages/webui/tests/chat-browser.test.mjs
|
||||
packages/webui/tests/chat-instances.test.mjs
|
||||
packages/webui/tests/chat-replies-browser.test.mjs
|
||||
packages/webui/tests/chat.test.mjs
|
||||
packages/webui/tests/conversation.test.mjs
|
||||
packages/webui/tests/decide-browser.test.mjs
|
||||
packages/webui/tests/decide-sso.test.mjs
|
||||
packages/webui/tests/discord.test.mjs
|
||||
packages/webui/tests/docs-browser.test.mjs
|
||||
packages/webui/tests/editor-browser.test.mjs
|
||||
packages/webui/tests/editor.test.mjs
|
||||
packages/webui/tests/granted-user.test.mjs
|
||||
packages/webui/tests/history-return-flow.test.mjs
|
||||
packages/webui/tests/instances-browser.test.mjs
|
||||
packages/webui/tests/instances.test.mjs
|
||||
packages/webui/tests/library-cli.test.mjs
|
||||
packages/webui/tests/library-routes.test.mjs
|
||||
packages/webui/tests/limits-browser.test.mjs
|
||||
packages/webui/tests/limits-route.test.mjs
|
||||
packages/webui/tests/messages.test.mjs
|
||||
packages/webui/tests/pinned-copy.test.mjs
|
||||
packages/webui/tests/preview.test.mjs
|
||||
packages/webui/tests/processing-browser.test.mjs
|
||||
packages/webui/tests/processing.test.mjs
|
||||
packages/webui/tests/project-file.test.mjs
|
||||
packages/webui/tests/proof-cli.test.mjs
|
||||
packages/webui/tests/proof.test.mjs
|
||||
packages/webui/tests/reads.test.mjs
|
||||
packages/webui/tests/relaunch.test.mjs
|
||||
packages/webui/tests/return-flow.test.mjs
|
||||
packages/webui/tests/room-browser.test.mjs
|
||||
packages/webui/tests/room-http.test.mjs
|
||||
packages/webui/tests/routes-browser.test.mjs
|
||||
packages/webui/tests/routes.test.mjs
|
||||
packages/webui/tests/send-browser.test.mjs
|
||||
packages/webui/tests/send.test.mjs
|
||||
packages/webui/tests/serve.test.mjs
|
||||
packages/webui/tests/settings-form.test.mjs
|
||||
packages/webui/tests/settings-write.test.mjs
|
||||
packages/webui/tests/shell.test.mjs
|
||||
packages/webui/tests/sso-browser.test.mjs
|
||||
packages/webui/tests/sso.test.mjs
|
||||
packages/webui/tests/tester-browser.test.mjs
|
||||
packages/webui/tests/tester-cli.test.mjs
|
||||
packages/webui/tests/tester-listener.test.mjs
|
||||
packages/webui/tests/thread-claims-browser.test.mjs
|
||||
packages/webui/tests/thread-claims.test.mjs
|
||||
packages/webui/tests/thread-sharing.test.mjs
|
||||
packages/webui/tests/usage-browser.test.mjs
|
||||
packages/webui/tests/usage.test.mjs
|
||||
packages/webui/tests/user-option-swap.test.mjs
|
||||
packages/webui/tests/views.test.mjs
|
||||
@@ -1685,3 +1685,7 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-24 | Fethry14f80e3c | Row328 T9 timer race (#1826) | New fethry/row328 worktree on2ce476d54, owner start rev3992 opfethry-0037. Test-only +4/-3 under Mos1594, no assertion/product changes. Local root npm ci, no links. Static-only stress/M1 preparation and untested push; waiting for Sage CLEAR, then Boyd/Dewey review.
|
||||
|
||||
2026-10-24 | Fethry14f80e3c | Row328 T9 race acceptance evidence | sage4214 on06c57a7db:400 concurrent M1 attempts yield26 expected failures, fixed400/400, whole file6/6; exact restoration/clean checks and process sweep empty. All801 receipts/nice/load preserved. Manifest1e010468 one approved+4/-3 old test, no assertion/product change. Earlier aborted and serial non-repro receipts separate. Review handoff Boyd/Dewey, Sage landing gate pending.
|
||||
|
||||
2026-10-24 | Sage 1ef1e4f8 | Landings 257, 266, 224, 328; model-proxy rows 329-333 filed | 257 73692e1bb, 266 3e5041b43, 224 5e77cdd09, 328 8bb5b3c73 landed on GREEN gates. Rows 329-333 briefed from 7fe32d722 (mos-1600/1601); 330 blocked on Jason's subdomain answer (mos-1602). Magica holds the slot for 329's builder gate.
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 P3 proxy harness configuration at c8a9bcf94 | Started recovery and design; npm ci in isolated worktree; tests held for Sage CLEAR; no live proxy or credentials accessed.
|
||||
2026-10-24 | fethry (65673bd9-3bfa-412b-85d5-9b364187d8b6) | row332 crash stop (mos-1630) | Documentation-only handoff on fethry/row332; four unrun test files and import audit script left uncommitted; no tests/probes/production edits; no jobs restarted or other threads messaged.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Brief: the Case 2 proxy unit, so a hub installs P1's proxy unit
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1616 ruling 1, which approved this row. Mos ruled that Sage
|
||||
approves the brief if it only adds P1 item 7's unit to `scripts/case2-install.sh` and
|
||||
`scripts/case2-unit.sh`: the same unit text, the same sha256 refusal and the Wants/After lines. Anything
|
||||
beyond that goes to Mos. Read the rows doc's P1 brief (`docs/plans/2026-10-24_model-proxy-rows.md`, P1
|
||||
item 7) and row 209's brief (`docs/plans/2026-10-24_row209-hub-54-window.md`, entry check 2) first. The
|
||||
rows doc's "Rules for every row" apply here unchanged. Line numbers are at origin/refactor 58b2bbb3a, and
|
||||
P1's paths are at its round 2 candidate magica/row329 86cb5c221. Check both once P1 lands.
|
||||
|
||||
## Brief: Case 2 proxy unit in case2-unit.sh and case2-install.sh
|
||||
|
||||
**Why.** A Case 2 hub (.54) installs through `scripts/case2-install.sh` (as the stack user) and
|
||||
`scripts/case2-unit.sh` (as root, system units). P1 adds `mosaic-proxy.service` only to the user-unit path
|
||||
(`scripts/instance.sh`, `scripts/proxy-service.sh`, `scripts/bus-service.sh`). `case2-unit.sh` writes the
|
||||
bus and Console units and nothing else (:3-5, :56-57), and its bus unit has no `Wants=` or `After=` on a
|
||||
proxy. Without this row, row 209 phase A can't run the proxy on .54.
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **The proxy unit, from P1's template.** `case2-unit.sh` renders a third system unit,
|
||||
`mosaic-proxy.service`, from P1's `packages/cli/systemd/mosaic-proxy.service.in`, which this row doesn't
|
||||
change.
|
||||
- It uses the same render as the bus (`render()` :435): the copy for `@REPO@`, the unit PATH, `--node`
|
||||
for `@NODE@`, and `@CONFIG_ENV@` deleted, because `case2-unit.sh` renders the default instance only.
|
||||
- It gets the same added lines as the bus: `User=`, `Group=` and the four hardening lines, with no
|
||||
capabilities and no `EnvironmentFile=`. `WantedBy=multi-user.target` replaces `default.target`, as for
|
||||
the other two units.
|
||||
- `check_unit` gets a `proxy` kind. Its fixed lines are those of P1's template: `ExecStart=` exactly
|
||||
`<node> <copy>/packages/model-proxy/src/run.mjs`, and `NoNewPrivileges=yes`. Every check that applies to
|
||||
both units today applies to this one too.
|
||||
- A new test seam `CASE2_PROXY_TEMPLATE`, like `CASE2_BUS_TEMPLATE` (:44, :54).
|
||||
2. **The sha256 refusal is P1's.** The unit starts P1's `run.mjs`, which checks the binary against PIN
|
||||
before it spawns it. This row adds no second check and no other way to start the proxy. A test runs the
|
||||
rendered `ExecStart=` in a temp root, with a made-up config and a binary that doesn't match PIN, and
|
||||
checks P1's refusal code and exit status.
|
||||
3. **Only when the instance has a proxy.** Root passes a new bare flag `--proxy` when the instance's config
|
||||
has a `proxy` section.
|
||||
- Without it, the render is byte for byte what it is today, and no proxy unit is written.
|
||||
- With it, the bus unit gets `Wants=mosaic-proxy.service` and `After=mosaic-proxy.service`, in the place
|
||||
where P1's `bus-service.sh` puts them, right after `Description=`. The Console unit doesn't change.
|
||||
- Root still never imports copy code or reads the config (:16-21). A wrong flag fails closed: with no
|
||||
proxy section, P1's runner refuses and the unit stops. With a proxy section and no flag, the proxy
|
||||
doesn't run and P2's launch preflight refuses. BUILD.md checks both against the landed P1 and P2 code
|
||||
and names the refusal codes.
|
||||
4. **Re-install and start.**
|
||||
- With `--proxy`, the three installed units must name one copy, and each must equal the render once its
|
||||
copy id is the new one, the same rule as now (:557-571).
|
||||
- Without `--proxy`, an installed `mosaic-proxy.service` refuses the run. The script never deletes a
|
||||
unit file.
|
||||
- Enable all of them, then start the proxy, then the bus, then the Console. A unit that was already
|
||||
running is reported the way the bus and Console are now (:576-610), and the message names all three as
|
||||
the units to restart together.
|
||||
5. **case2-install.sh.** Only its closing "Next:" line (:231) changes, to tell root to add `--proxy` when the
|
||||
config has a `proxy` section. If the builder finds that line needs no change, BUILD.md says why and the
|
||||
file stays as it is.
|
||||
|
||||
**Out of scope.** P1's template, runner and PIN; the user-unit path; any named-instance render; reading the
|
||||
config from root; the proxy's config or key; `mosaic proxy init`; any live host, unit or `/etc` write
|
||||
outside a test's temp root.
|
||||
|
||||
**Tests**, in `packages/cli/tests/case2-unit.test.mjs` (and `case2-install.test.mjs` if item 5 changes the
|
||||
script):
|
||||
- with no `--proxy`, the bus and Console renders equal today's, byte for byte;
|
||||
- with `--proxy`, the proxy unit's content, and the bus unit's `Wants=` and `After=` lines;
|
||||
- each `check_unit` refusal on the proxy kind, by its message;
|
||||
- P1's PIN refusal through the rendered `ExecStart=`;
|
||||
- a re-install whose three units name two copies refuses;
|
||||
- an installed proxy unit with no `--proxy` refuses and deletes nothing;
|
||||
- the start order through the `CASE2_SYSTEMCTL` seam's log.
|
||||
|
||||
**Gate.**
|
||||
- The builder is a new Codex gpt-6.1-sol thread and worktree off refactor (Q246, mos-1542). The row starts
|
||||
when P1 (row 329) lands, and the builder rebases on it before any run.
|
||||
- Boyd gives the first Codex review. Webby gives the Claude review of the root-run unit boundary and the
|
||||
landing. Sage runs the landing gate.
|
||||
- The run list for every whole-file run comes from imports and goes in the builder's report before the run.
|
||||
`packages/cli/tests/case1-accept.test.mjs` stays off unless Mos approves (mos-1612).
|
||||
- Every touched test file runs whole. Every package suite but queue, plus every `scripts/test-*.sh`, is
|
||||
green on Sage's landing gate.
|
||||
- Named mutants: the bus unit loses its `After=` line under `--proxy`; the proxy unit's `ExecStart=` skips
|
||||
`run.mjs` and starts the binary directly.
|
||||
@@ -0,0 +1,308 @@
|
||||
# Briefs: the model proxy build rows P1 to P5
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1600, which approved the Vikunja 237 design
|
||||
(`docs/plans/2026-10-24_stack-model-proxy.md`, 9a50f082f) with changes. Read the design first. These briefs
|
||||
cite its decision numbers (D1 to D20) and carry Mos's changes, which win over the design where they differ.
|
||||
Line numbers are at origin/refactor 06f507dbc. Check them before you edit.
|
||||
|
||||
Every row is a Codex gpt-6.1-sol build on its own new thread and worktree off refactor (mos-1542). A separate
|
||||
Codex thread gives the first review and a Claude reviewer gives the second, because every row touches
|
||||
credentials or the Console's security boundary. Sage runs the landing gate.
|
||||
|
||||
## Rules for every row
|
||||
|
||||
- **Secrets and the live proxy.** No agent reads `<configDir>/secrets/proxy-management.key` or anything under
|
||||
a real instance's `<dataRoot>/proxy/` (`config.yaml`, `auth/`, `logs/`). That covers Mos, Sage, every
|
||||
builder and every reviewer (mos-1600 ruling 2). Tests and acceptance runs use their own made-up config,
|
||||
key and data root in a fresh temp root. The host proxy on 127.0.0.1:8317, its binary under
|
||||
`~/.local/opt/EasyCLIProxyAPI` and its config are never started, stopped, read, copied or called. No real
|
||||
upstream credential and no request off the machine except P1's pinned fetch.
|
||||
- **No live change.** No row edits a live instance config, starts `mosaic-proxy`, restarts a bus or Console,
|
||||
or changes `claude-session.env`. Cutover is P7, with Jason present.
|
||||
- **Refusals.** Every new fail-closed refusal gets its own test, by its code (mos-1590). A red in an old test
|
||||
goes to Sage before any rerun, including a rerun you expect to be timing (mos-1592).
|
||||
- **Runs.** TMPDIR and nice as BRIEF-TEMPLATE's Gate says. All test runs are slot work: ask Sage for CLEAR.
|
||||
Design notes and receipts go in `agents/<seat>/work/queue-<row>/BUILD.md`. Report to Sage's thread.
|
||||
|
||||
## Order
|
||||
|
||||
P1 and P4 start now. P4 builds against the config contract in P1 item 3 and rebases on P1 before its
|
||||
gate, and P4 lands after P1. P2 starts when P1 lands. P3 and P5 start when P2 lands and run in parallel.
|
||||
P6 waits for row 157 to resume. P7 waits for P1 to P4 and for Jason.
|
||||
|
||||
## Brief: P1 the model-proxy package, pinned fetch, proxy init and the proxy unit
|
||||
|
||||
**Why.** D1 to D6 and D11's sentinel need a pinned binary, a config the stack writes once, a management
|
||||
client that only the bus uses, and a human-only way to set the management key. Mos dropped J1 and merged J2
|
||||
into J3: a row does the fetch, and `mosaic proxy init` writes the proxy settings, so Jason never edits a
|
||||
file by hand (mos-1600 rulings 1 and 7, Q126).
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **`packages/model-proxy/`**, a new package with its own `tests/`, so the gate's package loop picks it up.
|
||||
2. **PIN and the fetch.** `packages/model-proxy/PIN` names the CLIProxyAPI version, the linux-amd64 release
|
||||
asset, the control panel asset (`router-for-me/Cli-Proxy-API-Management-Center`) and the sha256 of each.
|
||||
- Pin the newest release that the acceptance run below passes on. Record in BUILD.md which release that
|
||||
is, and whether it is 8.0.27, the version row 96 tested.
|
||||
- Record each hash from the release as downloaded. The reviewer downloads the same assets and measures
|
||||
them independently.
|
||||
- A fetch step (`mosaic proxy fetch`) downloads the pinned assets from the release URLs only and
|
||||
verifies each against PIN. It installs them under `<dataRoot>/opt/cliproxyapi/<version>/`, outside
|
||||
`<dataRoot>/proxy/`. A hash mismatch, a redirect off GitHub's release hosts, or an existing target
|
||||
with different content refuses, and a refusal leaves nothing installed.
|
||||
- The gate's tests use a local fake file server. Only the builder's and the reviewer's own measurement
|
||||
runs touch GitHub.
|
||||
3. **The proxy settings, written by `mosaic proxy init`.**
|
||||
- Invariant 2 says config.json is created only by bootstrap and never overwritten. So init doesn't touch
|
||||
config.json. It creates `<configDir>/proxy.json` once, exclusively (`wx`, the same rule as
|
||||
`createExclusive` in `scripts/mosaic-config.mjs`:398), with `{ "port": N, "binary": "<path>" }`.
|
||||
- The loader (`scripts/mosaic-config.mjs` `validate` :293 and `load` :362) reads it when present, checks
|
||||
it, and returns it as `proxy` in the validated config. A missing file means no proxy, which is today's
|
||||
behaviour. A present file with an unknown key, a missing field, a port outside 1024 to 65535, port
|
||||
8317, or a binary that isn't the pinned install path refuses.
|
||||
- The port is a setting with a default. Init picks a free loopback port other than 8317 unless the human
|
||||
names one, and refuses a port that something already listens on.
|
||||
- The binary path is the one the fetch installed. Init refuses if the fetch hasn't run or the installed
|
||||
file doesn't match PIN.
|
||||
- The config contract for the other rows is "the validated config has `proxy.port` and `proxy.binary`".
|
||||
P4 reads `proxy.port` from it and nothing else.
|
||||
4. **The management key.** Init is human-only. It prompts twice with no echo. It sends the key through the
|
||||
bus's human transport with the path-bound proof (`packages/bus/src/human-cli.mjs`, #225), the same way
|
||||
the human-only settings verbs work (`packages/bus/src/runtime.mjs`:158). A tester or agent caller
|
||||
refuses. The bus writes `<configDir>/secrets/proxy-management.key` (0600, exclusive) and the initial
|
||||
`<dataRoot>/proxy/config.yaml` (0600, directory 0700, `auth/` 0700, `logs/`), once (D5, D6). Running init
|
||||
again refuses and changes nothing. No verb, log or error returns the key.
|
||||
5. **The config template** sets these values:
|
||||
- `host: 127.0.0.1` and `allow-remote: false`;
|
||||
- `disable-auto-update-panel: true`, with the panel from the pinned install;
|
||||
- `request-retry: 0`;
|
||||
- D10's routing defaults (fill-first, session affinity on, rebind on quota, no alias pools);
|
||||
- a client-key list holding only the sentinel (`msk-<tag>-sentinel-...`, D11);
|
||||
- the management key.
|
||||
6. **The management client** (`packages/model-proxy/src/client.mjs`) is used by the bus alone (D12). It
|
||||
GETs and PUTs the v8 client-key list and never uses the v0 `DELETE ?value=` (D14). It sends the
|
||||
management key only in the `Authorization` header and only to loopback. It logs a receipt per call
|
||||
(call, status, key ids as the first 12 hex of sha256) and never a key value or a list body.
|
||||
7. **The unit.** A user unit `mosaic-proxy.service` (named instance: `mosaic-proxy-NAME.service`), from a
|
||||
template beside `packages/cli/systemd/mosaic-bus.service.in`. `scripts/instance.sh` gets a `PROXY_UNIT`
|
||||
name next to `CONSOLE_UNIT` (:84). The unit's start step refuses a binary whose sha256 doesn't match
|
||||
PIN. The bus unit gets `Wants=` and `After=` on it only when the instance has a proxy. Tests render and
|
||||
check the unit in a temp root. Nothing is installed or started for real.
|
||||
8. **Correction to D3.** At boot the proxy is already listening on its port, so "refuse if another process
|
||||
listens" can't work as the design wrote it. Init refuses a port in use. At boot, P2's sentinel check
|
||||
(D11) is what proves the listener is this instance's proxy.
|
||||
|
||||
**Acceptance run, recorded in BUILD.md.** This runs the pinned binary under `unshare -rn` against a
|
||||
stand-in upstream, the way row 96 did (`agents/researcher/work/i1-proxy-test/`). It lives in a script
|
||||
outside the gate's `scripts/test-*.sh` set, and its output goes in BUILD.md as plain text. It must answer
|
||||
each of these:
|
||||
- what the proxy does with an empty client-key list (D11);
|
||||
- whether `GET /v1/models` with a client key is answered locally, with no upstream call (D17);
|
||||
- the body and status for a revoked or wrong key, and what Claude Code does with it (row 96 point 4 and
|
||||
note b), recorded only, with no shape fix;
|
||||
- whether the version can restrict a client key to some models (design section 3, for P2);
|
||||
- whether the pinned panel works under a `/proxy/` sub-path through a minimal loopback forward, and under
|
||||
`Content-Security-Policy: sandbox allow-scripts allow-forms` with no `allow-same-origin` (needed by P4);
|
||||
- what redirect or callback URL the panel's OAuth start returns for Claude and Codex, and whether that
|
||||
callback can come back through `/proxy/`. If it can't, J3 gets the proxy's CLI login in Jason's own
|
||||
shell as the fallback, and BUILD.md gives the exact command (mos-1600 ruling 3).
|
||||
|
||||
**Out of scope.** Per-agent keys (P2), harness config (P3), the Console forward (P4), usage (P5), any live
|
||||
instance, and the 8317 proxy.
|
||||
|
||||
**Gate.** Boyd gives the first Codex review, and re-measures both hashes from the release. Dewey reviews the
|
||||
landing candidate. The new package suite runs whole, `scripts/test-config.sh` and every touched test file
|
||||
run whole, and every package suite but queue plus every `scripts/test-*.sh` is green on Sage's landing gate.
|
||||
BUILD.md has the acceptance output.
|
||||
|
||||
## Brief: P4 the Console forwards /proxy/ to the instance proxy in a sandbox
|
||||
|
||||
**Why.** D8: Jason reaches the panel at `https://mosaic.woltje.com/proxy/` behind the Console's existing
|
||||
sign-in, with no new DNS name (Q116). The Console never injects or stores the management key (mos-1600
|
||||
ruling 3).
|
||||
|
||||
One risk the design missed: the forward serves the panel's HTML and scripts on the Console's own origin.
|
||||
Those scripts would pass the Console's same-origin checks (`Origin` equal to its own and
|
||||
`sec-fetch-site: same-origin`, `packages/webui/src/serve.mjs`:463-464) and could call every Console
|
||||
write route as the owner. The session cookie is HttpOnly (`packages/webui/src/sso.mjs`:19), but a
|
||||
same-origin fetch still carries it. A bug in the third-party panel would become a bug in the Console.
|
||||
So the forward puts the panel in a CSP sandbox with an opaque origin, and the panel never runs as the
|
||||
Console.
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **The route.** `/proxy/` and below on the human listener is forwarded to `127.0.0.1:<proxy.port>` (P1
|
||||
item 3's contract), and only when the validated config has `proxy`. Otherwise it answers 404
|
||||
`not-configured`.
|
||||
- The route is owner-only. It is refused for a granted user (before `userRoute`, `serve.mjs`:745), on the
|
||||
tester listener and on a loopback Host.
|
||||
- It needs the same sign-in as other owner pages: a session under `--sso`, or the preview header equal
|
||||
to the user.
|
||||
- The path mapping (prefix strip or base path) follows P1's acceptance result for the pinned panel.
|
||||
Until P1 has it, use a fake upstream and record which mapping you built.
|
||||
2. **Requests.**
|
||||
- Strip the Console session cookie, every `x-authentik-*` header, the preview header, the proof header
|
||||
and `x-mosaic-*` before forwarding.
|
||||
- Pass `Authorization`, the management key the panel sends, through unchanged. Never log it, store it or
|
||||
add one.
|
||||
- Only the panel's own paths and its management API paths go through. Anything else under `/proxy/`
|
||||
answers 404.
|
||||
3. **Responses.**
|
||||
- Every forwarded response gets `Content-Security-Policy: sandbox allow-scripts allow-forms` with no
|
||||
`allow-same-origin`. It also gets the Console's `nosniff`, `no-referrer` and `frame-ancestors 'none'`.
|
||||
- Upstream `Set-Cookie` headers are dropped.
|
||||
- The panel then runs with an opaque origin, so its calls arrive with `Origin: null`. The forward
|
||||
accepts that only on `/proxy/` management paths. Every Console route keeps refusing it, and a test
|
||||
proves that.
|
||||
4. **Stop condition.** First check on the stand-in whether the pinned panel can sign in and list keys while
|
||||
sandboxed and while the sign-in cookies stay SameSite=Lax. If it can't (for example it needs
|
||||
localStorage, or its API calls lose the sign-in), stop and report to Sage with the evidence. Don't ship
|
||||
an unsandboxed forward. A subdomain is Jason's call (Q116), and a forward without the sandbox is a
|
||||
security boundary change that needs Mos.
|
||||
5. **Logs.** Each forwarded call logs method, path, status and user, and never headers or bodies.
|
||||
|
||||
**Tests.** Cover each of these:
|
||||
- owner allowed, and a granted user, the tester listener, a loopback Host and a signed-out request each
|
||||
refused;
|
||||
- not configured;
|
||||
- headers stripped;
|
||||
- `Authorization` passed through and absent from logs;
|
||||
- the CSP sandbox and the dropped `Set-Cookie` on every response;
|
||||
- `Origin: null` accepted on `/proxy/` and refused on a Console write route;
|
||||
- paths outside the allowed set refused;
|
||||
- an upstream that is down answering 502 with no retry.
|
||||
|
||||
The fake upstream is a local test server. P4 doesn't need the binary. Before its gate, P4 reruns P1's panel
|
||||
acceptance step against the real forward and records the result.
|
||||
|
||||
**Out of scope.** Traefik, DNS, Authentik settings, the .54 Console's provider (Q218), and the management key.
|
||||
|
||||
**Gate.** Gladstone gives the first Codex review, Filbert reviews the landing candidate. P4 rebases on P1's
|
||||
landing. The webui suite runs whole with `MOSAIC_REQUIRE_EDITOR_BUILD=1`, and every package suite but queue
|
||||
plus every `scripts/test-*.sh` is green on Sage's landing gate.
|
||||
|
||||
## Brief: P2 per-agent model keys, reconcile and the launch preflight
|
||||
|
||||
**Why.** D11 to D14 and D17: each agent gets its own client key from the stack, revoked before a fire
|
||||
delists the agent, and no session starts on a key the proxy refuses.
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **The store kind.** A row 199 store kind `model` (`packages/bus/src/credentials.mjs`: `issue` :291,
|
||||
`revoke` :383, `remove` :400, `status` :474) at `<configDir>/secrets/agents/<business>/<agent>/model`
|
||||
(0600).
|
||||
- Its status holds the key id, the issue time and the state, never the value.
|
||||
- The kind is implicit for every agent of an instance with a proxy, with no `credentials.model`
|
||||
reference and no change to `roles/*.json` (D13, mos-1600 ruling 4).
|
||||
- Key format `msk-<instanceTag>-<32 random bytes, base64url>` (D11).
|
||||
2. **Issue.** At hire (`packages/business/src/instance-settings.mjs`:354) and at the first launch of an
|
||||
agent with no key, the bus GETs the list, PUTs it with the new key, and GETs again to confirm the key is
|
||||
there. Only then does it write the file. Any failure writes nothing and refuses `proxy-key-issue-failed`.
|
||||
3. **Rotate.** `rotateBy` defaults to 30 days for model keys. The bus PUTs the new key beside the old one,
|
||||
writes the new file and launches new sessions on it. The old key goes once the runs started on it have
|
||||
ended, or after `proxy.rotateGraceMinutes` (default 120), whichever is first. A session still running
|
||||
then is stopped. The rotation period is human-only (Q222, D20).
|
||||
4. **Revoke.** `revoke()` in `packages/bus/src/runtime.mjs`:166-197 gains the model kind: GET, PUT without
|
||||
the key, then GET to confirm, and only then remove the file. A failure refuses the fire with
|
||||
`credential-revoke-failed`, as for the other kinds. It never uses the v0 DELETE.
|
||||
5. **Reconcile at boot.** The bus GETs the list once.
|
||||
- A key in the record but missing from the proxy is PUT back.
|
||||
- A key with this instance's prefix that is in the proxy but not in the record is removed.
|
||||
- Keys without the prefix are left alone.
|
||||
- A list with no sentinel refuses every launch with `proxy-open`. That check is also what proves the
|
||||
listener is this instance's proxy (P1 item 8).
|
||||
- The reconcile writes a receipt with key ids only.
|
||||
- One broker runs per data root, so one bus holds the management client per instance. Say in BUILD.md
|
||||
how the code relies on that.
|
||||
6. **Preflight (D17).** Before each launch in proxy mode, the bus sends one `GET /v1/models` with the agent's
|
||||
key. A 401 refuses `proxy-key-refused`, and a connection failure refuses `proxy-down`. A session with no
|
||||
output for `proxy.firstOutputSeconds` (default 120) is stopped and logged as `proxy-stall`.
|
||||
7. If P1 found that a client key can be limited to some models, narrow each agent's key to its role's
|
||||
models. If it can't, say so in BUILD.md. The bundle's model choice stays the guard.
|
||||
|
||||
**Tests.** Use a fake management server for every step above, every refusal code by its own test, and a
|
||||
receipt check that no key value appears in any log, error or status. Add a fire test showing the key is gone
|
||||
from the proxy before the agent is delisted.
|
||||
|
||||
**Out of scope.** Harness env and files (P3), usage (P5), the Console.
|
||||
|
||||
**Gate.** Boyd gives the first Codex review, Dewey reviews the landing candidate (credentials). Every touched
|
||||
test file runs whole, the named mutants below are killed, and every package suite but queue plus every
|
||||
`scripts/test-*.sh` is green on Sage's landing gate. Mutants: revoke skips the confirming GET; reconcile
|
||||
removes a key without the prefix; the preflight treats a 401 as success.
|
||||
|
||||
## Brief: P3 harness config from the proxy
|
||||
|
||||
**Why.** D16: in proxy mode each session gets only its own key and the proxy's address, written fresh on every
|
||||
launch, and none of the instance-wide provider variables (mos-1600 ruling 6).
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. The bus passes the harness module `{ baseUrl: "http://127.0.0.1:<port>", key }` with today's `env`
|
||||
answer.
|
||||
2. **Dropped variables.** In proxy mode `sessionEnv` (`packages/seat/src/session.mjs`:256, `ENV_ALLOW` :242)
|
||||
stops passing `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_*`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY` and
|
||||
`ZAI_API_KEY` from the instance. Without a proxy nothing changes.
|
||||
3. **Claude Code** (`packages/plugins/harness/claude-code/index.mjs`, `env` :119-126) sets
|
||||
`ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN=<key>`.
|
||||
4. **pi** (`packages/plugins/harness/pi/index.mjs`, `env` :63-70) writes `<run>/home/.pi/agent/models.json`
|
||||
with the provider's `baseUrl` (no `/v1`) and sets `ANTHROPIC_API_KEY=<key>`.
|
||||
- A non-Anthropic route names that provider's base URL in the same file.
|
||||
- pi gets a preflight that refuses `no-session-credential` when it has neither a proxy key nor its own
|
||||
credential.
|
||||
- Nobody maps `CLAUDE_CODE_OAUTH_TOKEN` into a pi variable. Whether this closes P19 #383 is Jason's call,
|
||||
and the row doesn't close it.
|
||||
5. **Business settings.** `launch.models` (`packages/business/src/launch.mjs`:12-24) accepts provider
|
||||
`proxy`, and `launch.account` (:81-89) can name the proxy. A business without `proxy` entries is
|
||||
unchanged. Record the exact key names in BUILD.md.
|
||||
6. No harness reads the host's `~/.claude`, `~/.pi` or `~/.codex`. The Codex harness config waits for a
|
||||
Codex harness row and isn't here.
|
||||
|
||||
**Tests.** Cover each of these:
|
||||
- proxy mode drops each named variable, and non-proxy mode keeps today's env exactly;
|
||||
- the Claude Code env;
|
||||
- pi's `models.json` content and mode, rewritten on every launch;
|
||||
- the pi preflight refusal;
|
||||
- the `proxy` provider in `launch.models`;
|
||||
- a session env that never holds another agent's key.
|
||||
|
||||
**Out of scope.** Key issue and revoke (P2), the proxy itself (P1), and live sessions.
|
||||
|
||||
**Gate.** Gladstone gives the first Codex review, Dewey reviews the landing candidate. Every touched test
|
||||
file runs whole, and every package suite but queue plus every `scripts/test-*.sh` is green on Sage's
|
||||
landing gate. Mutant: proxy mode still passes `CLAUDE_CODE_OAUTH_TOKEN`.
|
||||
|
||||
## Brief: P5 usage plug-in and per-key limits
|
||||
|
||||
**Why.** D18 and D19 (Q153, Q154, Q222).
|
||||
|
||||
**Correction to D18 (mos-1600).** D18 said the stack counts per account, but D9 says the stack never sees
|
||||
which account served a request. The stack's own count is per agent key. Per-account numbers come only from
|
||||
the `cliproxy-queue` plug-in or from the panel.
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **Per-key quotas.** `launch.limits.tokens` (`packages/business/src/launch.mjs`:60-75) accepts an agent's
|
||||
key id as a `keys` name, so a daily quota per agent key is a business setting. It is unset by default,
|
||||
which means no quota, and it is human-only (Q222).
|
||||
2. **The plug-in.** A `usage` plug-in kind with one optional module, `cliproxy-queue`. It drains
|
||||
`GET /v8/management/observability/usage/queue?count=100` through the bus's management client every
|
||||
`usage.cliproxyQueue.pollSeconds` (default 15, under the proxy's 60 s retention).
|
||||
- It maps each event's client key to a key id and drops the value.
|
||||
- It maps the upstream account field to an account id the same way. Row 96 saw that field carry an
|
||||
upstream key in plain text (`REPORT.md`:161), and the stack never stores an upstream credential (D7).
|
||||
- It records upstream failures per key id. It is off by default and turned on by `plugins.set`.
|
||||
- It never enforces a quota alone.
|
||||
- A `msk-` key that isn't in the record goes to the reconcile log.
|
||||
3. **The Console view.** Usage shows the stack's per-key count. When the plug-in is on, it also shows the
|
||||
plug-in's per-account numbers, labelled as the proxy's.
|
||||
|
||||
**Tests.** Use a fake queue that returns events with made-up client keys and upstream values, and check that
|
||||
no value of either kind appears in any store, log or view. Also test the poll interval setting, plug-in off
|
||||
by default, a quota per key id, and the unknown-key reconcile entry.
|
||||
|
||||
**Out of scope.** Quota enforcement from proxy numbers, and the panel.
|
||||
|
||||
**Gate.** Boyd gives the first Codex review, Dewey reviews the landing candidate. Every touched test file runs
|
||||
whole, and every package suite but queue plus every `scripts/test-*.sh` is green on Sage's landing gate.
|
||||
Mutant: the reader stores the upstream account value instead of its id.
|
||||
@@ -0,0 +1,76 @@
|
||||
# Brief amendment: row 332 (P3), pi's GLM and GPT routes through the proxy
|
||||
|
||||
Written 2026-10-24 by Sage. Mos approved items 4a to 4c in mos-1616 with three conditions, which are written
|
||||
into the items below. Item 4d is settled there too. This came out of mos-1615 (DECISIONS Q253): on .54 Bean
|
||||
runs GLM through a Z.ai key and Chet runs GPT through his ChatGPT/Codex login, both through the proxy. The
|
||||
P3 brief in the rows doc (`docs/plans/2026-10-24_model-proxy-rows.md`, "Brief: P3 harness config from the
|
||||
proxy", item 4) proves only the Anthropic route. Row 96 proved only that route for pi, too
|
||||
(`agents/researcher/work/i1-proxy-test/REPORT.md`, S2-pi-A).
|
||||
|
||||
The rows doc isn't edited, because rows 329 to 333 pin its blob. This file replaces one line of P3 item 4,
|
||||
"A non-Anthropic route names that provider's base URL in the same file.", and adds to P3's Tests and Gate.
|
||||
Everything else in P3 stands. Line numbers are at origin/refactor 58b2bbb3a. Check them before you edit.
|
||||
|
||||
## Brief: P3 amendment, pi's GLM and GPT routes
|
||||
|
||||
**What pi 0.85.1 does today.** These facts come from the pinned pi's docs and its built-in provider data.
|
||||
- `models.json` providers take `baseUrl`, `api`, `apiKey` and `models`. A built-in provider can be
|
||||
overridden with a `baseUrl`.
|
||||
- `apiKey` takes a literal, a `$ENV` reference, or `!command`, which pi runs as a shell command.
|
||||
- The built-in `zai` provider is `openai-completions` at `https://api.z.ai/api/coding/paas/v4`, with z.ai
|
||||
compat fields (`thinkingFormat: "zai"`, `zaiToolStream`).
|
||||
- The built-in `openai-codex` provider speaks `openai-codex-responses` to chatgpt.com/backend-api with pi's
|
||||
own OAuth, so it can't be pointed at the proxy.
|
||||
|
||||
**4a. One entry per route.** `<run>/home/.pi/agent/models.json` gets one provider entry for each route the
|
||||
agent's `launch.models` names, each at the proxy's address from P3 item 1.
|
||||
- Anthropic: as P3 item 4 says now, `anthropic-messages` with no `/v1`.
|
||||
- GLM: `openai-completions` at `http://127.0.0.1:<port>/v1`, with the model id that the proxy's `/v1/models`
|
||||
lists for the Z.ai key.
|
||||
- GPT through the Codex login: a provider of the stack's own, never `openai-codex`, at
|
||||
`http://127.0.0.1:<port>/v1`, with the model ids that the proxy lists for the Codex credential.
|
||||
- The builder picks `openai-responses` or `openai-completions` once, at build time, from what the pinned
|
||||
proxy passes for pi's requests. The harness has no fallback at run time: it writes the one shape the
|
||||
build chose.
|
||||
- BUILD.md records the choice and its evidence: the pinned proxy version, the requests tried on each shape
|
||||
and what came back.
|
||||
|
||||
**4b. The key goes by reference.**
|
||||
- Every entry's `apiKey` is `"$ANTHROPIC_API_KEY"`, the reference P3 item 4 already sets. `ENV_ALLOW`
|
||||
(`packages/seat/src/session.mjs`:242) doesn't change.
|
||||
- In proxy mode that variable holds only the session's own proxy key, because P3 item 2 drops the instance's
|
||||
provider variables.
|
||||
- `models.json` never holds a key value, and no value in it starts with `!`.
|
||||
|
||||
**4c. Tests.** These add to P3's Tests list:
|
||||
- the `models.json` content for each of the three routes;
|
||||
- every `apiKey` in `models.json` is the `$ANTHROPIC_API_KEY` reference;
|
||||
- no key value appears anywhere in the file;
|
||||
- no value in the file starts with `!`;
|
||||
- an offline pi turn through the pinned proxy under `unshare -rn` for the GLM route, against a stand-in
|
||||
upstream, the way row 96 and P1's acceptance run did. It runs from a script outside the gate's
|
||||
`scripts/test-*.sh` set, and its output goes in BUILD.md as plain text.
|
||||
|
||||
The Codex route can't run offline without a real auth file, and no agent touches one. Its first real turn is
|
||||
row 209 phase A step 7 (`docs/plans/2026-10-24_row209-hub-54-window.md`). BUILD.md says so.
|
||||
|
||||
BUILD.md also records whether the z.ai compat fields survive through the proxy, or whether the proxy drops
|
||||
them, from the offline GLM turn.
|
||||
|
||||
**4d. One proxy per user (settled, mos-1616).** Unless P2 item 7 finds that the pinned proxy can limit a
|
||||
client key to some models, Bean's key can reach Chet's ChatGPT login through the same proxy, and Chet's key
|
||||
can reach Bean's Z.ai key. Mos accepted that for .54. Q249 sets one proxy per user and their agents, so both
|
||||
agents and both accounts on .54 are Melanie's. If P1 found no per-key model limit, the bundle's model choice
|
||||
stays the guard. If P1 found one, P2 narrows each key as P2 item 7 says. This row changes neither.
|
||||
|
||||
**Gate additions.** One more named mutant: `models.json` gets the key value instead of the
|
||||
`$ANTHROPIC_API_KEY` reference. The 4c tests must kill it. The rest of P3's Gate stands: Gladstone's first
|
||||
Codex review, Dewey's landing review, every touched test file whole, every package suite but queue plus
|
||||
every `scripts/test-*.sh` green on Sage's landing gate, and the mutant where proxy mode still passes
|
||||
`CLAUDE_CODE_OAUTH_TOKEN`.
|
||||
|
||||
**Start and landing (mos-1616 ruling 5).** P3 may start now on a new Codex gpt-6.1-sol thread and worktree,
|
||||
building against P2's interface `{ baseUrl, key }` (P3 item 1). It lands only after P1 (row 329) and P2
|
||||
(row 331) land, and after its own gate (mos-1351). It rebases on both before that gate. The run list for
|
||||
every whole-file run comes from imports and goes in the builder's report before the run.
|
||||
`packages/cli/tests/case1-accept.test.mjs` stays off unless Mos approves (mos-1612).
|
||||
@@ -0,0 +1,150 @@
|
||||
# Brief: row 330 (P4), the Console proxy page
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1606, which carries Jason's Q46 and Q47 answers (DECISIONS Q250).
|
||||
Mos approved it in mos-1608, adding upstream API keys (item 4) and the account id rule (item 2).
|
||||
Read the design (`docs/plans/2026-10-24_stack-model-proxy.md`, amended for Q250) and the rows doc
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`) first. The rows doc's "Rules for every row" apply here
|
||||
unchanged. Line numbers are at origin/refactor 0ce22dde6. Check them before you edit.
|
||||
|
||||
This brief replaces two parts of the rows doc:
|
||||
- its P4 brief (the `/proxy/` forward to the upstream panel, rows doc :118-182), which is dropped with D8;
|
||||
- P1's two panel acceptance bullets (rows doc :104-108). P1's finding on the OAuth callback URL for Claude
|
||||
and Codex still counts, and this row uses it.
|
||||
|
||||
The rows doc isn't edited, because every row from 329 to 333 pins that file's blob and row 329 is near its
|
||||
landing. The P4 brief lives here instead.
|
||||
|
||||
## Brief: P4 the Console proxy page
|
||||
|
||||
**Why.** Q250: Jason enters accounts in the Console's own proxy page, not in the upstream panel. That
|
||||
covers both his OAuth accounts and his API keys (Q248 item 1, Q249, mos-1608). The page calls bus routes,
|
||||
and the bus calls the proxy's management API (D12). The management key stays in the bus (D6). No new
|
||||
hostname is added. A panel subdomain is a fallback only if our page can't keep up with the
|
||||
management API across pinned proxy upgrades, and that call is Jason's (Q46).
|
||||
|
||||
**What ships.**
|
||||
|
||||
1. **Bus verbs.** New broker verbs for the page, for example `proxy.accounts`, `proxy.login.start`,
|
||||
`proxy.login.submit`, `proxy.login.status`, `proxy.login.cancel`, `proxy.apikey.add`,
|
||||
`proxy.priority.set` and `proxy.keys`. Settle the names in BUILD.md.
|
||||
- Every verb is human-only. A tester, agent or granted-user caller refuses `human-required`, the way
|
||||
the grants admin verbs do (`packages/bus/src/broker.mjs`:3721-3731).
|
||||
- With no proxy in the validated config (P1 item 3), every verb refuses `not-configured`.
|
||||
- Each verb calls the management client from P1 (`packages/model-proxy/src/client.mjs`). P4 adds the
|
||||
calls it needs under P1's rules: the key goes only in the `Authorization` header, only to loopback,
|
||||
and each call writes a receipt with no body and no secret.
|
||||
- A proxy that doesn't answer refuses `proxy-down`, with no retry.
|
||||
2. **Accounts.** `proxy.accounts` lists the upstream accounts from the pinned version's credentials
|
||||
endpoint (`/credentials` in the 8.0.27 doc,
|
||||
`agents/researcher/work/i1-proxy-test/docs/management-api-v8.0.27.md`:101).
|
||||
- The bus copies only allowlisted fields: provider, a display name, status and priority, plus an
|
||||
account id. Every other field is dropped, so no token can pass through even if the proxy returns one.
|
||||
- The account id is the first 12 hex of the sha256 of the proxy's own credential identifier, never of a
|
||||
token or key value (mos-1608).
|
||||
- Record in BUILD.md which endpoint and fields the pinned version has.
|
||||
3. **OAuth sign-in** through `oauth/auth-url`, `oauth/callback` and `oauth/status`
|
||||
(management-api-v8.0.27.md:108-138).
|
||||
- **Start.** The page picks a provider from an allowlist: `claude` and `codex`. Adding a provider is a
|
||||
reviewed change. The bus calls `auth-url` and returns the authorization URL and the `state`. The page
|
||||
opens the URL in a new tab with `noopener` and `noreferrer`.
|
||||
- **Return.** After Jason signs in, the provider redirects to the callback URL that the proxy named.
|
||||
P1's acceptance records that URL for each provider. If the redirect reaches the proxy directly, the
|
||||
status poll shows the result. If it doesn't, Jason pastes the full URL from the address bar into the
|
||||
page, and the bus POSTs it to `oauth/callback` as `redirect_url`.
|
||||
- **Checks on a pasted URL.** It must parse as a URL, stay under a size limit, and carry a `state` that
|
||||
this bus started and that hasn't finished. Anything else refuses `login-state-unknown` or
|
||||
`callback-url-invalid`. The URL carries the authorization code, so it is never logged, stored or
|
||||
echoed back.
|
||||
- **Status.** Only an `ok` from `oauth/status` counts as signed in. An accepted callback with status
|
||||
`wait` doesn't, because the doc says acceptance isn't completion (:136-138). The bus stops polling
|
||||
on `ok`, `error`, or after a timeout setting (default 300 s).
|
||||
- **Cancel** calls `DELETE oauth/session`.
|
||||
4. **Upstream API keys** (mos-1608). A human-only verb (for example `proxy.apikey.add`; name it in
|
||||
BUILD.md) adds one upstream API key.
|
||||
- The provider allowlist starts with z.ai only. Adding a provider is a reviewed change. BUILD.md records
|
||||
which management endpoint the pinned 8.0.42 uses for it.
|
||||
- The Console route has the same checks as the other writes (item 8). The page's input is
|
||||
`type=password` with `autocomplete=off`.
|
||||
- The value is write-only. Neither the Console nor the bus logs it, stores it, caches it, or puts it in
|
||||
a receipt or an error. No part of it is echoed, not even the last four characters. The bus passes it
|
||||
straight to the management API in the request body and then drops it.
|
||||
- After the add, the key shows in `proxy.accounts` through the same allowlisted fields, and priority
|
||||
(item 5) works on it.
|
||||
- An empty value, one over a size limit, or one outside a charset refuses `apikey-invalid`. A provider
|
||||
outside the allowlist refuses `provider-not-allowed`.
|
||||
5. **Priority.** `proxy.priority.set` sets one account's priority, an integer in a range the builder
|
||||
records. Use the pinned version's credential-field endpoint (`/credentials/fields` PATCH in the 8.0.27
|
||||
doc). Confirm it on the stand-in and record what it changes. A bad account id or value refuses
|
||||
`priority-invalid`.
|
||||
6. **Client keys, list only.** `proxy.keys` reads `GET /config/access/api-keys`, maps each value to its key
|
||||
id in memory and drops the value (D15).
|
||||
- It joins P2's record to show agent, issue time and state for each key id.
|
||||
- The sentinel is labelled. A key without this instance's prefix shows as "not stack-managed".
|
||||
- The page has no key write. Issue and revoke stay with hire and fire (P2).
|
||||
7. **Usage.** P5's Console view (rows doc P5 item 3) sits on this page. Whichever of P4 and P5 lands
|
||||
second puts it there. P4 adds no usage source.
|
||||
8. **The page and its Console routes.** An owner-only `/proxy` page in `packages/webui`, built like the
|
||||
existing pages, with routes under `/api/proxy/`.
|
||||
- Each route makes one call over the owner's human transport, the way the Agents section does
|
||||
(`AGENT_ROUTES` at `packages/webui/src/serve.mjs`:451, `agentCaller` at
|
||||
`packages/webui/src/console-bus.mjs`:12). `consoleBus` gets a `PROXY_VERBS` list, and the caller
|
||||
refuses any other verb.
|
||||
- Writes need a signed-in owner, the Console's own `Origin`, `sec-fetch-site: same-origin`, `--send`
|
||||
and a named host, as `agentSend` checks (`serve.mjs`:459-470). The routes are refused for a granted
|
||||
user (`userRoute`), on the tester listener and on a loopback Host.
|
||||
- The Console never holds the management key, never reads `proxy` from the config, and never opens a
|
||||
connection to the proxy port.
|
||||
- Logs record method, route, status and user only. They never record the authorization URL, a pasted
|
||||
callback URL, a state, an API key or a body.
|
||||
9. **Upgrade check.** An acceptance script, beside P1's and outside the gate's `scripts/test-*.sh` set,
|
||||
runs the bus verbs against the pinned binary under `unshare -rn`. It uses a made-up config, key and
|
||||
data root in a fresh temp root, the same way P1's run does.
|
||||
- With no network, it checks what can be checked offline. The account list starts empty. `auth-url`
|
||||
returns a URL and a state for `claude` and for `codex`. A callback with a made-up code never reports
|
||||
`ok`. A made-up z.ai API key added through the verb shows in the account list, and a priority set on
|
||||
it reads back. The key list returns ids only.
|
||||
- It prints the endpoints and fields the page relies on, so a version change shows up as a diff.
|
||||
- `packages/model-proxy/README.md` records the rule: a PIN change lands only with this script's output
|
||||
for the new version in that row's BUILD.md, and Sage's landing gate checks for it.
|
||||
- If the script fails on a new version and the row can't fix the page, the row stops. Sage takes Q46's
|
||||
fallback to Jason through Mos.
|
||||
|
||||
**Tests.** Cover each of these:
|
||||
- every verb against a fake management server;
|
||||
- each refusal code by its own test (mos-1590): `not-configured`, `human-required` for the tester, an
|
||||
agent and a granted user, `provider-not-allowed` for sign-in and for an API key, `login-state-unknown`,
|
||||
`callback-url-invalid`, `apikey-invalid` (empty, over the size limit and outside the charset, each its
|
||||
own case), `priority-invalid` and `proxy-down`;
|
||||
- an API-key canary: a unique made-up value goes through the Console route against the fake management
|
||||
server. Afterwards every Console and bus log, receipt, response, store file and error output from that
|
||||
run has zero matches for it, and the fake received it exactly once;
|
||||
- an added API key listed by `proxy.accounts` with allowlisted fields only, and priority set on it;
|
||||
- account ids derived from the credential identifier, with a fake whose token values never appear in an id;
|
||||
- a fake that puts token-like values in every credential field, where no value reaches a response, log or
|
||||
receipt;
|
||||
- a fake key list with full values, where only ids come back, the sentinel is labelled and a foreign key
|
||||
is marked;
|
||||
- no authorization URL, callback URL, code or state in any log or receipt;
|
||||
- an accepted callback with status `wait` not reported as signed in;
|
||||
- the Console routes: owner allowed, and a granted user, the tester listener, a loopback Host, a
|
||||
signed-out request, a cross-origin request and a request without `sec-fetch-site` each refused, and
|
||||
every write read-only without `--send`;
|
||||
- no `packages/webui` module imports the model-proxy client or reads the `proxy` config.
|
||||
|
||||
**Out of scope.**
|
||||
- Removing or disabling an account. Mos is filing a Vikunja task for a later row (mos-1608).
|
||||
- Every routing setting other than account priority.
|
||||
- Issuing or revoking client keys (P2), and any usage source (P5).
|
||||
- The upstream panel, any forward to the proxy port, and the subdomain fallback.
|
||||
- Traefik, DNS, Authentik settings, and the .54 Console's provider (Q218).
|
||||
- Any live instance, the management key, and the 8317 proxy.
|
||||
|
||||
**Order.** P4 starts when P1 lands, because it needs P1's client and config contract. It rebases on P2
|
||||
before its gate, for the key join, and lands after P2.
|
||||
|
||||
**Gate.** Gladstone gives the first Codex review, and Filbert reviews the landing candidate. The model-proxy,
|
||||
bus and webui suites run whole, webui with `MOSAIC_REQUIRE_EDITOR_BUILD=1`. Every touched test file runs
|
||||
whole. The named mutants below are killed. Every package suite but queue plus every `scripts/test-*.sh` is
|
||||
green on Sage's landing gate. BUILD.md has the upgrade-check output on the pinned version. Mutants: the
|
||||
accounts verb returns an unfiltered field; the callback verb skips the state check; a `/api/proxy/` route
|
||||
accepts a granted user; the API-key verb echoes the value in an error or a log.
|
||||
@@ -0,0 +1,109 @@
|
||||
# Brief: row 209, the .54 window with its own proxy
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1615, which carries Jason's "Q54: yes" (DECISIONS Q253). Q253:
|
||||
.54 waits for its own model proxy. Bean's Z.ai key and Chet's ChatGPT/Codex login both go in through the P4
|
||||
Console proxy page. Chet runs GPT through his Codex login for 10-27, not GLM, which replaces Q186.
|
||||
|
||||
This brief replaces row 209's old brief (`docs/plans/2026-10-20_federation-r1-r2-r5.md`, "Brief: FED R8, the
|
||||
hub window on 10.1.1.54"). That section isn't edited. Phase D below runs it unchanged. Read the model-proxy
|
||||
design (`docs/plans/2026-10-24_stack-model-proxy.md`, sections 1, 5, 9 and 10), the rows doc
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`) and the P4 brief (`docs/plans/2026-10-24_proxy-page-brief.md`)
|
||||
first. Line numbers are at origin/refactor 95d4991d2. Check them before you rely on them.
|
||||
|
||||
## Brief: row 209, the .54 window: proxy, Bean's and Chet's accounts, then FED R8
|
||||
|
||||
**What this row is.** A live window on 10.1.1.54, not a code row. Jason and Mos run it. Nothing here
|
||||
authorizes a step on any host. Each step waits for the window's go, and Console start and restart stay
|
||||
with Jason or Mos (Q173).
|
||||
|
||||
**Secrets.** The rows doc's rules apply on .54 too. No agent reads `<configDir>/secrets/proxy-management.key`
|
||||
or anything under .54's `<dataRoot>/proxy/` (`config.yaml`, `auth/`, `logs/`), and no agent reads Hermes's
|
||||
auth files or Chet's login in any form (D6, mos-1600 ruling 2). Agents read only what a command prints:
|
||||
hashes, unit state, refusal codes and the proxy page's allowlisted fields.
|
||||
|
||||
### Entry checks, all before phase A
|
||||
|
||||
1. P1 (row 329), P2 (331), P3 (332) and P4 (330) have landed and passed Sage's landing gate. P3 carries the
|
||||
amendment for pi's GLM and GPT routes that Mos rules on (sage-4285). Without it P3 proves only the
|
||||
Anthropic route.
|
||||
2. **Gap to close first.** `.54` installs through `scripts/case2-install.sh` and `scripts/case2-unit.sh`.
|
||||
P1 adds `mosaic-proxy.service` to `scripts/instance.sh` only (rows doc P1 item 7). `case2-unit.sh` writes
|
||||
no model-proxy unit today, and its bus unit has no `Wants=` or `After=` on one. A code row has to add the
|
||||
proxy unit to the Case 2 install, with the same sha256 refusal, before phase A. Sage files it once Mos
|
||||
rules (sage-4285).
|
||||
3. The .54 stack copy is re-pinned to a commit that holds rows 329 to 332 and the item 2 row.
|
||||
4. .54's Console signs Jason in through Authentik (Q218). The proxy page's writes need a signed-in owner
|
||||
(P4 item 8).
|
||||
5. Phase B has a recorded result, or Chet's login waits for phase C as phase B says.
|
||||
|
||||
### Phase A: the proxy on .54
|
||||
|
||||
Design section 9, for .54 as the `mosaic` user under `/srv/mosaic` (D1).
|
||||
|
||||
1. **Fetch.** P1's pinned fetch runs on .54. Sage compares the printed sha256 values with PIN.
|
||||
2. **Config and key.** Jason adds the `proxy` section to .54's `config.json` in an editor (J2), runs
|
||||
`mosaic proxy init` in his own terminal (J3) and starts the proxy unit.
|
||||
3. **Bean's key.** Jason pastes Bean's Z.ai API key into .54's proxy page (J4, P4 item 4) and sets its
|
||||
priority. The page never shows it again.
|
||||
4. **Chet's login.** Only after phase B passes. Jason signs the proxy in to Chet's ChatGPT/Codex account
|
||||
through the page's own OAuth flow (P4 item 3). The proxy holds a sign-in of its own. Nobody copies
|
||||
Hermes's auth file.
|
||||
5. **Business settings.** Bean's and Chet's `launch.models` name provider `proxy` (P3 item 5): Bean on his
|
||||
GLM model, Chet on his GPT model, with the model ids the proxy's `/v1/models` lists. The edit goes in
|
||||
through the human path that P3's BUILD.md names.
|
||||
6. **Restart.** Jason or Mos restarts the bus and Console. The bus reconciles, issues a key for each agent
|
||||
(P2) and runs D17's preflight for each one. Any refusal stops phase A. The fix is to remove the section
|
||||
and restart.
|
||||
7. **One real turn per route.** The tester actor launches one pi session for Bean (GLM through the Z.ai key)
|
||||
and one for Chet (GPT through the Codex login). Each makes one real turn through the proxy. Jason
|
||||
watches the proxy page's usage section and confirms each request landed on the account he expects (J5).
|
||||
The result goes into the row's record with who ran it.
|
||||
8. **Unit cleanup.** Once both turns pass, the bus unit drops the provider env file per design section 9
|
||||
step 7. The file stays for the 7-day rollback window.
|
||||
|
||||
Rollback at any point: remove the `proxy` section and restart.
|
||||
|
||||
### Phase B: two clients on one ChatGPT login
|
||||
|
||||
Hermes holds Chet's ChatGPT login until #423 step 5. Before that login goes into the .54 proxy (phase A
|
||||
step 4), a check has to show that two clients on one ChatGPT login don't revoke each other's refresh tokens
|
||||
(mos-1615 item 3). Row 229's brief named the same risk for a copied token.
|
||||
|
||||
- Two separate sign-ins, never a copied file: Hermes keeps its own, and the proxy gets its own through the
|
||||
page.
|
||||
- Pass: each client goes through at least one token refresh, and afterwards Hermes still answers on Chet's
|
||||
login and the proxy page still lists the account as active. Fail: either one drops out or needs a new
|
||||
sign-in.
|
||||
- The check is Jason's. It uses the real account, and no agent reads either client's token or auth file.
|
||||
Agents record what Jason reports and what the page lists.
|
||||
- **Open before the window.** I don't know how to make each client refresh on demand without someone reading
|
||||
a token, or how long ChatGPT's access tokens last. Researcher answers from the pinned 8.0.42 docs and
|
||||
Hermes's docs: how each side refreshes, whether either can be made to refresh, and how long the check has
|
||||
to run. The answer goes in this row's record.
|
||||
- If the check fails, or there's no safe way to run it, Chet's login goes in after #423 step 5, when Hermes
|
||||
has let go of it. Phase A step 7's GPT turn waits for that, and Chet doesn't go live on GPT until it passes.
|
||||
|
||||
### Phase C: Case 2 go-live, then Hermes stops
|
||||
|
||||
Bean and Chet go live in Case 2 on the proxy (#416). Hermes stops after that (#423 step 5, mos-1615), and
|
||||
that stays Melanie's choice (`docs/plans/2026-10-20_case2-rows.md`:1231). If phase B didn't pass, Chet's login goes into the proxy here and phase A step 7's GPT turn
|
||||
runs before Chet goes live.
|
||||
|
||||
### Phase D: FED R8
|
||||
|
||||
The old brief, unchanged: entry checks, build notes N1 and N2, and its done condition
|
||||
(`docs/plans/2026-10-20_federation-r1-r2-r5.md`, "Brief: FED R8, the hub window on 10.1.1.54").
|
||||
|
||||
### Done when
|
||||
|
||||
Phase A's two real turns are recorded, each on the account Jason expected. Phase B's result is recorded
|
||||
along with who ran it. Bean and Chet are live on the proxy. FED R8's own done condition holds.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Any code change. The item 2 gap and the P3 amendment are code rows of their own.
|
||||
- The dragon-lin default instance and .53. Their cutovers are P7 and row 246.
|
||||
- Hermes's server and profiles, beyond Jason's phase B check.
|
||||
- The 8317 host proxy and `~/.local/opt/EasyCLIProxyAPI`.
|
||||
- Row 229 (Vikunja 428). The proxy route supersedes it, and the queue parks it with that reason. Its live
|
||||
check is phase B.
|
||||
@@ -0,0 +1,361 @@
|
||||
# Design: CLIProxyAPI as the stack's model credential and routing layer
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1597 (Vikunja 237). It carries out Jason's Q249 ruling that CLIProxyAPI
|
||||
becomes the stack's credential and routing layer, with Q116 and Q127 still in force. This is a design document.
|
||||
Mos rules it before any code. Its build rows go to new Codex gpt-6.1-sol threads (Q246), and each gets its own
|
||||
brief once Mos has ruled. Line numbers are at origin/refactor 2ce476d54.
|
||||
|
||||
**Amendments.** Mos's mos-1600, mos-1601 and mos-1602 changes live in the row briefs
|
||||
(`docs/plans/2026-10-24_model-proxy-rows.md`), which win over this design where they differ. Jason's Q46
|
||||
and Q47 answers (DECISIONS Q250, mos-1606, 2026-10-24) are written into this file: accounts are entered in
|
||||
the Console's own proxy page, and the `/proxy/` forward to the upstream panel (D8) is dropped. The P4 brief
|
||||
for that page is `docs/plans/2026-10-24_proxy-page-brief.md`. Text marked "(Q250)" below is that change.
|
||||
|
||||
Not to be confused with queue row 237 ("one decision lands once", `2026-10-20_agent-credentials.md`). This is
|
||||
Vikunja task 237.
|
||||
|
||||
## Inputs
|
||||
|
||||
- **Q116** (jason-master-restart DECISIONS.md:709, :724-725; lead-decisions.md:2578-2585). The WebUI goes under
|
||||
mosaic.woltje.com behind Authentik, on a path of the existing host. A new DNS record or subdomain goes back
|
||||
to Jason first. Authentik admits only jason.woltje. No agent reads the management key.
|
||||
- **Q127** (:779-782). Seats talk only to a local proxy, using keys the stack issues and rotates. Upstream
|
||||
tokens and keys stay in the proxy. Switching live seats to the proxy, and the subscription-terms check, go
|
||||
back to Jason.
|
||||
- **Q249** (:2512). The proxy serves one user's agents on that user's machine. It is not shared with other
|
||||
users and not used in a commercial app. The host proxy on 127.0.0.1:8317 is not touched. Decision 108's
|
||||
token file and claude-session.env are a stopgap. One proxy per user.
|
||||
- **Q153/Q154** (:1088, :1096): usage sources and quota enforcement sit behind a plug-in interface. **Q222**
|
||||
(:2194): limits are settings with defaults, and a setting that guards the human from agents is
|
||||
human-only.
|
||||
- **Row 96** (#1590), `agents/researcher/work/i1-proxy-test/REPORT.md`, tested against CLIProxyAPI 8.0.27. Issue
|
||||
is 1 PUT, revoke is 1 DELETE, with no restart and effect on the next request. Keys survive a restart.
|
||||
Claude Code works with `ANTHROPIC_BASE_URL` + `ANTHROPIC_AUTH_TOKEN`. Pinned pi 0.85.1 needs a `models.json`
|
||||
in the session HOME. Eight open points (:190-216) and six design items (:218-236). Row 96's queue note adds
|
||||
two more. `GET api-keys` returns every client key in full. The claim that the 401 body shape causes the
|
||||
Claude Code stall is unverified, so test it before any fix aimed at the shape.
|
||||
- **Row 199** (landed cb5fdb064): the agent credential store, files under
|
||||
`<configDir>/secrets/agents/<business>/<agent>/<kind>`, 0700 directories and 0600 files
|
||||
(`agents/megavolt/work/queue-199/DESIGN.md`:63-65). Class `Credentials` is in
|
||||
`packages/bus/src/credentials.mjs` (`issue` :291, `revoke` :383, `remove` :400, `useFor` :417, `status` :474).
|
||||
D7 (:118-125) deferred per-agent model tokens to "a proxy ... its own row". This is that row.
|
||||
- **Row 194** (landed fd6dd327c): fire stops the agent's runs, then delists it
|
||||
(`packages/bus/src/broker.mjs` `#chiefLifecycle` :5038, `instance.fire` :5117). `revoke()` in
|
||||
`packages/bus/src/runtime.mjs`:166-197 revokes before the write and refuses `credential-revoke-failed`.
|
||||
- **How sessions get model credentials today.** The bus unit loads `claude-session.env`
|
||||
(`packages/cli/systemd/mosaic-bus.service.in`:21-25). `sessionEnv` passes every `ENV_ALLOW` variable to every
|
||||
session (`packages/seat/src/session.mjs`:242-262). Inside the sandbox, HOME is the run's own `home/`
|
||||
(:240-241). The sandbox doesn't unshare the network (`packages/seat/src/sandbox.mjs`:70), so a session reaches
|
||||
host loopback. Pi has no model credential today (P19 #383). There is no Codex harness plug-in
|
||||
(`packages/plugins/harness/` holds claude-code and pi only).
|
||||
|
||||
## 1. Where the proxy runs
|
||||
|
||||
D1. **One proxy per stack instance, run by the stack.** It runs as the instance's OS user, and since every
|
||||
instance has one user (invariant 2) that gives one proxy per user per machine. The default instance on
|
||||
dragon-lin gets one. Melanie's .54 hub gets its own as `mosaic` under `/srv/mosaic`. Nothing is shared
|
||||
between instances. The host proxy on 127.0.0.1:8317 is not started, stopped, read or configured by any of
|
||||
this, and its config and data directory are not read.
|
||||
|
||||
D2. **Process and files.**
|
||||
- A systemd user unit `mosaic-proxy.service`, written by the same scripts that write `mosaic-bus.service`
|
||||
(`scripts/instance.sh`, `scripts/case2-unit.sh`). The bus unit gets `Wants=` and `After=` on it. The bus
|
||||
refuses to launch a session when the proxy is configured but not answering (D17).
|
||||
- The proxy's own directory, `<dataRoot>/proxy/`, mode 0700, holds `config.yaml` (0600), `auth/` (the
|
||||
`oauth.auth-dir` for its OAuth logins, 0700) and `logs/`. No other component reads files in it. Run records
|
||||
never copy from it.
|
||||
- It listens on `127.0.0.1:<proxy.port>`. `allow-remote: false`, so management calls are accepted from
|
||||
loopback only.
|
||||
|
||||
D3. **System config.** The instance config gets an optional `proxy` section: `{ "port": N, "binary": "<path>" }`.
|
||||
With no section, the instance runs as today on claude-session.env. That is how migration works one instance
|
||||
at a time (section 9). With the section, both fields are required, and a missing or bad value refuses. The
|
||||
port can't be 8317, and the bus refuses at boot if another process already listens on it. Bootstrap writes
|
||||
the section only for a new config, as it does for every other field. An existing config gets it by a human
|
||||
edit at cutover (Jason's step J2). This changes the config schema, so Mos must rule on it.
|
||||
|
||||
D4. **Pinned binary.** `packages/model-proxy/PIN` records the CLIProxyAPI version, the release asset name and
|
||||
its sha256, the same way pi is pinned exactly. The tested version is 8.0.27. The unit starts only a binary
|
||||
whose sha256 matches PIN. The control panel asset is pinned the same way, with
|
||||
`disable-auto-update-panel: true`. The proxy otherwise downloads the panel from GitHub at runtime
|
||||
(`config.example.v8.0.27.yaml`:94-103), and that would be unreviewed code behind Authentik. The stack never
|
||||
copies the 8317 binary. The first fetch of the release asset counts as external access, so it is Jason's
|
||||
step J1 or a row Mos approves. For containerized installs the binary and panel go into the proxy image
|
||||
(section 8). Q250: nothing serves the panel to the Console now. P1 keeps the pin as built, and
|
||||
removing it is a later cleanup.
|
||||
|
||||
D5. **Initial config, then hands off.** The stack writes `config.yaml` once, when `<dataRoot>/proxy/` is
|
||||
first created, from a template in `packages/model-proxy/`, and never writes the file again. After that the
|
||||
stack changes the proxy only through the management API, and Jason changes it only through the Console's
|
||||
proxy page, which calls the bus, which calls the management API (Q250).
|
||||
Row 96 point 2 (every management write rewrites the whole file and reorders it) then stops mattering,
|
||||
because nobody edits the file by hand. The template sets the defaults in section 3 and section 7,
|
||||
`request-retry: 0` so no hidden retries happen (as in row 96), the client-key list holding only the
|
||||
sentinel key (D11), and the management key (D6).
|
||||
|
||||
## 2. Credential entry
|
||||
|
||||
D6. **Management key.** Jason sets the management key once, at a human-only prompt
|
||||
(`mosaic proxy init`, run in his own shell, path-bound human proof as for other human verbs, #225). The
|
||||
prompt doesn't echo the key. It writes the key to `<configDir>/secrets/proxy-management.key` (0600) and into
|
||||
the initial `config.yaml`. The proxy hashes it in place on its first start (row 96 point 6). The stack reads
|
||||
only the secrets file, never the hashed value in config. The holder is the bus process alone (D12). The
|
||||
sandbox doesn't mount `<configDir>/secrets`, so no session can read the key, and no verb returns it. Jason
|
||||
types the key only at that prompt. The Console's proxy page needs only his Console sign-in, and the bus adds
|
||||
the key to its own management calls (Q250). An agent never runs the prompt (Q173).
|
||||
|
||||
D7. **Upstream credentials.** Accounts are entered in the Console's proxy page (Q250). It covers OAuth
|
||||
sign-ins for Claude and Codex through the management API's `oauth/auth-url`, `oauth/callback` and
|
||||
`oauth/status`, upstream API keys (z.ai first, write-only and never echoed, mos-1608), and account
|
||||
priority. The page shows each account by an id, the first 12 hex of the sha256 of the proxy's own
|
||||
credential identifier, never of a token or key value (mos-1608). The credentials live in the
|
||||
proxy's `auth/` directory and config and nowhere else. There is no per-seat credential file. The stack never
|
||||
reads, copies or logs an upstream credential.
|
||||
|
||||
D8. **Dropped (Q250).** The design had the Console forward `https://mosaic.woltje.com/proxy/` to the
|
||||
proxy's own panel. Jason's Q46 and Q47 answers replace it with the Console's own proxy page (D7). The page
|
||||
calls bus routes, the bus calls the management API (D12), and the management key stays in the bus (D6). No
|
||||
new hostname is added. A panel subdomain is a fallback only if our page can't keep up with the management
|
||||
API across pinned proxy upgrades, and that call is Jason's. Each pinned upgrade runs the page's acceptance
|
||||
script against the new version (P4 brief item 9).
|
||||
|
||||
## 3. Priority routing, and how the business routing table maps onto it
|
||||
|
||||
D9. **Who picks what.** The business routing table picks the harness, model and thinking for a role
|
||||
(`packages/business/src/vars.mjs`:139-147, resolved by `sessionModel` in `packages/harness/src/bundle.mjs`:77-86).
|
||||
The proxy picks which upstream account serves that model. The two don't overlap:
|
||||
- The model id in the business table is the id the session sends. The proxy routes it to an account that
|
||||
serves that model.
|
||||
- The business `fallback.*` vars stay the stack's. They still apply only when the primary harness's preflight
|
||||
fails (`packages/cli/src/launcher.mjs`:390-397).
|
||||
- Account failover inside one model is the proxy's job. The stack doesn't model accounts per session.
|
||||
|
||||
D10. **Priority settings.** Defaults set in the template are listed below (Q222: settings with defaults).
|
||||
Jason sets account priority in the Console's proxy page. The other routing settings keep the template
|
||||
defaults, and a page control for them is a later row (Q250).
|
||||
- `routing.strategy: fill-first`. A subscription account is used until it is rate-limited or out of quota, then
|
||||
the next one (`config.example`:150).
|
||||
- Each account gets a `priority` (larger first, `config.example`:480). Jason sets the order in the Console's
|
||||
proxy page (J4).
|
||||
- `routing.session-affinity: true`, so a session stays on one account while it works and keeps its prompt
|
||||
cache (:154-171). Default `session-affinity-rebind-on: quota`, so a transient 5xx doesn't move a session.
|
||||
- No model aliases or alias pools by default. A pool that maps one name to several upstream models
|
||||
(:869) would let the proxy change the model under a role. Model choice stays in the business table, where
|
||||
a reviewed edit can see it. If Jason adds an alias himself, that's his call.
|
||||
|
||||
`launch.models` (`packages/business/src/launch.mjs`:12-24) gets a provider value `proxy`, meaning "this model id
|
||||
through the instance proxy". `launch.account` (:81-89) with a proxy names the proxy rather than an
|
||||
environment variable set. The row that implements it settles the exact key name. A business without
|
||||
`proxy` providers keeps working unchanged.
|
||||
|
||||
Not settled by the design, and checked in the build row: whether 8.0.27 can restrict a client key to some
|
||||
models. If it can, an agent's key is narrowed to its role's models as a second guard. If not, the bundle's
|
||||
model choice is the only guard, as it is today.
|
||||
|
||||
## 4. Per-agent keys
|
||||
|
||||
D11. **Key format and the sentinel.** A stack-issued client key is `msk-<instanceTag>-<32 random bytes, base64url>`.
|
||||
The prefix tells stack keys apart from any key Jason adds himself. The stack manages only keys with its own
|
||||
prefix and leaves every other key alone. One sentinel key, `msk-<tag>-sentinel-...`, is written by the
|
||||
template and never handed out. It keeps the list non-empty, so the proxy never falls into a possible open
|
||||
mode on an empty list (row 96 point 5). The build row tests the empty-list behavior on a stand-in, and the
|
||||
result goes in BUILD.md either way. The bus refuses to launch (`proxy-open`) if a `GET` of the list shows
|
||||
no sentinel.
|
||||
|
||||
D12. **Who calls the management API.** The bus process only, through a new `packages/model-proxy/` client. It
|
||||
reads the management key from D6's file, holds it in memory, and sends it only in the `Authorization` header to
|
||||
loopback. No CLI verb, Console route or session tool exposes it. Every management call is logged as a
|
||||
receipt (call, status, key id) with no key value.
|
||||
|
||||
D13. **The stack's record.** The key is a row 199 store kind, `model`, one per agent:
|
||||
`<configDir>/secrets/agents/<business>/<agent>/model` (0600). This replaces D7's "per stack instance only"
|
||||
for instances with a proxy. The file holds the key value, so the bus can hand it to a session. Its status
|
||||
record (`Credentials.status`) holds a key id (the first 12 hex of its sha256), the issue time and the state.
|
||||
It never holds the value. The record is authoritative. The proxy's list is derived state, and the bus
|
||||
reconciles it.
|
||||
- **Boot.** The bus GETs the list once and compares it with the record. A key that is in the record but not
|
||||
in the proxy is re-PUT. A stack-prefixed key that is in the proxy but not in the record, or is revoked
|
||||
there, is removed. The reconcile writes a receipt.
|
||||
- **No role-file change.** The model key needs no `credentials.model` reference in the business file. Every
|
||||
agent of an instance with a proxy gets one implicitly. The tradeoff: a business can't opt a role out. The
|
||||
other way would add `model` to every role definition, which means changing `roles/*.json`, and that is a
|
||||
reviewed policy change for no benefit today.
|
||||
|
||||
D14. **Issue, rotate, revoke.**
|
||||
- **Issue.** `instance.hire` (`packages/business/src/instance-settings.mjs`:354) and the first launch of an agent
|
||||
without a key both call `Credentials.issue(model)`. The bus GETs the list, adds the key and PUTs the list.
|
||||
It then GETs again and checks the key is present before it writes the store file. If any step fails, no
|
||||
file is written and the bus refuses `proxy-key-issue-failed`.
|
||||
- **Rotate.** `rotateBy` (row 199 D2) defaults to 30 days for model keys (Q222 setting). The bus PUTs the new
|
||||
key alongside the old one, writes the new file, and launches new sessions with the new key. It removes the
|
||||
old key once the agent's runs that started on it have ended, or after `proxy.rotateGraceMinutes` (default
|
||||
120), whichever comes first. A session still running at that point is stopped. That's 3 calls with no
|
||||
restart, and the two keys overlap for the window (row 96 rotation item).
|
||||
- **Revoke.** Fire already stops the agent's runs before it delists the agent (row 194). The revoke step in
|
||||
`runtime.mjs revoke()` gains the model kind: a GET, then a PUT of the list without the key, then a GET to
|
||||
confirm. The store file goes only after that. If the revoke fails, the fire is refused
|
||||
`credential-revoke-failed`, as for the other kinds. Revoke never uses the v0 `DELETE ?value=<key>`, because it
|
||||
writes the full key into the access log (row 96 point 1). The v8 PUT doesn't.
|
||||
|
||||
D15. **Plaintext at rest, accepted.** The proxy keeps client keys in plaintext in `config.yaml`, and `GET api-keys`
|
||||
returns them in full (row 96 point 1, queue note a). This is accepted, with these limits:
|
||||
- the file and the store sit under the instance user's 0700 directories, which no session mounts;
|
||||
- the management client never logs a list body, and the GET response stays in memory only;
|
||||
- the usage reader (section 7) maps a key to its id at read time and stores only the id.
|
||||
|
||||
A key leak lets someone spend through the proxy until the key is revoked. It never exposes an upstream
|
||||
credential.
|
||||
|
||||
## 5. Harness config written from the proxy
|
||||
|
||||
D16. **One source.** At launch the bus resolves the session's route and the agent's key, and passes the harness
|
||||
module `{ baseUrl: "http://127.0.0.1:<port>", key }` alongside today's `env` answer. Each harness module writes
|
||||
its own config from that. It's rewritten on every launch, so a port or key change reaches the next session
|
||||
with nothing cached. No harness reads `~/.claude`, `~/.pi` or `~/.codex` of the host (managed-session-config.md:36-39).
|
||||
When the proxy is configured, `sessionEnv` stops passing the instance-wide `CLAUDE_CODE_OAUTH_TOKEN`,
|
||||
`ANTHROPIC_*`, `OPENAI_API_KEY`, `OPENROUTER_API_KEY` and `ZAI_API_KEY`. A session gets only its own key, so a
|
||||
proxy-mode session can't reach an upstream with the instance's token.
|
||||
- **Claude Code** (`packages/plugins/harness/claude-code/index.mjs`, `env` :119-126): `ANTHROPIC_BASE_URL` and
|
||||
`ANTHROPIC_AUTH_TOKEN=<key>`. This already satisfies `SESSION_CREDENTIALS` (`session.mjs`:249-253) and
|
||||
passes `ENV_ALLOW`. Row 96 ran exactly this.
|
||||
- **pi** (`packages/plugins/harness/pi/index.mjs`, `env` :63-70): writes `<run>/home/.pi/agent/models.json` with
|
||||
`{"providers":{"anthropic":{"baseUrl":"<baseUrl>"}}}` (no `/v1`), and sets `ANTHROPIC_API_KEY=<key>` (row 96
|
||||
pi finding, :123-136). For a non-Anthropic route (z.ai through the proxy), the same file names that
|
||||
provider's base URL. pi gains a preflight that refuses `no-session-credential` when neither a proxy key nor
|
||||
a pi credential is present. This settles P19 #383 for proxy-mode instances: pi gets a key of its own, and
|
||||
nobody maps `CLAUDE_CODE_OAUTH_TOKEN` into a pi variable. Whether that closes #383 is Jason's call.
|
||||
- **Codex.** The stack has no Codex harness plug-in yet. This design fixes its proxy config for when one
|
||||
exists: `CODEX_HOME=<run>/home/.codex`, with `config.toml` naming a `model_provider` with
|
||||
`base_url = "<baseUrl>/v1"`, `env_key = "OPENAI_API_KEY"` and `wire_api = "responses"`, and
|
||||
`OPENAI_API_KEY=<key>`. The Codex harness row writes it. The T3 Codex threads the development seats use are
|
||||
not stack sessions and are out of scope.
|
||||
|
||||
## 6. Row 96's open points
|
||||
|
||||
| # | Point | Disposition |
|
||||
|---|---|---|
|
||||
| 1 | Plaintext keys in config, the usage queue and the v0 DELETE log | Accepted at rest (D15). Avoided in logs: no v0 DELETE (D14), and the usage reader stores ids (section 7) |
|
||||
| 2 | Every management write rewrites the whole config | Solved: nobody edits the file after creation (D5) |
|
||||
| 3 | No per-key accounting, 60 s queue window | Solved by the stack counting its own tokens (REQ-LIMIT-2). The queue reader is an optional plug-in that polls faster than the window (section 7) |
|
||||
| 4 | Revoked or wrong key looks like a hang in Claude Code | Solved without relying on the client (D17). The 401-shape cause stays unverified and no shape fix is in scope. The build row tests it on the stand-in and records the result (queue note b) |
|
||||
| 5 | Empty key list unknown | Solved by the sentinel (D11). Tested on a stand-in, result recorded |
|
||||
| 6 | Management key hashed in place at startup | Solved: the stack reads only its own secrets file (D6) |
|
||||
| 7 | pi ignores `ANTHROPIC_BASE_URL` | Solved: pi gets a session `models.json` (D16) |
|
||||
| 8 | Stand-in limits: no real API, rate limits, token counting or routing | Accepted for the build rows, which run on stand-ins. The first real upstream traffic is Jason's cutover step (Q127), J5 |
|
||||
| a | `GET api-keys` returns every key in full | Accepted, in memory only (D15) |
|
||||
| b | 401 shape as the stall cause, unverified | Tested before any shape fix. None is planned (point 4) |
|
||||
|
||||
D17. **Key and proxy preflight.** Before a launch in proxy mode, the bus makes one request with the agent's key,
|
||||
`GET /v1/models`. The build row confirms on the stand-in that this is answered locally with no upstream call. A
|
||||
401 refuses the launch with `proxy-key-refused`. A connection failure refuses with `proxy-down`. Neither starts a
|
||||
session, so a revoked key never reaches Claude Code's silent retry. A key that is revoked mid-session is
|
||||
revoked by the stack, which stops that session first (D14). A session with no output for
|
||||
`proxy.firstOutputSeconds` (default 120) is stopped and logged as `proxy-stall`.
|
||||
|
||||
## 7. Usage and limits
|
||||
|
||||
D18. **What counts.** The stack counts the tokens its own sessions use, per agent key and per account, as
|
||||
REQ-LIMIT-2 says (docs/prd/mosaic-stack.md:964, `packages/tasks/requirements.json`:57). It's the default and
|
||||
only built-in source. The existing `launch.limits.tokens` keys (`keys`, `daily`;
|
||||
`packages/business/src/launch.mjs`:60-75) get the agent key id as a valid `keys` name, so a daily quota per agent
|
||||
key is a business setting. It defaults to unset, which means no per-key quota.
|
||||
|
||||
D19. **The plug-in.** Under Q153/Q154, a `usage` plug-in kind takes one optional module, `cliproxy-queue`. It
|
||||
drains `GET /v8/management/observability/usage/queue?count=100` through the bus's management client every
|
||||
`usage.cliproxyQueue.pollSeconds` (default 15, under the 60 s retention). It maps each event's `api_key` to a key
|
||||
id, then drops the value. It records upstream failures (`fail.status_code`) per key id. It is off by default,
|
||||
and turning it on is a `plugins.set` change. When on, it is a second source that the Console shows next to the
|
||||
stack's own count. It never enforces a quota alone. A queue event for a key with a `msk-` prefix that isn't
|
||||
in the record goes in the reconcile log.
|
||||
|
||||
D20. **Settings and who changes them.** Every number in this design is a setting with the default shown:
|
||||
`rotateBy` 30 d, `proxy.rotateGraceMinutes` 120, `proxy.firstOutputSeconds` 120, `usage.cliproxyQueue.pollSeconds`
|
||||
15, routing strategy fill-first, affinity on. The per-key daily quota and the rotation period guard the human
|
||||
from agents, so only a human can change them (Q222). The routing settings live in the proxy and only Jason
|
||||
changes them. Account priority is in the Console's proxy page, and the rest keep their defaults (Q250).
|
||||
|
||||
## 8. Row 157 re-scope (#1652, Vikunja 617)
|
||||
|
||||
Row 157 is on hold (Q248, mos-1568/1569), and this design doesn't start it. When it resumes, its brief is
|
||||
amended this way, and Mos rules the amendment:
|
||||
- **Egress.** The `agents` network's allowed list is the proxy container only. api.anthropic.com, api.z.ai and
|
||||
api.openai.com come off the agents list (bentina/row157's draft `contracts/egress.json`) and go to a new `proxy`
|
||||
list. The proxy container sits on `mosaic-agents` and `mosaic-egress` (containerized-install-design.md:786-830),
|
||||
and its CONNECT calls are logged to `<dataRoot>/state/egress.jsonl` like any other.
|
||||
- **The real-token run is cancelled.** `provider-hosts.sh` and `provider-runner.mjs` passed
|
||||
`CLAUDE_CODE_OAUTH_TOKEN` and `ZAI_API_KEY` into a CLI child. That run is not done, and is replaced by a
|
||||
stand-in run. A session container reaches the stand-in only through the proxy container, and a direct
|
||||
connection to an upstream name is refused.
|
||||
- Decision 108's mounted token file (`/run/mosaic/secrets/claude-code-oauth.token`) is not mounted into session
|
||||
containers of a proxy-mode install. The proxy container holds the upstream credentials in its own volume.
|
||||
|
||||
## 9. Migration
|
||||
|
||||
claude-session.env (host installs) and decision 108's file (containerized) stay until each instance switches.
|
||||
An instance switches only when its config gains the `proxy` section (D3). Until then nothing about it
|
||||
changes. Cutover, per instance, with Jason present (Q127):
|
||||
|
||||
1. The build rows land and pass the landing gate. They don't change any instance yet.
|
||||
2. Jason fetches and verifies the pinned binary and panel (J1). ops-01 or Sage checks the sha256 against PIN.
|
||||
3. Jason adds the `proxy` section to the instance config (J2), runs `mosaic proxy init` (D6), and starts
|
||||
`mosaic-proxy.service`. The bus isn't restarted yet.
|
||||
4. Jason adds the accounts in the Console's proxy page (J3, J4). No session uses the proxy yet.
|
||||
5. A Console or bus restart (Jason or Mos, per Q173) picks up the section. The bus reconciles, issues a key
|
||||
for each listed agent, and runs D17's preflight for each one. Any refusal stops the cutover, and the fix
|
||||
is to remove the section and restart.
|
||||
6. One test session per harness, launched by the tester actor, makes one real turn through the proxy (J5).
|
||||
This is the first real upstream traffic on the new path.
|
||||
7. Once both harnesses pass, the bus unit drops `EnvironmentFile=` for claude-session.env. The file stays on disk
|
||||
for a 7-day rollback window, then Jason deletes it (J6). Rollback is removing the section and restoring the
|
||||
`EnvironmentFile=` line.
|
||||
|
||||
Order: the dragon-lin default instance first, then .53, then Melanie's .54 hub once Jason has picked its
|
||||
accounts (J7). The 8317 host proxy and its clients are not part of any step.
|
||||
|
||||
## 10. Jason's steps
|
||||
|
||||
- **J1.** In a browser, open the CLIProxyAPI release page for the version in PIN and download the named asset
|
||||
and the panel asset into the folder the cutover note names. Sage checks the hashes. (If Mos assigns the
|
||||
fetch to a row instead, J1 drops.)
|
||||
- **J2.** Add `"proxy": { "port": <port from the cutover note>, "binary": "<path>" }` to the instance's
|
||||
`config.json`, in an editor.
|
||||
- **J3.** In a terminal of your own, run `mosaic proxy init` and type a new management key twice. Keep it in your
|
||||
password manager. You don't type it anywhere else (Q250).
|
||||
- **J4.** Open the Console's proxy page and sign in to Authentik as jason.woltje. Sign in to each subscription
|
||||
account you want this instance to use (Claude, Codex). If the sign-in tab doesn't return on its own, copy
|
||||
the full address from that tab and paste it into the page. Set each account's priority. The highest one is
|
||||
used first, and the next one only when the first is limited. Paste each API key you want it to have (for
|
||||
example z.ai) into the page's API-key field. The page never shows it again.
|
||||
- **J5.** Watch the Console while the two test sessions run (cutover step 6), and confirm the proxy page's usage
|
||||
section shows their requests on the account you expect.
|
||||
- **J6.** After 7 days on the proxy, delete `claude-session.env` for that instance.
|
||||
- **J7.** For Melanie's .54 hub, decide which accounts it uses: hers, yours, or new ones. Then do J2 to J4 on .54
|
||||
through its own Console's Authentik provider (Q218). Only accounts you entered for that hub go into its
|
||||
proxy.
|
||||
|
||||
## Build rows (proposed, each briefed after Mos rules)
|
||||
|
||||
All are Codex builds on new gpt-6.1-sol threads (Q246), first review by a separate Codex thread. They touch
|
||||
credentials, so a Claude reviewer (Dewey) gives the second review. Sage runs the landing gate.
|
||||
|
||||
| Row | Piece | Depends on |
|
||||
|---|---|---|
|
||||
| P1 | `packages/model-proxy/`: PIN, template, management client, `mosaic proxy init`, the `mosaic-proxy` unit, config `proxy` section, stand-in tests (empty list, `/v1/models` local, 401 shape, OAuth callback URL; the panel checks are dropped, Q250) | Mos's ruling on D3 |
|
||||
| P2 | Per-agent keys: `model` store kind, issue, rotate, revoke, reconcile, D17 preflight | P1, rows 194 and 199 |
|
||||
| P3 | Harness config: Claude Code env, pi `models.json` and preflight, `sessionEnv` proxy mode | P2 |
|
||||
| P4 | The Console proxy page and its bus verbs (Q250, `2026-10-24_proxy-page-brief.md`) | P1; lands after P2 |
|
||||
| P5 | `usage` plug-in kind and the `cliproxy-queue` module; key ids in `launch.limits.tokens` | P2 |
|
||||
| P6 | Row 157 brief amendment (section 8) | Row 157 resuming |
|
||||
| P7 | Cutover note and per-instance window (section 9) | P1 to P4 landed; Jason present |
|
||||
|
||||
The Codex harness config (D16) goes with the Codex harness row, which isn't on this list.
|
||||
|
||||
## What Mos rules
|
||||
|
||||
1. D1/D3: the `proxy` section in the instance config (a schema change), and the proxy as its own user unit.
|
||||
2. D6: the management key set by Jason at a human-only prompt and held only by the bus process.
|
||||
3. D8: ruled. Jason's Q46 and Q47 (Q250) drop the forward for the Console's own proxy page.
|
||||
4. D13: an implicit per-agent model key with no role-file change.
|
||||
5. D15: plaintext keys at rest accepted under those limits.
|
||||
6. D16: proxy mode drops the instance-wide provider variables from every session.
|
||||
7. J1: Jason fetches the binary, or a row does.
|
||||
@@ -59,6 +59,17 @@ fails it on any commit (row 323, mos-1561). A builder whose cli suite
|
||||
can't run says so in the row, and the cli suite counts from the lead's
|
||||
landing gate.
|
||||
|
||||
Make the TMPDIR with shell `mktemp -d` and that template only. Python's
|
||||
`tempfile.mkdtemp` adds `_` and runs to 48 bytes, which fails
|
||||
`claude-session.test.mjs:298` and the socket limit (Vikunja 590). A
|
||||
script that starts several runs makes one TMPDIR per run.
|
||||
|
||||
Set nice absolutely, not relative. The host's ananicy renices node, so
|
||||
`nice -n 10` can land at 16 or at -4. In shell use
|
||||
`nice -n $((19 - $(nice))) CMD`. In a Node or Python launcher set 19 in
|
||||
each child (`os.setpriority` or `os.nice(19 - os.nice(0))` in preexec).
|
||||
Record the nice each run saw in BUILD.md (Vikunja 623, mos-1598).
|
||||
|
||||
Evidence that lands with a row is plain text: logs, diffs, manifests. No
|
||||
gzip or other binary file, so the lead's push guard runs its binary and
|
||||
secret checks with no waiver. If a log is too big, cut it to the lines
|
||||
@@ -85,6 +96,12 @@ suite by design and runs through a script puts that entry in the brief's
|
||||
Gate and in BUILD.md with the row, and the lead adds it to the landing
|
||||
gate's list. Any unlisted skip stays RED.
|
||||
|
||||
A builder's list of whole test files to run for a row comes from imports,
|
||||
not from a text grep for a path, and goes in the builder's report before
|
||||
the run. A file on the mos-1592 list (`packages/cli/tests/case1-accept.test.mjs`
|
||||
for now) stays off the list unless Mos has approved that run (mos-1612,
|
||||
after row 329's round 2 grep matched fixture text and reran the F7 file).
|
||||
|
||||
Any row that builds businessDoc() with a library project sets the chief's
|
||||
gitea ref in its fixture (mos-1584, after row 285 gave the chief of staff
|
||||
a read-scoped gitea credential that the validator requires when a business
|
||||
@@ -103,4 +120,8 @@ pushes a build or a new round, or the reviewer posts a verdict, that seat
|
||||
messages the next seat's T3 thread and Sage's with
|
||||
`mcp__t3__t3_send_message`. Claude and Codex seats both have it. Copy
|
||||
Mos's thread only when Mos has a step. Nobody waits on a date or a
|
||||
schedule when the next step is ready.
|
||||
schedule when the next step is ready. Reports, questions and slot
|
||||
requests for Sage go to Sage's thread,
|
||||
`1ef1e4f8-3ead-4208-beca-38f9f1add079`, with the `[from: ... -> to: ...]`
|
||||
header, never to Mos's thread in Sage's place (mos-1591, after Beakley,
|
||||
Fethry and Herb reports reached Mos).
|
||||
+14
-8
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
|
||||
|
||||
<!-- mosaic-queue:begin -->
|
||||
|
||||
Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
Generated from `docs/plans/queue.json` revision 4020 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
|
||||
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
@@ -260,10 +260,10 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 221 | Case 2 C2-20 Bean and Chet persona import, with a count-only dry run | gosalyn; reviewers webby | #1722 | done | — | Webby approves the manifest. The test file runs green on its own and the cli suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-20 Bean and Chet persona import, with a count-only dry run | LANDED 300766450, pushed. Cand 8f403d0d4, manifest 20f05e3a6ad5, 22 paths, verify-commit ok, patch-id dfbb754c6fb4 equals 62ddc96cc..8f403d0d4. Gate 11:07:50-11:19Z on b1fece6f0: test file alone 11/0, harness 140/0, cli 677/0, all scripts green. Brief Gate met: webby-0073 approve, test file alone, cli, scripts. No live run in the Gate; the import itself waits on Jason. N1 N2 N4 go to C2-20b, N3 to DEFERRED. |
|
||||
| 222 | Case 2 C2-03a fetch-only sync | megavolt; reviewers dewey | #1723 | done | — | Dewey approves the manifest. The library and bus suites are green on Sage's gate, along with every `scripts/test-*.sh`, and the mutant is killed. A search of the test run's output for the fixture token finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-03a fetch-only sync | sage-2024 on megavolt-0089: core.createObject=rename in the fetch argv is accepted as a brief departure, since C2-04 inspectMirror refuses nlink 2. Dewey checks it. The askpass env keys, export SAFE in project/src/git.mjs, bus/tests/library-sync.test.mjs and the SURVEY-RUN user line are accepted. The 5 s client timeout residual goes in BUILD.md and Dewey rules fix or defer. Slot after 197 proof 3 and the 249 gate. |
|
||||
| 223 | Case 2 C2-07 runner retry on a guard refusal, and the runner's notices | researcher; reviewers filbert | #1724 | blocked (from briefed): Q226: C2-07 folded into C2-06 (row 217). No work here; Jason parks it. | — | Filbert approves the manifest. The harness suite is green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-07 folded into C2-06 | — |
|
||||
| 224 | Case 2 C2-05b broker lookup verbs and typed tools | della; reviewers dewey | #1725 | in-progress | 216 done | Dewey approves the manifest. The bus and harness suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05b broker lookup verbs and typed tools | — |
|
||||
| 225 | Case 2 C2-10b Console chat: replies, citations, write notices and the status line | fethry; reviewers dewey | #1726 | briefed | 217 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every `scripts/test-*.sh`. Grep for hash pins on `bus.js` and `serve.mjs` before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10b Console chat: replies, citations, write notices and the status line | — |
|
||||
| 224 | Case 2 C2-05b broker lookup verbs and typed tools | della; reviewers dewey | #1725 | done | 216 done | Dewey approves the manifest. The bus and harness suites are green on Sage's gate, along with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05b broker lookup verbs and typed tools | LANDED 5e77cdd09 19:18Z 10-24 (13 Della commits on 7fe32d722). Gate GREEN 19:16:37Z on 27624aa66, drift docs-only. Dewey APPROVE c31619. #1725 closed c31623. |
|
||||
| 225 | Case 2 C2-10b Console chat: replies, citations, write notices and the status line | fethry; reviewers dewey | #1726 | done | 217 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every `scripts/test-*.sh`. Grep for hash pins on `bus.js` and `serve.mjs` before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10b Console chat: replies, citations, write notices and the status line | LANDED 9b6345466 on refactor, pushed 15:55:22Z: fethry r2 c1f9dfd3c cherry-picked onto c63801959, 8 commits equal by range-diff and 3 differ only in log hunks (both sides kept), 13 manifest paths match 517af41b. Dewey APPROVE c31599 rev 3960, request c31600. Sage gate GREEN 15:54:58Z: every package suite but queue 0 fail (webui 442, cli 988+1 allowed FED R7 skip), 12 scripts rc 0. #1726 closed c31604. |
|
||||
| 226 | Case 2 C2-13 install script and precheck for the host | gosalyn; reviewers webby | #1727 | done | 215 done, 221 done, 222 done, 230 done | Webby approves the manifest. The test file and the cli suite are green on Sage's gate, along with every `scripts/test-*.sh`. The run on the host is a separate window with Jason's go. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-13b install script and library precheck | landed 661890e90 (replay of bfed0aa6c 893b4fd25 f3fabf51b onto 8352da88c, patch-ids equal, four blobs and modes equal to f3fabf51b) and fd3d2a99f (Darkwing 8a6321790 replay, mos-1519), tree 78fc5ad21 equals merge-tree. Webby APPROVE rev 3641. Sage gate GREEN 19:56Z on f3fabf51b: 17 package suites and 10 scripts, 0 fail. Webby N1-N4 DEFERRED e2b873390. #1727 closed. |
|
||||
| 227 | Case 2 C2-14 end-to-end acceptance on a scratch root | gizmoduck; reviewers dewey | #1728 | in-progress | — | Dewey runs the script and approves. It is green on Sage's gate with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-14 end-to-end acceptance on a scratch root | — |
|
||||
| 227 | Case 2 C2-14 end-to-end acceptance on a scratch root | steelbeak; reviewers dewey | #1728 | in-progress | — | Dewey runs the script and approves. It is green on Sage's gate with every `scripts/test-*.sh`. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-14 end-to-end acceptance on a scratch root | — |
|
||||
| 228 | Row A: ZAI_API_KEY passes through ENV_ALLOW to sessions, README line, pass-through test (XS, Haiku high) | researcher; reviewers webby | #1729 | done | — | Webby approves the manifest. The mutant is killed. The seat suite and the cli suite are green on Sage's gate with every scripts/test-*.sh. (sage) | `docs/plans/2026-10-20_case2-rows.md` § Brief: row A, ZAI_API_KEY in the session env allowlist | Landed 86249ac55 (pushed 07:25Z) on 59f817c32: the 3 manifest paths from cf18c42b9. Patch-id b6ffe31feb0d equals the candidate's; modes and blobs match; verify-commit ok (3 paths). Sage's combined 228+238 gate on c515273db plus both rows' 6 paths, 07:11:30Z to 07:23:34Z: seat 165/0, cli 640/0, all ten scripts/test-*.sh 585/0. |
|
||||
| 229 | Row B: per-instance pi agent dir for Chet's Codex login, design only, starts after 10-27 (#428) | gosalyn; reviewers webby | #1730 | briefed | — | A live check, with Jason's or Mos's go, that two clients on one ChatGPT login don't revoke each other's refresh tokens. No build row starts before it passes. Held until after 10-27. (sage) | `docs/plans/2026-10-20_case2-rows.md` § Brief: row B, a per-instance pi agent dir for Chet's Codex login (#428) | — |
|
||||
| 230 | Case 2 C2-13a unit render and host precheck (case2-unit.sh, case2-precheck.sh) | gosalyn; reviewers webby | #1732 | done | — | Webby approves the manifest. The test file and the cli suite are green on Sage's gate, along with every scripts/test-*.sh. A run on a host is a separate window with Jason's go. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-13a unit render and host precheck | Landed c8bc5cf4c on refactor (base 390ea5261), pushed. Patch-id f4b49eb68997 matches git diff 722136772..96768766c on the 5 manifest paths (manifest fbb0d7e42c67, Webby approve r2 rev 2563). packages/cli/README.md 3-way with row 199, hunks apart. Gosalyn gate on 96768766c: cli 541/0, scripts 429/0, mutants 10/10. Merged tree check: case2-unit 30/0, host 42/0. |
|
||||
@@ -293,7 +293,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 254 | Library sync follow-up: client timeout, STATUS codes, README (222 deferrals, #1759) | megavolt; reviewers dewey | #1759 | done | 196 done | Dewey approves the manifest; bus, library and webui suites whole and every scripts/test-*.sh green on Sage's slot; the timeout and STATUS mutants killed (sage) | `docs/plans/2026-10-21_library-sync-followup.md` § Gate | Landed 331597709 (candidate b31300d79 blobs and modes on 41b0cf755; patch-id 96216216 equal, range-diff differs only in the message) plus doc eb9672c91 (Dewey N1 README sentence closed; N2 four 502 codes to DEFERRED, binding the Console library routes row). Merged-tree rerun on Sage's slot 23:44-23:55Z: library 77/0, bus 628/0, webui 305/0 with 1 skip, cli 749/0. Megavolt's gate megavolt-0102 covers scripts and both mutants. |
|
||||
| 255 | Bundled Pocket ID for users with no OIDC provider, design (Q203, #1760) | launchpad; reviewers webby | — | queued | 252 done | Webby approves DESIGN.md; design only, build rows follow after Sage checks it against Q203, O6, REQ-WEB-2 and REQ-INST-1 (sage) | `docs/plans/2026-10-21_bundled-pocket-id.md` § Gate | Vikunja 458, Gitea #1760, mos-1202 item 2. REQ-INST-2 lands in PRD 1.10 as post-v1 (mos-1204). Design only: no install, no download, no .53/.54, dragon-lin keeps Authentik. |
|
||||
| 256 | the human deletes a creative instance; any other actor refused, fire unchanged (REQ-COS-3, before the v1 tag) | honker; reviewers webby | #1761 | done | 194 done | Webby approves the manifest; touched test files whole plus business, bus, cli and webui suites and every scripts/test-*.sh green on Sage's slot; both mutants killed (sage) | `docs/plans/2026-10-21_creative-delete-human.md` § Brief: the human deletes a creative instance (REQ-COS-3, after row 194) | LANDED f12441d4f ff from 0165be266 (4 Honker commits, patch-id b28e6374). Webby APPROVE manifest-r3 01eb75aa (10 paths), request c30518. Gate 10:33-10:52Z: 4 touched files 31/0, 18 packages 3757/0 (bus 788, business 231, cli 832, webui 335), 10 scripts rc=0, mutants M1-M4b killed; skips named, env only (webui esbuild pin, test-queue root, test-task live x2). Overlap 13/257 clean. Lands before the 10-27 v1 tag. |
|
||||
| 257 | Q198 S1: pressure parser and pure rules evaluator (post-v1) | morgana; reviewers filbert | #1762 | in-review, round 2, request posted | — | Reviewer approves the manifest; touched test files whole plus the package suite green on Sage's slot; both brief mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: Q198 S1 pressure parser and pure rules evaluator | — |
|
||||
| 257 | Q198 S1: pressure parser and pure rules evaluator (post-v1) | morgana; reviewers filbert | #1762 | done | — | Reviewer approves the manifest; touched test files whole plus the package suite green on Sage's slot; both brief mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: Q198 S1 pressure parser and pure rules evaluator | Landed 73692e1bb, pushed 17:41:58Z 10-24: Morgana's 4 commits cherry-picked onto 487500a16, patches unchanged; 9 supervisor paths match Filbert r2 manifest b176a88f (APPROVE c30190, rev 3037). Gate RED 17:20Z (webui row278 sign-out timing, old test, row touches no webui), full rerun GREEN 17:41:11Z, cli skip FED R7 allowed. push-check clean. #1762 c31613, closed. Carry 447286192 lands doc-only. |
|
||||
| 258 | Email E01: mail PRD addendum draft and schemas (post-v1) | ludwig; reviewers della | #1763 | in-progress | — | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E01 mail PRD addendum draft and schemas | — |
|
||||
| 259 | Email E03: mail ledger, state and quotas (post-v1) | unassigned | #1764 | queued | 258 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E03 mail ledger, state and quotas | — |
|
||||
| 260 | Email E04: bounded MIME parsing and quarantine (post-v1) | unassigned | #1765 | queued | 258 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E04 bounded MIME parsing and quarantine | — |
|
||||
@@ -302,7 +302,7 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 263 | Email E07: address and rule evaluator (post-v1) | unassigned | #1768 | queued | 258 done, 259 done, 260 done | Reviewer approves the manifest; the row's named tests whole plus the mail suite and touched suites green on Sage's slot; reviewer-named mutants killed (post-v1, MVP keeps slot priority) (sage) | `docs/plans/2026-10-21_post-v1-row-briefs.md` § Brief: E07 address and rule evaluator | — |
|
||||
| 264 | T1 item 7: independent sessions, compaction counts and clear context | gizmoduck; reviewers duckworth | #1769 | done | — | The named reviewer approves the manifest. The design's T1-T7 acceptance tests run whole plus every touched suite on Sage's slot; T3 waits on Vikunja 466 (N2, Claude's compaction count format and the managed dataRoot reader). The design's section 6 mutants are killed. (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | LANDED 84f53540d ff on e479521d3 14:18:23Z, 21 commits = gizmoduck/row264-r13 tip (no rewrite, range-diff identity). Manifest 257408e9 approved Duckworth rev 3214 (c30640). Gate r13 2cc34aef8: 20 files, bus 827, cli 837, webui 345/1 skip, 10 shells; M1-M4 assertion kills (mos-1279 B, M2 4 files). Overlap none (13, 257). N1 actor_run binds row 269; N2 esbuild skip later gate (sage-2695). |
|
||||
| 265 | Case 2 C2-09b rooms: room.open, room.send with @tags, room.read | goldie; reviewers boyd | #1770 | done | 219 done | Boyd approves the manifest. The brief's tests run whole plus the bus suite and every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-09b rooms: room.open, room.send with @tags, room.read | LANDED 200dd396c (cherry-pick of ef4c93853, 50 blobs equal, log appends equal) 05:13Z on full gate 04:57-05:12Z: all package suites except queue 0 fail 0 skip (bus 957, cli 986, seat 218), every scripts/test-*.sh green. Boyd c31501 incl mos-1567 mount read. 22 gzip logs scanned decompressed, clean. #1770 closed c31510. Bundle 02c3c286. |
|
||||
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | researcher; reviewers dewey | #1771 | queued | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | Haiku high subagent under decision 129. |
|
||||
| 266 | Case 2 C2-10c Console room view: merged timeline and @name completion | fethry; reviewers boyd, dewey | #1771 | done | 225 done, 265 done | Dewey approves the manifest. The webui suite is green on Sage's gate, along with every touched suite. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-10c Console room view: merged timeline and @name completion | LANDED 3e5041b43 18:00:49Z 10-24, patch-ids equal to Fethry b8b26ae4d..6418ec063; gate GREEN 18:00:08Z first run (bus 1000/0, webui 458/0, cli 989+1 FED R7 allowed, 12 scripts rc 0); Boyd c31609, Dewey c31610; #1771 c31615 closed; Dewey N2 T9 race goes to Mos |
|
||||
| 267 | Case 2 C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | morgana; reviewers filbert | #1772 | queued | 216 done, 217 done, 224 done | Filbert approves the manifest. The brief's tests and the end-to-end crossover run whole plus every touched suite on Sage's slot. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-15 hybrid universes: exclusions, pins and the end-to-end crossover | R8 accepted (sage-2218). Webby's R8 noise note (gosalyn-0127) is for the builder. |
|
||||
| 268 | Case 2 C2-01c playground levels: universe-level prefixes and draft universes | gizmoduck; reviewers webby | #1773 | done | — | Webby approves the manifest. The brief's tests run whole plus the library suite and every touched suite on Sage's slot; the brief's gate grep over packages and docs/case2 finds no library-playground-outside-book. Grep for hash pins on changed files before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-01c playground levels: universe-level prefixes and draft universes | Landed c27207f6e fast-forward on ce6a96ad6 (1 commit, 9 paths, digest caa0fa4e). r4 is a carry of r3 aca53c4f0: range-diff =, patch-id 2942acc8 equal, blobs equal. Webby APPROVE round 2 on ecd9b35eb stands; carries webby-0112 (r3) and webby-0113 (r4, notes 34e1ab2bf). Gate: Gizmoduck r4 rerun brief 14/14, library 83/83, business 209/209, 0 skips; gate grep empty. 194 rebases next (business.test.mjs). |
|
||||
| 269 | 264b T1 item 7: per-session native store, workspace and single-writer fence | gizmoduck; reviewers duckworth | #1774 | done | 264 done | Duckworth approves this slice's manifest. Its design tests run whole plus every touched suite on Sage's slot, and its mutants are killed (RULINGS Q1). Tests T1 run half, T2 workspace and path mutants; whichever of 191 and 264b lands second wires 191's admission checks (Q5). (sage) | `docs/plans/2026-10-21_t1-sessions.md` § T1 item 7: independent sessions, compaction counts and clear context | Landed efcc9ea8d fast-forward on 94c33a43c (13 commits, 12 paths, manifest 455c12ee). r3 is a carry of r2 aafcc857c, range-diff = (Duckworth 0194, c30756, rev 3307). Gate r3 18:37-18:50Z all green, 0 skips: bus 834, seat 172, cli 869, case2-unit 43, singles 38. M1-M12 kills carry. Re-pin needs the Console restart by Jason or Mos right after the host. A 264a rollback refuses a v2 sessions.json, see BUILD.md Rollback. |
|
||||
@@ -323,10 +323,10 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 284 | Case 2 C2-05c the supporting scope and blob in the lookups | della; reviewers dewey | #1789 | briefed | 224 done, 283 done | Dewey approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate. Grep packages/*/tests for the scope enum pin and ['canon', 'playground'] before in-review; update each. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-05c the supporting scope and blob in the lookups (sage-2833) | — |
|
||||
| 285 | Case 2 C2-08b role changes for agent writes and the chief's read grant | magica; reviewers poe, webby | #1790 | done | 213 done, 291 done | Webby approves the manifest. Business suite, test-config.sh and every scripts/test-*.sh green on Sage's gate. Grep for CREATIVE_KINDS and 64-hex pins of changed role files. Sage checks it against mos-1309 item 5. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-08b role changes for agent writes and the chief's read grant (mos-1309 item 5) | LANDED 1f3d0350f 13:33:42Z: candidate 2e4503dc7 (14 commits) cherry-picked onto 25874bc86. Logs kept both sides; otherwise the tree equals the candidate except on 7 base paths. Poe c31568 rev 3919, Webby c31569 rev 3920. Sage gate GREEN 13:28:46Z with its own npm ci: 4236 package tests, 0 fail, 1 allowed FED R7 skip; 12 scripts rc 0. No CREATIVE_KINDS, no role-file hash pins. Roles match mos-1309 item 5. 286 rebases as r5. |
|
||||
| 286 | Case 2 C2-03b push and per-seat credentials for the library mirror (with R4, mos-1323) | rockerduck; reviewers boyd, dewey | #1791 | done | 222 done | Dewey approves the manifest. Library and bus suites and every scripts/test-*.sh green on Sage's gate, mutant killed. A search of the run output for the fixture token finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-03b push and per-seat credentials for the library mirror | LANDED 5d7801dc1 14:37:02Z: r6 75f6d56b1 (19 commits) picked onto d55944edb; logs kept both sides, code equals the candidate. Boyd c31587 rev 3942, Dewey c31588 rev 3943 (r2 c31584/c31585). Sage gate GREEN 14:36:42Z, own npm ci: 4251 tests, 0 fail, 1 allowed FED R7 skip; 12 scripts rc 0; fixture values in 0 of 29 outputs; B285-M1 kills T7/T9. r6 shortened two fixtures after push-check STOP secrets (sage-4112). Chief seed per sage-4100. |
|
||||
| 287 | Case 2 C2-21 the grant executor for a library project | megavolt; reviewers dewey | #1792 | briefed | 226 done, 285 done | Dewey approves the manifest. Bus suite, Gitea Docker test, install test and every scripts/test-*.sh green on Sage's gate. A search of the run output for the fixture admin and seat tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-21 the grant executor for a library project | — |
|
||||
| 287 | Case 2 C2-21 the grant executor for a library project | steelbeak; reviewers boyd, dewey | #1792 | done | 226 done, 285 done | Dewey approves the manifest. Bus suite, Gitea Docker test, install test and every scripts/test-*.sh green on Sage's gate. A search of the run output for the fixture admin and seat tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-21 the grant executor for a library project | LANDED 0e935776e 16:12:35Z 10-24 (cand c2338ed58 + a5370b198 on 9b6345466). Boyd APPROVE c31602 rev 3967, Dewey APPROVE c31603 rev 3968. Sage gate GREEN 16:11:38Z: bus 998/0 (Gitea Docker F-A/F-B + X-G3, minted 4 hits 0), business 283/0, cli 989 +1 allowed FED R7 skip, webui 442/0, 12 scripts rc 0. F-C negative record-only follow-up. Dewey N1-N3 non-blocking. #1792 closed c31606. No P19 task. |
|
||||
| 288 | Case 2 C2-02 agent writes, the notice, revert and her Console saves | bubba; reviewers webby | #1793 | briefed | 215 done, 282 done, 283 done, 284 done, 285 done, 286 done | Webby approves the manifest. Library, bus and harness suites and every scripts/test-*.sh green on Sage's gate, mutants killed. A search of the run output for the fixture tokens finds nothing. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-02 agent writes, the notice, revert and her Console saves | A2 revert contract (morgana-0199, sage-3596): library.revert is a human verb. Args {project, commit}, strict keys; project is the business.projects key (the chat repo link), not a UUID. Result {project, path, reverted, commit}. Refusals: 409 stale-base, and 409 library-revert-not-agent for an agent caller. Owner Bubba under Q245 (mos-1506). |
|
||||
| 289 | Case 2 C2-11b Console library pages and her saves (Haiku subagent, decision 129) | researcher; reviewers filbert | #1794 | briefed | 214 done, 288 done | Filbert approves the manifest. The webui suite and every scripts/test-*.sh are green on Sage's gate. Grep for hash pins on bus.js and serve.mjs before in-review. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-11b Console library pages and her saves | — |
|
||||
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | briefed | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | — |
|
||||
| 290 | Case 2 C2-12b the setup sheet rewrite for free chat, supporting folders and saves | louie; reviewers webby | #1795 | done | 217 done, 218 done, 285 done, 291 done | Doc only. Webby approves it against the ruling. Before landing, grep packages/*/tests and scripts/test-*.sh to confirm no test reads the file. (sage) | `docs/plans/2026-10-20_case2-rows.md` § C2-12b the setup sheet rewrite for free chat, supporting folders and saves | LANDED 3d7dff345 on refactor, pushed 14:55:11Z: cherry-pick of 963303403..22510988d onto 18334d713, four commits equal by patch-id and range-diff, SETUP-SHEET.md blob 37b451c6 as approved. Gate per brief (doc only): Webby approve c31596 rev 3954; grep of packages/*/tests, scripts/test-*.sh and tests/ for SETUP-SHEET finds nothing (positive control hits). No suite run: no test reads the file (decision 137). #1795 closed c31597. |
|
||||
| 291 | PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | gosalyn; reviewers webby | #1796 | done | 282 done | Webby approves the text. Jason says yes to the exact text (Mos asks him, mos-1328 step 3); no landing before that. requirements.test.mjs and every scripts/test-*.sh that reads the PRD or requirements.json are green on Sage's gate. Mos gets the landing commit. (sage) | `docs/plans/2026-10-22_prd-1-16-brief.md` § PRD 1.16 library.write routes under REQ-LIB-6 (mos-1328) | Landed 4874d1e83 on e909ef51a: 482e865ac replayed, patch-id equal (cef7de74), range-diff =. Webby APPROVE c30848 on 482e865ac; Jason approved the text in Q229. Gate 16:15Z on the tip tree: every package suite but queue, 0 fail 0 skipped (tasks 100, bus 891, cli 941, plugins 119, webui 392, business 231), every scripts/test-*.sh rc 0 (queue-root and row 244 skips only). PRD 1.16. 1.17 is row 308. |
|
||||
| 292 | stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | gizmoduck; reviewers duckworth | #1797 | done | 203 done, 270 done, 271 done | Duckworth approves the manifest. The touched test files run whole, the cli suite and every scripts/test-*.sh are green on Sage's gate, and the mutants are killed. (sage) | `docs/plans/2026-10-22_config-checks-any-key-brief.md` § stack-release.sh config_checks runs the target loader on any config (row 270 rollback hazard) | Landed 6aab979e6 (cherry-pick of c902a767d..90be6cccd, range-diff all =, patch-id a45c9ba4 equal). Tested head 0d0021454: stack-release.test 224/224, cli suite r2 red only compactions-launch:229 session-invalid 22 (idle-stop race, Vikunja 588, row 302), r3 rerun 910/910 under mos-1411, ten shells green, M1-M9 killed. Duckworth APPROVE rev 3433 on 2fa7ae17. |
|
||||
| 293 | 574: maintenance-free test fixture commits (Vikunja 574) | gander; reviewers filbert | #1805 | done | — | Filbert approves. The four whole files, then the seat and cli suites, are green on Sage's gate. (sage) | `docs/plans/2026-10-23_mvp-readiness-rows.md` § Brief: 574, maintenance-free test fixture commits (Vikunja 574) | LANDED e51d11640 03:55Z 10-23 (cherry-pick of gander b23783f39..a9cae2bd6 onto 464010fbe; range-diff 6/6 equal, range patch-id equal). Filbert APPROVE c30982 cdda41f5; run 2 4 files 153/0, seat 202/0, cli 905/0, 312 flagged/0 maintenance children. N1: lifecycle.test needs TMPDIR of 50 bytes or less. Residual: queue store.mjs, discord git.mjs, harness tools.mjs unflagged. #1805 closed c31013, P19 574 c888. |
|
||||
@@ -364,6 +364,12 @@ Generated from `docs/plans/queue.json` revision 3945 by `queue render`. Do not e
|
||||
| 325 | Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563) | goldie; reviewers gladstone | #1821 | done | — | New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455) (sage) | `docs/plans/2026-10-24_case1-project-key-brief.md` § Brief: re-key the Case 1 project to stack | LANDED c5f96836d (ff from 99930362e), tip 3d2569ce2. Gladstone approve c31484 rev 3788 on c5f96836d. Sage gate green 01:31Z on c5f96836d: all package suites 0 fail (business 264, cli 986), every scripts/test-*.sh rc 0. Project key is now stack, matching .mosaic/project.json; new case1-project-key test checks the real project file. Old-test line :70 under mos-1564. |
|
||||
| 326 | Host run sheet: five operator gaps (B2-2 proof format, 323 N2, pre-install key check, failed-first-install retry, backup SHA256SUMS form) | goldie; reviewers gladstone | #1822 | done | — | Gladstone approves the manifest; every new claim cites its line; no test reads the sheet; Sage's full landing gate green (sage) | `docs/plans/2026-10-24_run-sheet-doc-brief.md` § Brief: five run-sheet gaps | LANDED 33f49f7d5 (cherry-pick of a1b13c60e, patch-id match, sheet e4c14516) in f58049de0 with row 208 on one full gate 04:36-04:51Z, all suites green. Gladstone c31489. #1822 closed c31507. B2-2 closes in DEFERRED. Bundle 9fc7754a. |
|
||||
| 327 | Push check in the repository: scripts/push-check.sh passes a binary only when an approved review candidate lists its path and sha256, plus scripts/test-push-check.sh (#1825, mos-1572 A) | bradford; reviewers dewey, poe | #1825 | done | — | Poe gives the first Codex review, Dewey reviews the landing candidate; scripts/test-push-check.sh and every other scripts/test-*.sh green on Sage's gate; the mutants killed; Sage's next push runs through scripts/push-check.sh (sage) | `docs/plans/2026-10-24_push-guard-binary-manifest.md` § Brief: push-check.sh passes a binary only when the reviewed candidate lists its path and sha256 | LANDED 68c08b4e7 (pushed 07:18:03Z 10-24): 3 commits on 6a5e12511, blobs equal round 3 d858c9f8b; Poe c31525 rev 3882, Dewey c31526 rev 3883; gate GREEN 07:15:49Z with its own npm ci (first run red on empty canonical node_modules, sage-4023); 6 push-check mutants KILLED; next push 42ba0f5bc ran through scripts/push-check.sh --fetch, PUSH_READY; #1825 closed c31530. |
|
||||
| 328 | T9 library-credential test: the second broker start passes Node's TIMEOUT_MAX interval so no timer tick overlaps the verb sync; mutant fails a 50-run stress loop (#1826, Vikunja 622, mos-1594) | fethry; reviewers boyd, dewey | #1826 | done | — | Boyd gives the first Codex review, Dewey reviews the landing candidate; touched tests run whole, the 50-run stress loop passes with the fix and M1 fails it; bus and every scripts/test-*.sh green on Sage's gate (sage) | `docs/plans/2026-10-24_t9-library-sync-race.md` § Brief: the T9 verb sync on the second start runs with no timer tick able to overlap it | LANDED 8bb5b3c73 19:40Z 10-24 (8 Fethry commits on 5e77cdd09). Gate GREEN 19:38:54Z on 8bb5b3c73, no drift. Boyd APPROVE c31621, Dewey APPROVE c31622. #1826 closed c31625; Vikunja 622 done. |
|
||||
| 329 | Model proxy P1: model-proxy package, pinned fetch with sha256 refusal, mosaic proxy init, proxy unit (237, mos-1600) | magica; reviewers boyd, dewey | #1827 | in-progress | — | Boyd first review, re-measuring both release hashes; Dewey landing review; BUILD.md has the acceptance output. Sage's landing gate: every package suite but queue plus every scripts/test-*.sh green, touched test files whole, refusals each tested, named mutants killed (brief Gate). (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P1 the model-proxy package, pinned fetch, proxy init and the proxy unit | — |
|
||||
| 330 | Model proxy P4: Console forwards /proxy/ owner-only in a CSP sandbox (237, mos-1600) | poe; reviewers filbert, gladstone | #1828 | blocked (from briefed): panel needs localStorage, CSP sandbox forbids it; waits on Jason's subdomain answer, Vikunja 624 (mos-1602) | — | Gladstone first review; Filbert landing review; starts now, lands after P1 rebased on it; webui with MOSAIC_REQUIRE_EDITOR_BUILD=1. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P4 the Console forwards /proxy/ to the instance proxy in a sandbox | — |
|
||||
| 331 | Model proxy P2: per-agent model keys, reconcile and launch preflight (237, mos-1600) | sage; reviewers boyd, dewey | #1829 | briefed | 329 done | Boyd first review; Dewey landing review; three named mutants killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P2 per-agent model keys, reconcile and the launch preflight | P2 input from magica-0020 (acceptance-probe-4): 8.0.42 revocation lags ~105ms after v8 PUT (first call 200, then 401); reconcile/revoke must poll to 401, not assume instant. Per-key model objects give 422; no per-key model restriction. |
|
||||
| 332 | Model proxy P3: harness config from the proxy (237, mos-1600) | sage; reviewers dewey, gladstone | #1830 | briefed | 331 done | Gladstone first review; Dewey landing review; named mutant killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P3 harness config from the proxy | — |
|
||||
| 333 | Model proxy P5: usage plug-in and per-key limits, per-key count only (237, mos-1600) | sage; reviewers boyd, dewey | #1831 | briefed | 331 done | Boyd first review; Dewey landing review; named mutant killed. Sage gate: all package suites but queue and every scripts/test-*.sh green; brief Gate. (sage) | `docs/plans/2026-10-24_model-proxy-rows.md` § Brief: P5 usage plug-in and per-key limits | — |
|
||||
|
||||
<!-- mosaic-queue:end -->
|
||||
|
||||
|
||||
+2502
-40
File diff suppressed because it is too large.
Load diff
@@ -101,7 +101,16 @@ export const TASK_PAGE_MAX = 200;
|
||||
// request allowed past the ordinary 64 KiB envelope (server.mjs and client.mjs), up to its content cap.
|
||||
export const PROJECT_FILE_VERBS = Object.freeze(['project.files', 'project.file', 'project.file.save']);
|
||||
// C2-03a: library.sync is the human's fetch, and with `accept` the one way past library-diverged.
|
||||
export const LIBRARY_VERBS = Object.freeze(['library.file', 'library.status', 'library.sync']);
|
||||
// Row 224: pure agent lookups are separate from the three human-only verbs. Listing them
|
||||
// here also makes server.mjs await their host effects instead of serializing a Promise as {}.
|
||||
const LIBRARY_LOOKUPS = Object.freeze({
|
||||
'library.find': { method: 'find', fields: ['universe', 'query', 'type', 'book', 'scope', 'offset'], required: ['universe', 'query'] },
|
||||
'library.get': { method: 'get', fields: ['universe', 'path', 'from', 'links_from'], required: ['universe', 'path'] },
|
||||
'library.search': { method: 'search', fields: ['universe', 'text', 'book', 'scope', 'offset'], required: ['universe', 'text'] },
|
||||
'library.manuscript': { method: 'manuscript', fields: ['universe', 'book', 'scope', 'chapter', 'from', 'to'], required: ['universe', 'book'] },
|
||||
'library.universe': { method: 'universeInfo', fields: ['universe'], required: ['universe'] },
|
||||
});
|
||||
export const LIBRARY_VERBS = Object.freeze(['library.file', 'library.status', 'library.sync', ...Object.keys(LIBRARY_LOOKUPS)]);
|
||||
export const PROJECT_SAVE_VERB = 'project.file.save';
|
||||
export const PROJECT_SAVE_REQUEST_MAX = 1024 * 1024 + 64 * 1024;
|
||||
export const WEB_VERBS =Object.freeze(['web.search', 'web.fetch']);
|
||||
@@ -5352,10 +5361,42 @@ export class Broker {
|
||||
throw this.#refused(s, ProjectFailure && e instanceof ProjectFailure ? new BusError(e.code) : e, request);
|
||||
}
|
||||
}
|
||||
// Row 224 C2-05b: the agent never supplies a host repo, commit or business. Its current
|
||||
// holder and business entry are rechecked before/after awaits; runtime owns Git and queries.
|
||||
async #requestLibraryLookup(s, { verb, args }) {
|
||||
try {
|
||||
this.#agent(s);
|
||||
const business = this.#business(s.business);
|
||||
const projects = Object.entries(business.projects ?? {}).filter(([, entry]) => entry.kind === 'library');
|
||||
if (!projects.length || typeof this.#library?.lookup !== 'function') fail('library-not-configured');
|
||||
const spec = LIBRARY_LOOKUPS[verb];
|
||||
keys(args, ['project', ...spec.fields], spec.required);
|
||||
if (!text(args.universe, 160)) fail('invalid-request');
|
||||
if (args.project === undefined && projects.length !== 1) fail('invalid-request');
|
||||
const project = args.project ?? projects[0][0];
|
||||
if (!text(project, 160) || !projects.some(([name]) => name === project)) fail('library-not-configured');
|
||||
const revision = JSON.stringify(business.projects[project]);
|
||||
const authorize = () => {
|
||||
this.#agent(s);
|
||||
this.#processingGate(s, verb);
|
||||
if (JSON.stringify(this.#business(s.business).projects?.[project]) !== revision) fail('library-not-configured');
|
||||
};
|
||||
const { project: ignored, ...query } = args;
|
||||
authorize();
|
||||
const result = await this.#library.lookup({ business, project }, { method: spec.method, args: query, authorize });
|
||||
authorize();
|
||||
this.#secretCheck(result);
|
||||
return result;
|
||||
} catch (error) {
|
||||
const Failure = this.#library?.LibraryError;
|
||||
throw this.#refused(s, Failure && error instanceof Failure ? new BusError(error.code) : error, { verb, args });
|
||||
}
|
||||
}
|
||||
// C2-04 M4: only a verified human reads library text or status. Host Git
|
||||
// functions stay injected, preserving this module's sandbox import closure.
|
||||
async #requestLibrary(s, request) {
|
||||
const { verb, args } = request;
|
||||
if (Object.hasOwn(LIBRARY_LOOKUPS, verb)) return this.#requestLibraryLookup(s, request);
|
||||
try {
|
||||
this.#human(s);
|
||||
const fields = verb === 'library.file' ? ['project', 'path'] : verb === 'library.sync' ? ['project', 'accept'] : ['project'];
|
||||
|
||||
@@ -87,6 +87,38 @@ export async function harnessLevels({ log = (e) => process.stderr.write(`plugins
|
||||
}
|
||||
return (name) => levels.get(name) ?? null;
|
||||
}
|
||||
// Row 224 C2-05b: a host-only adapter. A failed fetch keeps serving immutable old
|
||||
// objects, as C2-03a requires; no caller may select a repo, commit or sync --accept.
|
||||
export function createLibraryLookup({ dataRoot, sync, now = () => Date.now() }) {
|
||||
const methods = { find: libraryModule.find, get: libraryModule.get, search: libraryModule.search,
|
||||
manuscript: libraryModule.manuscript, universeInfo: libraryModule.universeInfo };
|
||||
return async (context, { method, args, authorize }) => {
|
||||
if (!Object.hasOwn(methods, method)) throw new libraryModule.LibraryError('query-argument-invalid', 'unknown lookup');
|
||||
authorize();
|
||||
const source = { ...context, dataRoot };
|
||||
let state = await libraryModule.libraryStatus(source);
|
||||
authorize();
|
||||
// sage-2819 / dewey-0852: failed attempts also pace inline retries. Otherwise an
|
||||
// outage makes every lookup refetch while lastFetch remains old or null.
|
||||
const recentAttempt = Math.max(state.lastFetch === null ? -Infinity : Date.parse(state.lastFetch),
|
||||
state.lastError === null ? -Infinity : Date.parse(state.lastError.at));
|
||||
const age = now() - recentAttempt;
|
||||
if (age > libraryModule.SYNC_INTERVAL_MS || age < 0) {
|
||||
try { await sync(source); }
|
||||
catch (error) {
|
||||
// A timer may already be fetching this mirror. Its current served state is still safe.
|
||||
if (!(error instanceof libraryModule.LibraryError) || error.code !== 'library-sync-busy') throw error;
|
||||
}
|
||||
authorize();
|
||||
state = await libraryModule.libraryStatus(source);
|
||||
}
|
||||
if (!state.served) throw new libraryModule.LibraryError('library-commit', 'library has never been synced');
|
||||
authorize();
|
||||
return methods[method]({ repo: libraryModule.mirrorPath(source), commit: state.served,
|
||||
mapping: libraryModule.checkMapping(context.business.projects[context.project]) }, args);
|
||||
};
|
||||
}
|
||||
|
||||
export async function startBroker({
|
||||
dataRoot,
|
||||
businesses,
|
||||
@@ -310,6 +342,8 @@ export async function startBroker({
|
||||
store,
|
||||
businesses,
|
||||
library: {
|
||||
lookup: createLibraryLookup({ dataRoot, sync: context =>
|
||||
processing.work(() => libraryModule.sync(context, syncOptions(context.business))) }),
|
||||
readFile: context => libraryModule.readLibraryFile({ ...context, dataRoot }),
|
||||
status: context => libraryModule.libraryStatus({ ...context, dataRoot }),
|
||||
sync: (context, { accept }) => processing.work(() => libraryModule.sync({ ...context, dataRoot }, { ...syncOptions(context.business), accept })),
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
// Row 224: exercise broker authorization, real C2-05a queries and host served snapshots.
|
||||
// All bodies and remotes are made up. External sync is replaced only at its trusted host seam.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { Store } from '../src/store.mjs';
|
||||
import { Broker } from '../src/broker.mjs';
|
||||
import { createLibraryLookup, startBroker } from '../src/runtime.mjs';
|
||||
import { serve } from '../src/server.mjs';
|
||||
import { Client } from '../src/client.mjs';
|
||||
import * as library from '../../library/src/index.mjs';
|
||||
import { scratchRepo, note } from '../../library/tests/query-fixture.mjs';
|
||||
import { mappingEntry, git } from '../../library/tests/helpers.mjs';
|
||||
|
||||
const NOW = Date.parse('2026-10-22T16:00:00.000Z');
|
||||
const VERBS = ['library.find', 'library.get', 'library.search', 'library.manuscript', 'library.universe'];
|
||||
const GET = { universe: 'harbor', path: 'Book One/Characters/Tomas.md' };
|
||||
const ARGS = [
|
||||
{ universe: 'harbor', query: 'Tomas' }, GET,
|
||||
{ universe: 'harbor', text: 'ferry' }, { universe: 'harbor', book: 'Book One' }, { universe: 'harbor' },
|
||||
];
|
||||
const TEXTS = {
|
||||
'Book One/Characters/Tomas.md': note('character', 'Tomas', 'He runs the ferry. [[Elsewhere]]'),
|
||||
'Book One/Chapters/01.md': '# A ferry\nOne line.\n',
|
||||
'Book One/Playground/What If.md': note('character', 'Maybe Tomas', 'Unwritten ferry.'),
|
||||
'Second World/Characters/Elsewhere.md': note('character', 'Elsewhere', 'Other ferry.'),
|
||||
};
|
||||
|
||||
async function world(t, { projects, adapter, claimed = true, now = () => NOW } = {}) {
|
||||
const f = scratchRepo(t, TEXTS), dataRoot = join(f.root, 'data');
|
||||
mkdirSync(dataRoot);
|
||||
const business = { id: 'acme', human: 'jason', vars: { 'bus.tester': true },
|
||||
projects: projects ?? { books: mappingEntry() }, arbiters: { technical: 'coder', delivery: 'coder' },
|
||||
roles: { coder: { definition: 'coder', authority: { withinRole: [], crossRole: [] } } } };
|
||||
const context = { business, project: 'books', dataRoot };
|
||||
let mirror = null, state = null, fetches = 0;
|
||||
if (business.projects.books?.kind === 'library') {
|
||||
mirror = await library.ensureMirror(context);
|
||||
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served');
|
||||
state = { version: 1, branch: 'main', served: f.commit, lastFetch: new Date(NOW).toISOString(),
|
||||
lastError: null, diverged: null, problems: [] };
|
||||
writeFileSync(join(mirror, library.SYNC_STATE_FILE), JSON.stringify(state), { mode: 0o600 });
|
||||
}
|
||||
const setState = patch => {
|
||||
state = { ...state, ...patch };
|
||||
writeFileSync(join(mirror, library.SYNC_STATE_FILE), JSON.stringify(state), { mode: 0o600 });
|
||||
};
|
||||
const lookup = createLibraryLookup({ dataRoot, now, sync: async () => {
|
||||
fetches++;
|
||||
setState({ lastFetch: new Date(now()).toISOString() });
|
||||
return { state, code: null };
|
||||
} });
|
||||
const store = new Store(dataRoot);
|
||||
t.after(() => store.close());
|
||||
const broker = new Broker({ store, businesses: { acme: business },
|
||||
library: adapter ?? { lookup, LibraryError: library.LibraryError } });
|
||||
const agent = broker.bindLaunch({ business: 'acme', role: 'coder', run: 'r224', harness: 'pi' });
|
||||
if (claimed) broker.request(agent, { verb: 'role.claim' });
|
||||
const human = broker.bindHuman({ business: 'acme', human: 'jason', via: 'cli', outsideAgent: true });
|
||||
const reader = broker.bindReader({ business: 'acme' }), tester = broker.bindTester({ business: 'acme' });
|
||||
const call = (verb, args = {}, cap = agent) => Promise.resolve().then(() => broker.request(cap, { verb, args }));
|
||||
return { ...f, dataRoot, business, context, mirror, store, broker, agent, human, reader, tester,
|
||||
call, lookup, setState, fetches: () => fetches };
|
||||
}
|
||||
|
||||
test('all five agent verbs read the configured served commit through real queries', async t => {
|
||||
const f = await world(t);
|
||||
const [find, get, search, manuscript, universe] = await Promise.all(VERBS.map((verb, i) => f.call(verb, ARGS[i])));
|
||||
for (const result of [find, get, search, manuscript, universe]) assert.equal(result.commit, f.commit);
|
||||
assert.deepEqual(find.results.map(x => x.path), ['Book One/Characters/Tomas.md']);
|
||||
assert.match(JSON.stringify(get.lines), /He runs the ferry/);
|
||||
assert.deepEqual(get.links_out, []); // The only target is in another universe.
|
||||
assert.ok(search.results.length > 0);
|
||||
assert.ok(search.results.every(x => !x.path.startsWith('Second World/') && x.scope === 'canon'));
|
||||
assert.equal(manuscript.path, 'Book One/Chapters/01.md');
|
||||
assert.deepEqual(universe.books, ['Book One']);
|
||||
assert.equal(f.fetches(), 0);
|
||||
const playground = await f.call('library.find', { universe: 'harbor', query: '', scope: 'playground' });
|
||||
assert.deepEqual(playground.results.map(x => x.path), ['Book One/Playground/What If.md']);
|
||||
assert.equal(await f.call('library.get', { universe: 'harbor', path: 'Second World/Characters/Elsewhere.md' }), null);
|
||||
});
|
||||
|
||||
test('each verb refuses missing universe and unknown keys before host work', async t => {
|
||||
let calls = 0;
|
||||
const f = await world(t, { adapter: { lookup: () => { calls++; } } });
|
||||
for (const [i, verb] of VERBS.entries()) {
|
||||
const { universe, ...missing } = ARGS[i];
|
||||
await assert.rejects(f.call(verb, missing), { code: 'invalid-request' });
|
||||
for (const extra of [{ repo: '/elsewhere' }, { commit: 'b'.repeat(40) }, { business: 'other' }, { accept: 'b'.repeat(40) }])
|
||||
await assert.rejects(f.call(verb, { ...ARGS[i], ...extra }), { code: 'invalid-request' });
|
||||
await assert.rejects(f.call(verb, { ...ARGS[i], universe: '' }), { code: 'invalid-request' });
|
||||
}
|
||||
assert.equal(calls, 0);
|
||||
});
|
||||
|
||||
test('no library, missing host adapter and undeclared project refuse library-not-configured', async t => {
|
||||
const noLibrary = await world(t, { projects: {} });
|
||||
const noAdapter = await world(t, { adapter: {} });
|
||||
const f = await world(t);
|
||||
for (const [i, verb] of VERBS.entries()) {
|
||||
await assert.rejects(noLibrary.call(verb, ARGS[i]), { code: 'library-not-configured' });
|
||||
await assert.rejects(noAdapter.call(verb, ARGS[i]), { code: 'library-not-configured' });
|
||||
await assert.rejects(f.call(verb, { ...ARGS[i], project: 'other' }), { code: 'library-not-configured' });
|
||||
}
|
||||
f.business.projects.second = mappingEntry();
|
||||
f.broker.reloadBusiness('acme', f.business);
|
||||
await assert.rejects(f.call('library.get', GET), { code: 'invalid-request' });
|
||||
assert.equal((await f.call('library.get', { ...GET, project: 'books' })).commit, f.commit);
|
||||
});
|
||||
|
||||
test('reads need the current holder on each call; human/tester/reader are not agents', async t => {
|
||||
const f = await world(t, { claimed: false });
|
||||
for (const [i, verb] of VERBS.entries()) {
|
||||
await assert.rejects(f.call(verb, ARGS[i]), { code: 'not-holder' });
|
||||
await assert.rejects(f.call(verb, ARGS[i], f.human), { code: 'agent-required' });
|
||||
await assert.rejects(f.call(verb, ARGS[i], f.tester), { code: 'tester-refused' });
|
||||
await assert.rejects(f.call(verb, ARGS[i], f.reader), { code: 'read-only' });
|
||||
}
|
||||
f.broker.request(f.agent, { verb: 'role.claim' });
|
||||
assert.equal((await f.call('library.get', GET)).commit, f.commit);
|
||||
f.broker.request(f.agent, { verb: 'role.release' });
|
||||
await assert.rejects(f.call('library.get', GET), { code: 'not-holder' });
|
||||
});
|
||||
|
||||
test('releasing a claim during an async lookup fences the response', { timeout: 30_000 }, async t => {
|
||||
let release, entered;
|
||||
const started = new Promise(resolve => { entered = resolve; });
|
||||
const f = await world(t, { adapter: { lookup: async () => {
|
||||
entered(); await new Promise(resolve => { release = resolve; }); return { lines: [[1, 'not returned']] };
|
||||
} } });
|
||||
const pending = f.call('library.get', GET);
|
||||
await started;
|
||||
f.broker.request(f.agent, { verb: 'role.release' });
|
||||
release();
|
||||
await assert.rejects(pending, { code: 'not-holder' });
|
||||
});
|
||||
|
||||
test('a business library removed during an async read cannot leak its result', { timeout: 30_000 }, async t => {
|
||||
let release, entered;
|
||||
const started = new Promise(resolve => { entered = resolve; });
|
||||
const f = await world(t, { adapter: { lookup: async () => {
|
||||
entered(); await new Promise(resolve => { release = resolve; }); return { lines: [[1, 'not returned']] };
|
||||
} } });
|
||||
const pending = f.call('library.get', GET);
|
||||
await started;
|
||||
delete f.business.projects.books;
|
||||
f.broker.reloadBusiness('acme', f.business);
|
||||
release();
|
||||
await assert.rejects(pending, { code: 'library-not-configured' });
|
||||
});
|
||||
|
||||
test('freshness boundary, unknown last fetch and failed fetch still use immutable served state', async t => {
|
||||
const f = await world(t);
|
||||
f.setState({ lastFetch: new Date(NOW - 60_000).toISOString() });
|
||||
await f.call('library.get', GET);
|
||||
assert.equal(f.fetches(), 0);
|
||||
f.setState({ lastFetch: new Date(NOW - 60_001).toISOString() });
|
||||
await f.call('library.get', GET);
|
||||
assert.equal(f.fetches(), 1);
|
||||
f.setState({ lastFetch: null });
|
||||
await f.call('library.universe', { universe: 'harbor' });
|
||||
assert.equal(f.fetches(), 2);
|
||||
let attempted = 0, clock = NOW;
|
||||
const failing = createLibraryLookup({ dataRoot: f.dataRoot, now: () => clock, sync: async () => {
|
||||
attempted++;
|
||||
f.setState({ lastError: { code: 'library-fetch-failed', at: new Date(clock).toISOString() } });
|
||||
return { code: 'library-fetch-failed' };
|
||||
} });
|
||||
f.setState({ lastFetch: null });
|
||||
const result = await failing(f.context, { method: 'get', args: GET, authorize() {} });
|
||||
assert.equal(attempted, 1);
|
||||
assert.equal(result.commit, f.commit);
|
||||
for (const elapsed of [1, 59_999, 60_000]) {
|
||||
clock = NOW + elapsed;
|
||||
const retry = await failing(f.context, { method: 'get', args: GET, authorize() {} });
|
||||
assert.equal(retry.commit, f.commit, 'serve the old pin during the failure cooldown');
|
||||
assert.equal(attempted, 1, 'recent lastError prevents another sync even though lastFetch is null');
|
||||
}
|
||||
clock = NOW + 60_001;
|
||||
assert.equal((await failing(f.context, { method: 'get', args: GET, authorize() {} })).commit, f.commit);
|
||||
assert.equal(attempted, 2, 'retry only after the failed-attempt interval expires');
|
||||
});
|
||||
|
||||
test('an unreadable sync state fails closed; a busy sync keeps serving the existing pin', async t => {
|
||||
const f = await world(t);
|
||||
const lookup = createLibraryLookup({ dataRoot: f.dataRoot, now: () => NOW, sync: async () => {
|
||||
throw new library.LibraryError('library-sync-busy', 'another fetch');
|
||||
} });
|
||||
f.setState({ lastFetch: null });
|
||||
assert.equal((await lookup(f.context, { method: 'get', args: GET, authorize() {} })).commit, f.commit);
|
||||
writeFileSync(join(f.mirror, library.SYNC_STATE_FILE), '{bad json', { mode: 0o600 });
|
||||
await assert.rejects(f.call('library.get', GET), { code: 'library-state-invalid' });
|
||||
});
|
||||
|
||||
test('a stale lookup sees the commit published by its sync, not the prior served commit', async t => {
|
||||
const f = await world(t);
|
||||
writeFileSync(join(f.repo, GET.path), note('character', 'Tomas', 'Changed ferry.'));
|
||||
git(f.repo, 'add', '-A'); git(f.repo, 'commit', '-qm', 'second fixture');
|
||||
const next = git(f.repo, 'rev-parse', 'HEAD');
|
||||
const lookup = createLibraryLookup({ dataRoot: f.dataRoot, now: () => NOW, sync: async () => {
|
||||
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', f.mirror, 'HEAD:refs/heads/served');
|
||||
f.setState({ served: next, lastFetch: new Date(NOW).toISOString() });
|
||||
return { code: null };
|
||||
} });
|
||||
f.setState({ lastFetch: null });
|
||||
const result = await lookup(f.context, { method: 'get', args: GET, authorize() {} });
|
||||
assert.equal(result.commit, next);
|
||||
assert.match(JSON.stringify(result.lines), /Changed ferry/);
|
||||
});
|
||||
|
||||
test('real socket awaits lookup results and rechecks rejected query arguments', { timeout: 30_000 }, async t => {
|
||||
const f = await world(t);
|
||||
const path = join(f.root, 'lookup.sock');
|
||||
const server = await serve({ broker: f.broker, path }); t.after(() => server.close());
|
||||
const client = new Client({ path, cap: f.agent });
|
||||
const result = await client.call('library.get', GET);
|
||||
assert.equal(result.commit, f.commit);
|
||||
await assert.rejects(client.call('library.find', { universe: 'harbor', query: '', scope: 'anything' }), /query-argument-invalid/);
|
||||
});
|
||||
|
||||
test('runtime actually injects the lookup adapter, without exposing human library.file', { timeout: 30_000 }, async t => {
|
||||
const f = scratchRepo(t, TEXTS), dataRoot = join(f.root, 'runtime'); mkdirSync(dataRoot);
|
||||
const business = { id: 'acme', human: 'jason', plugins: { harness: ['pi'] }, projects: { books: mappingEntry() },
|
||||
arbiters: { technical: 'coder', delivery: 'coder' },
|
||||
roles: { coder: { definition: 'coder', authority: { withinRole: [], crossRole: [] } } } };
|
||||
const runtime = await startBroker({ dataRoot, businesses: { acme: business } }); t.after(() => runtime.close());
|
||||
const mirror = library.mirrorPath({ business, project: 'books', dataRoot });
|
||||
git(f.repo, '-c', 'protocol.file.allow=always', 'push', '-q', mirror, 'HEAD:refs/heads/served');
|
||||
writeFileSync(join(mirror, library.SYNC_STATE_FILE), JSON.stringify({ version: 1, branch: 'main', served: f.commit,
|
||||
lastFetch: new Date().toISOString(), lastError: null, diverged: null, problems: [] }), { mode: 0o600 });
|
||||
const cap = runtime.broker.bindLaunch({ business: 'acme', role: 'coder', run: 'r224-runtime', harness: 'pi' });
|
||||
runtime.broker.request(cap, { verb: 'role.claim' });
|
||||
assert.equal((await runtime.broker.request(cap, { verb: 'library.get', args: GET })).commit, f.commit);
|
||||
await assert.rejects(runtime.broker.request(cap, { verb: 'library.file', args: { project: 'books', path: GET.path } }),
|
||||
{ code: 'human-required' });
|
||||
});
|
||||
@@ -528,6 +528,7 @@ export function createLauncher({
|
||||
contract: readFileSync(resolved.contract, "utf8"),
|
||||
arbiter: Object.values(b.arbiters).includes(instance),
|
||||
tracker,
|
||||
library: Object.values(b.projects ?? {}).some(project => project.kind === "library"),
|
||||
credentials,
|
||||
session: { run, launchedBy, workspace: inSandbox, toolSocket, turnMarker, checkouts: inSandbox === workspace ? checkouts : checkouts.map((c) => ({ ...c, path: join(inSandbox, relative(workspace, c.path)) })) },
|
||||
harness,
|
||||
|
||||
@@ -27,6 +27,7 @@ import { bootConfig, loadSystem } from "../src/config.mjs";
|
||||
import { startTimeOf } from "../src/host.mjs";
|
||||
import { createLauncher } from "../src/launcher.mjs";
|
||||
import { fakeHarnesses, fixture, tmp } from "./helpers.mjs";
|
||||
import { mappingEntry } from "../../library/tests/helpers.mjs";
|
||||
// Row 264a (#1769): a thread made by hand gets the default session the 3k-3l migration gives a
|
||||
// legacy thread, with the thread's id.
|
||||
const DEFAULT_SESSION = [
|
||||
@@ -533,3 +534,29 @@ test('C2-09a: terminating one creative thread does not signal an unselected peer
|
||||
await until(() => !readSessions(f.dataRoot).some(row => row.run === runs[0]));
|
||||
assert.ok(readSessions(f.dataRoot).some(row => row.run === runs[1]));
|
||||
});
|
||||
|
||||
// Row224: the launcher must derive library presence from business projects, not
|
||||
// from session checkouts (a library never has an agent clone).
|
||||
test("C2-05b a real launched bundle gets lookups for a library project without a library checkout", { skip, timeout: 60000 }, async t => {
|
||||
const prepared = prepare(t, { idle: 10, edit: doc => {
|
||||
doc.projects.books = mappingEntry();
|
||||
doc.roles.chief.credentials = { gitea: { ...doc.roles.coder.credentials.gitea } };
|
||||
return doc;
|
||||
} });
|
||||
const { f } = prepared;
|
||||
const host = await start(t, prepared);
|
||||
const project = host.call("project.create", { name: "library work", repo: "stack" }).id;
|
||||
const thread = host.call("thread.create", { project, name: "lookup" }).id;
|
||||
const sent = host.call("thread.send", { thread, body: "inspect bundle" });
|
||||
// broker.mjs:4854 uses async citation hydration for a business with a library.
|
||||
const reply = await until(async () => (await host.call("thread.messages", { thread })).find(m => m.in_reply_to === sent.id && m.class === "RESULT"));
|
||||
assert.equal(reply.from_role, "assistant");
|
||||
assert.equal(reply.to_role, "human");
|
||||
const run = launchLog(f).find(e => e.event === "launch" && e.thread === thread).run;
|
||||
const tools = JSON.parse(readFileSync(join(f.dataRoot, "launches", "acme", run, "bundle", "tools.json"), "utf8"));
|
||||
assert.deepEqual(tools.filter(tool => tool.name.startsWith("library_")).map(tool => tool.name),
|
||||
["library_find", "library_get", "library_search", "library_manuscript", "library_universe"]);
|
||||
const [session] = threadRuns(f, thread);
|
||||
assert.ok(session);
|
||||
assert.ok(!session.checkouts?.some(checkout => checkout.project === "books"));
|
||||
});
|
||||
@@ -292,6 +292,7 @@ function prompt({ contract, resolved, session, tools, untyped, builtins, persona
|
||||
// resolved resolveInstance(...) output
|
||||
// contract the role contract's text
|
||||
// arbiter true when the business names this instance as an arbiter
|
||||
// library true when the trusted business configuration has a library project
|
||||
// tracker true when the broker has task verbs for the business
|
||||
// credentials the broker's credentialStatus(...) for the instance (metadata only)
|
||||
// session { run, launchedBy, workspace, toolSocket, turnMarker, checkouts }
|
||||
@@ -309,11 +310,11 @@ function prompt({ contract, resolved, session, tools, untyped, builtins, persona
|
||||
// (row 213); an instance with persona.sha256 and no personaDir refuses
|
||||
// Returns { manifest, files, policy, tools, env } with absolute paths; env is the module's
|
||||
// adapter variables, which the host writes into session.json.
|
||||
export function buildBundle({ dir, resolved, contract, arbiter = false, tracker = false, credentials = [], session, harness, route = "primary", routeReason = null, harnessVersion = null, models = null, land = false, skillsDir = null, personaDir = null }) {
|
||||
export function buildBundle({ dir, resolved, contract, arbiter = false, tracker = false, library = false, credentials = [], session, harness, route = "primary", routeReason = null, harnessVersion = null, models = null, land = false, skillsDir = null, personaDir = null }) {
|
||||
if (route !== "primary" && route !== "fallback") throw new BundleError(`route ${JSON.stringify(route)} isn't primary or fallback`);
|
||||
const model = sessionModel(resolved, models, route);
|
||||
const web = webPlan(resolved, model.harness, harness.builtinWeb());
|
||||
const tools = typedTools({ resolved, arbiter, tracker, projects: (session.checkouts ?? []).map((c) => c.project), webTyped: !web.builtin, land });
|
||||
const tools = typedTools({ resolved, arbiter, tracker, library, projects: (session.checkouts ?? []).map((c) => c.project), webTyped: !web.builtin, land });
|
||||
const untyped = untypedActions(resolved, tools);
|
||||
const persona = readPersona(resolved, personaDir);
|
||||
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
|
||||
@@ -78,9 +78,10 @@ const TASK_TOOLS = {
|
||||
|
||||
const MESSAGE_CLASSES = ["REQUEST", "ASSIGNMENT", "REVIEW-REQUEST", "REVIEW-RESULT", "RESULT", "INFO", "DECISION", "REACTION", "BLOCK"];
|
||||
|
||||
// typedTools({ resolved, arbiter, tracker, projects, land }) -> [{ name, verb, action, description, parameters }]
|
||||
// typedTools({ resolved, arbiter, tracker, projects, land, library }) -> [{ name, verb, action, description, parameters }]
|
||||
// resolved resolveInstance(...) output for the session's instance
|
||||
// arbiter true when the business names this instance as an arbiter
|
||||
// library true when the trusted business configuration has a library project
|
||||
// tracker true when the broker has task verbs for the business
|
||||
// projects the project ids the session has a clone of
|
||||
// land true when the role and thread hold a merge decision a landing could use (landHeld)
|
||||
@@ -88,7 +89,7 @@ const MESSAGE_CLASSES = ["REQUEST", "ASSIGNMENT", "REVIEW-REQUEST", "REVIEW-RESU
|
||||
// `verb` is the broker verb; the `launch` tool has verb "launch" and goes
|
||||
// to the host instead, and "working.push", "working.fetch" and
|
||||
// "working.land" go to the host's git socket.
|
||||
export function typedTools({ resolved, arbiter = false, tracker = false, projects = [], webTyped = true, land = false }) {
|
||||
export function typedTools({ resolved, arbiter = false, tracker = false, projects = [], webTyped = true, land = false, library = false }) {
|
||||
const held = new Set([...resolved.limits.authority.withinRole, ...resolved.limits.authority.crossRole]);
|
||||
const tools = [];
|
||||
const add = (name, verb, action, description, parameters) => tools.push({ name, verb, action, description, parameters });
|
||||
@@ -222,6 +223,32 @@ export function typedTools({ resolved, arbiter = false, tracker = false, project
|
||||
url: str("The https url to fetch", { maxLength: 2048 }),
|
||||
}, ["url"]));
|
||||
}
|
||||
// Row224: no library clone or token reaches a session. The broker chooses the served
|
||||
// commit and requires a universe on every call; cursors page without cutting replies.
|
||||
if (library) {
|
||||
const universe = str("Universe ID in the configured library", { minLength: 1 });
|
||||
const project = str("Library project ID; omit only when the business has one library");
|
||||
const scope = str("canon by default; playground is opt-in", { enum: ["canon", "playground"] });
|
||||
const book = str("Book folder from library_universe");
|
||||
const offset = { type: "integer", minimum: 0, description: "next from the previous page" };
|
||||
const line = { type: "integer", minimum: 1, description: "One-based line number" };
|
||||
const base = { universe, project };
|
||||
add("library_find", "library.find", null, "Find names in one universe, at the served commit. At most 50 results; continue with next as offset.", obj({
|
||||
...base, query: str("Case-insensitive name or filename substring; empty matches all"),
|
||||
type: str("Mapped type", { enum: ["character", "place", "research", "tracker", "summary"] }),
|
||||
book, scope, offset,
|
||||
}, ["universe", "query"]));
|
||||
add("library_get", "library.get", null, "Read a mapped note and visible links in one universe at the served commit. next pages lines; links_next pages links. An explicit own-universe playground path is readable.", obj({
|
||||
...base, path: str("Mapped note path relative to the library"), from: line, links_from: offset,
|
||||
}, ["universe", "path"]));
|
||||
add("library_search", "library.search", null, "Search text in one universe at the served commit. At most 50 hits; use next as offset. Playground is opt-in.", obj({
|
||||
...base, text: str("Nonempty case-insensitive substring", { minLength: 1 }), book, scope, offset,
|
||||
}, ["universe", "text"]));
|
||||
add("library_manuscript", "library.manuscript", null, "Read one chapter of a book at the served commit, at most 400 lines. Omit chapter for the first; continue with next or next_chapter.", obj({
|
||||
...base, book, scope, chapter: str("Chapter filename without extension"), from: line, to: line,
|
||||
}, ["universe", "book"]));
|
||||
add("library_universe", "library.universe", null, "Read a universe's mapping at the served commit: books, types, playground prefixes, and hybrid sources/constraints/notes or draft metadata.", obj(base, ["universe"]));
|
||||
}
|
||||
add("read_spec", "spec.read", null, "Read a project's committed north-star spec and its file history. commit is an optional full reachable commit ID. The file commit, rather than HEAD, is what a decision cites. confirmed says the file commit was authored by the business's human.", obj({
|
||||
project: str("Project ID from the business file"),
|
||||
commit: str("Optional full historical commit ID", { pattern: "^[0-9a-f]{40}$" }),
|
||||
@@ -277,6 +304,11 @@ export function typedTools({ resolved, arbiter = false, tracker = false, project
|
||||
if (held.has("task.create"))
|
||||
add("list_requirements", "requirements", null, "List the approved PRD's requirement ids, each with a one-line title. task_create takes only these. When none fits a request, raise a prd.approve decision instead of creating the task.", obj({}));
|
||||
}
|
||||
// mos-1308 / M3: no generic spec, evidence, tracker, decision or write tools. The
|
||||
// C2-08 web grant still supplies broker web reads to companion and research only.
|
||||
if (CREATIVE_DEFINITIONS.includes(resolved.definition))
|
||||
return tools.filter(tool => tool.name === "send_message" || tool.name.startsWith("library_") ||
|
||||
tool.name === "web_search" || tool.name === "web_fetch");
|
||||
return tools;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Row224 M3: exact creative tool sets preserve the existing C2-08 web grants.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdirSync, readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { typedTools } from '../src/tools.mjs';
|
||||
import { buildBundle } from '../src/bundle.mjs';
|
||||
import { TYPES } from '../../library/src/mapping.mjs';
|
||||
import { HARNESS, REPO_SKILLS, resolvedFor, scratch } from './helpers.mjs';
|
||||
|
||||
const LOOKUPS = ['library_find', 'library_get', 'library_search', 'library_manuscript', 'library_universe'];
|
||||
const CREATIVE = ['companion', 'research', 'ideas', 'proofreader', 'fact-checker', 'continuity'];
|
||||
const role = (t, definition, harness = 'pi') => resolvedFor(t, 'bean', d => {
|
||||
d.roles.bean = { definition, credentials: { gitea: { ...d.roles.coder.credentials.gitea } }, vars: { harness, model: 'claude-sonnet-5-5', thinking: 'high' } };
|
||||
});
|
||||
const expected = definition => ['send_message', ...(['companion', 'research'].includes(definition)
|
||||
? ['web_search', 'web_fetch'] : []), ...LOOKUPS];
|
||||
|
||||
test('M3: per-role snapshots contain six names, or eight with the C2-08 web grant', t => {
|
||||
for (const definition of CREATIVE) {
|
||||
const tools = typedTools({ resolved: role(t, definition), library: true, tracker: true, arbiter: true });
|
||||
assert.deepEqual(tools.map(t => t.name), expected(definition), definition);
|
||||
assert.ok(tools.every(t => t.parameters.additionalProperties === false));
|
||||
}
|
||||
});
|
||||
|
||||
test('every noncreative role gets lookups when a library is configured, not otherwise', t => {
|
||||
for (const instance of ['coder', 'reviewer', 'pm', 'chief']) {
|
||||
const resolved = resolvedFor(t, instance);
|
||||
const ordinary = typedTools({ resolved }).map(t => t.name);
|
||||
assert.ok(!ordinary.some(name => name.startsWith('library_')));
|
||||
const tools = typedTools({ resolved, library: true });
|
||||
assert.deepEqual(tools.filter(t => t.name.startsWith('library_')).map(t => t.name), LOOKUPS);
|
||||
assert.deepEqual(tools.filter(t => !t.name.startsWith('library_')).map(t => t.name), ordinary);
|
||||
}
|
||||
});
|
||||
|
||||
test('lookup schemas require a universe even for get, include paging, and cannot select repo or commit', t => {
|
||||
const tools = typedTools({ resolved: role(t, 'ideas'), library: true });
|
||||
const required = [ ['universe', 'query'], ['universe', 'path'], ['universe', 'text'], ['universe', 'book'], ['universe'] ];
|
||||
for (const [i, tool] of tools.filter(t => t.name.startsWith('library_')).entries()) {
|
||||
assert.deepEqual(tool.parameters.required, required[i]);
|
||||
assert.equal(tool.verb, LOOKUPS[i].replace('_', '.'));
|
||||
assert.equal(tool.action, null);
|
||||
assert.equal(tool.parameters.additionalProperties, false);
|
||||
for (const key of ['repo', 'commit', 'business', 'accept']) assert.equal(Object.hasOwn(tool.parameters.properties, key), false);
|
||||
}
|
||||
const byName = Object.fromEntries(tools.map(t => [t.name, t]));
|
||||
assert.deepEqual(byName.library_find.parameters.properties.scope.enum, ['canon', 'playground']);
|
||||
assert.deepEqual(byName.library_find.parameters.properties.type.enum, [...TYPES]);
|
||||
assert.equal(byName.library_find.parameters.properties.offset.minimum, 0);
|
||||
assert.equal(byName.library_get.parameters.properties.from.minimum, 1);
|
||||
assert.equal(byName.library_get.parameters.properties.links_from.minimum, 0);
|
||||
assert.equal(byName.library_manuscript.parameters.properties.chapter.type, 'string');
|
||||
});
|
||||
|
||||
test('bundle flag reaches both harness tool manifests and creative prompt without generic tools', t => {
|
||||
for (const definition of CREATIVE) for (const harness of ['pi', 'claude-code']) {
|
||||
const dir = scratch(t), workspace = join(dir, 'ws'); mkdirSync(workspace);
|
||||
const b = buildBundle({ dir: join(dir, 'bundle'), resolved: role(t, definition, harness), library: true,
|
||||
contract: '# Creative\n', tracker: true, arbiter: true, skillsDir: REPO_SKILLS,
|
||||
session: { run: 'r224', launchedBy: 'chief', workspace, toolSocket: join(dir, 't.sock'), turnMarker: join(dir, 'turn.done') },
|
||||
harness: HARNESS[harness] });
|
||||
assert.deepEqual(b.tools.map(t => t.name), expected(definition), `${definition} ${harness}`);
|
||||
const serialized = JSON.parse(readFileSync(b.files.tools, 'utf8'));
|
||||
assert.deepEqual(serialized.map(t => t.name), expected(definition));
|
||||
const prompt = readFileSync(b.files.prompt, 'utf8');
|
||||
assert.match(prompt, /library_find/);
|
||||
assert.doesNotMatch(prompt, /Bus tools: [^\n]*(read_spec|list_agents|inbox|trail|write_record|raise_decision|list_tasks)/);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user