# Acceptance What has been verified against a running Host, how, and what has not. Kept honest on purpose: a gate that silently never runs looks exactly like a gate that declines, so each row names the observation that would have distinguished them. ## Verified ### The off-duty reply is a real message and costs nothing **How.** With the clock pinned past lights-out, a subagent was asked for a single word and answered `明天再说。`. Its session log was then decoded frame by frame: | Field | Value | |---|---| | `message.content` | `[{ type: 'text', text: '明天再说。' }]` | | `usage` | `{ inputTokens: 0, outputTokens: 0, totalTokens: 0 }` | | `stream` | an array of 5 records: `block-start, text-chunks, block-end, usage, finish` | | lifecycle | `turn/start → step/start → … → assistant/message → step/end → turn/end` | Every field whose absence has broken a session's history elsewhere is present, `stream` included, and the message is committed by DSH rather than written by this plugin. ### The reply budget follows the curve **How.** Two sessions issued a request at the same instant and differed only in whether they were listed in `exemptSessions`. Their logged `request/header` configs: | Session | `maxTokens` | `adapterDefaults` | |---|---|---| | exempt | `131072` | `{ maxTokens: true }` — the machine's own default, untouched | | not exempt | `2048` | `null` — set explicitly by this plugin | `2048` is the 23:00 anchor, so the curve reached the request rather than merely being computed. The control group matters: without it, "the hook declined" and "the hook never ran" are indistinguishable. ### The tired persona reaches the prompt **How.** The subagent's logged `system/message` was searched for the injected line. Its system prompt ends with: ``` Your working directory is . 可以正常回答,但不要主动扩展话题。 ``` That is band 0 of `tierTexts`, which is what a 23:00 verdict should select. ### The schedule itself **How.** `node --test` — 58 cases, no Host required. They cover the duty cycle, the curve landing exactly on every anchor, all three curve shapes, peak windows on weekdays, weekends, statutory holidays, the wrap past midnight, the lights-out boundary at 03:00 and wake-up at 09:00, the time machine, session exemption, dry-run semantics, and duration parsing. ### The peak gate on real traffic, on the real clock **How.** Left running unattended overnight and into a weekday morning — no `debugNow`, nobody watching. On 2026-10-08 at 10:48, inside the 09:00–12:00 peak window, a session belonging to a different workspace was short-circuited. Its own log shows the turn a user would have seen: ``` assistant/message seq 33 content: [{ "type": "text", "text": "。" }] usage: { "inputTokens": 0, "outputTokens": 0, "totalTokens": 0 } stream: an array ``` All three of that session's replies from the morning carry zero output tokens. The plugin's journal recorded the matching decision — `action: short-circuit`, `state: off-duty`, `reason: "2026-10-08 10:48 · peak hours"` — so the verdict and the durable message agree. This also crosses the real 09:00 wake-up boundary on the way in, and the real 03:00 lights-out boundary on the way out, both without a time machine. **And it stands aside correctly the rest of the time.** Re-checked at 12:29, between the two peak windows: a fresh session's `request/header` recorded `maxTokens: 131072` with `adapterDefaults: { maxTokens: true }`, meaning the machine's own default was left untouched. ### The client half's surfaces are live **How.** The client `Slots` inspection provider queries the running page, not a mock. It reports `ai-curfew` among the occupants of `sidebar.footer.action` (order 60, active) and of `settings.section` (order 40, active). Since the page only lists what it actually mounted, that confirms the bundle was served, materialized, and ran both registrations — the part a shell client cannot see, because the bundle route answers an unauthenticated request with 401/404. **And the capsule renders the right verdict.** Observed in the running shell at 12:42 on a Thursday, inside the 12:00–14:00 valley: the capsule read `🟢 上班中`. That is the correct verdict for that moment, so the full chain is exercised end to end — the state route, the connection trust fence, the client bundle, its poll, and the render. ### The client half's schedule band **How.** `test/client.test.mjs` loads `client.js` through a stub `__ModuleLoader__` with a minimal React, calls the registered settings component, and inspects the element tree it returns. The band is asserted block by block against the same day: 00:00 is winding because the window opened at 23:00, 03:00–08:45 is lights-out, 09:00–11:45 and 14:00–17:45 are off duty, the middle of the day is on duty, and 23:00 closes the loop. Disabling the peak gate flattens the morning. A missing schedule degrades to a flat band rather than throwing. This is a fake-DOM test, not a browser test: it proves the classification and the element shapes, not that the shell renders them. ### The `/curfew` subcommands **How.** `test/command.test.mjs` drives the handler directly — it is a pure function of the configuration, the file overlay, and whatever the previous command left in memory. Each branch is asserted: `status` reports the verdict, the budget and the config path; a bare invocation equals `status`; `off` forces lights-out and refuses a duration, pointing at `overtime` instead; `overtime` takes a shift length with a default and rejects a malformed or zero one; `auto` drops every override including the time machine; `debug` moves the clock, survives a full timestamp, and refuses what it cannot parse; an unknown verb lists what is available, and the two retired spellings (`on`, `now`) do not resolve. The test suite is also what caught the status text reporting `winding · 560 tokens` while an overtime shift was in force — the schedule's verdict, not the effective one. Both the command and the capsule now report what will actually happen and keep the schedule as context. ### Typing `/curfew` reaches the handler **How.** Observed in the running shell on a weekday afternoon, inside the real 14:00–18:00 peak window. `/curfew` rendered the status block — `off-duty`, `2026-10-08 14:10 · peak hours`, `不发送请求`, `回法 "。"`, `时间来源 真实时间` — and `/curfew overtime 4h` answered `已强制加班 240 分钟`, after which the next message in that session took a real model call instead of being short-circuited. That closes the composer-to-handler path, the one link the direct command tests cannot reach. ### The settings page renders **How.** Looked at in the running shell: the status card, the 24-hour band with its hour ticks and cursor at the current minute, the colour legend, and the command table all render as intended. The README's badge row renders too. Two defects were found on the way there and fixed: - **The page had no CSS at all.** The client asked the runner for a stylesheet seat and quietly did without when there wasn't one, which turned "no theming" into "no styles": axis labels ran together on one line and legend swatches collapsed. It now falls back to a package-owned `