Repository navigation
Commit c02fa12
fix(runtime): the package install door parses its whole body through PackageInstallBodySchema (#20218)
Refs #19328. This PR closes rows 1b, 3 and 4. It closes row 2 in two of
its three positions. **The row left open is row 2's third position**: an
unknown key at the TOP LEVEL of the wrapped form (`{ manifest, bogus
}`). It still answers `201`, with `bogus` dropped, because the
declaration puts the wrapped branch in strip mode and its docblock
forbids closing it. That makes it a spec seam, not a door defect (see
*Open question 1*). Per triage `5780789216`, this round lands as `Refs`.
Clause-②: no (narrowing)
## What changed
`POST /api/v1/packages` (`packages/runtime/src/domains/packages.ts`,
`handlePackagesRequest`, the `parts.length === 0 && m === 'POST'`
branch) now parses the whole body once, at the top of the install path.
It uses the union `@objectstack/spec` already declares for this door,
`PackageInstallBodySchema.safeParse(body)`. The door's own prose had
named this call twice as the one call that closes the residual classes.
- **What the door now enforces: the WHOLE declaration, not only the
card's four rows.** Before this change, the door parsed only the `id`
and `version` legs of the manifest. Each of the following therefore
answered `201` and now answers `400`: `name` (the manifest's other
required key); the `namespace` grammar; the closed value sets of
`scope`, `runtime` and `packaging`; a declared key's type; the
retired-key tombstones (`configuration`, `capabilities`, `extensions`,
`loading`); and unknown keys inside the nested blocks the declaration
closes. Those blocks are `contributes` and its `kinds[]`, `data[]` seeds
(`SeedSchema`), `navigationContributions[]` and their items, `engine`,
`engines`, and the structured form of `permissions`. The list was
measured by walking `ManifestSchema` at this head. §8 of the
body-contract file pins nine of these at the door, and the changeset's
BREAKING paragraph names all of them, with a FROM → TO bullet for
`name`.
- **Verdict placement.** The verdict is answered after the `id` and
`version` legs, which keep their published sentences. It comes before
the duplicate-id `409`, so a request-shape refusal never depends on
server state.
- **Envelope.** The envelope is the one the `id` and `version` legs
already use, `deps.error(msg, 400)`: status `400`, code
`VALIDATION_ERROR`, taken from the standard catalog's 400 member. **No
error code was minted.**
- **Reads go through the parsed value.** `overwrite`, `settings` and
`enableOnInstall` are read off the parsed wrapped request, never the raw
body. That makes the string-vs-boolean inversion in row 3 structurally
impossible rather than patched.
- **Telling the forms apart.** The two forms are distinguished by
`'manifest' in parsed.data`. This is the declaration's own disjointness:
`ManifestSchema` is a strict close with no `manifest` key, and the
wrapped branch requires one.
- **The manifest is stored as sent.** The door hands the install writer
the manifest the caller sent, not the parsed copy. Parsing through
`ManifestSchema` adds defaults (`scope: 'project'`, `defaultDatasource:
'default'`). The existing pins
(`packages-install-manifest-version.test.ts` §2) and
`withWritableVerdict`'s header both record that this door stores a
key-by-key copy with no defaults. The parse is a gate here, not a
normaliser.
- **The refusal sentence.** `installBodyRefusal` re-parses the branch of
the form the caller wrote, to locate the failure. The verdict is still
the union's. It surfaces each issue's own message verbatim, as the `id`
leg surfaces `manifestIdRefusal`. When install options are spelled on
the bare form, it appends one prescription, the declaration's own
remedy: send the wrapped form, or `?overwrite=true` for `overwrite`. The
option key set is read off `PackageInstallRequestSchema.shape` and is
never listed by hand. The shared union-branch ranking
(`zodIssuesToFields`) is not used for this. Measured, it picks the bare
branch for `{ manifest: null, … }` and reports both branches for a bare
body with no `type`.
- **Untouched regions.** The `@objectstack/spec/kernel` import line and
the `PATCH /packages/:id` version check are not touched (the PR #19637
fence). The new import is on its own line.
## Before and after, measured against the real door
Method: the real `HttpDispatcher` over a real `SchemaRegistry`, with
`OS_HOME` redirected, driven by a one-shot probe that is not committed.
"Before" is `origin/main` `1c8b320a8`. "After" is this branch at
`df0108189`.
| row | request | before: status · installed · enabled | after: status ·
installed · enabled |
|--:|:--|:--|:--|
| 1b | wrapped, manifest with no `type` | 201 · yes · true | **400
VALIDATION_ERROR** · no · – |
| 1b | bare, no `type` | 201 · yes · true | **400 VALIDATION_ERROR** ·
no · – |
| 2 | wrapped, unknown key at the TOP LEVEL | 201 · yes · true | 201 ·
yes · true (**open: declared strip, the seam**) |
| 2 | wrapped, unknown key INSIDE the manifest | 201 · yes · true, key
**stored** | **400 VALIDATION_ERROR** · no · – |
| 2 | bare, unknown key | 201 · yes · true, key **stored** | **400
VALIDATION_ERROR** · no · – |
| 3 | wrapped, `enableOnInstall: 'false'`, fresh id | 201 · yes · **true
(inverted)** | **400 VALIDATION_ERROR** · no · – |
| 3 | wrapped, `enableOnInstall: 'true'`, fresh id | 201 · yes · true |
**400 VALIDATION_ERROR** · no · – |
| 3 | wrapped, `overwrite: 'true'`, id already installed | 409
RESOURCE_CONFLICT (read as absent) | **400 VALIDATION_ERROR** · existing
row untouched |
| 4 | bare, `enableOnInstall: false`, fresh id | 201 · yes · true
(ignored, stored as a manifest key) | **400 VALIDATION_ERROR** · no · –
|
| 4 | bare, `overwrite: true`, id already installed | 201 ·
**honoured**, stored as a manifest key | **400 VALIDATION_ERROR** ·
existing row untouched |
| 4 | bare, `settings: {a:1}` | 201 · **honoured** as install settings,
stored as a manifest key | **400 VALIDATION_ERROR** · no · – |
| ctl | wrapped, well-formed | 201 · yes · true | 201 · yes · true |
| ctl | bare, well-formed | 201 · yes · true | 201 · yes · true |
| ctl | SDK wire shape, `enableOnInstall: false` | 201 · yes · false |
201 · yes · false |
| ctl | SDK wire shape, `overwrite: true`, id already installed | 201 |
201 |
| ctl | bare, well-formed, `?overwrite=true`, id already installed | 201
| 201 |
**Row 4 had moved from what the card says.** The card says install
options on the bare form are "ignored". Measured, they were handled key
by key: `enableOnInstall` was ignored, while `overwrite: true` and
`settings` were honoured. All three were stored inside the manifest. The
door read `body?.overwrite` and `body.settings` without checking the
form. Rows 1b, 2 and 3 read as the card relayed them.
**Row 4's answer, decided by the declared schema.** `ManifestSchema`'s
strict close refuses `settings`, `enableOnInstall` and `overwrite` on a
bare body by name. The docblock states the remedy: «a caller that needs
an option sends the wrapped form». So the declared answer is a refusal,
and the refusal names the wrapped form. Neither branch is relaxed. A
bare body keeps `?overwrite=true`, which the docblock names as its one
route.
## The SDK control (its request shape is the one production caller of
`enableOnInstall`)
- **Permanent pin, door side.** `packages-install-body-contract.test.ts`
§7 builds the body exactly the way `client.packages.install(m, options)`
builds it, and round-trips it through JSON. It checks three calls.
`install(m, { enableOnInstall: false })` answers 201 and is disabled in
all three records: the returned row, the registry, and the durable file.
`install(m)` answers 201 and is enabled. `install(m, { overwrite: true
})` over an installed id answers 201. The SDK side of the same shape is
already pinned in `packages/client/src/client.test.ts`.
- **One-shot probe, not committed.** The real `ObjectStackClient` ran
through the real `HttpDispatcher` from the rebuilt
`@objectstack/runtime` dist, built from `0e3d3fdf5`. Results:
- `install(m, { enableOnInstall: false })` → `enabled: false`, and the
registry row is `false` too.
- `install(m)` → enabled.
- `install(m, { overwrite: true, enableOnInstall: true })` → 201, and
the manifest was replaced.
- `install(m, { settings: { a: 1 } })` → the settings landed.
- A manifest with no `type` → the client threw, with code
`VALIDATION_ERROR` and status 400.
- In the same run, `packages-write-envelope.test.ts`, `client.test.ts`
and `readme-package-install-example.test.ts` stayed green: 4 files, 222
tests.
**The objectui caller, read by the seat at `.objectui-sha` `f8a9d0fb`
(REWORK `5855224201`):** «The create path sends `POST /api/v1/packages`
with `{ manifest: manifestBody }`, where `manifestBody = { ...draft, id,
name, version, type: draft.type ?? 'app' }`.» The file is
`packages/app-shell/src/views/metadata-admin/PackageFormDialog.tsx`.
Studio's create-package dialog therefore always sends a declared `type`,
and row 1b's refusal does not break it.
## Fixture triage (off-spec door fixtures, by disposition)
- **Declaration added** (the fixture was never spec-legal; its subject
is something else). Each fixture gains `type: 'app'`:
- `package-door-namespace-conflict-code.test.ts` (the `manifest`
helper);
- `domain-handler-registry.test.ts` (the duplicate-id drive);
- `packages-capability-gate.test.ts` (the install write row);
- `http-dispatcher.test.ts` (the 409 and `?overwrite=true` cases).
- **Spelling corrected.** In `http-dispatcher.test.ts`, the two `POST
/packages install` cases changed `type: 'application'` to `'app'`.
`'application'` is not a member of the declared enum.
- **Replaced whole** (the pin asserted the very branch this PR removes):
- `packages-install-enable-on-install.test.ts`. The case "the BARE body
form does NOT honour the key" asserted `201` plus ENABLED. That was a
standing pin on row 4, which the card believed had none. It now asserts
`400 VALIDATION_ERROR` and that nothing installed.
- `packages-install-preserves-lifecycle.test.ts`. The case "the BARE
body form still sets nothing" now asserts `400 VALIDATION_ERROR`. It
still asserts that the operator's disable stands in the registry and on
disk.
## Pins (new file
`packages/runtime/src/domains/packages-install-body-contract.test.ts`)
Every refusal asserts `status` 400, `error.code` `VALIDATION_ERROR` and
`success: false`. It also asserts that nothing was written: the id is
absent from the registry, or the existing row is unchanged.
- **§0** Every row body is refused by the declaration itself.
- **§1** Row 1b, both forms.
- **§2** Row 2, the in-manifest and bare positions.
- **§3** Row 3: `'false'` is never installed-enabled, `'true'` is
refused, and `overwrite: 'true'` is not read as absent.
- **§4** Row 4, `enableOnInstall` / `overwrite` / `settings`. The
message names the misplaced key, `"manifest"`, and `?overwrite=true`.
- **§5** Parity for the top-level wrapped unknown key: the door answers
what the declaration answers. The pin stays green whichever way the seam
is decided.
- **§6** Ordering. The `version` leg's sentence survives when a body
also lacks `type`. An off-spec body answers 400, not 409, on an
installed id.
- **§7** Controls: both forms install, the manifest is stored as sent,
wrapped `settings` land, and the SDK shapes behave as above.
- **§8** The rest of the declaration, added in patch round 2. Nine cases
each answer `400 VALIDATION_ERROR`, install nothing, and name the path:
- no `name`, in the wrapped and the bare form;
- a `namespace` outside the grammar;
- a `scope` outside its set;
- a non-string `description`;
- the retired `capabilities`;
- an unknown key inside `contributes`, inside a `data[]` seed, and
inside `engines`.
Each case is first asserted off-declaration by the declaration itself,
beside a control that parses green and differs by the defect alone.
## Ablation (commit first, then mutate, then restore)
- **Mutation.** At `e2c4c7d90`, `node scripts/ablation-replace.mjs`
replaced the anchor `const declaredBody =
PackageInstallBodySchema.safeParse(body);` with a verdict that always
passes and hands back the raw body. The anchor count went 1 to 0, the
replacement count 0 to 1, and the blob `4f6b4be7` to `4a881701`.
- **No dist involved.** The subject is reached through a relative import
(`../http-dispatcher.js` to `./domains/packages.ts`), so no `dist/` sits
on the path. `ablation-dist-preflight --absent` confirms the marker is
absent from runtime `dist/` and that the tree is clean.
- **Red leg.** The three files went **13 failed, 42 passed**. Every row
pin in §1–§4 failed, as did §6's 409-ordering case and both replaced
bare-form pins. §0, §5, the `version`-leg ordering case and every §7
control stayed green. The direction was predicted before the run: red.
- **Restore.** The tool restored with `git checkout HEAD`, then showed
the blob after restore equals HEAD (`4f6b4be7`) and `git diff HEAD` is
empty. Re-verified by hand, the tree is clean.
- **Green leg.** The same three files then passed 55 of 55.
## Tests and gates (head `df0108189` unless stated; the patch rounds are
recorded at the end of this section)
- **Runtime door tests.** 31 door-touching test files, plus the
uncommitted probe: **32 files, 779 tests passed**.
- **Full runtime suite.** `pnpm --filter @objectstack/runtime exec
vitest run --project local --maxWorkers=2`: **280 files, 3927 passed, 1
skipped**, at `0e3d3fdf5`, before any merge of `origin/main`. The branch
has merged `origin/main` three times:
- The first merge (`df0108189`, base `e7f69dbba`) brought no change to
`packages/runtime`, the install schemas or the lockfile.
- The second merge (`f2e2c99bd`, base `4df101c38`) brought the #19543
flow-list retirement: `domains/automation.ts`, `dispatcher-plugin.ts`,
`route-ledger.ts`, `packages/client/src/index.ts`, seven runtime test
files (`dispatcher-plugin.anonymous-gate.integration`,
`domain-handler-registry`, `anonymous-gate-actions-automation`,
`automation-run-read-permission-gate`,
`automation-write-capability-gate`,
`http-dispatcher.tenancy-posture-outage`, `http-dispatcher`) and
`test-typecheck-debt.json`. It does not touch the install door,
`PackageInstallBodySchema`, `ManifestSchema` or the install writers. 10
door and runtime test files re-ran on the merged tree at `481ad07f`:
**515 passed**.
- The third merge (`dd49317f1`, base `805af4f29`) brought no
`packages/runtime` change and no lockfile change.
- **Typecheck.** `pnpm --filter @objectstack/runtime typecheck` exited
0. Its test layer (`tsconfig.test.json`) compiles the new file;
`--listFilesOnly` counts it. The debt ledger held at 27 files, 191
errors and 69 signatures.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` derived 60 families: **59 exited
0**. The one other is `pnpm check:dual-build-cjs-loads`: **NOT
MEASURED** (exit 3, `PREREQUISITE NOT MET`: 31 packages have no `dist/`
without a full build, which is CI's). As a scoped substitute,
`require('packages/runtime/dist/index.cjs')` loads (`HttpDispatcher` is
a function). `--ran` reconciliation: 60 derived, 59 run, 1 NOT MEASURED,
0 UNRUN.
- **Lint.** `pnpm lint`, the full `eslint . --no-inline-config`, not
narrowed: exit 0.
- **Issue citations.** `node scripts/check-issue-citations.mjs --base
origin/main`: exit 0 (12 citations: 11 resolve, 1 cross-repo).
- **Patch round 1 (head `481ad07f`, changeset only).**
- `check-adr-0087-registration --base origin/main` → 0, reading
`[BREAKING+clause-②-narrowing]`.
- `check-changeset-no-major --base origin/main` → 0.
- 10 door and runtime test files → 515 passed.
- Derived gates: 60 derived, 59 exit 0, `check:dual-build-cjs-loads` NOT
MEASURED (exit 3). `pnpm lint` → 0; the real issue-citation check → 0.
- **Patch round 2 (head `1358147bc`: the changeset states the whole
declaration, plus the §8 pins).**
- `check-adr-0087-registration --base origin/main` → 0, reading
`[BREAKING+clause-②-narrowing]` with the one marker.
- `check-changeset-no-major --base origin/main` → 0.
- The same 10 files → **524 passed** (515 plus the 9 §8 cases).
- `pnpm --filter @objectstack/runtime typecheck` → 0.
- Ablation, with the same anchor as before (the body parse replaced by a
verdict that always passes): the body-contract file went 20 failed / 10
passed, and every §8 case answered `201` where it expects `400`, i.e.
installed before the fix. The restore was byte-identical to HEAD.
- Derived gates: 60 derived, 59 exit 0, `check:dual-build-cjs-loads` NOT
MEASURED (exit 3; 36 packages have no `dist/`). `pnpm lint` → 0; the
real issue-citation check → 0 (8 citations, all resolve).
## Changeset and bump
`.changeset/19328-install-door-body-parse.md`, `@objectstack/runtime:
minor`, carrying `Clause-②: no (narrowing)`, a **BREAKING for callers of
the install door** paragraph with a FROM → TO remedy for each refused
shape, and exactly one ADR-0087 `not-required
(no-migration-prescription)` disposition. It was rewritten in patch
round 1, after contract review `5855204061` answered FAIL on the bump
and the seat's REWORK `5855224201` upheld it. The claim's own line stays
`Clause-②: no`, because a claim line carries only `yes | no`.
Why `minor` and `(narrowing)`: the diff shrinks the door's observed wire
accept set. Bodies that answered `201` now answer `400`, and bare-form
`overwrite: true` and `settings` were HONOURED before and are refused
now. `scripts/pm/clause2-line.mjs` defines `no (narrowing)` as «NOT a
widening, but breaking». AGENTS.md Post-Task Checklist step 3 makes
`(narrowing)` BREAKING: the changeset must carry its migration, and must
state its ADR-0087 disposition in writing. The same door's two earlier
pull-backs to the same declaration carry exactly this form, and this PR
narrows more than either:
- `.changeset/19120-install-door-parses-manifest-version.md` (#19120,
the `version` leg);
- `.changeset/19417-install-door-parses-manifest-id.md` (#19417, the
`id` leg).
Each carries `minor`, `Clause-②: no (narrowing)`, a BREAKING paragraph
and `adr-0087: not-required (no-migration-prescription)`. `node
scripts/check-adr-0087-registration.mjs --base origin/main` now reads
this changeset as `[BREAKING+clause-②-narrowing]` and accepts it with
its marker (exit 0). For each refused shape, the changeset names what a
client should send instead.
✅ **Settled in patch round 1.** The seat answered *Open question 2* with
A (REWORK `5855224201`). The text below is kept as the record of why it
was raised. The two earlier PRs on this same door, for #19120 (the
`version` leg) and #19417 (the `id` leg), each pulled the door back to
the same declaration. Both declared `Clause-②: no (narrowing)`, bumped
`minor` and wrote an ADR-0087 `not-required` disposition. This PR
narrows more than either did: row 4's bare `overwrite` and `settings`
were **honoured** before and are refused now.
## Acceptance notes (observations, not filed)
- **A JSON `null` body answers 500.** Through the dispatcher, a `null`
or `undefined` body answers `500 INTERNAL_ERROR` with the TypeError text
("Cannot read properties of null (reading 'manifest')"). The cause is
the unchanged `body.manifest || body` line ahead of the id gate. The
Hono adapter maps an unparseable body to `{}`, but a literal JSON `null`
would reach the door as `null`. That path is inferred from the adapter
code, **not measured through a public door**, so it is not filed. Owner:
none.
- **`?overwrite` is compared by hand.** The query parameter is still
read with a hand-written `=== 'true' || === true`, not with
`parseBooleanParam`, which is the file's own declared query-boolean
coercion. That code is outside this card's rows and unchanged here.
Owner: none.
- **Nested strip-mode shapes are stored as sent.** Some nested shapes
are still in strip mode, and those are stored as the caller sent them. A
walk of `ManifestSchema` at this head found them only in the object form
of a navigation item's `visible` and its `meta`. The gate refuses
unknown keys in the manifest and in every nested block the declaration
closes: `contributes` and its `kinds[]`, `data[]` (`SeedSchema` is a
`strictObject`), `navigationContributions[]` and their items, `engine`,
`engines`, and the structured `permissions`. §8 pins three of these at
the door. The earlier sentence here named `data[]` as strip mode and
said the gate closed top-level keys only; both were wrong, and patch
round 2 corrected them. Owner: none.
## Spec-side follow-up: filed as #20219 (the #19327 pattern; it lands
with or right after this PR)
`@objectstack/spec`'s `PackageInstallBodySchema` docblock goes stale
when this lands. Four places are affected:
- The "measured residual" list still records classes 1b, 2, 3 and 4 as
answering `201`.
- The paragraph on the two runtime door drives says they post bodies
with no `type`; they now carry `type`.
- The `enableOnInstall` docblock says the door "reads the raw body".
- `package-api.test.ts` has a block, "the measured bare-form senders are
the RESIDUAL", that makes the same claims.
That docblock ships in `dist/api/index.d.ts`, per the #19327 changeset.
`packages/spec/**` is read-only for this card, so this is the #19327
pattern: a spec-lane docs follow-up, not a rider here.
## Open questions
1. **The seam, row 2 at the top level of the wrapped form.** Should
`PackageInstallRequestSchema` be closed (`.strict()`)? The door now asks
the declaration, so it follows with no edit, and §5 stays green either
way.
- **For closing, measured:** no first-party producer sends an undeclared
top-level key. The SDK sends `manifest`, `settings`, `enableOnInstall`
and `overwrite`. objectui's `PackageFormDialog` sends `{ manifest }`.
- **The cost of leaving it:** a misspelt option such as
`enabledOnInstall: false` is silently dropped, and the package installs
ENABLED. That is the same harm as row 3, reached by a typo.
- **Why this PR does not do it:** the docblock ties the strip mode to
maintainer ruling A (batch #148 item 4), and this card may not move a
spec shape. So it is a spec-lane and maintainer decision.
2. **The changeset's `Clause-②` arm and bump.** ✅ Answered A by the seat
(REWORK `5855224201`) and applied in patch round 1. The claim declares
`Clause-②: no`, and this PR copies it. The same-door precedents (#19120,
#19417) declared `no (narrowing)` with `minor`. Should the seat
re-declare `no (narrowing)` with `minor` and an ADR-0087 `not-required`
disposition? Recommended, because refusing previously honoured bare-form
`overwrite` and `settings` is a narrowing of observed wire behaviour.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UYBdGBzWSrAMzpW8ah3GbP)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3cb84d0 commit c02fa12
9 files changed
Lines changed: 828 additions & 51 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 | + | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
597 | 597 | | |
598 | 598 | | |
599 | 599 | | |
600 | | - | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
601 | 606 | | |
602 | 607 | | |
603 | 608 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
149 | 152 | | |
150 | 153 | | |
151 | 154 | | |
| |||
0 commit comments