Repository navigation
Commit a1e51d6
fix(runtime)!: the /mcp bridge reads objects from the protocol's merged view, so a package's objects are listed, described and gated (#25136)
Fixes #25120
Clause-②: no (narrowing)
Metadata-protocol: none
The `/mcp` HTTP bridge now reads objects from the protocol's merged
view, the read REST serves. `list_objects` and `describe_object` see
every object `GET /api/v1/meta/object` lists to the same caller. The
API-exposure gate the data verbs ask refuses a package object declared
`apiEnabled: false`, and refuses every object of a disabled package `404
OBJECT_PACKAGE_DISABLED` on every verb, `aggregate_records` included, as
REST refuses it. Nothing is copied into the `metadata` service: a second
copy would be a second thing to keep in sync.
## This round: the review of record on `d19b65bf1c`
The contract-tier review of record (comment 6328012432, VERDICT FAIL)
found one widening. The gate read the protocol's
`OBJECT_PACKAGE_DISABLED` refusal as "no declaration", on the premise
that the data plane then refuses with that code. Five of `callData`'s
six arms reach the protocol, whose registry gate refuses it. The
`aggregate` arm does not: it asks the engine, and the engine serves a
disabled package's objects. So on `d19b65bf1c`, `aggregate_records`
served a disabled package's object declared `apiEnabled: false` wherever
the `metadata` service held a copy, where the copy had refused it `404
OBJECT_API_DISABLED`. Contract item 6 and the changeset's "Also changed"
sentence were false for that verb.
Closed here. `callData`'s gate refuses a disabled package's object
itself, with the protocol's own error, so every arm answers `404
OBJECT_PACKAGE_DISABLED`. The same rows also close a hole that predates
this PR (review boundary flag ③ 5): `aggregate_records` served a
disabled package's *exposed* object on every kernel, before this PR and
after it. Both are pinned below.
REST's answer was measured on the same kernels, through a `RestServer`
served over each kernel's protocol. It refuses both objects `404
OBJECT_PACKAGE_DISABLED` on every well-formed `/data` record request. It
answers a body that is not a record `400 VALIDATION_FAILED` first,
because `enforceApiAccess` reads the refusal as "no declaration" and its
request gate runs next. The dispatcher's `/data` door asks this gate
early too, as the twin of `enforceApiAccess`. With the refusal thrown at
that early ask, the door answered those three malformed bodies `404
OBJECT_PACKAGE_DISABLED`, a new divergence from REST (measured on both
kernel shapes, both objects). So the early ask keeps REST's reading,
`callData` refuses, and the door now answers every request what REST
answers. That is pinned too.
## The defect, measured on base `4e2c42f363`
`buildMcpBridge` answered `list_objects` and `describe_object` from the
`metadata` service (`listObjects()` / `getObject()`). The exposure gate
every runtime data door asks (`refuseUnexposedDataAction` in
`packages/runtime/src/action-execution.ts`, called by `callData`) read
the same service and treated a missing copy as "no declaration", that
is, allowed. That service holds a copy of an object only where something
put one there. On a kernel with an `environmentId`, the full boot bridge
is off. That covers `createStandaloneStack` (`os dev` as `env_local`,
`os serve`, `os start`) and the cloud single-environment runtime. So an
object that a package or a plugin registers in the `SchemaRegistry`
during the boot gets no copy.
Real kernels (`ObjectKernel` + `MetadataPlugin` + `ObjectQLPlugin` over
better-sqlite3 `:memory:`). The `environmentId` shape uses
`createStandaloneStack`'s plugin options verbatim. The fixture package
has two objects, one declared `apiEnabled: false`, registered through
the kernel's `manifest` service:
| shape, when the package registered | `metadata.getObject` | REST lists
| bridge lists | describe | gated `query` | gated `create` |
| :-- | :-- | :-- | :-- | :-- | :-- | :-- |
| `env_local`, during the boot | absent | 2 | **0** | **null** |
**served** | **reached the driver** |
| `env_local`, installed after the boot (`source: 'marketplace'`) |
copied | 2 | 2 | served | 404 | 404 |
| no `environmentId`, during the boot | copied | 2 | 2 | served | 404 |
404 |
| no `environmentId`, installed after the boot | copied | 2 | 2 | served
| 404 | 404 |
The first row is the card's live reading in miniature: a short list
presented as a complete `totalCount`, "not found" for every package
object, and `query_records` served. The other three rows are controls:
the boot bridge and the late-manifest bridge already copy, so they were
right and must stay right.
**The gate half is real.** The same object declared `apiEnabled: false`
was served by `query_records`, and `create_record` got past the gate to
the driver. The runtime dispatcher's `/data` door shares the gate, and
on that shape it answered `GET /data/qa_pkg_gated` with 200 and `POST`
reached the driver. `RestServer` refused the same object, because its
gate already reads the protocol's by-name read.
**The multi-tenant (`environmentId` kernel) path.** The merged read is
per environment there. The bridge resolves `protocol` through
`deps.resolveService(context, 'protocol', envId)`, the same scoped seam
`metadata` and the skill read use. Each environment kernel's
`getMetaItems` reads its own `ObjectQL` registry
(`readFlattenedMetaItems`: "the SchemaRegistry is owned by THIS kernel's
ObjectQL instance"). Measured: two `environmentId` kernels behind one
host each list and gate their own package and none of the other's. That
run is red with the fix ablated, because both lists come back empty.
**The stdio half measures the same defect.** That transport is held off
by the dispatch, so this is reported and not widened. Same fixture,
`env_local`, during the boot, driven through the real `MCPServerPlugin`
stdio session: `list_objects` gave `{"objects": [], "totalCount": 0}`,
`describe_object` gave `Object "qa_pkg_open" not found`, and
`query_records` on the `apiEnabled: false` object was served (`total:
0`). The control without an `environmentId` lists, describes and refuses
`OBJECT_API_DISABLED`.
## What changed
- **`packages/runtime/src/domains/mcp.ts`.** `list_objects` /
`listObjectsDiagnosed` and `describe_object` read `getMetaItems({ type:
'object' })` through the existing `McpMergedMetadataRead` seam. This is
the shape commit e783e16 gave the skill read. The unwrap moved into
one helper (`readMergedItems`) shared by the skill read and the object
reads, and the skill read's behaviour is unchanged. `describe_object`
answers from the same listing, so every listed object can be described.
The three outcomes are the skill read's. With no merged read on the
host, the code reads the `metadata` service as before. When the merged
read answers, it is served. When the merged read throws, the error goes
to the client and nothing falls back. `listObjectsDiagnosed` keeps
asking `listDiagnosed('object')` for the `partial` verdict.
- **`packages/runtime/src/action-execution.ts`.** The exposure gate
reads the object by name through the protocol, the read `RestServer`'s
own gate makes (`enforceApiAccess`). It uses the execution face
(`getMetaItemForExecution`) where the protocol has one, and
`getMetaItem` otherwise. A host whose protocol has no by-name read keeps
reading the `metadata` service. A merged read that throws falls back to
the `metadata` service's copy, the source this gate read before: an
outage must not widen exposure, and the caller's fail-open `catch` is
unchanged.
- A refusal `OBJECT_PACKAGE_DISABLED` is a refusal, never a read
failure. `readExposureDeclaration` throws it
(`isObjectPackageDisabledRefusal`), and `refuseUnexposedDataAction`
answers it by call site (`disabledPackage`).
- `callData` asks with `'refuse'`. The protocol's own error (`code`,
`status` 404, `object`) is thrown past the fail-open `catch`, on every
arm, the `aggregate` arm included.
- The dispatcher's `/data` door asks early, before its request gate,
with the default `'defer'`. That is "no declaration", as
`enforceApiAccess` reads it, so a malformed body is answered by the
request gate first, as on REST. A well-formed request is refused when it
reaches `callData`.
- **`packages/runtime/src/domains/data-write-request-gate.test.ts`, a
fixture re-judged.** Its dispatcher door used to model the gate's old
source (a `metadata` fake answered from the registry). The rig's
protocol cannot read without a metadata store, so with this change that
door passed only through the outage fallback. The registry-backed
by-name stub now lives in `bootStore` (`stubObjectReads`) and serves
both doors, and the dispatcher door is given no `metadata` service. A
gate that stopped reading the protocol reds 12 of its rows (measured on
`d19b65bf1c`). 73 of 73 rows are green with the fix.
- **`.changeset/25120-mcp-package-objects.md`.** A `patch` for
`@objectstack/runtime`, with the BREAKING banner and the ADR-0087
disposition `not-required (no-migration-prescription)`. Both declaration
lines are in the body. Its "Also changed" sentence now states the
disabled-package refusal on every verb.
## Contract
Every input whose answer changes, old to new:
1. **`list_objects` (`POST /api/v1/mcp`) on a kernel with an
`environmentId`, for an object a package or plugin registered during the
boot.** Old: omitted, with `totalCount` short and no `partial`. New:
listed. The non-`sys_` set equals what `GET /api/v1/meta/object` serves
the same caller.
2. **`describe_object` and `validate_expression` on such an object.**
Old: `Object "NAME" not found`. New: described (fields, label,
`enableFeatures`).
3. **`query_records`, `get_record`, `aggregate_records`,
`create_record`, `update_record`, `delete_record` on such an object
declared `enable.apiEnabled: false`.** Old: served, and the writes
reached the store. New: `404 OBJECT_API_DISABLED`, and the store is
never asked. With an `apiMethods` whitelist, a verb it leaves out gets
`405 OBJECT_API_METHOD_NOT_ALLOWED` with `allowed`.
4. **The same declaration on every other door that asks this gate** (the
runtime dispatcher's `/data` door, the `@objectstack/hono` catch-all;
`callData` under business actions and declarative endpoints). Old:
served. New: the same refusals as item 3, the codes `RestServer`
answers.
5. **A disabled package's objects, where the `metadata` service still
holds a copy.** For example, a kernel without an `environmentId`. Old:
`list_objects` listed them and `describe_object` described them, while
REST listed neither. New: omitted and not found, as REST answers.
6. **Each of the six MCP data verbs on an object of a disabled package,
whatever the object declares.** New, on every kernel shape: `404
OBJECT_PACKAGE_DISABLED`, the protocol's own error, refused at the gate
with the store never asked. That is the code and status `RestServer`
answers for the same object on every `/data` record route. Old:
- declared `apiEnabled: false`, with a `metadata` copy (a kernel without
an `environmentId`; a marketplace install): `404 OBJECT_API_DISABLED` on
all six verbs;
- declared `apiEnabled: false`, with no copy (an `environmentId` kernel,
registered during the boot): `404 OBJECT_PACKAGE_DISABLED` on the five
verbs that reach the protocol, and `aggregate_records` **served**;
- exposed, on every shape: `404 OBJECT_PACKAGE_DISABLED` on the five,
and `aggregate_records` **served**. This hole predates this PR: the
`aggregate` arm never asked the protocol.
No refusal's status moves, and `aggregate_records` stops serving.
7. **The dispatcher's `/data` door on an object of a disabled package.**
A well-formed request: `404 OBJECT_PACKAGE_DISABLED`, as REST answers.
Old: the same for an exposed object; `404 OBJECT_API_DISABLED` for one
declared `apiEnabled: false` with a copy. A body that is not a record
(`POST /data/:object` or `PATCH /data/:object/:id` with an array, `POST
/data/:object/query` with `{ "limit": "ten" }`): `400
VALIDATION_FAILED`, as REST answers. Old: the same for an exposed
object; `404 OBJECT_API_DISABLED` for one declared `apiEnabled: false`
with a copy. Nothing is served either way. Only which refusal comes
first moves, to REST's order.
8. **An object whose served definition differs from the `metadata`
service's copy** (for example, a stored environment row layered over
it). Old: MCP discovery and the gate read the copy. New: they read the
definition REST serves.
9. **`list_objects` / `describe_object` while the metadata store cannot
be read.** Old: the `metadata` service's listing. New: the protocol's
`503 SERVICE_UNAVAILABLE` error envelope, which is what `GET
/api/v1/meta/object` answers and what the skill prompt read already
answers.
Unchanged: objects that already had a copy are listed and gated as
before. That covers every object on a kernel without an `environmentId`,
a marketplace install on any kernel, and a runtime-authored object. A
host with no merged read keeps reading the `metadata` service. The
gate's outage branch judges the copy, as before. `packages/mcp` is
untouched.
## Pins
`packages/runtime/src/domains/mcp-package-object-discovery.test.ts`, 70
cases. The pins read the bridge the real `/mcp` entry
(`HttpDispatcher.handleMcp`) hands the MCP service, so `list_objects`
renders `totalCount` from `listObjectsDiagnosed().objects`:
- For each of the four rows above: `list_objects` lists the package's
objects, its non-`sys_` set equals `GET /meta/object`'s for the same
caller, and it is not `partial`. `describe_object` answers both objects,
including `apiEnabled: false`. Each of the six data verbs refuses the
gated object with `{ code: 'OBJECT_API_DISABLED', status: 404 }`, and no
protocol data method or engine `aggregate` is called. Control:
`query_records` serves the exposed object.
- A disabled package, on both kernel shapes (the copy's existence a
precondition on the shape that copies):
- its objects are neither listed nor described;
- REST answers both objects `404 OBJECT_PACKAGE_DISABLED` on each of its
six well-formed `/data` record requests, read off a `RestServer` served
over the same kernel's protocol;
- the dispatcher's `/data` door answers each of nine requests what REST
answers: the six well-formed ones and three whose body is not a record;
- each of the six MCP data verbs refuses both objects, the one declared
`apiEnabled: false` and the exposed one, with `{ code:
'OBJECT_PACKAGE_DISABLED', status: 404 }`, and no protocol data method
or engine `aggregate` is called.
- Two environments behind one host each list, describe and gate their
own package and not the other's.
**Red before, green after,** on `e471998c5`, whose code and pin blobs
`c0fb1c715` carries unchanged (it changes only the changeset). Each
leg's direction and count was predicted before running, and each landed
as predicted. Mutations were landed and restored by
`scripts/ablation-replace.mjs` (anchor 1 to 0, blob changed; restored
blob equals HEAD, `git diff HEAD` empty), under the verify lock. The
whole-file leg ran under a script with an `EXIT`/`INT`/`TERM` trap. It
restored with `git checkout HEAD` and checked blob equality and an empty
`git diff HEAD`. The runtime's own source is read by relative import, so
no leg depends on a `dist/` build.
| leg | mutation | result |
| :-- | :-- | :-- |
| main | both source files at base `abc25a353` (blobs `616639b765`,
`5d19bd3b7a`) | 38 failed, 32 passed. Failed: `env_local` during-boot
list, describe and six verbs (8); four two-environment pins; disabled
package without an `environmentId`: listing, door parity and twelve verb
rows (14); disabled package on `env_local`: twelve verb rows (12) |
| discovery | `readMergedObjects` answers `undefined` (the `metadata`
service read) | 5 failed, 65 passed |
| gate | `readExposureDeclaration` always reads the copy | 33 failed, 37
passed. Failed: `env_local` during-boot six verbs; two two-environment
gate pins; disabled package without an `environmentId`: twelve verb rows
and door parity; disabled package on `env_local`: twelve verb rows |
| refusal | `OBJECT_PACKAGE_DISABLED` read as "no declaration" again
(the reading on `d19b65bf1c`) | 24 failed, 46 passed. Failed: every
disabled-package verb row on both shapes. The four `aggregate_records`
rows answer `SERVED`; the other twenty reached the protocol before it
refused |
| door twin | `disabledPackage` defaults to `'refuse'` (the refusal
thrown at every ask) | 2 failed, 68 passed. Failed: door parity on both
shapes, where a body that is not a record answers `404
OBJECT_PACKAGE_DISABLED` and REST answers `400 VALIDATION_FAILED` |
| fix | HEAD | 70 passed; rig 73 passed |
The first attempt at the gate leg was a no-op. Its replacement text
already occurred twice in the file, so `ablation-replace.mjs` refused it
and ran nothing. It was re-run with a unique replacement, and the row
above is that run. The rig leg (12 failed, 61 passed) was measured on
`d19b65bf1c` and not re-run: the rig and its healthy path are unchanged.
## Local verification
On `c0fb1c715`, the final commit, with `origin/main` `abc25a353` merged
in and the workspace built (`turbo run build
--filter=!@objectstack/docs`, 76 tasks, exit 0). Every heavy run went
through the verify lock, and each line cites the lock's `VERDICT` line.
- `@objectstack/runtime` unit layer (`vitest run --project local`),
every file in one run: 433 files passed; 6864 tests passed, 19 skipped,
0 failed. That is 29 more than on `d19b65bf1c`, the pin's 41 cases now
70. `test:repo` is CI's: the diff touches none of its files, and the
derivation does not name it.
- `pnpm --filter @objectstack/runtime typecheck`: exit 0 (`tsc
--noEmit`, and `check:test-typecheck` OK: 27 files, 190 errors, 68
pinned signatures held in the ledger). Both edited test files are in the
`tsconfig.test.json` program (`--listFilesOnly`). A first run, before
the peer `@objectstack/driver-turso` was built, reported TS2307 on an
untouched file; that was build state, not this change.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 68 commands, each exit code captured before
any pipe, all exit 0. Two first answered 3 (PREREQUISITE NOT MET).
`check:dual-build-cjs-loads` had no full build yet;
`check:type-check-debt` timed out queueing for the lock behind another
session's run. Both were re-run after the full build, exit 0. The 10
families declared WIDE-population were run too, all exit 0.
- `--ran` reconciliation: `✓ dispatch-gates --ran: 68 derived famil(ies)
accounted for — 68 run, 0 NOT-MEASURED (a DERIVED zero — all 68 recorded
an exit code and none of them is 3).`
- `pnpm lint` (`eslint . --no-inline-config`): exit 0.
- `node scripts/check-changeset-no-major.mjs --base origin/main --event
PAYLOAD`, with PAYLOAD a `pull_request` event carrying this body: exit
0. `✓ This diff introduces no major bump.` and `✓ LEVEL AXIS: this PR
declares clause-② no (narrowing) and metadata-protocol none ...`. `node
scripts/check-adr-0087-registration.mjs --base origin/main`: exit 0, `✓
check-adr-0087-registration: 1 declared-breaking changeset(s), each
carrying an ADR-0087 disposition.`
- `node scripts/check-closing-keyword-parity.mjs --body` on this body
binds `#25120` alone.
- The full dogfood battery, once on this head (`pnpm --filter
@objectstack/dogfood test`, no shard): `VERDICT command-exit 0`; Test
Files 287 passed, 3 skipped (290); Tests 2395 passed, 18 skipped (2413);
no failure.
CI on `c0fb1c715`: 41 check runs, 34 `success` and 7 `skipped` by
design, 0 failures. The five required contexts are `success`: Lint &
Repo Gates, TypeScript Type Check, Test Core, Build Core and Governed
Surface Queue Guard.
## Acceptance notes
- **Card acceptance item 3 (walked on a cloud environment with a
marketplace app installed): NOT MEASURED.** The cloud host lives in
`objectstack-ai/cloud`, outside this session. In this repo the shape is
the `environmentId` kernel, pinned above with `createStandaloneStack`'s
options. On that shape, a package registered during the boot is the one
that had no copy. This repo's marketplace install and rehydrate register
after the boot and were already copied. Whether the cloud runtime
registers HotCRM during the boot was not walked. The card's reading (18
`crm_*` and 8 `ai_*` objects missing) fits that path.
- **Closed in passing: the `aggregate` arm never enforced
`OBJECT_PACKAGE_DISABLED`** (review boundary flag ③ 5). Before this PR,
`aggregate_records` served a disabled package's exposed object on every
kernel, because `callData`'s `aggregate` arm asks the engine, never the
protocol. The gate's refusal closes it. Pinned by the
`aggregate_records` rows on the exposed object, which the refusal leg
turns to `SERVED`.
- **REST's order on a malformed body, measured, not changed.**
`RestServer` answers a body that is not a record `400 VALIDATION_FAILED`
on a disabled package's object, before its data plane's `404
OBJECT_PACKAGE_DISABLED`, because `enforceApiAccess` reads that refusal
as "no declaration". For an object declared `apiEnabled: false` it is
the other way round: the exposure refusal comes first. The dispatcher's
door now follows REST. Whether both doors should refuse a disabled
package's object before the shape verdict is REST's question, and
nothing here changes it.
- **The outage fallback stays silent** (review judgment 3,
non-blocking). `RestServer`'s `loadObjectItem` logs a warn when a thrown
by-name read makes it fail open. This gate does not fail open on that
throw: it judges the `metadata` service's copy, so REST's line ("failing
open") would say something false here. `ActionExecutionDeps` also
carries no logger. Left silent, as the old `catch` was.
- **Finding, same defect class, not fixed here: `list_actions` /
`run_action`.** `collectActionDeclarations` reads
`metadata.listObjects()`, and action resolution reads the same service.
Measured on the same `env_local` during-boot fixture: an object action
declared `operation: 'update'` with `ai: { exposed: true }` is in the
registry, but `list_actions` answers `[]` and `run_action` answers
`Action 'mark_done' not found on object 'qa_pkg_open'`. Control without
an `environmentId`: listed and resolved. Queued as #25139.
- **Finding, the stdio half (`packages/mcp/src/stdio-data-bridge.ts`):**
the same three answers, measured above. Held off by the dispatch, queued
as #25138. Its fix should read `OBJECT_PACKAGE_DISABLED` as this gate
now does on the arm that does not reach the protocol.
- **Note:** the `GATED_ACTIONS` docblock in
`packages/mcp/src/stdio-data-bridge.ts` says the HTTP bridge answers
`listObjects` / `describeObject` "straight off the metadata service".
After this change, it reads the protocol's merged listing. The parity
claim it makes (those two are not gated through `callData`) still holds.
Only the wording is stale. It rides on #25138.
- **Note, not changed (review note 9):** `describe_object` serves the
raw `fields` map and reads the whole served listing per call.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01AFrKwndmrCLtF5V9hNtrw3)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 5d66021 commit a1e51d6
5 files changed
Lines changed: 815 additions & 62 deletions
File tree
- .changeset
- packages/runtime/src
- domains
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
289 | 289 | | |
290 | 290 | | |
291 | 291 | | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
292 | 385 | | |
293 | 386 | | |
294 | 387 | | |
| |||
314 | 407 | | |
315 | 408 | | |
316 | 409 | | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
317 | 430 | | |
318 | 431 | | |
319 | 432 | | |
320 | 433 | | |
321 | 434 | | |
322 | 435 | | |
323 | 436 | | |
| 437 | + | |
324 | 438 | | |
325 | 439 | | |
326 | 440 | | |
327 | 441 | | |
328 | | - | |
329 | | - | |
330 | | - | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
331 | 448 | | |
332 | 449 | | |
333 | 450 | | |
| |||
349 | 466 | | |
350 | 467 | | |
351 | 468 | | |
352 | | - | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
353 | 472 | | |
354 | 473 | | |
355 | 474 | | |
| |||
0 commit comments