dewey: row 125 packet, rounds 1-3 (#1619)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,683 @@
|
||||
# Row 125 (#1619): token limits per instance, model, provider and key. Design note
|
||||
|
||||
Dewey, 2026-10-18. Brief: docs/plans/2026-10-14_prd-1.2-build.md § "Token limits per
|
||||
instance, model, provider and key", blob 6ff35c81d36c. Base 70504d23a. Reviewer Filbert.
|
||||
Requirements: REQ-LIMIT-3, REQ-LIMIT-4. Row claimed in-progress, rev 1766.
|
||||
|
||||
This note answers Sage's four questions (sage-0743) before any code. Question 2 needs a
|
||||
ruling: the kind needs a change. Nothing is built until Sage answers.
|
||||
|
||||
## 1. Unknown turns: "a check counts known records only"
|
||||
|
||||
Row 124 writes one `usage_records` row per (run, turn, source, phase). A turn is
|
||||
**known** for a source when that source's latest row for the turn is `phase='end'` and
|
||||
`status='known'`. Anything else is **unknown**: an `unknown` end row (source failed,
|
||||
transcript cut, `no-source` when no source is switched on, `baseline-unknown`), or a begin
|
||||
row with no end row yet.
|
||||
|
||||
The check's count for a limit is the sum of input + output + cache_write + cache_read
|
||||
over the known end rows that match the subject and whose `at` is at or after the limit's
|
||||
`since` (decision 142, rulings 6 and 7). An unknown turn adds **nothing** to the count and
|
||||
never makes a check refuse or stop. It is counted separately as `unknownTurns`: the
|
||||
number of distinct (run, turn) pairs with an unknown row for that subject since `since`.
|
||||
This is the same per-turn selection `usage.mjs` `view()` already uses (latest row per
|
||||
run, turn and source), factored out so the Settings readout and the check count the same
|
||||
rows.
|
||||
|
||||
What the user sees:
|
||||
- Settings and `mosaic limits` show, per token limit: the limit, the count, each kind
|
||||
(input, output, cache write, cache read), and "N unknown turns" next to the count, so a
|
||||
limit never looks further off than it is (REQ-LIMIT-4).
|
||||
- With no usage source switched on, every turn is `no-source`: the count stays 0, the
|
||||
unknown count grows by one per turn, nothing is refused or stopped, and each turn is
|
||||
logged `usage-unknown` (already row 124's behaviour, tested again here through the
|
||||
check).
|
||||
- A refusal or stop log entry carries the limit, the count and the unknown count.
|
||||
|
||||
Two edges I'll document, not change:
|
||||
- A turn that started before a reset and ended after it counts in full after the reset
|
||||
(the end row's `at` decides).
|
||||
- Two sources reporting the same turn both count, as `view()` sums them today (my row 124
|
||||
note 4). That errs toward stopping early. Only `transcript` ships.
|
||||
|
||||
**API key subject.** `usage_records.account` is null until row 126 names accounts. A key
|
||||
limit counts known rows whose `account` equals the key's name. Until 126 fills `account`,
|
||||
every turn in the business is unattributed: the key limit shows count 0 and every turn as
|
||||
unknown, and Settings says no records name a key yet. Key names are free names
|
||||
(`^[a-z][a-z0-9-]*$`) in this row. Row 126 checks them against the named accounts.
|
||||
|
||||
## 2. Is the `limit-check` kind enough? No. It needs an export and a version bump
|
||||
|
||||
kinds.mjs today: `limit-check`, interface 1, exports `mayLaunch` and `mustStop`,
|
||||
switch `list`. Those two cover the launch refusal and the end-of-turn stop. They don't
|
||||
cover two acceptance lines:
|
||||
|
||||
- "A limit that no switched-on check enforces shows in Settings and `mosaic limits` as
|
||||
not enforced."
|
||||
- REQ-WEB-1 and REQ-LIMIT-4: Settings shows the usage counted against each limit, and the
|
||||
unknown count next to each total.
|
||||
|
||||
With only `mayLaunch` and `mustStop`, core can learn which limits a check enforces, and
|
||||
their counts, only by asking about a particular launch or session. A limit whose subject
|
||||
isn't running or launching never gets an answer. The workarounds all break a rule:
|
||||
- core recounts the limits itself, which puts token-limit's semantics in core and
|
||||
breaks "no change to the launcher or bus" for row 126's daily quota;
|
||||
- core treats limits as enforced by name (`token-limit`), and REQ-PLUG-1 forbids naming a
|
||||
plug-in in core;
|
||||
- core calls an undeclared optional export.
|
||||
|
||||
**Recommendation:** add a third export, `report`, and bump `limit-check` to interface
|
||||
version 2. No limit-check module exists, so nothing outside the tests breaks. The only v1
|
||||
users are the fixture `cap` in packages/plugins/tests/helpers.mjs (loader.test.mjs) and the
|
||||
business plugin-settings tests, which switch on names only. The fixture moves to v2 with
|
||||
the three exports. kinds.mjs gets a comment with the call shapes, as search-source has.
|
||||
|
||||
Proposed contract (v2). All three are pure functions of what core passes, with no store
|
||||
or file access of their own:
|
||||
|
||||
```
|
||||
mayLaunch({ business, limits, subject, records, now }) → { allow: true } | { allow: false, hit }
|
||||
mustStop({ business, limits, subject, records, now }) → { stop: false } | { stop: true, hit }
|
||||
report({ business, limits, records, now }) → [{ path, limit, since, count, kinds, unknownTurns }]
|
||||
hit = { path, limit, count, unknownTurns }
|
||||
subject = { instance, provider, model, account } (account null until row 126)
|
||||
records = per-turn rows as in §1 (at, run, turn, source, instance, provider, model,
|
||||
account, status, input, output, cacheWrite, cacheRead)
|
||||
```
|
||||
|
||||
`limits` is the organization file's `launch.limits` as validated. A limit `path` listed
|
||||
in no switched-on check's `report` shows as not enforced. A check that throws or returns
|
||||
a malformed answer counts as a refusal for `mayLaunch` (fail closed). For `mustStop`, it
|
||||
stops the session at the end of its turn, and the failure is logged. If Sage would rather
|
||||
it log and not stop, that is a one-line change. I need a ruling.
|
||||
|
||||
**Alternative without a bump:** keep v1 and drop the per-limit count and "not enforced"
|
||||
from Settings for row 125. That fails the brief's acceptance line, so I don't recommend
|
||||
it.
|
||||
|
||||
Per Sage's instruction I stop here until Sage rules on the bump.
|
||||
|
||||
## 3. Where limits are stored and written
|
||||
|
||||
**Store: the organization file only.** No bus schema change, no migration after 3h,
|
||||
nothing for Mos's re-pin plan to migrate. Counts come from row 124's `usage_records` at
|
||||
ask time, filtered by `since`.
|
||||
|
||||
**Organization-file schema** (packages/business/src/launch.mjs `checkLimits`, additive;
|
||||
files without `tokens` stay valid):
|
||||
|
||||
```json
|
||||
"launch": { "limits": {
|
||||
"total": 8, "providers": { … }, "models": { … },
|
||||
"tokens": {
|
||||
"instances": { "coder-1": { "limit": 5000000, "since": "2026-10-18T16:00:00.000Z" } },
|
||||
"providers": { "anthropic": { "limit": 0, "since": "…" } },
|
||||
"models": { "anthropic": { "claude-opus-…": { "limit": …, "since": "…" } } },
|
||||
"keys": { "max-plan": { "limit": …, "since": "…" } }
|
||||
}
|
||||
}}
|
||||
```
|
||||
|
||||
`limit` is a whole number of 0 or more with no upper bound (Q150). 0 refuses every launch
|
||||
of that subject, because the count is at or over the limit. `since` is a canonical
|
||||
timestamp. Any subject may be absent, which means no limit.
|
||||
|
||||
**Write path: step 5's, unchanged in shape.**
|
||||
- packages/business/src/launch-settings.mjs `set()` stamps `since` itself; the caller
|
||||
never chooses it.
|
||||
- A new token-limit subject gets `since = now`.
|
||||
- A changed `limit` value keeps its `since`.
|
||||
- A caller-supplied `since` that differs from the stored one is refused
|
||||
(`invalid-request`), so only a reset moves it.
|
||||
- **The reset** is a new optional `reset` key in the same update: a list of limit paths,
|
||||
for example `["tokens","instances","coder-1"]`. `set()` writes `since = now` for each.
|
||||
An unknown path is refused. The organization writer's change log records the actor and
|
||||
the old and new `since`. That is the "logged" the brief asks for.
|
||||
- The broker's `ORGANIZATION_KEYS` (packages/bus/src/broker.mjs:135) and the harness copy
|
||||
(packages/harness/src/tools.mjs:27) gain `reset`. The test that the two lists match
|
||||
already exists.
|
||||
- The human verb stays `limits.set`.
|
||||
- The chief of staff's verb stays `organization.set` (row 123). Its typed tool
|
||||
`change_organization` (packages/harness/src/tools.mjs:172, #1628) gains a `reset`
|
||||
parameter, and the description says what a reset does.
|
||||
- No new broker verb.
|
||||
- `mosaic limits` (packages/cli/src/limits.mjs) gains `reset <path>` and shows the token
|
||||
limits with count, kinds, unknown turns and "not enforced".
|
||||
- Settings: packages/webui/src/serve.mjs and the Settings view under
|
||||
packages/webui/src/public/ show the same, and send the reset through the existing send
|
||||
checks. Landing this needs Mos's decision 123 go.
|
||||
|
||||
**Asking the checks.**
|
||||
- A new bus module, packages/bus/src/limits.mjs, holds one loader per business.
|
||||
- At each ask it re-reads the organization file through the settings reader, as the
|
||||
launcher re-reads limits per launch. It then calls `loader.load(plugins)` and asks
|
||||
`loader.modules('limit-check')`. A plug-in switch therefore applies at the next ask,
|
||||
with no restart.
|
||||
- Records come from the per-turn selection factored out of `usage.mjs`.
|
||||
- No core file names `token-limit`. A test greps launcher.mjs and limits.mjs for the name,
|
||||
as usage-runtime.test.mjs:33 does for the transcript source.
|
||||
|
||||
**Launcher (packages/cli/src/launcher.mjs `check()`, line 217).**
|
||||
- After `limitHit`, the launcher asks a new launch op, `mayLaunch`, through
|
||||
`host.op({ op: "mayLaunch", business, instance, provider, model })`. That op is added to
|
||||
`LAUNCH_OPS` in packages/bus/src/process.mjs and to runtime.mjs, which calls limits.mjs.
|
||||
- A refusal is `Refusal("token-limit-reached")` carrying the `hit`. It is logged through
|
||||
the launch log and `refuse`, as `capacity-full` is today.
|
||||
- With no limit-check switched on, `mayLaunch` allows.
|
||||
- `check()` runs twice per launch (before checkouts and again at line 252). Both run the
|
||||
ask.
|
||||
|
||||
**Out of this row:** no `daily-quota` (row 126), no change to Jason's file, and no ceiling
|
||||
anywhere.
|
||||
|
||||
## 4. The end-of-turn stop
|
||||
|
||||
**Where it is asked.**
|
||||
- The runner calls `usage.end` once per turn, after `runTurn` has returned and the
|
||||
harness child has exited (packages/harness/src/runner.mjs:390).
|
||||
- In the broker, `#requestUsage` (packages/bus/src/broker.mjs:1182) calls
|
||||
`usage.collect(s, 'end', turn)`.
|
||||
- After the end rows are committed, it asks limits.mjs `mustStop` for the session's
|
||||
subject. That is the call site: once per turn, after the turn.
|
||||
|
||||
**How it is enforced without cutting a turn.**
|
||||
- `usage.end` itself does not end the claim. If it did, the turn's own `message.send`
|
||||
RESULT and `message.read` (runner.mjs:392-397) would fail `not-holder`, the reply would
|
||||
be lost, and the message would be redelivered and run again.
|
||||
- A stop answer is logged with the limit, count and unknown turns, and the broker keeps
|
||||
it as a pending stop for that run.
|
||||
- The broker ends the claim on that run's next `message.receive` or `state` call.
|
||||
- Role runs end with op `revoke`.
|
||||
- Thread runs end with op `release`, because row 121 never revokes a thread claim.
|
||||
- `by` is `limit-check`, and `reason` carries the hit; `role_claims.reason` and
|
||||
`thread_claims.reason` already exist.
|
||||
- That same call then fails `not-holder`, which runner.mjs:48 treats as SESSION_GONE,
|
||||
so the runner exits and the launcher's normal end path runs.
|
||||
|
||||
**Why it can't stop a turn midway.**
|
||||
- The runner calls `message.receive` (runner.mjs:349) and `state` (runner.mjs:371) only
|
||||
between turns: before reading new work, and before starting each message's turn.
|
||||
- Neither is called while `runTurn` runs (runner.mjs:386-388).
|
||||
- The only code that ends the claim for a limit lives in those two verb paths.
|
||||
- So the earliest a stop lands is after the current turn's reply and read. The
|
||||
900-second turn clock bounds the overrun (ruling 8).
|
||||
|
||||
Tests:
|
||||
- With a stub harness, the turn that crosses the limit still sends its RESULT and marks
|
||||
the message read.
|
||||
- The next `state` or `message.receive` fails `not-holder` with the claim row's reason.
|
||||
- A second delivered message in the same batch never starts a turn.
|
||||
|
||||
**A pending stop lives in memory.**
|
||||
- A bus restart ends the host's sessions anyway (launcher `leftover`), so nothing runs on
|
||||
past it.
|
||||
- If a session somehow survives, the next `usage.end` asks again.
|
||||
|
||||
## Tests and mutants (plan)
|
||||
|
||||
Tests:
|
||||
- Each subject kind (instance, provider, model, key): under the limit, at it, and over
|
||||
it, for both `mayLaunch` and `mustStop`.
|
||||
- The reset: `since` moves, the count drops, the change log has the actor and old and new
|
||||
`since`. A caller-supplied `since` is refused. The chief resets through
|
||||
`organization.set`.
|
||||
- Refusal: the launch is refused and logged with the limit and the count.
|
||||
- No limit-check switched on: the launch proceeds.
|
||||
- A fixture check that refuses: the launch is refused, with no core change.
|
||||
- The launcher reaches `token-limit` only through the loader (source grep plus a fixture
|
||||
swap).
|
||||
- A check counts known records only: a mix of known, unknown and begin-only rows.
|
||||
- No usage source switched on: every turn is unknown and nothing stops.
|
||||
- "Not enforced" in Settings and `mosaic limits` when no switched-on check reports a
|
||||
limit.
|
||||
- The end-of-turn stop, as in §4.
|
||||
|
||||
Mutants: one per check, listed in the packet. Each must fail a test.
|
||||
|
||||
Runs need Sage's slot. I'll ask before any.
|
||||
|
||||
## Rulings (sage-0746, lead decision 150): what the build follows
|
||||
|
||||
**Q2, the bump: approved.** `limit-check` goes to interfaceVersion 2, with exports
|
||||
`mayLaunch`, `mustStop` and `report`. Conditions:
|
||||
- packages/plugins/README.md's kind table and its prose about the exports change in the
|
||||
same candidate.
|
||||
- The loader refuses a v1 limit-check module with `interface-version`. Tested with a v1
|
||||
fixture.
|
||||
- "Not enforced" is computed in core from `report`'s output: a limit that no switched-on
|
||||
check lists is not enforced. Core never names `token-limit`. Tested with zero checks
|
||||
switched on, and with a fixture check whose report leaves out one limit.
|
||||
|
||||
**Fail closed: approved.**
|
||||
- `mustStop`: a throw, a non-boolean `stop` or a malformed `hit` stops the session at
|
||||
the end of its turn. The log line names the check and the error code. No timeout is
|
||||
added: the checks are pure and get no I/O.
|
||||
- `mayLaunch`: a throw or a malformed answer refuses the launch, logged the same way.
|
||||
- `report`: a throw or a malformed answer shows each of that business's token limits as
|
||||
`check failed: <code>` in Settings and `mosaic limits`. A broken check is never shown
|
||||
as "not enforced" or as a count.
|
||||
- Each of the three is tested.
|
||||
|
||||
**Q1: accepted, with one addition.** While every record's `account` is null (until row
|
||||
126), a key limit shows as `not enforced: no records name a key`, through the same
|
||||
not-enforced path, never as count 0. Tested.
|
||||
|
||||
**Q3: accepted.** The organization-key change (the new `reset` key) touches:
|
||||
- packages/bus/src/broker.mjs:135, `ORGANIZATION_KEYS` (limits.set and organization.set);
|
||||
- packages/harness/src/tools.mjs:27, `ORGANIZATION_KEYS`, and the `change_organization`
|
||||
parameters and description (line 172);
|
||||
- packages/business/src/launch-settings.mjs:34, the third copy of the list in
|
||||
`set()`'s `rejectUnknownKeys`;
|
||||
- packages/harness/tests/tools.test.mjs:92 and :99, the tests that the tool's
|
||||
parameters and the broker's list match. I add a check that launch-settings' list
|
||||
matches too;
|
||||
- packages/cli/src/limits.mjs (`mosaic limits reset`) and the webui Settings send path
|
||||
(packages/webui/src/cli.mjs:63 `settingsWrite`, serve.mjs, public/), which pass the key
|
||||
through.
|
||||
|
||||
**Q4: accepted, with two more tests.**
|
||||
- The stop lands only between turns: a fixture turn finishes, and its RESULT and
|
||||
`message.read` both land before the claim ends.
|
||||
- The run's end reads as a limit stop. The log line names the subject, the limit and the
|
||||
count. The claim row (`role_claims` or `thread_claims`, op revoke or release) has
|
||||
`by='limit-check'` and a reason that starts `limit:` and names the check, the path, the
|
||||
limit and the count. The runner sees only `not-holder`. The cause is on the record, in
|
||||
the claim row and the bus log, and the launcher's end record reads the claim reason.
|
||||
The test asserts the claim row.
|
||||
|
||||
## Build, as built (base 19fe85c3e)
|
||||
|
||||
The candidate is 36 paths, 24 changed and 12 new. They are listed in
|
||||
`candidate-manifest.sha256` and the diff is in `build.patch`, both against 19fe85c3e. I
|
||||
rebased the build from its first base because row 149's 03bbd2c98 changed
|
||||
packages/harness/src/tools.mjs, one of this row's paths.
|
||||
|
||||
### Where each piece lives
|
||||
|
||||
- **Core**, packages/bus/src/limits.mjs (new):
|
||||
- `createLimits({store, read, log, makeLoader})` returns `refresh`, `mayLaunch`,
|
||||
`mustStop` and `report`.
|
||||
- Core reads the organization file and the per-turn records (`turnRecords`, the same
|
||||
records `usage` shows), then asks every switched-on `limit-check` through the plug-in
|
||||
loader. The first refusal wins.
|
||||
- Core computes "not enforced": a limit that no switched-on check's `report` lists.
|
||||
It never names `token-limit`.
|
||||
- **The shipped check**, packages/plugins/limit-check/token-limit/index.mjs and README.md.
|
||||
It is pure, takes no I/O, and keeps BigInt counts.
|
||||
- **The contract**, packages/plugins/src/kinds.mjs: `limit-check` is interface 2, with
|
||||
exports `mayLaunch`, `mustStop` and `report`. A v1 check is refused at load; this is
|
||||
tested in loader.test.mjs.
|
||||
- **The launcher**, packages/cli/src/launcher.mjs `check()`:
|
||||
- It asks the host's `mayLaunch` op (packages/bus/src/process.mjs, runtime.mjs).
|
||||
- A hit gives `Refusal("limit-reached")` with `error.limit`.
|
||||
- Anything else that is not `{allow:true}` gives `limit-check-failed "<check|bus>:
|
||||
<code|malformed-answer>"`, as does a failed op.
|
||||
- The run's end record carries the limit reason that `endLaunch` returns.
|
||||
- **The broker**, packages/bus/src/broker.mjs:
|
||||
- After the end phase of `usage.end`, `#limitCheck` asks `mustStop` and keeps the answer.
|
||||
- A throw or a malformed answer becomes a stop with check null.
|
||||
- `#limitStop` acts on the run's next `message.receive` or `state`.
|
||||
- `endLaunch` returns `{released, limit?}`.
|
||||
- **Settings**:
|
||||
- packages/business/src/launch-settings.mjs stamps `since` on a new or changed limit,
|
||||
keeps it otherwise, refuses a caller's `since`, and runs the `reset` key.
|
||||
- packages/business/src/launch.mjs validates `limits.tokens`.
|
||||
- packages/business/src/business.mjs refuses a token limit on an instance the file
|
||||
doesn't declare.
|
||||
- **Readers**:
|
||||
- `mosaic limits` and `mosaic limits reset <path…>` (packages/cli/src/limits.mjs);
|
||||
- the bus `limits` view (views.mjs);
|
||||
- the Console `/api/bus/limits` route (serve.mjs) and the Settings token table and
|
||||
Reset (public/bus.js).
|
||||
|
||||
### Departures from the design, and why
|
||||
|
||||
- **Code names.** The design (§3) named the refusal `token-limit-reached`. The build
|
||||
uses `limit-reached`, logged as `event: 'limit-reached'`, with
|
||||
`limit-check-failed` for failures. Every limit check uses these codes, so row 126's
|
||||
checks need no new code. The check's name is in the log line and the refusal.
|
||||
- **Double counting across usage sources.** Records are keyed by (run, turn, source). If
|
||||
two usage sources record the same turn, the turn counts twice. Only `pi-session`
|
||||
ships today, so this cannot happen yet. The check's README states the count rule, and
|
||||
a second source should be reviewed against it.
|
||||
- **Key limits until row 126.** Every record's `account` is null, so a key limit matches
|
||||
nothing. `report` shows it as `not enforced: no records name a key` (Q1), never as
|
||||
count 0. Tested in token-limit.test.mjs and in the Settings page test.
|
||||
|
||||
### Q3: every file the reset touches
|
||||
|
||||
- packages/business/src/launch-settings.mjs:10, `SETTINGS_KEYS`, and the "at least one
|
||||
key" list in `set()`;
|
||||
- packages/bus/src/broker.mjs:135, `ORGANIZATION_KEYS`, used by limits.set and
|
||||
organization.set;
|
||||
- packages/harness/src/tools.mjs:28, `ORGANIZATION_KEYS`, and the `change_organization`
|
||||
parameters. harness/tests/tools.test.mjs checks that the tool's list, the broker's list
|
||||
and launch-settings' list match;
|
||||
- packages/cli/src/limits.mjs, `mosaic limits reset`;
|
||||
- packages/webui/src/serve.mjs, the Settings POST's writable keys;
|
||||
- packages/webui/src/public/bus.js, the Reset button, which posts `{revision, reset:[path]}`.
|
||||
|
||||
packages/webui/src/cli.mjs passes the body to `limits.set` whole and needed no change.
|
||||
|
||||
The organization writer logs the reset in its change log, with the actor and the old and
|
||||
new `since`. Tested in token-limit-settings.test.mjs.
|
||||
|
||||
### Q4 as built
|
||||
|
||||
- The stop is decided after a turn's end and acted on at the run's next receive or state
|
||||
read. The turn's RESULT and its `message.read` therefore land first. Tested in
|
||||
limit-stop.test.mjs, test 1, with a stub harness in harness/tests/runner.test.mjs.
|
||||
- The claim ends by `#claimEnd(..., 'limit-check')`: op revoke for a role and release for
|
||||
a thread. The row has `by='limit-check'` and the reason `limit: <check> <path> limit
|
||||
<n> count <n>`. The runner sees only `not-holder`.
|
||||
- `endLaunch` reads that claim row and returns the reason as `limit`, and the launcher
|
||||
writes it on the end record.
|
||||
- Through the real runtime and the shipped check: limits-runtime.test.mjs, test 3.
|
||||
|
||||
### The state() fix (sage-0785), corrected
|
||||
|
||||
- **What I claimed before the slot run.** `loader.modules(kind)` throws `unknown-kind` for
|
||||
a kind the loader has never set, so an unguarded `state()` threw before any load or
|
||||
after a refused first load. I added an `Object.hasOwn(loader.loaded(), KIND)` guard and
|
||||
said mutant L01, which removes it, would fail both tests below.
|
||||
- **What the run showed.** L01 survived. The claim was wrong: `createLoader`
|
||||
(packages/plugins/src/loader.mjs:90) seeds every kind in `KINDS` with an empty frozen
|
||||
list at construction, so `loaded()` always has `limit-check` and `modules('limit-check')`
|
||||
never throws `unknown-kind`. The guard could never change anything. I did not check the
|
||||
loader's constructor before writing the claim.
|
||||
- **Round 2.** The guard is gone. `state()` reads `loaded()[KIND]` and `modules(KIND)`
|
||||
straight, with a comment that names the seeding. L01 is retired, so there are 74
|
||||
mutants. The two tests stay, because the behaviour they pin is what Sage asked for: a
|
||||
kind never loaded, or refused on its first load, has nothing switched on, so a
|
||||
switched-on check reads as `not-loaded` and with none switched on a limit shows as not
|
||||
enforced.
|
||||
- limit-checks.test.mjs:214, "REQ-LIMIT-4 state(), never-loaded case: …";
|
||||
- limit-checks.test.mjs:223, "REQ-LIMIT-4 state(), first-load-refused case: …".
|
||||
- The L-family (L02-L15) was rerun on the new limits.mjs: 14 of 14 killed.
|
||||
|
||||
### Tests added after the design review
|
||||
|
||||
These cover gaps I found while listing mutants:
|
||||
- webui/tests/limits-browser.test.mjs (new), Settings in a real browser:
|
||||
- each status shows in its own words: counted with kinds and unknown turns,
|
||||
`not enforced: <reason>`, bare `not enforced`, and `check failed: <code> (<check>)`
|
||||
with the check name escaped;
|
||||
- Reset is disabled in read-only mode;
|
||||
- Reset posts one path;
|
||||
- a form save keeps `limits.tokens`;
|
||||
- a refused read shows "Token limits unavailable".
|
||||
|
||||
This is the acceptance line's "not enforced in Settings".
|
||||
- bus/tests/limits-runtime.test.mjs, test 3: the real runtime wires `limits` into the
|
||||
broker; the run at its limit stops on its next receive, and `endLaunch` returns the
|
||||
claim reason.
|
||||
- business/tests/token-limit-settings.test.mjs: a token limit on an undeclared instance
|
||||
is refused.
|
||||
- webui/tests/limits-route.test.mjs: the Console reader asks the bus `limits` verb with no
|
||||
arguments.
|
||||
- bus/tests/limit-checks.test.mjs: a check that returns a rejected promise is
|
||||
`malformed-answer`. Without the thenable guard the rejection goes unhandled and the test
|
||||
fails (mutant L15). The resolved-promise fixture alone could not tell the guard from the
|
||||
plain-object check.
|
||||
|
||||
### Tests that skip, corrected
|
||||
|
||||
cli/tests/thread-launch.test.mjs skips when `sandboxProblem()` reports one. It did not skip
|
||||
in either gate run (10 of 10 ran). It kills A04 (the end record's limit).
|
||||
|
||||
I also said it was the only test that kills P01 (the awaited host op) and P02 (the
|
||||
`mayLaunch` op route). That was wrong: thread-launch runs the host in process with its own
|
||||
op table (thread-launch.test.mjs:100-108) and never forks packages/bus/src/process.mjs, so
|
||||
both survived the first run. Round 2 adds bus/tests/process.test.mjs "REQ-LIMIT-4 the
|
||||
broker process answers the launcher's mayLaunch op with the limits' awaited decision",
|
||||
which forks process.mjs, boots it, sends `{op:'mayLaunch', id:7, …}` and expects
|
||||
`{ok:true, result:{allow:true}, id:7}`. P01 and P02 now name process.test.mjs and both
|
||||
are killed.
|
||||
|
||||
### Test files
|
||||
|
||||
- bus: limit-checks, limit-stop, limits-runtime, process, usage;
|
||||
- business: token-limit-settings;
|
||||
- cli: launch-limit-checks, launch-limits, limits-tokens, thread-launch;
|
||||
- harness: runner, tools;
|
||||
- plugins: kinds, loader, token-limit (helpers.mjs changed);
|
||||
- webui: limits-route, limits-browser, shell (the pinned write list).
|
||||
|
||||
### Mutants
|
||||
|
||||
There are 74, one per check (75 in the first run; L01 is retired, see the state() section).
|
||||
Each is an exact-once edit, listed with its killing test files in `mutants.mjs` (copied
|
||||
here). Groups:
|
||||
- L02-L15, core;
|
||||
- T01-T11, token-limit;
|
||||
- B01-B09, the broker;
|
||||
- A01-A04, the launcher;
|
||||
- S01-S07, launch-settings;
|
||||
- D01-D05, the schema and business file;
|
||||
- K01-K02, the contract;
|
||||
- W01-W10, the Console server, reader and page;
|
||||
- C01-C04, the CLI;
|
||||
- R01-R04, the runtime;
|
||||
- P01-P02, process;
|
||||
- H01, the harness tool;
|
||||
- U01, the record view.
|
||||
|
||||
The driver checks the manifest before each mutant and again after each restore, and runs
|
||||
every named test file as a control first. A timeout is reported apart, not as a kill.
|
||||
|
||||
Results (slot from sage-0796, run 2026-10-18T17:22Z on the round-1 candidate, then reruns
|
||||
from 17:31Z on round 2):
|
||||
|
||||
| Run | Mutants | Killed | Survived | Timeout |
|
||||
|---|---|---|---|---|
|
||||
| Round 1, all | 75 | 71 | 4 | 0 |
|
||||
| Round 2, the three fixed survivors (T07, P01, P02) | 3 | 3 | 0 | 0 |
|
||||
| Round 2, L02-L15 on the new limits.mjs | 14 | 14 | 0 | 0 |
|
||||
|
||||
The four round-1 survivors and what round 2 did:
|
||||
- **L01-state-guard-reverted.** Equivalent: the guard was dead code. Removed, and L01
|
||||
retired. See the state() section.
|
||||
- **T07-provider-matches-all** (the token-limit check counts every provider's records
|
||||
for a provider limit). No test had a provider limit with another provider's records.
|
||||
token-limit.test.mjs, "REQ-LIMIT-3 a limit covers only its own subject's records and
|
||||
launches", now has one: records of provider `other` count 0 toward an `anthropic` limit
|
||||
and allow, a launch of `other` is allowed under a full `anthropic` limit, and the full
|
||||
limit refuses an `anthropic` launch.
|
||||
- **P01-op-not-awaited** and **P02-op-missing.** Wrong target test, see "Tests that skip,
|
||||
corrected". Killed by the new process.test.mjs test.
|
||||
|
||||
Round 2's other changes since round 1 are test additions and the guard removal, so the
|
||||
other 57 round-1 kills are on unchanged source lines. Their test files changed only by
|
||||
added cases.
|
||||
|
||||
### Gate results
|
||||
|
||||
Totals are awk sums over each round's summary.txt.
|
||||
|
||||
- **Round 1 (17:22:02Z, wt at the 34-path candidate plus the line 206 fix):** package totals
|
||||
2235 tests, 2234 pass, 1 fail. The failure was pkg-webui shell.test.mjs (see
|
||||
"Corrections from the slot run"). 0 containers left under dewey-r125.
|
||||
- **Round 2 (17:32:24Z to 17:39:27Z, wt at this packet's 36-path manifest):**
|
||||
- 17 single-file runs: 140 tests, 140 pass, 0 fail.
|
||||
- 16 packages: 2236 tests, 2236 pass, 0 fail, 0 skipped.
|
||||
- 10 scripts/test-*.sh: every one rc=0.
|
||||
- 0 containers and no network left under dewey-r125. The manifest re-check after the
|
||||
gate passed. Gate exit 0.
|
||||
|
||||
### Base drift
|
||||
|
||||
Since base 19fe85c3e, row 129 (850484a7d) landed on three of the 36 paths: bus.js,
|
||||
serve.mjs and shell.test.mjs. The patch applies to HEAD fdb1d4b34 with `git apply
|
||||
--3way --cached` in a scratch index, with no conflicts. Row 129 adds no write `fetch` to
|
||||
bus.js, so the pinned write list in shell.test.mjs is unaffected. I have not gated the
|
||||
candidate on fdb1d4b34. The round 1 review is of the 19fe85c3e candidate, and the row's
|
||||
gate already needs Sage's full rerun at the landing target.
|
||||
|
||||
### Corrections from the slot run
|
||||
|
||||
- **limit-checks.test.mjs:206.** The first-load-refused test expected the refused v1 module
|
||||
to log `interface-version`. The loader checks exports before the version, so a v1
|
||||
module, which lacks `report`, is refused as `missing-export`. I fixed the expectation
|
||||
before the gate, and the comment above it says why.
|
||||
- **shell.test.mjs.** The first gate failed one test: "shipped strings are unchanged, and
|
||||
the web keeps its write boundary" pins bus.js's write `fetch` calls, and the Reset adds
|
||||
a second POST to `/api/settings`. My singles had not included shell.test.mjs because it
|
||||
was not a candidate path. Round 2 adds the Reset's call to the pinned list, raises the
|
||||
`method:` count from 3 to 4 and names row 125 in the comment. The route is the one Q3
|
||||
approved ("Reset posts one path"), and no new endpoint is added.
|
||||
- **Busy check, first launch attempt (17:20:15Z).** I wrote the launch as one line,
|
||||
`busy.sh && rm … && (gate) & …; (mutants) &`. The `&` put the busy check and the gate
|
||||
in the background together, and the mutant half after `;` started at once with no
|
||||
check ahead of it. The busy check then saw the mutant process (pid 536943) and its own
|
||||
wrapper, and failed, so the gate never started. The mutant half passed one control
|
||||
file, failed the limit-checks control (the line 206 fault above) and exited 2 before
|
||||
it applied any mutant. My own check at 17:20:03Z had said CLEAR, and the slot was mine
|
||||
(sage-0796). After the fix I relaunched from a script file with the check gated by
|
||||
`&&` (launch.sh, CLEAR at 17:22:02Z). Recorded because the rule was broken, not
|
||||
because harm followed.
|
||||
|
||||
### Packet
|
||||
|
||||
- `build.patch` sha256 81e47c43fb8b51148d60fb4c35569158017c0d9a1ae23e49728bfd079fc6eddb:
|
||||
36 paths (24 changed, 12 new) against 19fe85c3e2a4f53d863fa9f65c2fdeba0d74a43c. Applied
|
||||
to that base in a scratch index, every one of the 36 blobs matches the manifest.
|
||||
- `candidate-manifest.sha256` sha256
|
||||
e688651be8070e48c9ede5e239149342145804cf803dccad84dffb91beac93da, the candidate digest.
|
||||
It replaces the 34-path draft (patch 4800f2eb…, manifest bb948903…), which predates the
|
||||
slot run's fixes.
|
||||
- `mutants.mjs` (`list`, `check`, `apply`), `mutants.sh` (the driver) and `gate.sh`.
|
||||
- The gate runs each candidate test file alone, every package's tests, then
|
||||
scripts/test-*.sh.
|
||||
- Docker runs under Compose project `dewey-r125`; the gate removes only that project's
|
||||
network.
|
||||
|
||||
## Round 2 (Filbert's changes, comment 28965)
|
||||
|
||||
The base stays 19fe85c3e (sage-0829). The round 2 manifest has 38 paths: the 36 from round 1 plus `docs/TOOLS.md` and `packages/cli/README.md`. Nine paths changed from round 1 (see "Packet, round 2").
|
||||
|
||||
### Change 1: token limits at their schema depth only
|
||||
|
||||
Round 1 took any object with a `limit` key, at any depth of `launch.limits`, as a token-limit leaf. A concurrency provider or model named `limit` was taken for one, and a business holding that couldn't save from Settings.
|
||||
|
||||
- `packages/business/src/launch-settings.mjs`: `tokenLeaves` walks only `tokens.instances.X`, `tokens.providers.X`, `tokens.keys.X` and `tokens.models.P.M` (a `DEPTH` table). `stamp` sets `since` only on those leaves. `reset` accepts only a path that is one of them; any other path is refused `invalid-request`, a concurrency limit's or one above a token leaf included. An entry that isn't an object is left for the schema to refuse.
|
||||
- `packages/bus/src/limits.mjs`: `limitLeaves` walks the same `DEPTH` table, so the core and the writer agree on what a token limit is.
|
||||
- Choice: Filbert offered walking at schema depth or refusing `limit` as a reserved name. I took the depth walk. Concurrency names stay free strings, as `launch.mjs:5` allows, and the reserved-name option would refuse files that are valid on base.
|
||||
|
||||
Tests, each failing on the round 1 candidate (see "Runs, round 2"):
|
||||
- `token-limit-settings.test.mjs:61`: Filbert's four base-valid concurrency shapes save as sent. Token limits whose provider, model or key is named `limit`, beside concurrency limits of the same name, are stamped and reset. A reset of a concurrency path or a path above a token leaf is refused, and the file's bytes are unchanged.
|
||||
- `limit-checks.test.mjs:102`: `limitLeaves` returns the token leaves named `limit` under providers, models and keys, and nothing for concurrency limits of that name.
|
||||
- `limit-checks.test.mjs:303`: a token limit for a provider named `limit` is counted and refuses the launch, beside a concurrency limit of the same name, with no `limit-check-failed`.
|
||||
|
||||
Not tested: an instance named `limit`. Instance names must be roles the business file declares (`business.mjs:135`), so it needs a role of that name. Both files handle `instances` through the same `DEPTH` walk as `providers` and `keys`, and the other tests run `tokens.instances.coder` at depth 1 throughout. A test can be added if the reviewer wants one.
|
||||
|
||||
Mutants: S08 and S09 put round 1's any-depth walk back into `stamp` and `reset`; L16 puts it back into `limitLeaves`.
|
||||
|
||||
### Change 2: the CLI docs
|
||||
|
||||
- `packages/cli/README.md`, "Agent limits and models": where token limits sit and their `{limit}` shape; `since` set and kept by the writer, a different `since` refused; `mosaic limits reset WORD... [--business ID]` with an example; enforcement only by a switched-on `limit-check`, `not enforced`, and fail-closed `check failed`. It also says that the plain output is JSON without `tokens` followed by a `token limits:` text section, so it isn't JSON as a whole and scripts use `--json`.
|
||||
- `docs/TOOLS.md`, "Agent limits": the same points, short.
|
||||
- `packages/plugins/limit-check/token-limit/README.md`: with no usage source every turn's record is unknown (source `@none`), so every limit counts zero. Round 1 said no record was written, which was wrong.
|
||||
|
||||
`host.test.mjs:826` reads the CLI README; it passed in the round 2 gate (pkg-cli).
|
||||
|
||||
### Filbert's survivors and the no-source note
|
||||
|
||||
| Item | Test added | Mutant |
|
||||
|---|---|---|
|
||||
| f2, no `limits.refresh` after `plugins.set` (`runtime.mjs:97`) | `limits-runtime.test.mjs`, "switching a limit check on with plugins.set loads it without a launch or a turn" | R05 |
|
||||
| f3, a check gets `args` without `structuredClone` (`limits.mjs:85`) | `limit-checks.test.mjs:176`: `echo` nulls its tokens, and `refuse` after it still reads limit 100 | L17 |
|
||||
| f4, a reset under a dirty draft replaces the snapshot (`bus.js:609`) | `limits-browser.test.mjs`: a draft typed before the reset is kept, and the save sends the revision it was typed against | W11 |
|
||||
| No usage source, limit 1 | `limits-runtime.test.mjs`, "with no usage source a turn is unknown, counts zero and stops nothing under a limit of 1" | T12 |
|
||||
|
||||
### Runs, round 2
|
||||
|
||||
Slot granted by Sage (sage-0830). The run was `r2run.sh`, launched by `busy.sh && setsid -w timeout -k 10 2400 ./r2run.sh` (CLEAR 18:36:32Z, exit 0 at 18:46:08Z).
|
||||
|
||||
The round 1 candidate fails the new change 1 tests. Each changed test file was run singly against round 1's 36 files, in a second worktree that the run restored and re-checked:
|
||||
- `token-limit-settings.test.mjs`: 4 tests, 3 pass, 1 fail. The failing test is "only tokens.* at schema depth holds token limits…".
|
||||
- `limit-checks.test.mjs`: 16 tests, 14 pass, 2 fail. The failing tests are "a token limit for a provider named limit…" and "limitLeaves takes token limits at schema depth only…".
|
||||
- `limits-runtime.test.mjs` 5/5 and `limits-browser.test.mjs` 1/1 pass on round 1. These two files hold the survivor tests, which the mutants below cover, not change 1.
|
||||
|
||||
Gate (`gate.sh`, unchanged from round 1), 43 runs, all rc=0. Totals are an awk sum over `summary.txt`:
|
||||
- each candidate test file alone: 17 runs, 145 tests, 145 pass;
|
||||
- every package's tests: 16 runs, 2241 tests, 2241 pass;
|
||||
- `scripts/test-*.sh`: 10 runs, all rc=0. Only test-queue prints counts (156/156).
|
||||
- Afterwards there were 0 containers under `dewey-r125`, and the network was removed.
|
||||
|
||||
Mutants (`mutants.mjs`, `mutants.sh`), on the round 2 worktree:
|
||||
- `check`: 81 mutants, 0 problems.
|
||||
- Every control file passed before any mutant was applied.
|
||||
- Result: 81 killed, 0 survived, 0 timeouts. The new ones are L16, S08, S09, L17, R05, W11 and T12.
|
||||
- Round 1's 74 are all still killed.
|
||||
- At the end, the worktree matched the round 2 manifest again.
|
||||
|
||||
Busy check fix. The first launch, at 18:36:00Z, stopped before running anything. `r2run.sh`'s own busy check matched its launcher, because `r2run.sh ` matches the `*run.sh\ *` pattern. `busy.sh` now skips its own ancestors by walking the `PPid` chain in `/proc/<pid>/status`. Control: a process started as `exec -a "control gate.sh" sleep 4` was still reported BUSY (rc=1), and a plain run with nothing else live said CLEAR (rc=0). Sage accepted the fix (sage-0834).
|
||||
|
||||
### Packet, round 2
|
||||
|
||||
- `build.patch` sha256 0efb5cb68c20c71622809bfbd1c9e8ef8c0b8e1229701c795e66dc5eb6e59f6a: 38 paths (26 changed, 12 new) against 19fe85c3e2a4f53d863fa9f65c2fdeba0d74a43c. I applied it to that base in a scratch index (`mkpatch-r2.sh`), and every one of the 38 blobs matches the manifest. It replaces round 1's patch 81e47c43….
|
||||
- `candidate-manifest.sha256` sha256 71c55638fc1abe29024c4511e75b4c1e73fd9aa1a289c663d15d3600eb8388a8 is the candidate digest. It replaces round 1's e688651b…. These nine paths differ from round 1: `docs/TOOLS.md` and `packages/cli/README.md` (both new to the manifest), `packages/business/src/launch-settings.mjs`, `packages/business/tests/token-limit-settings.test.mjs`, `packages/bus/src/limits.mjs`, `packages/bus/tests/limit-checks.test.mjs`, `packages/bus/tests/limits-runtime.test.mjs`, `packages/plugins/limit-check/token-limit/README.md` and `packages/webui/tests/limits-browser.test.mjs`.
|
||||
- `mutants.mjs` has 81 mutants (round 1 had 74). `mutants.sh` and `gate.sh` are unchanged.
|
||||
|
||||
### Base drift, round 2
|
||||
|
||||
Since base 19fe85c3e, row 129 (850484a7d) has landed on four of the 38 paths. Three are bus.js, serve.mjs and shell.test.mjs, as in round 1. The fourth is `docs/TOOLS.md`, new to the manifest this round; row 129 added 26 lines to it. The round 2 patch applies to HEAD 6f963fd65 with `git apply --3way --cached` in a scratch index, with no conflicts. As in round 1, the candidate is gated on 19fe85c3e only; Sage's full rerun at the landing target is the row's gate (sage-0829).
|
||||
|
||||
## Round 3 (rebase onto row 164, sage-0847 and sage-0860)
|
||||
|
||||
Confirm-only: the round 2 candidate rebased onto `cfde62f9ff45e818ee44671ce23e29f644733d36` (row 164 landed as 008746b6e, packet cfde62f9f), plus Filbert's bracket from comment 28981. No other source change. The scripts and their outputs are in `r3/`.
|
||||
|
||||
### Rebase
|
||||
|
||||
`r3/mkwt3.sh` checks that the packet patch is round 2's (0efb5cb6…). It makes a detached worktree at cfde62f9f and applies the patch with `git apply --3way`. 36 paths went in clean (`r3/apply-3way.log`). The two conflicts are both on paths row 164 also changed, and `r3/resolve-r3.py` resolves them:
|
||||
|
||||
- `packages/plugins/tests/kinds.test.mjs:9-10`: round 2's test title ("usage-source at interface version 1, limit-check at 2 (row 125)") with row 164's key list `["usage-source", "limit-check", "search-source", "channel"]`. Round 2 never changed the key-list line; it conflicted only because it sits next to the title.
|
||||
- `packages/plugins/README.md`:
|
||||
- :134-136 hold round 2's three-line tail of the transcript paragraph, which drops "No limit check ships with this row.";
|
||||
- :138-194 hold round 2's "## Limit checks (row 125)" section, unchanged;
|
||||
- row 164's "## Human project/thread channels" section follows from :196, unchanged.
|
||||
- Round 2 put its section after that paragraph, and row 164 put its own there too, hence the conflict.
|
||||
|
||||
### Filbert's bracket
|
||||
|
||||
`docs/TOOLS.md:621` now reads `` `scripts/mosaic limits reset WORD... [--business ID]` ``, matching `packages/cli/README.md:811`. The line is 80 columns, so the paragraph is not rewrapped. No test or script reads that line (`grep` over `packages/` and `scripts/`, `*.mjs` and `*.sh`).
|
||||
|
||||
### Round 2 against round 3, line by line
|
||||
|
||||
`r3/cmp3.sh` compares two diffs per path, `+`/`-` lines only and not context:
|
||||
|
||||
- round 2's patch, split per file;
|
||||
- round 3's worktree diffed against cfde62f9f.
|
||||
|
||||
The result is `r3/plusminus-compare.txt` and `r3/plusminus-per-path.txt`. 37 of the 38 paths are byte-identical. The one difference is the TOOLS.md:621 bracket line.
|
||||
|
||||
Sage named eight paths, the ones that changed between round 2's base 19fe85c3e and cfde62f9f:
|
||||
|
||||
- row 129: docs/TOOLS.md, public/bus.js, serve.mjs, shell.test.mjs;
|
||||
- row 164: plugins/README.md, kinds.mjs, kinds.test.mjs, loader.test.mjs.
|
||||
|
||||
Those eight are also exactly the manifest blobs that differ from round 2. Seven of them have `+`/`-` lines identical to round 2, and TOOLS.md differs only by the bracket. So the rebase changed no row 125 line beyond the bracket. The four row 164 paths and the three other row 129 paths differ from round 2 only in the base lines underneath.
|
||||
|
||||
`node mutants.mjs check` (read only, no tests) on the round 3 tree gives "81 mutants, 0 problems" (`r3/mutants-check.txt`): every mutant's old text still matches exactly once.
|
||||
|
||||
### Runs, round 3
|
||||
|
||||
Slot granted by Sage (sage-0867, after Darkwing cleared at 19:46:14Z). The run was `r3/singles-r3.sh`, launched by `busy.sh && setsid -w timeout -k 10 5400 ./singles-r3.sh` (CLEAR 19:46:52Z, end 19:47:36Z, exit 0).
|
||||
|
||||
- `wt3` matched the round 3 manifest (8b71c405…) before the first test and after the last.
|
||||
- Each of the 17 touched test files ran alone with the spec reporter. TMPDIR was in my scratch.
|
||||
- All 17 had rc=0. An awk sum over the 17 logs gives 145 tests, 145 pass, 0 fail, 0 cancelled, 0 skipped. Round 2's single-file runs gave the same 17 runs and 145/145.
|
||||
- Per-file counts are in `r3/singles.out`, and the logs are in `r3/singles/`.
|
||||
- No process of mine was left with a cwd under the run directory.
|
||||
|
||||
This round reran no package suites, `scripts/test-*.sh` or mutants. The scope is confirm-only (sage-0847), and Sage's gate at the landing target is the row's full run. The mutant table still applies as text (`check` above).
|
||||
|
||||
### Packet, round 3
|
||||
|
||||
- `build.patch` sha256 2d310327c5a138810a420b15c9f8240fd879d440f049bc96a390d7d2c21ed7bb:
|
||||
- 38 paths (26 changed, 12 new) against cfde62f9ff45e818ee44671ce23e29f644733d36;
|
||||
- `r3/mkpatch-r3.sh` applied it to that base in a scratch index, and all 38 blobs match the manifest;
|
||||
- no path outside the 38 changed.
|
||||
- `candidate-manifest.sha256` sha256 8b71c405ed2365e2cf62eab8f8863535025c5bd4f037db5870b9c59a704bfe56 is the candidate digest.
|
||||
- Round 2's patch and manifest are kept in `r2/`, unchanged (0efb5cb6…, 71c55638…).
|
||||
- `mutants.mjs`, `mutants.sh` and `gate.sh` are unchanged; none of them names a base.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
f563fbb73f2d3ab9b8339f1f2ed7ea0d9c806326d232f34295f2272fb1d3867c docs/TOOLS.md
|
||||
49c6a1a8e667e286ffc30e279212fbbe9560d2a9abdec32ccffb305878808269 packages/business/src/business.mjs
|
||||
f6a09777413dd390f647903505f3292e0f9d08f9e24bdefd6645712d0fc654d1 packages/business/src/launch.mjs
|
||||
5a2ac82e0840f32fb1d2a5c7ff6cc3a835b13463af23512f4dcb5c42c53e6e49 packages/business/src/launch-settings.mjs
|
||||
1aaa73bbccc920933286844a653ff204a7549e2049eea1d5306a59636d581bfb packages/business/tests/token-limit-settings.test.mjs
|
||||
fcca77c9d6c8a2af82427b9119157167ebe3941acb592d754fcd5d4aa9d656cc packages/bus/src/broker.mjs
|
||||
445805ef4ddf82c28295f48e735ed4f6c30be69a5a6593500af5dbd937f196c9 packages/bus/src/limits.mjs
|
||||
f08a81c62e910df75ba5be659a3453e22904fc70c9d9002baed401049d6e0e00 packages/bus/src/process.mjs
|
||||
c505814351135ac7bbf75f784c75de519b65d55f70a01d55af07a9d7567c9413 packages/bus/src/runtime.mjs
|
||||
f3b3bf994fe3907b7655ba4d22499a964462ae2b465ef9d7e2e6c5ae2138dcd4 packages/bus/src/usage.mjs
|
||||
1f31aefa71984fba90c5390df5cc595d929681965f57485ad4972143d5c4b15f packages/bus/src/views.mjs
|
||||
4c48337722c10620d0d7677f891a11702c987318eedec438b2be3fee2e2393ba packages/bus/tests/limit-checks.test.mjs
|
||||
61d471451b8663c1c1880cb6886407cb2cc1ad03c74294ee9b02296c322975b1 packages/bus/tests/limits-runtime.test.mjs
|
||||
af1416ab850b6ed178103c2077b2a54527087dfcfcbf1f2e707bae636bbf1274 packages/bus/tests/limit-stop.test.mjs
|
||||
6547322921c6fda9e4de6b7999df8449dc5acd329a2d50dbe7857725acbfcf2d packages/bus/tests/process.test.mjs
|
||||
2a2f1e1d651fc128f3bd91a0ef6ae1ccfd6a10d908a46250cde9d26e91ea6290 packages/cli/README.md
|
||||
ae815de588196edae2cc4a2574a21d8f96c45e770c73eccdb874f3faafb59956 packages/cli/src/launcher.mjs
|
||||
121d842d3638d00402e8e3043dac22e3fb963d5555c653d480f038be4d55dedc packages/cli/src/limits.mjs
|
||||
5292b1207058235c74cf1357031253ea9d5ab8379bf2b9e509549e2085854302 packages/cli/tests/launch-limit-checks.test.mjs
|
||||
2627f1bcec64b35fc400e32b9ef5a87101179b91c3cfe14447170670cc8ca638 packages/cli/tests/launch-limits.test.mjs
|
||||
eb9ec7fc3d3fd5599bec6bc3b452d2c0c6c88b5184b32f0e918789290c986a80 packages/cli/tests/limits-tokens.test.mjs
|
||||
a8ea9b77b8dd0b4c77611076f738fdbe2a1f3d7342b17f5e570a77be42fefc49 packages/cli/tests/thread-launch.test.mjs
|
||||
2ea9a4f6ccb60a6c4a524d1b7dcd4764443e13eb711b681b6fed07cd20f57609 packages/harness/src/tools.mjs
|
||||
e2c88c661956fb39e157dbf6765f51b4d9bce4ee262d0e1f69f77bd7a41130ad packages/harness/tests/runner.test.mjs
|
||||
9fbdd09db963325b9f9d511b63c058f10376f25dd0a5160cc4ddba82e0b1c739 packages/harness/tests/tools.test.mjs
|
||||
d97470e6c1231756612d73b76b019ecfda90dc791e4b99a4263dac976c113fcd packages/plugins/limit-check/token-limit/index.mjs
|
||||
def48c3865bad92efa4bed5cc608c1b86053b9115719b8c51301e3638a8d3847 packages/plugins/limit-check/token-limit/README.md
|
||||
6e7ce40706ca6576e6e0aa86542fa0fd339dc5c398e45bee80e40bf4300311b5 packages/plugins/README.md
|
||||
14c3f687170f28da40d6a8c23ed3ce70b5b1513c95abccf8a4ccf90ea27dd373 packages/plugins/src/kinds.mjs
|
||||
5d5e5dccdae3f94b8540d187a8899ec36f9c9933cae3933249c2d8c400c75aa2 packages/plugins/tests/helpers.mjs
|
||||
62b25963d3a663528fa2f21836965d0e9e33ed168f3118163cbbb0368844975c packages/plugins/tests/kinds.test.mjs
|
||||
5c99d0007b1f80022ae92f02e64930c60a95e9a5439c4b8cb9468f3218386612 packages/plugins/tests/loader.test.mjs
|
||||
c7a5251b7a30c688b53a9fdddce6cb94bc011d720ea41a0d80f17b38a1bedf9d packages/plugins/tests/token-limit.test.mjs
|
||||
9ce9494c3500951727b9a7ac3f533810d8e93e466a8b778b86513e0e1c8ba2dc packages/webui/src/public/bus.js
|
||||
57f5409197d52fe1d3add601e0cc418f7ec304ee69c808b7554f1bce5ce16e44 packages/webui/src/serve.mjs
|
||||
91d870e05bcd16611151b8b36ee95cb758e574ee88c8454edb4574c32642ce07 packages/webui/tests/limits-browser.test.mjs
|
||||
2ce7e3b9f9d6dd0b9677bb9c694c0bbd3c7d62edf96a936b66a7ef4a08d9ec07 packages/webui/tests/limits-route.test.mjs
|
||||
ccfdd97600ce624ad979f082de801184dec941b10d487fd639c5c45b832b2150 packages/webui/tests/shell.test.mjs
|
||||
Executable
+41
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 (#1619) gate on the candidate worktree. Slot work: run only on Sage's go.
|
||||
# gate.sh <worktree> <outdir> <manifest>
|
||||
# 1. each candidate test file alone; 2. every package's tests; 3. the script suites (test-task uses
|
||||
# Docker, under project dewey-r125). Logs kept; summary.txt has one line per run, totals by awk.
|
||||
set -uo pipefail
|
||||
WT=$(realpath -e "${1:?worktree}") || exit 2
|
||||
OUT=$(realpath -m "${2:?outdir}")
|
||||
MANIFEST=$(realpath -e "${3:?manifest}") || exit 2
|
||||
export TMPDIR=$HOME/dewey-scratch/tmp/lt COMPOSE_PROJECT_NAME=dewey-r125
|
||||
mkdir -p "$TMPDIR" "$OUT" || { echo "abort: cannot create $TMPDIR or $OUT"; exit 2; }
|
||||
cd "$WT" || exit 2
|
||||
cleanup() {
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
n=$(docker ps -aq --filter label=com.docker.compose.project=dewey-r125 | wc -l)
|
||||
echo "containers left under dewey-r125: $n" >> "$OUT/summary.txt"
|
||||
if docker network inspect dewey-r125_default >/dev/null 2>&1; then
|
||||
docker network rm dewey-r125_default >/dev/null 2>&1 || echo "could not remove network dewey-r125_default" >> "$OUT/summary.txt"
|
||||
fi
|
||||
fi
|
||||
sha256sum --quiet -c "$MANIFEST" || echo "MANIFEST MISMATCH after gate" >> "$OUT/summary.txt"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
sha256sum --quiet -c "$MANIFEST" || { echo "abort: candidate differs from $MANIFEST"; exit 2; }
|
||||
git -C "$WT" rev-parse HEAD > "$OUT/base.txt" || exit 2
|
||||
: > "$OUT/summary.txt"
|
||||
|
||||
run() { # run <name> <command...>
|
||||
local name=$1; shift
|
||||
setsid -w timeout -k 10 1800 "$@" > "$OUT/$name.log" 2>&1 < /dev/null; local rc=$?
|
||||
echo "$name rc=$rc :: $(awk '/^ℹ (tests|pass|fail|skipped) [0-9]+$/ {printf "%s %s ", $2, $3}' "$OUT/$name.log")" >> "$OUT/summary.txt"
|
||||
}
|
||||
|
||||
for f in $(grep -E '\.test\.mjs$' <(awk '{print $2}' "$MANIFEST")); do run "single-$(echo "$f" | tr / _)" node --test --test-reporter=spec "$f"; done
|
||||
for p in packages/*/; do p=${p%/}; [ -d "$p/tests" ] && run "pkg-${p#packages/}" node --test "$p/tests/"; done
|
||||
for s in scripts/test-*.sh; do run "$(basename "$s" .sh)" bash "$s"; done
|
||||
echo DONE >> "$OUT/summary.txt"
|
||||
# Totals over the package runs only (the singles repeat them).
|
||||
awk '/^pkg-/ { for (i = 1; i <= NF; i++) if ($i ~ /^(tests|pass|fail|skipped)$/) t[$i] += $(i+1) }
|
||||
END { printf "package totals: tests %d pass %d fail %d skipped %d\n", t["tests"], t["pass"], t["fail"], t["skipped"] }' "$OUT/summary.txt" >> "$OUT/summary.txt"
|
||||
grep -q ' rc=[1-9]' "$OUT/summary.txt" && exit 1 || exit 0
|
||||
@@ -0,0 +1,152 @@
|
||||
// Row 125 (#1619) mutants: one per check, each an exact-once text edit in one candidate file,
|
||||
// each named with the test files that must fail on it. Driven by mutants.sh; run from the worktree.
|
||||
// node mutants.mjs list name<TAB>file<TAB>test files (space separated)
|
||||
// node mutants.mjs check read only: each old text matches once, each test file exists
|
||||
// node mutants.mjs apply NAME apply NAME's edit; exit 3 unless the old text matches exactly once
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
|
||||
const L = 'packages/bus/src/limits.mjs', LT = 'packages/bus/tests/limit-checks.test.mjs';
|
||||
const TL = 'packages/plugins/limit-check/token-limit/index.mjs', TT = 'packages/plugins/tests/token-limit.test.mjs';
|
||||
const B = 'packages/bus/src/broker.mjs', ST = 'packages/bus/tests/limit-stop.test.mjs', RT = 'packages/harness/tests/runner.test.mjs';
|
||||
const LA = 'packages/cli/src/launcher.mjs', LCT = 'packages/cli/tests/launch-limit-checks.test.mjs', THT = 'packages/cli/tests/thread-launch.test.mjs';
|
||||
const LS = 'packages/business/src/launch-settings.mjs', TS = 'packages/business/tests/token-limit-settings.test.mjs';
|
||||
const LM = 'packages/business/src/launch.mjs', BU = 'packages/business/src/business.mjs';
|
||||
const K = 'packages/plugins/src/kinds.mjs', KT = 'packages/plugins/tests/kinds.test.mjs', LOT = 'packages/plugins/tests/loader.test.mjs';
|
||||
const SV = 'packages/webui/src/serve.mjs', WRT = 'packages/webui/tests/limits-route.test.mjs';
|
||||
const BJ = 'packages/webui/src/public/bus.js', WBT = 'packages/webui/tests/limits-browser.test.mjs';
|
||||
const CL = 'packages/cli/src/limits.mjs', CLT = 'packages/cli/tests/limits-tokens.test.mjs';
|
||||
const RU = 'packages/bus/src/runtime.mjs', RUT = 'packages/bus/tests/limits-runtime.test.mjs';
|
||||
const PR = 'packages/bus/src/process.mjs', VW = 'packages/bus/src/views.mjs';
|
||||
const HS = 'packages/harness/src/tools.mjs', HT = 'packages/harness/tests/tools.test.mjs';
|
||||
const U = 'packages/bus/src/usage.mjs', UT = 'packages/bus/tests/usage.test.mjs';
|
||||
|
||||
const PRT = 'packages/bus/tests/process.test.mjs';
|
||||
const M = [
|
||||
// core: packages/bus/src/limits.mjs
|
||||
['L02-decide-skips-failed-check', L, [LT], "if (code) return halt({ check: check.name, code });", "if (code) continue;"],
|
||||
['L03-unreadable-state-goes', L, [LT], "return halt({ check: null, code: e.code });", "return { [key]: go };"],
|
||||
['L04-hit-any-path', L, [LT], "leaves.some(l => same(l.path, hit.path) && l.limit === hit.limit) && ", ""],
|
||||
['L05-reason-dropped', L, [LT], "reason: named ? named.e.notEnforced : null", "reason: null"],
|
||||
['L06-report-skips-failed-check', L, [LT], "if (code) return failedAll(check.name, code);", "if (code) continue;"],
|
||||
['L07-decide-no-not-loaded', L, [LT],
|
||||
" if (!st.checks) {\n log({ event: 'limit-check-failed', business, check: st.missing, export: name, code: 'not-loaded' });\n return halt({ check: st.missing, code: 'not-loaded' });\n }\n", ""],
|
||||
['L08-report-no-not-loaded', L, [LT], "if (!st.checks) return failedAll(st.missing, 'not-loaded');", "if (!st.checks) return [];"],
|
||||
['L09-answer-not-validated', L, [LT], " if (!valid(answer)) throw new Failure('malformed-answer');\n", ""],
|
||||
['L10-own-code-lost', L, [LT], "typeof e?.code === 'string' && CODE.test(e.code) ? e.code : 'check-threw'", "'check-threw'"],
|
||||
['L11-entry-limit-unchecked', L, [LT], "if (!leaf || e.limit !== leaf.limit || e.since !== leaf.since) return false;", "if (!leaf) return false;"],
|
||||
['L12-reach-not-logged', L, [LT], " log({ event: 'limit-reached', business, check: check.name, export: name, subject, ...answer.hit });\n", ""],
|
||||
['L13-decide-no-refresh', L, [LT], " await refresh(business);\n let st;\n", " let st;\n"],
|
||||
['L14-subject-account-dropped', L, [LT], "=> ({ instance, provider, model, account });", "=> ({ instance, provider, model });"],
|
||||
['L16-leaves-any-depth', L, [LT], "export function limitLeaves(limits) {\n", "export function limitLeaves(limits) { return anyLeaves(limits); }\nfunction anyLeaves(limits, path = []) {\n const out = [];\n if (!plainObject(limits)) return out;\n for (const [key, value] of Object.entries(limits)) {\n if (own(value, 'limit')) out.push({ path: [...path, key], limit: value.limit, since: value.since });\n else if (plainObject(value)) out.push(...anyLeaves(value, [...path, key]));\n }\n return out;\n}\nfunction depthLeaves(limits) {\n"],
|
||||
// round 2 (Filbert f3): each check gets its own copy of the args
|
||||
['L17-args-shared', L, [LT], "check.module[name](structuredClone(args))", "check.module[name](args)"],
|
||||
['L15-thenable-unguarded', L, [LT],
|
||||
" if (answer !== null && (typeof answer === 'object' || typeof answer === 'function') && typeof answer.then === 'function') {\n Promise.resolve(answer).catch(() => {});\n throw new Failure('malformed-answer');\n }\n", ""],
|
||||
// the shipped check: packages/plugins/limit-check/token-limit/index.mjs
|
||||
['T01-over-not-at', TL, [TT, LT], "if (count >= BigInt(leaf.entry.limit))", "if (count > BigInt(leaf.entry.limit))"],
|
||||
['T02-since-ignored', TL, [TT, LT], "if (!leaf.match(r) || time(r.at) < since) continue;", "if (!leaf.match(r)) continue;"],
|
||||
['T03-unknown-not-counted', TL, [TT], "else unknown.add(JSON.stringify([r.run, r.turn]));", "else void 0;"],
|
||||
['T04-unknown-counted-as-known', TL, [TT, LT], "if (r.status === 'known') for", "if (r.status !== null) for"],
|
||||
['T05-key-always-enforced', TL, [TT], "if (leaf.key && !named)", "if (false)"],
|
||||
['T06-instance-matches-all', TL, [TT], "match: x => x.instance === n });", "match: x => true });"],
|
||||
['T07-provider-matches-all', TL, [TT], "match: x => x.provider === p });", "match: x => true });"],
|
||||
['T08-model-ignores-model', TL, [TT], "match: x => x.provider === p && x.model === m });", "match: x => x.provider === p });"],
|
||||
['T09-key-matches-all', TL, [TT], "match: x => x.account === k, key: true });", "match: x => true, key: true });"],
|
||||
['T10-big-counts-lossy', TL, [TT], "const plain = n => n <= SAFE ? Number(n) : String(n);", "const plain = n => Number(n);"],
|
||||
['T11-bad-records-pass', TL, [TT], "const amount = value => Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : refuse('bad-record');", "const amount = value => BigInt(value ?? 0);"],
|
||||
// round 2 (Filbert's no-source note): an unknown turn adds nothing and stops nothing
|
||||
['T12-unknown-turn-stops', TL, [RUT], "if (count >= BigInt(leaf.entry.limit))", "if (count + BigInt(unknownTurns) >= BigInt(leaf.entry.limit))"],
|
||||
// broker: the end-of-turn stop
|
||||
['B01-throw-goes-on', B, [ST], "answer = { stop: true, check: null, code: 'check-threw' };", "answer = { stop: false };"],
|
||||
['B02-malformed-kept', B, [ST], "answer?.stop === true ? answer : { stop: true, check: null, code: 'malformed-answer' }", "answer"],
|
||||
['B03-no-stop-on-state', B, [ST], "(request.verb === 'message.receive' || request.verb === 'state')", "request.verb === 'message.receive'"],
|
||||
['B04-by-run-not-limit-check', B, [ST, RT], "this.#claimEnd(s, h, s.thread ? 'release' : 'revoke', null, reason, 'limit-check');", "this.#claimEnd(s, h, s.thread ? 'release' : 'revoke', null, reason);"],
|
||||
['B05-stop-before-result', B, [ST, RT], "if (phase === 'end') await this.#limitCheck(s);",
|
||||
"if (phase === 'end') { await this.#limitCheck(s); if (this.#stops.has(s.business + ':' + s.run)) this.#limitStop(s); }"],
|
||||
['B06-endlaunch-no-limit', B, [ST], "return { released, ...(limit ? { limit } : {}) };", "return { released };"],
|
||||
['B07-thread-revoked', B, [ST], "this.#claimEnd(s, h, s.thread ? 'release' : 'revoke', null, reason, 'limit-check');", "this.#claimEnd(s, h, 'revoke', null, reason, 'limit-check');"],
|
||||
['B08-reason-no-count', B, [ST, RT], " limit ${stop.hit.limit} count ${stop.hit.count}`", " limit ${stop.hit.limit}`"],
|
||||
['B09-org-keys-no-reset', B, [HT], "export const ORGANIZATION_KEYS = ['revision', 'limits', 'models', 'instances', 'reset'];", "export const ORGANIZATION_KEYS = ['revision', 'limits', 'models', 'instances'];"],
|
||||
// launcher
|
||||
['A01-op-failure-allows', LA, [LCT], "throw new Refusal(\"limit-check-failed\", `bus: ${e.code ?? \"launch-op-refused\"}`);", "may = { allow: true };"],
|
||||
['A02-only-false-refuses', LA, [LCT], "if (may?.allow !== true) {", "if (may?.allow === false) {"],
|
||||
['A03-hit-not-limit-reached', LA, [LCT], "if (may?.hit) { const error", "if (false) { const error"],
|
||||
['A04-end-record-no-limit', LA, [THT], ", ...(ended?.limit ? { limit: ended.limit } : {}) });", " });"],
|
||||
// business: the writer and the schema
|
||||
['S01-since-overwrite-accepted', LS, [TS], " if (own(entry, \"since\") && entry.since !== kept) throw new OrganizationError(\"invalid-request\");\n", ""],
|
||||
['S02-change-restamps', LS, [TS], "entry.since = kept ?? now;", "entry.since = now;"],
|
||||
['S03-reset-no-move', LS, [TS], " leaf.entry.since = now;\n", ""],
|
||||
['S04-reset-any-path', LS, [TS], "const leaf = leaves.find(l => l.path.length === path.length && l.path.every((part, i) => part === path[i]));", "const leaf = { entry: at(limits, path) };"],
|
||||
['S05-settings-keys-no-reset', LS, [TS, HT], "export const SETTINGS_KEYS = Object.freeze([\"revision\", \"limits\", \"models\", \"instances\", \"reset\"]);", "export const SETTINGS_KEYS = Object.freeze([\"revision\", \"limits\", \"models\", \"instances\"]);"],
|
||||
['S06-reset-alone-refused', LS, [TS], "if (![\"limits\", \"models\", \"instances\", \"reset\"].some(", "if (![\"limits\", \"models\", \"instances\"].some("],
|
||||
// round 2 (Filbert change 1): token limits only at schema depth; each mutant puts r1's any-object-with-limit walk back in one place
|
||||
['S08-stamp-any-depth', LS, [TS], "for (const { path, entry } of tokenLeaves(next)) {", "for (const { path, entry } of (function any(v, p = []) { return plain(v) ? (own(v, \"limit\") && p.length ? [{ path: p, entry: v }] : Object.entries(v).flatMap(([k, x]) => any(x, [...p, k]))) : []; })(next)) {"],
|
||||
['S09-reset-any-depth', LS, [TS], " const leaves = tokenLeaves(limits);\n", " const leaves = (function any(v, p = []) { return plain(v) ? (own(v, \"limit\") && p.length ? [{ path: p, entry: v }] : Object.entries(v).flatMap(([k, x]) => any(x, [...p, k]))) : []; })(limits);\n"],
|
||||
['S07-reset-empty-accepted', LS, [TS], "if (!Array.isArray(paths) || !paths.length) throw", "if (!Array.isArray(paths)) throw"],
|
||||
['D01-since-no-roundtrip', LM, [TS], "new Date(since).toISOString() !== since) refuse", "false) refuse"],
|
||||
['D02-limit-unchecked', LM, [TS], "return { limit: count(value.limit, `${at}.limit`), since };", "return { limit: value.limit, since };"],
|
||||
['D03-leaf-extra-keys', LM, [TS], " rejectUnknownKeys(value, [\"limit\", \"since\"], at);\n", ""],
|
||||
['D04-token-extra-kinds', LM, [TS], " rejectUnknownKeys(value, [\"instances\", \"providers\", \"models\", \"keys\"], where);\n", ""],
|
||||
['D05-instance-undeclared', BU, [TS], " for(const instance of Object.keys(limits.tokens?.instances??{}))if(!Object.hasOwn(roles,instance))refuse(", " for(const instance of [])if(!Object.hasOwn(roles,instance))refuse("],
|
||||
// plug-in contract
|
||||
['K01-version-1', K, [KT, LOT], "kind: \"limit-check\", interfaceVersion: 2,", "kind: \"limit-check\", interfaceVersion: 1,"],
|
||||
['K02-no-report-export', K, [KT, LOT], "exports: Object.freeze([\"mayLaunch\", \"mustStop\", \"report\"])", "exports: Object.freeze([\"mayLaunch\", \"mustStop\"])"],
|
||||
// Console server, page and reader
|
||||
['W01-no-tokens-check', SV, [WRT], "if (!value || !Array.isArray(value.tokens)) throw new BusReadError('invalid-response');", "if (!value) throw new BusReadError('invalid-response');"],
|
||||
['W02-writable-no-reset', SV, [WRT], "const writable = ['limits', 'models', 'instances', 'reset'];", "const writable = ['limits', 'models', 'instances'];"],
|
||||
['W03-limits-takes-query', SV, [WRT], " if (search) return json(res, 400, { error: 'invalid-request', message: 'limits takes no arguments' });\n", ""],
|
||||
['W04-whole-document-out', SV, [WRT], "return json(res, 200, { rows: value.tokens, at: new Date().toISOString() });", "return json(res, 200, { ...value, rows: value.tokens, at: new Date().toISOString() });"],
|
||||
['W05-view-not-limits', VW, [WRT], "limits: () => client.call('limits'),", "limits: () => client.call('usage'),"],
|
||||
['W06-page-save-drops-tokens', BJ, [WBT], " if(v.limits?.tokens)limits.tokens=structuredClone(v.limits.tokens);\n", ""],
|
||||
['W07-page-reason-dropped', BJ, [WBT], "`not enforced${t.reason ? `: ${txt(t.reason)}` : ''}`", "'not enforced'"],
|
||||
['W08-page-reset-ungated', BJ, [WBT], "data-limit-reset=\"${esc(JSON.stringify(t.path || []))}\"${can ? '' : ' disabled'}>", "data-limit-reset=\"${esc(JSON.stringify(t.path || []))}\">"],
|
||||
['W09-page-check-unescaped', BJ, [WBT], "`check failed: ${txt(t.code)}${t.check ? ` (${txt(t.check)})` : ''}`", "`check failed: ${txt(t.code)}${t.check ? ` (${t.check})` : ''}`"],
|
||||
['W10-page-refused-shows-none', BJ, [WBT], "limitsRefused = true; }", "}"],
|
||||
// round 2 (Filbert f4): a reset under a dirty draft keeps the snapshot the draft was typed on
|
||||
['W11-reset-replaces-draft-snapshot', BJ, [WBT], "if(!settingsInputs)settingsSnapshot=result;", "settingsSnapshot=result;"],
|
||||
// CLI
|
||||
['C01-cli-reason-dropped', CL, [CLT], "return `${head}, not enforced${t.reason ? `: ${t.reason}` : \"\"}`;", "return `${head}, not enforced`;"],
|
||||
['C02-reset-path-short', CL, [CLT], "reset: [parsed.positional.slice(1)]", "reset: [parsed.positional.slice(2)]"],
|
||||
['C03-reset-no-path-sent', CL, [CLT], "(verb === \"reset\" && parsed.positional.length < 2)) {", "false) {"],
|
||||
['C04-cli-tokens-in-json', CL, [CLT], "io.stdout.write(JSON.stringify(rest, null, 2)", "io.stdout.write(JSON.stringify(result, null, 2)"],
|
||||
// bus runtime and its process
|
||||
['R01-no-tokens', RU, [RUT], "const withTokens = (id, result) => ({ ...result, tokens: limits.report(id) });", "const withTokens = (id, result) => result;"],
|
||||
['R02-maylaunch-allows', RU, [RUT], "mayLaunch: ({ business, instance, provider, model }) => limits.mayLaunch(business, { instance, provider, model }),", "mayLaunch: async () => ({ allow: true }),"],
|
||||
['R03-broker-no-limits', RU, [RUT], "\n limits,\n", "\n"],
|
||||
['R04-reset-not-recounted', RU, [RUT], "if(verb==='limits.set')return withTokens(id,settings.set(id,args,actor));", "if(verb==='limits.set')return settings.set(id,args,actor);"],
|
||||
// round 2 (Filbert f2): plugins.set reloads the limit checks
|
||||
['R05-plugins-set-no-refresh', RU, [RUT], " limits.refresh(id).catch(e=>limitLog({event:'limit-refresh-failed',business:id,message:e.message}));\n", ""],
|
||||
['P01-op-not-awaited', PR, [PRT], "result: await launchOp(message)", "result: launchOp(message)"],
|
||||
['P02-op-missing', PR, [PRT], " if (m.op === 'mayLaunch') return runtime.mayLaunch(m);\n", ""],
|
||||
// harness tool
|
||||
['H01-tool-keys-no-reset', HS, [HT], "export const ORGANIZATION_KEYS = Object.freeze([\"revision\", \"limits\", \"models\", \"instances\", \"reset\"]);", "export const ORGANIZATION_KEYS = Object.freeze([\"revision\", \"limits\", \"models\", \"instances\"]);"],
|
||||
// usage records the checks count
|
||||
['U01-begin-row-known', U, [UT, LT], "const known = row.phase === 'end' && row.status === 'known';", "const known = row.status === 'known';"],
|
||||
];
|
||||
|
||||
const [verb, name] = process.argv.slice(2);
|
||||
if (verb === 'list') {
|
||||
const seen = new Set();
|
||||
for (const [n, file, tests] of M) {
|
||||
if (seen.has(n)) { console.error(`duplicate ${n}`); process.exit(2); }
|
||||
seen.add(n);
|
||||
console.log(`${n}\t${file}\t${tests.join(' ')}`);
|
||||
}
|
||||
} else if (verb === 'check') {
|
||||
// Read only: every mutant's old text must match exactly once, and every named test file must exist.
|
||||
let bad = 0;
|
||||
for (const [n, file, tests, before] of M) {
|
||||
const count = readFileSync(file, 'utf8').split(before).length - 1;
|
||||
if (count !== 1) { bad++; console.log(`${n}\t${file}\tmatches ${count}`); }
|
||||
for (const t of tests) try { readFileSync(t); } catch { bad++; console.log(`${n}\tmissing test ${t}`); }
|
||||
}
|
||||
console.log(`${M.length} mutants, ${bad} problems`);
|
||||
process.exit(bad ? 1 : 0);
|
||||
} else if (verb === 'apply') {
|
||||
const m = M.find(x => x[0] === name);
|
||||
if (!m) { console.error(`no mutant ${name}`); process.exit(2); }
|
||||
const [, file, , before, after] = m;
|
||||
const text = readFileSync(file, 'utf8'), n = text.split(before).length - 1;
|
||||
if (n !== 1) { console.error(`${name}: expected one match in ${file}, found ${n}`); process.exit(3); }
|
||||
writeFileSync(file, text.replace(before, () => after));
|
||||
} else { console.error('usage: mutants.mjs list | apply NAME'); process.exit(2); }
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 (#1619) mutant driver. Slot work: run only on Sage's go.
|
||||
# mutants.sh <worktree> <outdir> <manifest>
|
||||
# Verifies the candidate against the manifest, runs each named test file once as a control (all must
|
||||
# pass), then per mutant: back up the file, apply, run its test files, restore, re-verify the manifest.
|
||||
# A mutant is killed when its test run exits non-zero. MUTANT_DRY=1 lists the plan; MUTANT_ONLY=NAME
|
||||
# runs one mutant. Exit 0 only if every mutant was killed.
|
||||
set -uo pipefail
|
||||
WT=$(realpath -e "${1:?worktree}") || exit 2
|
||||
OUT=$(realpath -m "${2:?outdir}")
|
||||
MANIFEST=$(realpath -e "${3:?manifest}") || exit 2
|
||||
SPEC=$(realpath -e "$(dirname "$0")/mutants.mjs") || exit 2
|
||||
export TMPDIR=$HOME/dewey-scratch/tmp/lt
|
||||
mkdir -p "$TMPDIR" "$OUT" || { echo "abort: cannot create $TMPDIR or $OUT"; exit 2; }
|
||||
[ -d "$OUT" ] && [ -w "$OUT" ] || { echo "abort: $OUT not writable"; exit 2; }
|
||||
cd "$WT" || exit 2
|
||||
|
||||
BACKUP=""; TARGET=""
|
||||
restore() {
|
||||
if [ -n "$BACKUP" ] && [ -n "$TARGET" ]; then
|
||||
cp -p "$BACKUP" "$TARGET" || echo "RESTORE FAILED: $TARGET from $BACKUP"
|
||||
BACKUP=""; TARGET=""
|
||||
fi
|
||||
sha256sum --quiet -c "$MANIFEST" || echo "MANIFEST MISMATCH after restore"
|
||||
}
|
||||
trap restore EXIT
|
||||
|
||||
sha256sum --quiet -c "$MANIFEST" || { echo "abort: candidate differs from $MANIFEST"; exit 2; }
|
||||
node "$SPEC" check > "$OUT/check.txt" || { cat "$OUT/check.txt"; echo "abort: spec check failed"; exit 2; }
|
||||
|
||||
PLAN=$(node "$SPEC" list) || { echo "abort: spec list failed"; exit 2; }
|
||||
if [ -n "${MUTANT_ONLY:-}" ]; then
|
||||
PLAN=$(printf '%s\n' "$PLAN" | awk -F'\t' -v n="$MUTANT_ONLY" '$1==n')
|
||||
[ -n "$PLAN" ] || { echo "abort: no mutant $MUTANT_ONLY"; exit 2; }
|
||||
fi
|
||||
if [ "${MUTANT_DRY:-0}" = 1 ]; then printf '%s\n' "$PLAN"; exit 0; fi
|
||||
|
||||
run() { # run <log> <test files...>
|
||||
local log=$1; shift
|
||||
setsid -w timeout -k 10 600 node --test --test-reporter=spec "$@" > "$log" 2>&1
|
||||
}
|
||||
|
||||
# Controls: each test file the plan names, alone, on the unmutated candidate.
|
||||
CONTROLS=$(printf '%s\n' "$PLAN" | cut -f3 | tr ' ' '\n' | sort -u)
|
||||
for f in $CONTROLS; do
|
||||
log="$OUT/control-$(echo "$f" | tr / _).log"
|
||||
if run "$log" "$f"; then echo "control ok $f"; else echo "abort: control failed $f (rc $?), see $log"; exit 2; fi
|
||||
done
|
||||
|
||||
killed=0; survived=0; timedout=0
|
||||
while IFS=$'\t' read -r name file tests; do
|
||||
[ -n "$name" ] || continue
|
||||
TARGET="$WT/$file"; BACKUP="$OUT/$name.orig"
|
||||
cp -p "$TARGET" "$BACKUP" && cmp -s "$TARGET" "$BACKUP" || { echo "abort: backup failed for $file"; BACKUP=""; TARGET=""; exit 2; }
|
||||
node "$SPEC" apply "$name" || { echo "abort: apply $name failed"; exit 2; }
|
||||
cmp -s "$TARGET" "$BACKUP" && { echo "abort: $name changed nothing"; exit 2; }
|
||||
# shellcheck disable=SC2086
|
||||
run "$OUT/$name.log" $tests; rc=$?
|
||||
cp -p "$BACKUP" "$TARGET" || { echo "abort: restore failed for $file"; exit 2; }
|
||||
BACKUP=""; TARGET=""
|
||||
sha256sum --quiet -c "$MANIFEST" || { echo "abort: manifest mismatch after $name"; exit 2; }
|
||||
if [ "$rc" -eq 124 ] || [ "$rc" -eq 137 ]; then timedout=$((timedout+1)); echo "TIMEOUT $name rc=$rc"
|
||||
elif [ "$rc" -ne 0 ]; then killed=$((killed+1)); echo "killed $name rc=$rc"
|
||||
else survived=$((survived+1)); echo "SURVIVED $name"; fi
|
||||
done <<< "$PLAN"
|
||||
|
||||
echo "killed $killed survived $survived timeout $timedout"
|
||||
[ "$survived" -eq 0 ] && [ "$timedout" -eq 0 ]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
0555168fc322152fa4bc9c72ded3a954d48c69050e22e8106588203b53451c15 docs/TOOLS.md
|
||||
49c6a1a8e667e286ffc30e279212fbbe9560d2a9abdec32ccffb305878808269 packages/business/src/business.mjs
|
||||
f6a09777413dd390f647903505f3292e0f9d08f9e24bdefd6645712d0fc654d1 packages/business/src/launch.mjs
|
||||
5a2ac82e0840f32fb1d2a5c7ff6cc3a835b13463af23512f4dcb5c42c53e6e49 packages/business/src/launch-settings.mjs
|
||||
1aaa73bbccc920933286844a653ff204a7549e2049eea1d5306a59636d581bfb packages/business/tests/token-limit-settings.test.mjs
|
||||
fcca77c9d6c8a2af82427b9119157167ebe3941acb592d754fcd5d4aa9d656cc packages/bus/src/broker.mjs
|
||||
445805ef4ddf82c28295f48e735ed4f6c30be69a5a6593500af5dbd937f196c9 packages/bus/src/limits.mjs
|
||||
f08a81c62e910df75ba5be659a3453e22904fc70c9d9002baed401049d6e0e00 packages/bus/src/process.mjs
|
||||
c505814351135ac7bbf75f784c75de519b65d55f70a01d55af07a9d7567c9413 packages/bus/src/runtime.mjs
|
||||
f3b3bf994fe3907b7655ba4d22499a964462ae2b465ef9d7e2e6c5ae2138dcd4 packages/bus/src/usage.mjs
|
||||
1f31aefa71984fba90c5390df5cc595d929681965f57485ad4972143d5c4b15f packages/bus/src/views.mjs
|
||||
4c48337722c10620d0d7677f891a11702c987318eedec438b2be3fee2e2393ba packages/bus/tests/limit-checks.test.mjs
|
||||
61d471451b8663c1c1880cb6886407cb2cc1ad03c74294ee9b02296c322975b1 packages/bus/tests/limits-runtime.test.mjs
|
||||
af1416ab850b6ed178103c2077b2a54527087dfcfcbf1f2e707bae636bbf1274 packages/bus/tests/limit-stop.test.mjs
|
||||
6547322921c6fda9e4de6b7999df8449dc5acd329a2d50dbe7857725acbfcf2d packages/bus/tests/process.test.mjs
|
||||
2a2f1e1d651fc128f3bd91a0ef6ae1ccfd6a10d908a46250cde9d26e91ea6290 packages/cli/README.md
|
||||
ae815de588196edae2cc4a2574a21d8f96c45e770c73eccdb874f3faafb59956 packages/cli/src/launcher.mjs
|
||||
121d842d3638d00402e8e3043dac22e3fb963d5555c653d480f038be4d55dedc packages/cli/src/limits.mjs
|
||||
5292b1207058235c74cf1357031253ea9d5ab8379bf2b9e509549e2085854302 packages/cli/tests/launch-limit-checks.test.mjs
|
||||
2627f1bcec64b35fc400e32b9ef5a87101179b91c3cfe14447170670cc8ca638 packages/cli/tests/launch-limits.test.mjs
|
||||
eb9ec7fc3d3fd5599bec6bc3b452d2c0c6c88b5184b32f0e918789290c986a80 packages/cli/tests/limits-tokens.test.mjs
|
||||
a8ea9b77b8dd0b4c77611076f738fdbe2a1f3d7342b17f5e570a77be42fefc49 packages/cli/tests/thread-launch.test.mjs
|
||||
2ea9a4f6ccb60a6c4a524d1b7dcd4764443e13eb711b681b6fed07cd20f57609 packages/harness/src/tools.mjs
|
||||
e2c88c661956fb39e157dbf6765f51b4d9bce4ee262d0e1f69f77bd7a41130ad packages/harness/tests/runner.test.mjs
|
||||
9fbdd09db963325b9f9d511b63c058f10376f25dd0a5160cc4ddba82e0b1c739 packages/harness/tests/tools.test.mjs
|
||||
d97470e6c1231756612d73b76b019ecfda90dc791e4b99a4263dac976c113fcd packages/plugins/limit-check/token-limit/index.mjs
|
||||
def48c3865bad92efa4bed5cc608c1b86053b9115719b8c51301e3638a8d3847 packages/plugins/limit-check/token-limit/README.md
|
||||
b2e4f2bcdcede4474cb5f880d57d892e1719b9dbbcd726aab6c6b151e7ddcfbb packages/plugins/README.md
|
||||
aa8d82218d6dc4a2cc79f56949fe23a70aa33419ee877b11b7b322468310b59b packages/plugins/src/kinds.mjs
|
||||
5d5e5dccdae3f94b8540d187a8899ec36f9c9933cae3933249c2d8c400c75aa2 packages/plugins/tests/helpers.mjs
|
||||
dc6aa00ad3c926d1125196c51b30769779e26ce9de6e48b7c14b7d614359fc38 packages/plugins/tests/kinds.test.mjs
|
||||
33548ae31720de061d4e7dfda2f68a86e959585cf0fd16871f29c4a5e9e14c46 packages/plugins/tests/loader.test.mjs
|
||||
c7a5251b7a30c688b53a9fdddce6cb94bc011d720ea41a0d80f17b38a1bedf9d packages/plugins/tests/token-limit.test.mjs
|
||||
11fcdef487c142506fc4b8636f35e2ae73e5092a17f27bf7bdac404a6212b710 packages/webui/src/public/bus.js
|
||||
b010eafb3a48ff58ba675c09ff182cdeedd007eea169535675e74284bf4aa57f packages/webui/src/serve.mjs
|
||||
91d870e05bcd16611151b8b36ee95cb758e574ee88c8454edb4574c32642ce07 packages/webui/tests/limits-browser.test.mjs
|
||||
2ce7e3b9f9d6dd0b9677bb9c694c0bbd3c7d62edf96a936b66a7ef4a08d9ec07 packages/webui/tests/limits-route.test.mjs
|
||||
3e0a1373557c0f3e35b1ca0ef891dffdb4136f9b991275fc749d2d9e4314002d packages/webui/tests/shell.test.mjs
|
||||
@@ -0,0 +1,40 @@
|
||||
Applied patch to 'docs/TOOLS.md' cleanly.
|
||||
Applied patch to 'packages/bus/src/broker.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/bus/src/process.mjs' cleanly.
|
||||
Applied patch to 'packages/bus/src/runtime.mjs' cleanly.
|
||||
Applied patch to 'packages/bus/src/usage.mjs' cleanly.
|
||||
Applied patch to 'packages/bus/src/views.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Falling back to direct application...
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/bus/tests/process.test.mjs' cleanly.
|
||||
Applied patch to 'packages/business/src/business.mjs' cleanly.
|
||||
Applied patch to 'packages/business/src/launch-settings.mjs' cleanly.
|
||||
Applied patch to 'packages/business/src/launch.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/cli/README.md' cleanly.
|
||||
Applied patch to 'packages/cli/src/launcher.mjs' cleanly.
|
||||
Applied patch to 'packages/cli/src/limits.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/cli/tests/launch-limits.test.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/cli/tests/thread-launch.test.mjs' cleanly.
|
||||
Applied patch to 'packages/harness/src/tools.mjs' cleanly.
|
||||
Applied patch to 'packages/harness/tests/runner.test.mjs' cleanly.
|
||||
Applied patch to 'packages/harness/tests/tools.test.mjs' cleanly.
|
||||
Applied patch to 'packages/plugins/README.md' with conflicts.
|
||||
Falling back to direct application...
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/plugins/src/kinds.mjs' cleanly.
|
||||
Applied patch to 'packages/plugins/tests/helpers.mjs' cleanly.
|
||||
Applied patch to 'packages/plugins/tests/kinds.test.mjs' with conflicts.
|
||||
Applied patch to 'packages/plugins/tests/loader.test.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/webui/src/public/bus.js' cleanly.
|
||||
Applied patch to 'packages/webui/src/serve.mjs' cleanly.
|
||||
Falling back to direct application...
|
||||
Falling back to direct application...
|
||||
Applied patch to 'packages/webui/tests/shell.test.mjs' cleanly.
|
||||
U packages/plugins/README.md
|
||||
U packages/plugins/tests/kinds.test.mjs
|
||||
@@ -0,0 +1,38 @@
|
||||
docs/TOOLS.md
|
||||
packages/business/src/business.mjs
|
||||
packages/business/src/launch.mjs
|
||||
packages/business/src/launch-settings.mjs
|
||||
packages/business/tests/token-limit-settings.test.mjs
|
||||
packages/bus/src/broker.mjs
|
||||
packages/bus/src/limits.mjs
|
||||
packages/bus/src/process.mjs
|
||||
packages/bus/src/runtime.mjs
|
||||
packages/bus/src/usage.mjs
|
||||
packages/bus/src/views.mjs
|
||||
packages/bus/tests/limit-checks.test.mjs
|
||||
packages/bus/tests/limits-runtime.test.mjs
|
||||
packages/bus/tests/limit-stop.test.mjs
|
||||
packages/bus/tests/process.test.mjs
|
||||
packages/cli/README.md
|
||||
packages/cli/src/launcher.mjs
|
||||
packages/cli/src/limits.mjs
|
||||
packages/cli/tests/launch-limit-checks.test.mjs
|
||||
packages/cli/tests/launch-limits.test.mjs
|
||||
packages/cli/tests/limits-tokens.test.mjs
|
||||
packages/cli/tests/thread-launch.test.mjs
|
||||
packages/harness/src/tools.mjs
|
||||
packages/harness/tests/runner.test.mjs
|
||||
packages/harness/tests/tools.test.mjs
|
||||
packages/plugins/limit-check/token-limit/index.mjs
|
||||
packages/plugins/limit-check/token-limit/README.md
|
||||
packages/plugins/README.md
|
||||
packages/plugins/src/kinds.mjs
|
||||
packages/plugins/tests/helpers.mjs
|
||||
packages/plugins/tests/kinds.test.mjs
|
||||
packages/plugins/tests/loader.test.mjs
|
||||
packages/plugins/tests/token-limit.test.mjs
|
||||
packages/webui/src/public/bus.js
|
||||
packages/webui/src/serve.mjs
|
||||
packages/webui/tests/limits-browser.test.mjs
|
||||
packages/webui/tests/limits-route.test.mjs
|
||||
packages/webui/tests/shell.test.mjs
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 r3 step 2 (no tests): per path, compare the +/- lines of r3 (wt3 against BASE3) with r2 (build.patch.r2 against 19fe85c3e).
|
||||
# Prints each path whose +/- lines differ, with the difference. Context lines are not compared.
|
||||
set -uo pipefail
|
||||
R=$HOME/dewey-scratch/r125; C=/mnt/storage/src/mosaic-stack
|
||||
BASE3=${1:?r3 base sha}; O=$R/cmp3; rm -rf "$O"; mkdir -p "$O/r2" "$O/r3" || exit 2
|
||||
# r2: re-split the r2 patch per file; r3: diff of the worktree files against BASE3.
|
||||
python3 -I - "$R/build.patch.r2" "$O/r2" <<'EOF'
|
||||
import sys, re, os
|
||||
src, out = sys.argv[1], sys.argv[2]
|
||||
cur = None; buf = {}
|
||||
for line in open(src, encoding='utf-8', errors='surrogateescape'):
|
||||
m = re.match(r'^diff --git a/(\S+) b/(\S+)', line)
|
||||
if m: cur = m.group(2); buf[cur] = []; continue
|
||||
if cur is None or line.startswith(('+++ ', '--- ')): continue
|
||||
if line.startswith(('+', '-')): buf[cur].append(line)
|
||||
for p, ls in buf.items():
|
||||
with open(os.path.join(out, p.replace('/', '%')), 'w', encoding='utf-8', errors='surrogateescape') as f: f.writelines(ls)
|
||||
EOF
|
||||
cd "${WT:-$R/wt3}" || exit 2
|
||||
while read -r p; do
|
||||
git diff --no-color "$BASE3" -- "$p" | grep -E '^[+-]' | grep -vE '^(\+\+\+ |--- )' > "$O/r3/$(echo "$p" | tr '/' '%')"
|
||||
done < "$R/candidate-paths.r2.txt"
|
||||
n=0
|
||||
for f in "$O"/r2/*; do b=$(basename "$f")
|
||||
if ! cmp -s "$f" "$O/r3/$b"; then n=$((n+1)); echo "=== $(echo "$b" | tr '%' '/')"; diff "$f" "$O/r3/$b"; fi
|
||||
done
|
||||
echo "paths with +/- differences: $n of $(ls "$O/r2" | wc -l)"
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 r3: write the manifest of the 38 paths from wt3, cut build.patch.r3 against BASE3 with a scratch index,
|
||||
# then prove it by applying it to BASE3 in a second scratch index and comparing every manifest path's blob.
|
||||
set -euo pipefail
|
||||
R=$HOME/dewey-scratch/r125; cd "$R/wt3"
|
||||
BASE3=$(git rev-parse "${1:?r3 base sha}") M=$R/candidate-manifest.r3.sha256
|
||||
[ "$(git rev-parse HEAD)" = "$BASE3" ] || { echo "abort: wt3 HEAD is not the base"; exit 2; }
|
||||
[ -z "$(git diff --name-only --diff-filter=U)" ] || { echo "abort: unmerged paths"; exit 2; }
|
||||
if grep -lE '^(<<<<<<<|>>>>>>>|=======$)' $(cat "$R/candidate-paths.r2.txt") 2>/dev/null; then echo "abort: conflict markers"; exit 2; fi
|
||||
mapfile -t P < "$R/candidate-paths.r2.txt"
|
||||
[ "${#P[@]}" -eq 38 ] || { echo "abort: ${#P[@]} paths"; exit 2; }
|
||||
sha256sum -- "${P[@]}" > "$M"
|
||||
I1=$R/tmp-index-cut3 I2=$R/tmp-index-check3; rm -f "$I1" "$I2"
|
||||
GIT_INDEX_FILE=$I1 git read-tree "$BASE3"
|
||||
GIT_INDEX_FILE=$I1 git add -- "${P[@]}"
|
||||
GIT_INDEX_FILE=$I1 git diff --cached --binary "$BASE3" > "$R/build.patch.r3"
|
||||
changed=$(GIT_INDEX_FILE=$I1 git diff --cached --name-only "$BASE3" | wc -l)
|
||||
GIT_INDEX_FILE=$I2 git read-tree "$BASE3"
|
||||
GIT_INDEX_FILE=$I2 git apply --cached "$R/build.patch.r3"
|
||||
bad=0
|
||||
for p in "${P[@]}"; do
|
||||
want=$(awk -v p="$p" '$2 == p {print $1}' "$M")
|
||||
got=$(GIT_INDEX_FILE=$I2 git cat-file -p ":$p" | sha256sum | cut -d' ' -f1)
|
||||
[ "$want" = "$got" ] || { echo "MISMATCH $p"; bad=1; }
|
||||
done
|
||||
rm -f "$I1" "$I2"
|
||||
[ $bad -eq 0 ] || exit 3
|
||||
# Every path outside the 38 must be untouched against BASE3 (node_modules is the only untracked entry).
|
||||
extra=$(git status --porcelain --untracked-files=all | awk '{print $2}' | grep -vxF -f "$R/candidate-paths.r2.txt" | grep -vx node_modules || true)
|
||||
[ -z "$extra" ] || { echo "abort: paths outside the manifest changed: $extra"; exit 2; }
|
||||
echo "paths in patch: $changed; all 38 blobs match after apply to $BASE3"
|
||||
echo "build.patch.r3 $(sha256sum < "$R/build.patch.r3" | cut -d' ' -f1)"
|
||||
echo "manifest r3 $(sha256sum < "$M" | cut -d' ' -f1)"
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 r3 step 1 (no tests): a detached worktree wt3 at BASE (post-164 HEAD), with r2's build.patch applied by --3way.
|
||||
# Expected: the 36 other paths clean, conflicts only in kinds.test.mjs and plugins/README.md (dry3-notes.md).
|
||||
set -uo pipefail
|
||||
R=$HOME/dewey-scratch/r125; C=/mnt/storage/src/mosaic-stack
|
||||
BASE=${1:?base sha}
|
||||
P2=$C/agents/dewey/work/queue-125/build.patch
|
||||
[ "$(sha256sum < "$P2" | cut -d' ' -f1)" = "$(sha256sum < "$R/build.patch.r2" | cut -d' ' -f1)" ] || { echo "abort: packet patch is not r2"; exit 2; }
|
||||
[ -e "$R/wt3" ] && { echo "abort: wt3 exists"; exit 2; }
|
||||
git -C "$C" worktree add --detach "$R/wt3" "$BASE" > "$R/wt3-add.log" 2>&1 || { echo "abort: worktree add"; cat "$R/wt3-add.log"; exit 2; }
|
||||
ln -s "$C/node_modules" "$R/wt3/node_modules" || exit 2
|
||||
cd "$R/wt3" || exit 2
|
||||
git apply --3way "$P2" > "$R/wt3-apply.log" 2>&1; echo "apply --3way rc=$?"
|
||||
cat "$R/wt3-apply.log"
|
||||
git status --short | awk '{print $1}' | sort | uniq -c
|
||||
git diff --name-only --diff-filter=U
|
||||
@@ -0,0 +1 @@
|
||||
81 mutants, 0 problems
|
||||
@@ -0,0 +1,6 @@
|
||||
=== docs/TOOLS.md
|
||||
3c3
|
||||
< +and keeps `since`. `scripts/mosaic limits reset WORD... --business ID` (for
|
||||
---
|
||||
> +and keeps `since`. `scripts/mosaic limits reset WORD... [--business ID]` (for
|
||||
paths with +/- differences: 1 of 38
|
||||
@@ -0,0 +1,38 @@
|
||||
DIFF 11 docs/TOOLS.md
|
||||
same 1 packages/business/src/business.mjs
|
||||
same 34 packages/business/src/launch.mjs
|
||||
same 77 packages/business/src/launch-settings.mjs
|
||||
same 103 packages/business/tests/token-limit-settings.test.mjs
|
||||
same 69 packages/bus/src/broker.mjs
|
||||
same 189 packages/bus/src/limits.mjs
|
||||
same 5 packages/bus/src/process.mjs
|
||||
same 21 packages/bus/src/runtime.mjs
|
||||
same 31 packages/bus/src/usage.mjs
|
||||
same 1 packages/bus/src/views.mjs
|
||||
same 326 packages/bus/tests/limit-checks.test.mjs
|
||||
same 95 packages/bus/tests/limits-runtime.test.mjs
|
||||
same 151 packages/bus/tests/limit-stop.test.mjs
|
||||
same 18 packages/bus/tests/process.test.mjs
|
||||
same 31 packages/cli/README.md
|
||||
same 41 packages/cli/src/launcher.mjs
|
||||
same 43 packages/cli/src/limits.mjs
|
||||
same 77 packages/cli/tests/launch-limit-checks.test.mjs
|
||||
same 2 packages/cli/tests/launch-limits.test.mjs
|
||||
same 56 packages/cli/tests/limits-tokens.test.mjs
|
||||
same 53 packages/cli/tests/thread-launch.test.mjs
|
||||
same 8 packages/harness/src/tools.mjs
|
||||
same 38 packages/harness/tests/runner.test.mjs
|
||||
same 6 packages/harness/tests/tools.test.mjs
|
||||
same 82 packages/plugins/limit-check/token-limit/index.mjs
|
||||
same 30 packages/plugins/limit-check/token-limit/README.md
|
||||
same 80 packages/plugins/README.md
|
||||
same 10 packages/plugins/src/kinds.mjs
|
||||
same 8 packages/plugins/tests/helpers.mjs
|
||||
same 4 packages/plugins/tests/kinds.test.mjs
|
||||
same 24 packages/plugins/tests/loader.test.mjs
|
||||
same 143 packages/plugins/tests/token-limit.test.mjs
|
||||
same 28 packages/webui/src/public/bus.js
|
||||
same 22 packages/webui/src/serve.mjs
|
||||
same 63 packages/webui/tests/limits-browser.test.mjs
|
||||
same 69 packages/webui/tests/limits-route.test.mjs
|
||||
same 9 packages/webui/tests/shell.test.mjs
|
||||
@@ -0,0 +1,19 @@
|
||||
# Row 125 r3: resolve the two --3way conflicts against post-164 base cfde62f9f (run from wt3; "ours" is 164's base text, "theirs" is 125's r2 text).
|
||||
import re
|
||||
def split(path):
|
||||
s = open(path, encoding='utf-8').read().split('\n')
|
||||
a = s.index('<<<<<<< ours'); m = s.index('=======', a); b = s.index('>>>>>>> theirs', m)
|
||||
assert sum(l.startswith('<<<<<<<') for l in s) == 1
|
||||
return s[:a], s[a+1:m], s[m+1:b], s[b+1:]
|
||||
# kinds.test.mjs: theirs (125) title, ours (164) key list
|
||||
p = 'packages/plugins/tests/kinds.test.mjs'
|
||||
pre, ours, theirs, post = split(p)
|
||||
assert len(ours) == 2 and len(theirs) == 2 and '"channel"' in ours[1] and 'row 125' in theirs[0]
|
||||
open(p, 'w', encoding='utf-8').write('\n'.join(pre + [theirs[0], ours[1]] + post))
|
||||
# README.md: 125's three lines + Limit checks section, then 164's section (ours from its blank line on)
|
||||
p = 'packages/plugins/README.md'
|
||||
pre, ours, theirs, post = split(p)
|
||||
assert ours[3] == '' and ours[4] == '## Human project/thread channels', ours[:5]
|
||||
assert theirs[3] == '' and theirs[4] == '## Limit checks (row 125)' and theirs[-1] != ''
|
||||
open(p, 'w', encoding='utf-8').write('\n'.join(pre + theirs + ours[3:] + post))
|
||||
print('resolved')
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
# Row 125 r3 confirm run (slot work, only on Sage's go): every touched test file singly on wt3, checked against the r3 manifest.
|
||||
set -uo pipefail
|
||||
R=$HOME/dewey-scratch/r125; cd "$R" || exit 2
|
||||
export TMPDIR=$HOME/dewey-scratch/tmp/lt
|
||||
mkdir -p "$TMPDIR" || exit 2
|
||||
"$HOME/dewey-scratch/busy.sh" || { echo "abort: busy"; exit 2; }
|
||||
M=$R/candidate-manifest.r3.sha256
|
||||
(cd wt3 && sha256sum --quiet -c "$M") || { echo "abort: wt3 is not the r3 manifest"; exit 2; }
|
||||
echo "start $(date -u +%FT%TZ) manifest $(sha256sum < "$M" | cut -d' ' -f1)"
|
||||
O=$R/singles-r3; rm -rf "$O"; mkdir -p "$O" || exit 2
|
||||
grep -E '/tests/.*\.test\.mjs$' "$R/candidate-paths.r2.txt" > "$O/files.txt"
|
||||
while read -r f; do
|
||||
(cd wt3 && setsid -w timeout -k 10 900 node --test --test-reporter=spec "$f" > "$O/$(echo "$f" | tr '/' '%').log" 2>&1 < /dev/null); rc=$?
|
||||
echo "test $f rc=$rc :: $(awk '/^ℹ (tests|pass|fail|cancelled|skipped) [0-9]+$/ {printf "%s %s ", $2, $3}' "$O/$(echo "$f" | tr '/' '%').log")"
|
||||
grep -E '^✖ ' "$O/$(echo "$f" | tr '/' '%').log" | sed 's/^/ /' | sort -u
|
||||
done < "$O/files.txt"
|
||||
echo "files: $(wc -l < "$O/files.txt")"
|
||||
(cd wt3 && sha256sum --quiet -c "$M") && echo "wt3 manifest OK at end" || echo "WT3 MANIFEST MISMATCH at end"
|
||||
echo "end $(date -u +%FT%TZ)"
|
||||
@@ -0,0 +1,22 @@
|
||||
CLEAR 2026-10-18T19:46:52Z
|
||||
start 2026-10-18T19:46:52Z manifest 8b71c405ed2365e2cf62eab8f8863535025c5bd4f037db5870b9c59a704bfe56
|
||||
test packages/business/tests/token-limit-settings.test.mjs rc=0 :: tests 4 pass 4 fail 0 cancelled 0 skipped 0
|
||||
test packages/bus/tests/limit-checks.test.mjs rc=0 :: tests 16 pass 16 fail 0 cancelled 0 skipped 0
|
||||
test packages/bus/tests/limits-runtime.test.mjs rc=0 :: tests 5 pass 5 fail 0 cancelled 0 skipped 0
|
||||
test packages/bus/tests/limit-stop.test.mjs rc=0 :: tests 5 pass 5 fail 0 cancelled 0 skipped 0
|
||||
test packages/bus/tests/process.test.mjs rc=0 :: tests 8 pass 8 fail 0 cancelled 0 skipped 0
|
||||
test packages/cli/tests/launch-limit-checks.test.mjs rc=0 :: tests 4 pass 4 fail 0 cancelled 0 skipped 0
|
||||
test packages/cli/tests/launch-limits.test.mjs rc=0 :: tests 1 pass 1 fail 0 cancelled 0 skipped 0
|
||||
test packages/cli/tests/limits-tokens.test.mjs rc=0 :: tests 4 pass 4 fail 0 cancelled 0 skipped 0
|
||||
test packages/cli/tests/thread-launch.test.mjs rc=0 :: tests 10 pass 10 fail 0 cancelled 0 skipped 0
|
||||
test packages/harness/tests/runner.test.mjs rc=0 :: tests 26 pass 26 fail 0 cancelled 0 skipped 0
|
||||
test packages/harness/tests/tools.test.mjs rc=0 :: tests 20 pass 20 fail 0 cancelled 0 skipped 0
|
||||
test packages/plugins/tests/kinds.test.mjs rc=0 :: tests 5 pass 5 fail 0 cancelled 0 skipped 0
|
||||
test packages/plugins/tests/loader.test.mjs rc=0 :: tests 17 pass 17 fail 0 cancelled 0 skipped 0
|
||||
test packages/plugins/tests/token-limit.test.mjs rc=0 :: tests 10 pass 10 fail 0 cancelled 0 skipped 0
|
||||
test packages/webui/tests/limits-browser.test.mjs rc=0 :: tests 1 pass 1 fail 0 cancelled 0 skipped 0
|
||||
test packages/webui/tests/limits-route.test.mjs rc=0 :: tests 4 pass 4 fail 0 cancelled 0 skipped 0
|
||||
test packages/webui/tests/shell.test.mjs rc=0 :: tests 5 pass 5 fail 0 cancelled 0 skipped 0
|
||||
files: 17
|
||||
wt3 manifest OK at end
|
||||
end 2026-10-18T19:47:36Z
|
||||
@@ -0,0 +1,17 @@
|
||||
packages/business/tests/token-limit-settings.test.mjs
|
||||
packages/bus/tests/limit-checks.test.mjs
|
||||
packages/bus/tests/limits-runtime.test.mjs
|
||||
packages/bus/tests/limit-stop.test.mjs
|
||||
packages/bus/tests/process.test.mjs
|
||||
packages/cli/tests/launch-limit-checks.test.mjs
|
||||
packages/cli/tests/launch-limits.test.mjs
|
||||
packages/cli/tests/limits-tokens.test.mjs
|
||||
packages/cli/tests/thread-launch.test.mjs
|
||||
packages/harness/tests/runner.test.mjs
|
||||
packages/harness/tests/tools.test.mjs
|
||||
packages/plugins/tests/kinds.test.mjs
|
||||
packages/plugins/tests/loader.test.mjs
|
||||
packages/plugins/tests/token-limit.test.mjs
|
||||
packages/webui/tests/limits-browser.test.mjs
|
||||
packages/webui/tests/limits-route.test.mjs
|
||||
packages/webui/tests/shell.test.mjs
|
||||
@@ -0,0 +1,24 @@
|
||||
✔ REQ-LIMIT-3 limitLeaves finds every limit object and skips the agent numbers (1.263944ms)
|
||||
✔ REQ-LIMIT-3 limitLeaves takes token limits at schema depth only, whatever their names (0.234788ms)
|
||||
✔ REQ-LIMIT-3 with no check switched on a launch is allowed, a turn never stops, and every limit is not enforced (51.436937ms)
|
||||
✔ REQ-LIMIT-3 a fixture check that refuses refuses the launch and stops the turn, logged with the limit and the count (43.573761ms)
|
||||
✔ REQ-LIMIT-3 a report that omits a limit shows it not enforced; a counted entry and a reason come through (49.36336ms)
|
||||
✔ REQ-LIMIT-4 each check gets the business, a copy of the limits, the subject, the records and the time (83.217402ms)
|
||||
✔ REQ-LIMIT-4 fail closed: a throw refuses, stops and fails the report, naming the check and the code (43.581117ms)
|
||||
✔ REQ-LIMIT-4 fail closed: a malformed answer, a promise, a rejected promise, a non-boolean or a bad hit is malformed-answer (65.862574ms)
|
||||
✔ REQ-LIMIT-4 fail closed: a check switched on but not loaded refuses, stops and fails every limit as not-loaded (42.839455ms)
|
||||
✔ REQ-LIMIT-4 state(), never-loaded case: before any load the report shows not enforced, or not-loaded for a switched-on check (41.48867ms)
|
||||
✔ REQ-LIMIT-4 state(), first-load-refused case: a check refused on its first load is not-loaded in mayLaunch, mustStop and report (43.251355ms)
|
||||
✔ REQ-LIMIT-4 fail closed: an unreadable organization file refuses and stops with check null; the report throws (40.740476ms)
|
||||
✔ REQ-LIMIT-4 fail closed: unreadable records refuse and stop with check null and fail the report (0.479753ms)
|
||||
✔ REQ-LIMIT-3 the shipped token-limit, through the real loader, counts known records only since the limit was set (84.625567ms)
|
||||
✔ REQ-LIMIT-3 a token limit for a provider named limit is counted and refuses, beside an agent limit of the same name (50.924171ms)
|
||||
✔ REQ-LIMIT-3 core reaches limit checks only through the plug-in loader (0.569115ms)
|
||||
ℹ tests 16
|
||||
ℹ suites 0
|
||||
ℹ pass 16
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 717.037763
|
||||
@@ -0,0 +1,13 @@
|
||||
✔ REQ-LIMIT-4 a role run at its limit stops at the end of its turn: RESULT and read land first, the claim is revoked by limit-check (119.619415ms)
|
||||
✔ REQ-LIMIT-4 the stop also lands on state; a run with no stop ends without a limit (158.266841ms)
|
||||
✔ REQ-LIMIT-4 fail closed: a mustStop throw or a malformed answer stops the run, naming the bus and the code (370.900111ms)
|
||||
✔ REQ-LIMIT-4 a thread run at its limit is released with the reason, and its end record names the limit (128.951574ms)
|
||||
✔ REQ-LIMIT-4 a broker with no limit checks configured never stops a run (75.794813ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 915.425261
|
||||
@@ -0,0 +1,17 @@
|
||||
limits: {"event":"limit-reached","business":"demo","check":"token-limit","export":"mayLaunch","subject":{"instance":"coder","provider":"zai","model":"glm-5.3-flash","account":null},"path":["tokens","instances","coder"],"limit":0,"count":0,"unknownTurns":0}
|
||||
usage: {"event":"usage-unknown","business":"demo","run":"run1","instance":"coder","turn":"6f132bae-ced4-4c6f-b995-ab85d6304a9e","source":"@none","code":"baseline-unknown"}
|
||||
limits: {"event":"limit-reached","business":"demo","check":"token-limit","export":"mustStop","subject":{"instance":"coder","provider":"zai","model":"glm-5.3-flash","account":null},"path":["tokens","instances","coder"],"limit":0,"count":0,"unknownTurns":1}
|
||||
usage: {"event":"usage-unknown","business":"demo","run":"run1","instance":"coder","turn":"b4b0a018-3c8a-4000-8ab1-459c12bca582","source":"@none","code":"baseline-unknown"}
|
||||
✔ REQ-LIMIT-3 REQ-LIMIT-4 runtime: limits carries the token count, mayLaunch refuses at the limit, a reset moves since (167.576996ms)
|
||||
✔ REQ-LIMIT-3 runtime: with no limit check switched on, the limit shows not enforced and every launch is allowed (45.438929ms)
|
||||
✔ REQ-LIMIT-4 runtime: the broker asks the shipped token-limit after a turn, and the run at its limit stops on its next receive (101.497989ms)
|
||||
✔ REQ-LIMIT-4 runtime: with no usage source a turn is unknown, counts zero and stops nothing under a limit of 1 (77.487291ms)
|
||||
✔ REQ-LIMIT-3 runtime: switching a limit check on with plugins.set loads it without a launch or a turn (115.129546ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 549.331752
|
||||
@@ -0,0 +1,16 @@
|
||||
✔ broker process binds trusted launches, offers reader capabilities, refuses human mutation, closes cleanly (144.859638ms)
|
||||
✔ startup token refusal returns safe code without value or partial listening broker (82.504305ms)
|
||||
✔ loaded fixture token is absent from socket replies and SQLite, including refusal evidence (153.989927ms)
|
||||
✔ killed broker leaves an explicit stale lock; another process cannot silently reclaim it (209.910849ms)
|
||||
✔ trusted host registers later launches; socket clients never have a registration verb (171.284072ms)
|
||||
✔ REQ-LIMIT-4 the broker process answers the launcher's mayLaunch op with the limits' awaited decision (114.878788ms)
|
||||
✔ runtime excludes declared project roots even when host supplies no repoRoots (69.095194ms)
|
||||
✔ a refused launch binding leaves the broker and existing capabilities alive; bad protocol stops it (130.739511ms)
|
||||
ℹ tests 8
|
||||
ℹ suites 0
|
||||
ℹ pass 8
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1127.857467
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
✔ REQ-LIMIT-3 schema: each subject kind, limit 0 and no ceiling are accepted; bad limits, times, keys and undeclared instances refuse (12.615809ms)
|
||||
✔ REQ-LIMIT-3 the writer stamps since: a new limit gets now, a changed one keeps its time, a different time is refused (220.002859ms)
|
||||
✔ REQ-LIMIT-3 only tokens.* at schema depth holds token limits: agent limits named `limit` save as sent, and a token limit named `limit` is stamped and reset (251.806441ms)
|
||||
✔ REQ-LIMIT-3 a reset moves since to now, is logged with its actor, and refuses a path that is not a limit (137.827997ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 685.032945
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
✔ REQ-LIMIT-4 the launcher asks mayLaunch with the instance, provider and model, and goes on to authorization on an allow (10.693592ms)
|
||||
✔ REQ-LIMIT-3 REQ-LIMIT-4 a launch whose subject is at its limit is refused limit-reached and logged with the check, limit and count (4.312022ms)
|
||||
✔ REQ-LIMIT-4 fail closed: a refused op, a check failure or a malformed answer refuses limit-check-failed, naming the check and the code (33.886828ms)
|
||||
✔ REQ-LIMIT-4 the launcher reaches token-limit only through the bus: launcher.mjs imports no limit check (0.363524ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 123.752914
|
||||
@@ -0,0 +1,9 @@
|
||||
✔ each launch rereads limits and model selections; lowered limits keep sessions and log exact hit (18.837512ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 87.7727
|
||||
@@ -0,0 +1,12 @@
|
||||
✔ REQ-LIMIT-3 REQ-LIMIT-4 formatTokens shows each limit counted, not enforced, or check failed, in reset path words (0.709757ms)
|
||||
✔ REQ-LIMIT-3 the view prints the limits then the token lines; --json keeps tokens in the result (2.191497ms)
|
||||
✔ REQ-LIMIT-3 reset reads the revision, then sends limits.set with one path per limit, a word per level (0.597413ms)
|
||||
✔ REQ-LIMIT-3 reset with no path, or with --file, is refused before the bus (1.159064ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 110.242541
|
||||
@@ -0,0 +1,31 @@
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r98f8ed1c2be9","instance":"assistant","turn":"9d1c7c04-ce05-434c-ae05-8db76e3e51cf","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r428a3361eedc","instance":"assistant","turn":"37893b80-0acf-4b35-875f-2075884c57a1","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rbc964bce3437","instance":"assistant","turn":"c1a8da57-ce23-4057-b297-b223eae5a841","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r43b644d40a52","instance":"assistant","turn":"1de676fc-16ac-42b5-94dd-2fa9d8e76fb3","source":"@none","code":"baseline-unknown"}
|
||||
✔ 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 (5579.370064ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r21952d31ea4a","instance":"assistant","turn":"babc713b-27b3-4845-b31a-7c5b28271af3","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r4a3cf3dbf07b","instance":"assistant","turn":"b586f911-f016-429d-b10e-62d597a419b6","source":"@none","code":"baseline-unknown"}
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a running thread counts against its model's limit and an idle-stopped one doesn't (1816.724185ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r36f46c6d2262","instance":"assistant","turn":"d9fed2b9-559d-4d5c-bb7c-57d71d101da7","source":"@none","code":"baseline-unknown"}
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a turn longer than the idle time isn't stopped, and the thread idles out after it (4450.483674ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r9c640186216a","instance":"assistant","turn":"eaf06f94-05bf-47b4-ad64-bccd13e06130","source":"@none","code":"baseline-unknown"}
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"r9c640186216a","instance":"assistant","turn":"a15b6bcb-03df-43f9-b523-807bbd2c74ce","source":"@none","code":"baseline-unknown"}
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a message sent after the idle snapshot keeps the session, which answers it (2877.141758ms)
|
||||
✔ REQ-ROLE-2 REQ-LAUNCH-1 a thread held by a run the launcher didn't start gets no second session (102.370636ms)
|
||||
✔ REQ-LAUNCH-1 a run that ends without taking its thread's message isn't started again for that message (214.758614ms)
|
||||
✔ REQ-THREAD-2 REQ-LAUNCH-1 a thread id that isn't a safe segment, or a thread of a non-thread instance, launches nothing (244.406682ms)
|
||||
✔ REQ-THREAD-1 REQ-LAUNCH-1 the chief can't launch the assistant outside a thread (88.781308ms)
|
||||
limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mayLaunch","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":0}
|
||||
limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mayLaunch","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":0}
|
||||
✔ REQ-LIMIT-3 REQ-LIMIT-4 a thread start whose instance is at its token limit is refused, logged once with the limit and the count (203.70652ms)
|
||||
usage: {"event":"usage-unknown","business":"acme","run":"rf6db580030f7","instance":"assistant","turn":"78ee95eb-d5f3-4ff0-9d53-646baff1b44e","source":"@none","code":"baseline-unknown"}
|
||||
limits: {"event":"limit-reached","business":"acme","check":"token-limit","export":"mustStop","subject":{"instance":"assistant","provider":"zai","model":"claude-sonnet-5-5","account":null},"path":["tokens","instances","assistant"],"limit":0,"count":0,"unknownTurns":1}
|
||||
✔ REQ-LIMIT-4 a thread run at its token limit stops at the end of its turn: the reply lands, and the end record names the limit (350.349067ms)
|
||||
ℹ tests 10
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 16028.458237
|
||||
@@ -0,0 +1,34 @@
|
||||
✔ founderCheck: founder variables, then a needed service without a usable token (0.921126ms)
|
||||
✔ turnRequest names the sender, class, reply and decision (0.154213ms)
|
||||
✔ turnRequest says a tester's message is a test and never Jason's instruction (0.159083ms)
|
||||
✔ turnRequest shows the message's own sent time, a day old as well as new (0.108906ms)
|
||||
✔ stateBlock names the launch state with its time and event, and the open tasks with the cap (0.158296ms)
|
||||
✔ a message becomes a turn, the answer goes back as a RESULT, SIGTERM releases and exits 0 (232.23646ms)
|
||||
✔ launches revoked and then restored: the turn's block reads allowed and gives the restore time (272.518753ms)
|
||||
✔ a task closed on the tracker is missing from the next turn's open list (271.394588ms)
|
||||
✔ a message read a day after it was sent shows the time it was sent (171.47448ms)
|
||||
✔ a decision the human resolves becomes a turn citing the decision and the answer (230.185128ms)
|
||||
✔ a failed state read fails the turn: no adapter runs, the sender hears why, the message is read (147.477146ms)
|
||||
✔ a SIGTERM before the claim stops the runner with exit 0 and no claim (98.190187ms)
|
||||
✔ typed tools carry the runner's capability; launch goes to the host's launch socket (453.541593ms)
|
||||
✔ end_run and change_organization reach the broker with the tool's arguments, and refusals keep their codes (514.621778ms)
|
||||
✔ a RESULT gets no automatic reply; failed turns reply with the reason (1349.672547ms)
|
||||
✔ the bus's launch notice is one turn with no reply, and is not taken again (336.94214ms)
|
||||
✔ SIGTERM during a turn kills the turn's process group and still exits 0 (161.922719ms)
|
||||
✔ founder credentials stop before the claim (20) (190.917705ms)
|
||||
✔ a refused claim exits 21; an ended run's capability exits 22 (227.201583ms)
|
||||
✔ the launch ending under a running session exits 22 (153.801746ms)
|
||||
✔ a broker that stays unreachable exits 23 after brokerRetries polls (261.173693ms)
|
||||
✔ a broker that is down at the claim exits 23, not 21 (133.655809ms)
|
||||
✔ no capability, or a malformed one, on stdin exits 2 (202.107638ms)
|
||||
✔ a missing or malformed policy exits 2 before the claim (152.982517ms)
|
||||
✔ REQ-LIMIT-4 runner brackets its actual harness turn before the reply and read receipt (208.937138ms)
|
||||
✔ REQ-LIMIT-4 a runner at its limit stops at the end of its turn: RESULT and read first, then exit 22 and a limit-check revoke (222.350445ms)
|
||||
ℹ tests 26
|
||||
ℹ suites 0
|
||||
ℹ pass 26
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 12318.494821
|
||||
@@ -0,0 +1,28 @@
|
||||
✔ the PM gets its task verbs and the reads, and no launch (5.862858ms)
|
||||
✔ send_message offers the BLOCK class, beside the classes it already had (2.09053ms)
|
||||
✔ the chief of staff gets launch, send_message, end_run and change_organization, and no task verbs (1.215793ms)
|
||||
✔ request_staff is the pm's alone: staff.request over message.send, roles and counts bounded (7.317116ms)
|
||||
✔ end_run sends role and reason, no decision; change_organization sends revision and the organization keys (1.110102ms)
|
||||
✔ the organization keys change_organization takes are the broker's ORGANIZATION_KEYS and the settings' SETTINGS_KEYS (0.079455ms)
|
||||
✔ a role without role.end or organization.change gets neither tool, and each tool follows its own grant (4.848256ms)
|
||||
✔ the assistant gets send_message, the reads and the web tools (it holds the web grant), no launch (0.795312ms)
|
||||
✔ list_requirements reads the requirement ids, for a task.create holder with a tracker (1.576934ms)
|
||||
✔ a coder gets no launch, no resolve_decision, and no task tools without a tracker (0.758164ms)
|
||||
✔ a coder with a tracker gets its own task tools and no task_reassign (0.843663ms)
|
||||
✔ launch only when the business's launch block names the instance as launcher (1.490292ms)
|
||||
✔ an action outside the instance's authority has no tool (0.839379ms)
|
||||
✔ the review loop: push_working and request_review for the coder, fetch_review and review_verdict for the reviewer, none for the PM (3.310098ms)
|
||||
✔ land_work is there only with land, after push_working, with the task and the decision (1.712114ms)
|
||||
✔ callTool: one JSON line out, the result back, a refusal rejects (7.207674ms)
|
||||
✔ list_tasks takes the broker's filters and pages; get_task takes a task_ref (1.981949ms)
|
||||
✔ callTool sends list_tasks done false unless the session names done; other tools as given (1.830171ms)
|
||||
✔ replyText: compact JSON up to the cap, an error naming the size and the cap over it (0.496891ms)
|
||||
✔ web_search's query maxLength is the web layer's SEARCH_MAX_QUERY_CHARS (2.115214ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 20
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 117.245465
|
||||
@@ -0,0 +1,13 @@
|
||||
✔ REQ-PLUG-1 REQ-LIMIT-4 the registry: usage-source at interface version 1, limit-check at 2 (row 125), each a list (0.983852ms)
|
||||
✔ REQ-PLUG-1 checkPlugins accepts one key per kind, each a list of names, empty included (0.22314ms)
|
||||
✔ REQ-PLUG-1 checkPlugins refuses bad shapes, unknown kinds, bad names and duplicates (3.745452ms)
|
||||
✔ REQ-PLUG-1 a kind that switches on one module takes one name, not a list (0.147964ms)
|
||||
✔ REQ-PLUG-1 checkPlugins reports through the caller's fail callback (0.133853ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 46.480359
|
||||
@@ -0,0 +1,25 @@
|
||||
✔ REQ-PLUG-1 fixture modules of each kind switch on from a test organization file, no core file changed (8.001216ms)
|
||||
✔ REQ-PLUG-1 a name not matching ^[a-z][a-z0-9-]*$ refuses and keeps the old setting (3.589182ms)
|
||||
✔ REQ-PLUG-1 a missing module directory or index.mjs refuses and keeps the old setting (3.464671ms)
|
||||
✔ REQ-PLUG-1 a symlinked module directory or index.mjs refuses and keeps the old setting (2.532947ms)
|
||||
✔ REQ-PLUG-1 a symlinked kind directory refuses and keeps the old setting (2.241806ms)
|
||||
✔ REQ-PLUG-1 a symlinked packages/plugins refuses (run with --preserve-symlinks so the path keeps it) (25.379218ms)
|
||||
✔ REQ-PLUG-1 index.mjs swapped for a symlink after the lstat checks refuses at the realpath check (4.037026ms)
|
||||
✔ REQ-PLUG-1 a path that can't be read refuses as unreadable, is logged and keeps the old setting (3.387654ms)
|
||||
✔ REQ-PLUG-1 a path that becomes unreadable after the lstat checks refuses at either realpath (3.566883ms)
|
||||
✔ REQ-PLUG-1 a module that fails to load refuses and keeps the old setting (2.692352ms)
|
||||
✔ REQ-PLUG-1 a missing export refuses and keeps the old setting (3.294471ms)
|
||||
✔ REQ-PLUG-1 a different interface version refuses and keeps the old setting (2.573224ms)
|
||||
✔ REQ-PLUG-1 REQ-LIMIT-3 a version 1 limit check refuses after the bump to 2, and the old setting stays (2.887917ms)
|
||||
✔ REQ-PLUG-1 a kind refused on its first load has nothing switched on, and the log names kind and name (1.910821ms)
|
||||
✔ REQ-PLUG-1 the setting's shape: unknown kinds refuse alone, a bad whole setting keeps everything (2.703093ms)
|
||||
✔ REQ-PLUG-1 loads run one at a time, so the last requested setting wins (102.135586ms)
|
||||
✔ REQ-PLUG-1 the loader needs a log callback (1.132776ms)
|
||||
ℹ tests 17
|
||||
ℹ suites 0
|
||||
ℹ pass 17
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 224.874237
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
✔ REQ-LIMIT-3 token-limit exports interface version 2 with mayLaunch, mustStop and report (0.671568ms)
|
||||
✔ REQ-LIMIT-3 each subject kind refuses at its limit and allows under it (1.709963ms)
|
||||
✔ REQ-LIMIT-3 a limit covers only its own subject's records and launches (0.333599ms)
|
||||
✔ REQ-LIMIT-4 only known records count; an unknown turn adds nothing and is counted apart (0.247052ms)
|
||||
✔ REQ-LIMIT-3 a reset moves since: records before it no longer count (0.170427ms)
|
||||
✔ REQ-LIMIT-3 limit 0 refuses with no records; the first limit hit in file order is named (0.140368ms)
|
||||
✔ REQ-LIMIT-3 a key limit is not enforced while every record's account is null (decision 150 Q1) (0.17486ms)
|
||||
✔ REQ-LIMIT-3 counts past the safe integer range come back as digit strings (0.145463ms)
|
||||
✔ REQ-LIMIT-4 bad records or times throw a code, and the check reads nothing but its arguments (0.628292ms)
|
||||
✔ REQ-LIMIT-3 no token limits: every launch is allowed and the report is empty (0.236032ms)
|
||||
ℹ tests 10
|
||||
ℹ suites 0
|
||||
ℹ pass 10
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 49.709257
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
✔ REQ-LIMIT-3 REQ-LIMIT-4 Settings shows token limits counted, not enforced and check failed; Reset posts one path; a save keeps the token limits (961.453699ms)
|
||||
ℹ tests 1
|
||||
ℹ suites 0
|
||||
ℹ pass 1
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 1067.294387
|
||||
@@ -0,0 +1,13 @@
|
||||
✔ REQ-LIMIT-3 limits read returns only the token rows; no query or write is accepted (27.641408ms)
|
||||
✔ REQ-LIMIT-3 a limits answer without a token list is refused, not shown as no limits (8.21058ms)
|
||||
2026-10-18T19:47:27.764Z settings user="jason" changed
|
||||
✔ REQ-LIMIT-3 a Settings reset reaches the human write with its paths; a reset alone is enough (5.541205ms)
|
||||
✔ REQ-LIMIT-3 the Console reader asks the bus limits verb with no arguments (0.242483ms)
|
||||
ℹ tests 4
|
||||
ℹ suites 0
|
||||
ℹ pass 4
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 140.727003
|
||||
@@ -0,0 +1,13 @@
|
||||
✔ tokens.css and icons.svg are byte copies of docs/design (8.98031ms)
|
||||
✔ JetBrains Mono files match the digests in their sources file, with the OFL text (2.377672ms)
|
||||
✔ shipped strings are unchanged, and the web keeps its write boundary (2.168522ms)
|
||||
✔ shell in a browser: System default, seeded states, 400px, focus and the Ctrl+K palette (5600.30201ms)
|
||||
✔ five states on Board, Inbox, Tasks, Agents and Trail (2502.654111ms)
|
||||
ℹ tests 5
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 8232.95614
|
||||
Reference in New Issue
Block a user