Repository navigation
Commit 75ced06
fix(plugin-hono-server,hono,cloud-connection)!: refuse an unparseable request body with 400 INVALID_REQUEST instead of reading it as {} (#24005)
Fixes #23914
Clause-②: no (narrowing)
## What
A request body that an HTTP door cannot parse is now refused with `400
INVALID_REQUEST` and the ADR-0112 envelope. It is no longer handed to
the handler as `{}`. `INVALID_REQUEST` is an existing
`ERROR_CODE_LEDGER` code; no code or ledger row is added. The message
names the failure and never echoes the body.
| package | door | what changed |
|---|---|---|
| `@objectstack/plugin-hono-server` | `HonoHttpServer.runHandler` (every
route registered through `IHttpServer`, plus the fallback seam): the
`objectstack dev` / `os serve` door | `req.body` is an accessor. Reading
a body the adapter could not parse throws the refusal, and the adapter
answers it whatever the handler made of the throw |
| `@objectstack/hono` | `createHonoApp`'s `readDispatchBody`, used by
the `${prefix}/*` catch-all and the `/auth/*` legacy fallback | a body
declared `application/json` that is not blank and does not parse is
refused when the dispatched domain READS it, after the dispatcher's
gates; a domain that forwards the request reads its own bytes and
answers as before |
| `@objectstack/cloud-connection` | its own Hono routes: `/bind/start`,
`/bind/poll`, `/install`, and `marketplace/install-local` | same rule,
through a module-private `readJsonRequestBody` |
## Measured at the door
These are fresh `objectstack dev --seed-admin` showcase boots, base
`2054a0888c` against `b9df5b1d3` (`origin/main` merged in; the same
answers were read on `0660b5a16` before the merge). This door is
`plugin-hono-server`, which is byte-identical at the current head
`97d881a2e`. Every body was sent from a file with `curl --data-binary`.
The admin session came from `POST /api/v1/auth/sign-in/email`.
| request | base | head |
|---|---|---|
| step 1, `POST /data/showcase_account/query` `{"where": {"status":
"churned"}}` | 200, 2 rows | 200, 2 rows |
| step 2, the same with the closing brace missing | **200, 14 of 14
rows** | 400 `INVALID_REQUEST` "The request body could not be parsed as
JSON (it was sent as application/json). …" |
| step 3, `{ not json` | **200, 14 rows** | 400 `INVALID_REQUEST`, same
message |
| step 4, the valid where-body sent as `text/plain` | **200, 14 rows** |
400 `INVALID_REQUEST` "The request body was sent as text/plain, which
this server does not read into a request body. …" |
| step 5, `PATCH /data/showcase_account/ID` `{"name": "HACKED"` |
**200**, name unchanged, `updated_at` bumped | 400 `INVALID_REQUEST`;
name and `updated_at` unchanged |
| step 6, `POST /data/showcase_account` truncated, with `name` | 400
`VALIDATION_FAILED` "Account Name is required" | 400 `INVALID_REQUEST`,
the parse refusal |
| CONTROL: empty body, `application/json` | 200, 14 rows (empty query) |
200, 14 rows |
| CONTROL: `POST` with no body and no `Content-Type` | 200, 14 rows |
200, 14 rows |
| CONTROL: valid `PATCH` / valid create / empty-body create | 200 / 201
/ 400 `VALIDATION_FAILED` | 200 / 201 / 400 `VALIDATION_FAILED` |
| CONTROL: presign then raw `PUT` of a `.txt` file as `text/plain`, then
complete | 200 / 200 / 200 | 200 / 200 / 200 |
| CONTROL: presign then raw `PUT` of a JSON-with-comments file as
`application/json`, then complete | 200 / 200 / 200 | 200 / 200 / 200 |
| dispatcher-bridged doors (`0660b5a16`): `PUT
/meta/object/showcase_account`, `POST /keys`, truncated | (not run on
base) | 400 `INVALID_REQUEST`, the parse refusal |
| `POST /cloud-connection/bind/poll` truncated `{"device_code": "dc_1"`
/ `{}` | (not run on base) | 400 the parse refusal / 400 "device_code is
required" (unchanged) |
| `POST /marketplace/install-local` truncated `{"packageId":
"com.example.crm"` | (not run on base) | 400 `INVALID_REQUEST`, the
parse refusal |
On this boot the door is served by `plugin-hono-server`. `createHonoApp`
(`@objectstack/hono`) is the embed door, and nothing in `packages/cli`
mounts it. Both transports carried the swallow, so both change, as
triage asked.
## Why `plugin-hono-server` refuses when `req.body` is read, not before
the handler runs
The PM's mechanism assumption 3 ("who else relies on `{}`") was
measured, and it moved the design. One transport serves JSON doors and
byte doors, and only the route knows which one it is:
- The storage presign answers `headers: { "content-type":
"application/json" }` for a `.json` file, so the platform's own upload
flow PUTs a JSON-with-comments file (a `tsconfig.json`, say) as declared
JSON that does not parse. The raw-PUT route reads `req.rawBody()` and
stores it with `200` (measured on both boots above). objectui's
`UploadProvider` sends `Content-Type: mimeType` on that PUT.
- The dogfood suites PUT raw bytes as `text/plain`
(`attachments-authz-harness.ts`, `field-file-collection`, and others).
Refusing before the handler would have turned both uploads into `400`s.
Raised on read, the refusal reaches exactly the handlers that would
otherwise have acted on an invented `{}`, and it stops them at the read,
so nothing after it runs and nothing is written. Body presence for the
media types the adapter does not read is judged by the RFC 9112 framing
headers (`Content-Length` other than `0`, or `Transfer-Encoding`).
Reading the bytes to find out would buffer an upload before storage's
declared-length check refuses it. Reading `Request.body` cannot answer
either, because `@hono/node-server` gives every non-GET/HEAD request a
stream.
## One rule on both transports: the reader refuses (seat ruling B)
`createHonoApp` follows the same rule. Its body is a `dispatch()`
argument rather than a `req` property, so for a declared-JSON body that
does not parse, `readDispatchBody` hands `dispatch()` a body value whose
every read (a property, `in`, its keys, a spread, `JSON.stringify`)
throws `RequestBodyRefusal` and records that it was read. Each arm (the
`${prefix}/*` catch-all and the `/auth/*` legacy fallback) then answers
that refusal if, and only if, the body was read, whatever the domain
answered. This is the same override `plugin-hono-server` applies after
its `req.body` getter. There is no new export and no list of routes:
whether a domain reads the body is answered by the domain reading it.
- `dispatch()` itself never reads `body`. Its liveness short-circuit,
scope resolution, the ADR-0069 auth gate and project membership read the
path and headers, and `body` goes straight to the domain's handler. So
the gates still answer first, as they do ahead of a route handler on
`plugin-hono-server`.
- A domain that forwards the request reads its own bytes and never
touches `body`: `/webhooks/redeliver`, the inbound automation hooks, and
the `/approvals/act` form `POST`. It answers exactly as at base, a
malformed body included, after its own sign-in or signature check.
`hono-webhooks-redeliver.conformance.test.ts`'s `not JSON` row, which
holds the hosted door byte-equal to the self-hosted mount, stays as
pinned and is green.
`b9df5b1d3` refused before `dispatch()` instead. That reddened exactly
that row: the hosted door answered this adapter's message where the
mount answers the member's `"Request body must be JSON."`. It also
answered an anonymous caller `400` where the mount says `401`. The
seat's contract review ruled B.
## Step 4 (`text/plain`): refuse, not parse
`content/docs/api/wire-format.mdx` documents the contract: "All requests
and responses use `application/json`", and `Content-Type:
application/json` is a required request header. Parsing `text/plain` as
JSON would be a lenient second dialect. On `plugin-hono-server` it would
also mean reading every text upload eagerly. So step 4 is refused there.
`createHonoApp` was already content-type-blind (it parses a `text/plain`
body as JSON when it parses), and that is unchanged. See Acceptance
notes.
## Which doors treat an empty body as `{}`, legitimately
All three keep it. A blank or whitespace-only body declared as JSON, and
a request with no body whatever it declares, are still `{}`. The REST
query handler documents the reliance ("An empty request body is not
`null`: the HTTP adapter hands it over as `{}`, an empty query").
Bodiless action `POST`s rely on it too (`/bind/start`,
`/automation/NAME/trigger`). The empty-body controls above and in every
suite pin it.
## Sibling sweep (lane `domain:cli`)
The sweep covered `.json().catch(`, `catch {}` around a body parse, and
`return {}` from a body reader, over
`packages/{cli,runtime,verify,types,rest,mcp,observability,client*,cloud-connection,create-objectstack,adapters/*,qa}`
plus `plugin-hono-server` and `plugin-dev`.
In scope (a door that takes a client body), changed here:
- `plugin-hono-server/src/adapter.ts` `runHandler`: the `c.req.json()` /
`parseBody()` pair.
- `adapters/hono/src/index.ts` `readDispatchBody`: both `{}` fallbacks,
which serve both arms.
- `cloud-connection/src/cloud-connection-plugin.ts`: `/bind/start`,
`/bind/poll`, `/install`.
- `cloud-connection/src/marketplace-install-local-plugin.ts`:
`handleInstall`.
Out, with the reason:
- `cloud-connection-plugin.ts` `tokResp.json().catch`,
`bindResp.json().catch` and the install relay's `resp.json().catch`:
these read the cloud control plane's answers, a body the platform wrote
itself.
- `packages/qa/http-conformance/src/adapter.ts` `NodeHttpServer` (`catch
{ body = {} }`): `private: true`, ships nothing, and serves only the
conformance suite's own requests. It is a reference harness, not a
deployed door (Acceptance notes).
- `packages/qa/http-conformance` itself (this lane) gains
`src/hono-body-refusal.conformance.test.ts`, which pins ruling B against
the real dispatcher (Tests).
- `runtime/src/domains/meta.ts:1314`: a comment, not a body reader. It
describes the catch-all's pre-#22576 line. Its point, that falsy JSON
values arrive falsy, still holds, so `meta.ts` is untouched.
Not in this lane, so not changed here: the same swallow in
`plugin-auth`, measured at the door. Truncated `POST
/api/v1/auth/admin/unlock-user` `{"userId": "usr_nobody"` answers `400
"userId is required"`. That is reported to the seat. `plugin-webhooks`
already refuses ("Request body must be JSON."). `metadata`'s HMR reload
reads a body the CLI's own watch loop posts.
## Tests
All runs are under `scripts/pm/os-verify-lock.sh`, with each exit code
captured before any pipe. The current head is `97d881a2e`: `b9df5b1d3`
(which merges `origin/main` at `adce19297`) plus ruling B in
`@objectstack/hono`, its pins, and the corrected `hono` changeset.
`plugin-hono-server` and `cloud-connection` are byte-identical to
`b9df5b1d3`, so their runs there stand.
- Ruling B on `0cee7c3d9` (the code of `97d881a2e`, whose last commit
only rewrites the `hono` changeset):
- `pnpm --filter @objectstack/hono exec vitest run --maxWorkers=2`: 9
files, 180 passed.
- `pnpm --filter @objectstack/http-conformance exec vitest run
--maxWorkers=2`: 14 files, 150 passed, including the redeliver row as
pinned.
- `pnpm --filter @objectstack/hono typecheck` and `pnpm --filter
@objectstack/http-conformance typecheck`: exit 0.
- `@objectstack/http-conformance` at the merge base `adce19297` (a
detached compare worktree, closure built there): 13 files, 144 passed.
At `b9df5b1d3` it was 1 failed, 143 passed:
`hono-webhooks-redeliver.conformance.test.ts` line 273, row `not JSON`.
- `pnpm turbo run build --filter='!@objectstack/docs' --concurrency=2`
on `97d881a2e`: exit 0, 75 tasks.
- After the merge (AGENTS.md §9/§10): `pnpm install --frozen-lockfile`
exit 0; `pnpm turbo run build --filter='!@objectstack/docs'
--concurrency=2` exit 0, 75 tasks; `pnpm --filter @objectstack/spec
check:generated` exit 0.
- `pnpm --filter @objectstack/plugin-hono-server typecheck` on
`b9df5b1d3`: exit 0, including `check:test-typecheck` OK.
`@objectstack/hono` exit 0 and `@objectstack/cloud-connection` exit 0,
each `tsc --noEmit` plus `-p tsconfig.test.json`.
- On `b9df5b1d3`, `pnpm --filter @objectstack/plugin-hono-server exec
vitest run --maxWorkers=2`: 30 files, 386 tests passed.
- `pnpm --filter @objectstack/hono exec vitest run --maxWorkers=2`: 9
files, 176 passed.
- `pnpm --filter @objectstack/cloud-connection exec vitest run
--maxWorkers=2`: 46 files, 561 passed.
New pins:
- `plugin-hono-server/src/request-body-refusal.test.ts` (20 cases):
- each card body refused, with the envelope parsed against
`ApiErrorSchema` / `envelopeViolations`, the handler never acting, and
no echo;
- the handler's own catch arm answering 500 loses to the refusal;
- the fallback seam both ways;
- a real socket for step 4;
- CONTROL: valid, empty, form, valid PATCH, and three byte doors stored
byte for byte.
- `adapters/hono/src/hono-body-refusal.test.ts` (18 cases), over a
fixture dispatcher with a READER domain (reads `body` inside a `try`
whose catch answers its own 500) and a FORWARDER (reads the forwarded
request):
- the card bodies refused 400 by the reader, though it answered 500;
- a read that escapes `dispatch()`;
- the `/auth/*` fallback, `DELETE` included;
- the forwarder answering its own refusal with every byte;
- CONTROL rows;
- parity with `HonoHttpServer`.
-
`packages/qa/http-conformance/src/hono-body-refusal.conformance.test.ts`
(6 cases), on the real dispatcher and data domain through
`createHonoApp`:
- steps 2, 3, 5 and 6 refused, with the store read back unchanged;
- byte parity with a `HonoHttpServer` route;
- order: an active ADR-0069 gate whose session read fails answers 503,
not the body's 400;
- CONTROL: a valid query filters, and an empty body is the empty query.
- `hono-catch-all-raw-request.test.ts` gains the case that a
declared-JSON body that does not parse still reaches a forwarding domain
unread, byte for byte.
- `cloud-connection`: four refusal cases plus an empty-body CONTROL in
`error-envelope.conformance.test.ts`. In
`marketplace-install-local-install-modes.test.ts`, a refusal with zero
writes (no register, no sync, no import, no ledger entry), plus a
CONTROL.
- Fixture triage: `hono-catch-all-raw-request.test.ts`'s `['invalid
JSON', 'application/json', '{ not json', {}]` row pinned the branch this
removes, so it is replaced. The refusal moved to the new file, and the
row now pins the undeclared spelling, which is unchanged.
## Ablation (one per package; every suite resolves its subject from
source, so no rebuild)
Each leg ran through `node scripts/ablation-replace.mjs` in WRAP mode,
with the anchor hitting once, the write proven on disk, and the restore
proven.
| leg | mutation | blob | result |
|---|---|---|---|
| plugin-hono-server | the accessor's refusal branch becomes `return {}`
| `a331a0f6ed81` to `dee7fcca5045` | 10 failed: every refusal case, the
fallback-reads case, socket step 4. 10 passed: every CONTROL |
| hono, swallow (on `0cee7c3d9`) | `refusedOnRead(…)` becomes
`readable({})` | `fb199e0ebefb` to `9c5714c12488` | `hono`: 10 failed
(every reader refusal, the escaping read, `/auth/*`, parity), 8 passed.
http-conformance: 4 failed (steps 2, 3, 5 and 6, parity), 9 passed (the
order case, CONTROL, and every redeliver row) |
| hono, refuse before `dispatch()` (the `b9df5b1d3` shape, on
`0cee7c3d9`) | `refusedOnRead(…)` becomes an immediate throw |
`fb199e0ebefb` to `4f27b2629aad` | `hono`: 7 failed (the
reader-ownership cases, `/auth/*`, the forwarder), 11 passed.
http-conformance: 2 failed (the redeliver byte-parity row and the
gate-order case), 11 passed |
| cloud-connection (on `2d952cae1`, the file unchanged since) | the `ok:
false` parse refusal becomes `{ ok: true, body: {} }` | `61561f4f3e17`
to `7c27524a66d0` | 4 failed: the four refusals. 41 passed, including
both empty-body CONTROLs |
The plugin-hono-server leg ran on `0660b5a16`, and its file is
byte-identical at the current head. Restore proof after each run: `git
diff HEAD` is 0 bytes and `git status --porcelain` is 0 lines. Each
file's `git hash-object` equals its `HEAD:` blob (`a331a0f6ed81…`,
`fb199e0ebefb…`, `61561f4f3e17…`), and the marker count is 0.
## Gates
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `97d881a2e` (no paths, merge base
`adce19297`): 66 families. All 66 ran and every one exited 0, including
`check:route-envelope`, `check:dispatcher-error-vocabulary`,
`check:error-status-conformance`, `check:nul-bytes`,
`check:published-files`, `check:dts-closure`, `check:test-source-alias`,
`check:cross-package-test-inputs`, `check:adr-0087-registration`,
`check:empty-changeset` and `check-changeset-no-major`. `--ran`
reconciliation with `COMMAND :: exit CODE` records: "66 derived, 66 run,
0 NOT-MEASURED, 0 UNRUN".
- The derivation flags one stale input:
`scripts/check-route-envelope.mjs` changed on `main` after the merge
base (`d132140eb`, #23050 slice 10). That change only moves declared
counts from `rest-server.ts` to the new `ui-routes.ts`, and this diff
touches neither. The branch is merge-clean against `origin/main`, so it
is not merged (`git merge-tree` exit 0).
- `pnpm --filter @objectstack/http-conformance test` (the suite that
went red in CI's `Test Core (3/6)` on `b9df5b1d3`): base `adce19297` 13
files, 144 passed; head 14 files, 150 passed.
- `pnpm exec eslint --no-inline-config` over the 12 changed `.ts` files:
exit 0, no findings. The repo-wide run is CI's `Lint & Repo Gates`.
- On `0660b5a16`, the first sweep reddened `check:route-envelope`: the
new `cloud-connection` helper built a response, so the gate wanted a
`PLUGIN_ROUTE_MODULES` row. That script is outside this card's file
surface, so the helper now answers a message and each declared route
module builds its own `400` body.
## Acceptance notes
- **`createHonoApp` reads every content type as JSON.** This is
pre-existing and unchanged. A JSON body sent as `text/plain` is parsed
(step 4 is honoured there, not refused), and a non-JSON body that does
not declare JSON is `{}`. The reader-refuses mechanism could carry that
input too, but it would narrow a second input and is not this card's.
Carrier: none.
- **Three copies of one message.** The refusal text lives in
`plugin-hono-server/src/request-body.ts`, `adapters/hono/src/index.ts`
and `cloud-connection/src/request-json-body.ts`, each module-private. A
shared export would enlarge a package's public surface, which this
card's `Clause-②: no (narrowing)` does not declare. The hono parity case
holds the two transports' answers equal.
- **`@objectstack/http-conformance`'s `NodeHttpServer`** keeps its
`catch { body = {} }`. It is a private reference harness. The suites do
drive a malformed body: `hono-webhooks-redeliver`'s `not JSON` row and
the new `hono-body-refusal` file, both through the two Hono doors.
Neither drives one through `NodeHttpServer`. (The v1 sentence said no
suite drives a malformed body at all, which was false.)
- **`runtime/src/domains/meta.ts:1314`'s comment** still quotes the
catch-all's pre-#22576 line, `c.req.json().catch(() => ({}))`. Its
subject (falsy JSON values arrive falsy) is still true.
- **Ordering.** On both Hono transports the refusal comes from the
body's reader, so every gate ahead of the reader answers first: the
dispatcher's gates on `createHonoApp`, and a route's own checks on
`plugin-hono-server`. On `cloud-connection`'s `/bind/start` and
`/bind/poll`, the body is read before the session check, as it always
was. Neither refusal discloses anything.
## Contract review
`Clause-②: no (narrowing)`: the seat serves the `CONTRACT_REVIEW_TIER`
review before the queue. Three changesets (`.changeset/23914-*.md`) are
`minor` under the launch-window convention for breaking changes. Each
carries the `Clause-②` line, an ADR-0087 marker of kind `not-required
(no-migration-prescription)` with the other categories closed on facts,
and the FROM / TO for an integrator.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2c702fd commit 75ced06
15 files changed
Lines changed: 1276 additions & 51 deletions
File tree
- .changeset
- packages
- adapters/hono/src
- cloud-connection/src
- plugins/plugin-hono-server/src
- qa/http-conformance/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
0 commit comments