Repository navigation
Commit 0b8bce4
fix(plugin-security): a controlled_by_parent detail's read scope is the engine's ReadableIds leaf over its master (ADR-0139 D6, seventh consumer); Q2 A in service-analytics; the Turso remote nested leaf (#25198)
Fixes #24932
Clause-②: no
Metadata-protocol: none
## What
`controlled_by_parent` becomes the seventh consumer of the engine's
`ReadableIds` leaf (ADR-0139 D6; ruling B `6303721609`; built as ruling
Q1 C / Q2 A `6329027469` binds it, after #25144 / PR #25166 landed the
boot-time refusal of the two master shapes the leaf cannot serve).
- **plugin-security.** `computeControlledByParentFilter` answers `{
masterFK: { $in: ReadableIds.of(master) } }` for EVERY
`controlled_by_parent` object on a read verb, with no up-front routing
to an id list. The engine composes the leaf at its executor boundary
from this plugin's own `canReadObject` / `getReadFilter` of the master,
recursively down a master chain, and the driver runs ONE statement with
the master inside a subquery. The four call sites (the middleware's
principal and delegator halves, `getReadFilter`,
`computeDelegatedReadFilter`) answer the leaf. The cycle guard and the
depth bound stay and become a registry-only walk
(`controlledByParentChainRefusal`) made BEFORE the leaf is written: a
metadata cycle and a chain past `CBP_MAX_CHAIN_DEPTH` keep answering
zero rows (`$in: []`), and the cut is the engine's own
(`READABLE_IDS_MAX_CHAIN_DEPTH`), so the engine's refusals are never
reached for a declared chain. A write verb's predicate carries no leaf
(ADR-0139 D1): the derivation answers `null` for `update` / `delete`; a
predicate write of a detail is narrowed to the rows the read door
returns for the caller's own predicate (#21829, where the leaf composes)
and judged per master by the 2.8 gate (#25012, a verdict, never a
scope). The delegator half writes the same leaf, so a delegated read
carries one subquery twice: correct rows, left as the ruling's execution
parameter says.
- **service-analytics (Q2 A).** The engine-bound merges —
`ObjectQLStrategy.withReadScope`, `resolveFkAttr`, and the plugin's
record-label pass (the third engine-bound merge of that family, the
structural twin `resolveFkAttr`'s own comment names) — pass
`admitReadableIds: true` to `assertReadScopeComparandsRunnable`.
`NativeSQLStrategy.canHandle` declines a query whose base or joined
object's read scope carries a leaf (`readScopeCarriesReadableIds`, the
content-based decline the 2026-08-12 ruling on #7598 accepted), so the
query routes to the ObjectQL strategy. `compileScopedFilterToSql`
refuses a leaf-carrying scope first and by name, in its existing
withheld `READ_SCOPE_COMPILE_FAILED` / 500 envelope.
- **driver-turso.** `toRemoteWhere` resolves a composed leaf's scope
through itself with the outer read's options, so a nested composed leaf
(a two-level chain) resolves on the remote face instead of reaching the
transport unresolved.
- **spec.** One nested-leaf row in `READABLE_IDS_CASES` (every driver
suite measures the shape), and the comparand-shape door's docblock names
both callers that pass `admitReadableIds`. Measured: the docblock
sentence reaches no generated surface — `check:api-surface`,
`check:docs` and the rest of spec's `check:*` ran green on this head
with nothing regenerated.
- **ADR-0055 (Tier H).** The status line gains the 2026-10-22 amendment
(mechanism (b) and the set-size limit retired for
`controlled_by_parent`); the Consequences bullets `:74` (set-size
ceiling) and `:75` (per-request cost, "cached within the request" — it
never was, and it ran twice) each carry a dated correction; so do the
amendment's `What both halves do now — Read` paragraph and its `Cost`
sentence. **ADR-0139 (Tier H).** D5 gains the dated seventh-consumer
note worded "refuses at boot" (engine seat 1's pointer `6333147990`, the
late-registration limit included); D6's sentence "whether that id set
becomes a seventh consumer was ⛔ not decided here" gains a dated
parenthetical pointing at ADR-0055's amendment and D5's note. That D6
parenthetical is beside the ruling's named sentences, added because this
PR falsifies the clause next to it; the seat may strike it.
## Deviations, named
1. **The `/api/v1/analytics/sql` dry-run echo of a
`controlled_by_parent` object refuses** (`READ_SCOPE_COMPILE_FAILED` /
500, withheld) where it rendered the bound id list. The one visible
change, on a debugging aid, as ruling ⑤ names it.
2. **A delegated caller's detail now follows its master's delegated
read.** `explain-enforce-parity`'s row `an agent on behalf of the org
writer, a detail under someone else's master, update` moves from
`RECORD_NOT_FOUND` / 404 to `PERMISSION_DENIED` / 403, and
`controlled-by-parent-chain`'s delegated row (a hand-built two-human
context) from `line_us` to `line_us, line_jp`. Cause: the engine
composes the master's leaf with the delegated context, asking
`getReadFilter(master)` — the ADR-0090 D10 spelling #25106 landed, the
principal's leg at the ceiling's DECLARED depth (`org` when the ceiling
declares none). The id-list derivation resolved that half at the
ceiling's EFFECTIVE depth (`own` on a private master), so a detail was
absent to a caller who could read its master: the parity row's sibling,
`p_theirs` (the master shape), already answered 403 for the same caller.
The detail now reads exactly the rows a delegated direct read of its
master reads (ADR-0139 D5.1 as amended, ADR-0055 §2's equality). No row
is served that the same caller could not already read on the master; the
write is still refused at the master-edit gate. Reported here and in the
dev report as a measured delta, with the recommendation to accept it;
keeping the old narrower answer would need a second spelling of the
master's delegated scope, which ruling B ③ refuses.
3. **Bulk writes on a detail no longer carry the read derivation in
their predicate** (ADR-0139 D1). For an addressed predicate write
(`update` / `delete` with `multi`) the matched set is unchanged: #21829
narrows it to the rows the read door returns (through the leaf) and
#25012 judges every master; pinned on the real stack (a bulk update by a
caller touches exactly the rows under the masters it may read and edit).
The two #21829 stand-down shapes — a nested operation and the engine's
referential field clear — carry the RLS write filter only, where before
this PR they also carried `masterFK IN (readable master ids)`. NOT
MEASURED at run time: those two shapes are engine-internal writes (no
caller addresses them); named here so the seat can ask for a measurement
if it wants one.
4. **PR opened on the dispatch base `72a302a9d` without merging the six
`main` commits that landed meanwhile** (`b6e870707`..`14e7239e7`): none
of their 71 files is among this branch's 29, and the queue's merge-ref
run validates the merge; `02ad31a0c` (the spec parse refusing a
`controlled_by_parent` master reference ADR-0055 §1 does not admit) is
the one to watch on that run.
## Measurement conditions (the ruling's)
1. **PostgreSQL** — a local PostgreSQL 16 (`initdb` + `pg_ctl` as the
`postgres` user, port 54329, torn down with this work). driver-sql's
readable-ids conformance with `OS_TEST_POSTGRES_URL` set: `live postgres
RAN`, 36 passed / 1 skipped (the suite's own MySQL carve-out), the
nested row included. The switch's own rig ran its PostgreSQL cell too
(`on postgres`, every pin below: row sets, statements with double-quoted
identifiers, the 33,000-master read, the guards):
`controlled-by-parent-leaf-switch`,
`controlled-by-parent-master-object-read`, `explain-enforce-parity`
together 3 files / 231 tests passed on `2b988305d`.
2. **Dashboard latency, HotCRM fixture** (`objectstack-ai/hotcrm` @
`c8a7a7047`: the six `controlled_by_parent` objects the card names plus
`crm_campaign_member` and `crm_article_feedback`, which HotCRM declares
since; masters `crm_account` / `crm_opportunity` / `crm_event` private,
`crm_campaign` / `crm_knowledge_article` public_read; the chain
`crm_quote_line_item → crm_quote → crm_account`). Harness: the real
engine, SecurityPlugin and SharingService; SCALE = 10,000 masters per
master object (the caller owns every even one), 2 details each; 16
widgets = count and grouped-by-status per object, each run 7 times after
2 warm-ups, medians. Three cells: today's native face (NativeSQLStrategy
over the pre-resolved id list, the pre-scan timed inside the cell as the
retired derivation ran it), the engine face after the switch (the native
strategy declines, the ObjectQL strategy composes the leaf), and a
control (the engine face over the id list). SQLite, shared box:
| object | shape | today native (ms) | after engine (ms) | control
engine+idlist (ms) | ratio after/today |
|:--|:--|--:|--:|--:|--:|
| crm_contact | count | 51.3 | 47.7 | 92.4 | 0.93 |
| crm_contact | grouped | 39.2 | 28.8 | 49.1 | 0.74 |
| crm_quote | count | 50.9 | 23.8 | 43.7 | 0.47 |
| crm_quote | grouped | 33.0 | 24.8 | 42.3 | 0.75 |
| crm_contract | count | 45.1 | 36.4 | 55.3 | 0.81 |
| crm_contract | grouped | 31.0 | 30.7 | 41.3 | 0.99 |
| crm_quote_line_item | count | 82.6 | 86.0 | 130.5 | 1.04 |
| crm_quote_line_item | grouped | 89.7 | 71.6 | 142.3 | 0.80 |
| crm_opportunity_line_item | count | 53.4 | 41.8 | 39.6 | 0.78 |
| crm_opportunity_line_item | grouped | 34.5 | 27.4 | 44.0 | 0.79 |
| crm_event_attendee | count | 32.7 | 38.7 | 55.1 | 1.18 |
| crm_event_attendee | grouped | 42.2 | 39.5 | 44.4 | 0.93 |
| crm_campaign_member | count | 39.1 | 13.2 | 72.3 | 0.34 |
| crm_campaign_member | grouped | 40.5 | 17.2 | 68.0 | 0.43 |
| crm_article_feedback | count | 48.7 | 21.4 | 57.3 | 0.44 |
| crm_article_feedback | grouped | 41.5 | 19.2 | 53.8 | 0.46 |
| **total, 16 widgets** | | **755.4** | **568.4** | **1031.5** |
**0.75** |
Every cell answered the same counts. "Material" was judged as a widget
whose after/today median ratio exceeds 1.5 or whose total exceeds
today's by more than 20%; the two widgets above 1.0 (1.04, 1.18) sit
inside their own max spread (82.6 → max 138.2; 32.7 → max 52.3), the
total is 25% faster, and the control shows the gain is the subquery, not
the routing. No regression to report. PostgreSQL (same harness, after
the battery): total 1101.4 ms today → 510.0 ms after (ratio 0.46,
control engine+idlist 1432.1 ms), identical counts; per-widget
0.20–1.60. Two count widgets trip the per-widget threshold stated above:
crm_contact count 32.7 → 50.5 ms (1.54) and crm_quote count 33.9 → 54.4
ms (1.60), one-hop details of the private crm_account. Their grouped
twins are 1.19 and 0.38, and the absolute +18–20 ms sits inside the
box's own max spread (40.7 / 44.4 today vs 52.7 / 56.5 after). The
control cell shows the cost is the engine face's routing on a PG COUNT,
which the leaf recovers most of. This is reported as the ruling asks;
the materiality call is put to the maintainer (#24932).
3. **Turso remote nested leaf** — landed with the switch; the
conformance row measures it on both faces (27 passed), and the ablation
below shows the remote face refusing without the one line.
4. The cloud seat's staging read of a two-level `controlled_by_parent`
chain at its pin move: not this PR's; named here as owed.
## Pins (every one on the real stack unless named as a double)
`packages/plugins/plugin-security/src/controlled-by-parent-leaf-switch.test.ts`
— ObjectQL over better-sqlite3 (and over live PostgreSQL when
`OS_TEST_POSTGRES_URL` names one; both cells ran here), the real
SecurityPlugin and SharingService, member contexts and the
OAuth-assembled delegated context:
- every caller shape keeps its row set — restricted (ownership + a
share, both identities), unrestricted (`viewAllRecords`: every contact
whose account exists, never the null or dangling pointer), child-only
(`canReadObject(master)` false ⇒ no row, count 0), delegated (the
ADR-0090 D10 intersection), the two-level chain (line ⊂ quote ⊂ account,
for every caller), a system control;
- statements per request: find = 1 statement reading the detail with the
master inside its subquery and find + count = 2, for the plain,
unrestricted and two-level shapes; zero statements read the master on
its own; a delegated find is one statement carrying the master subquery
twice; the two-level chain nests the subqueries;
- past the dialect's variable limit: 33,000 masters (better-sqlite3
binds 32,766) — the detail answers 200 with exactly `bc1, bc2`, count 2,
one statement binding the owner and no master id;
- a metadata cycle and a chain past the depth bound answer zero rows,
200, reported (the log names the cycle and the bound), with no subquery
statement; the cut is the engine's: eight derived objects compose into
one statement with eight nested subqueries and serve the row, the ninth
is the empty set;
- ADR-0139 D1: a bulk update of the contacts by a caller lands, touching
exactly the rows under the accounts it may read and edit;
- the scope `getReadFilter` answers carries the branded leaf — one for a
direct caller, one per leg for a delegated one, the leaf over ITS master
for a derived master, and still the leaf for a caller the master object
does not admit (the engine answers it `$in: []`).
`readable-ids-nested-compose.test.ts` (reworked) — the stand-in wrapper
that spelled the switch is retired; the switch's path and a consumer's
leaf serve the same rows, one statement each, no pre-scan of the parent
or the grandparent at either level; `getReadFilter(PARENT)` carries the
leaf over GRAND, one per leg.
`controlled-by-parent-chain.test.ts`,
`controlled-by-parent-sharing.test.ts`, `check-only-write-scope.test.ts`
(engine doubles) — evaluate the injected filter through
`readable-ids-in-memory.testkit.ts`, the engine's five composition steps
over the double's rows with the real service answering the master; their
row-set pins stand unchanged except the two named under Deviations, and
the two master-scan pins flip to "the derivation does no store work; the
grant table is read when the master's scope is composed".
Ten real-stack rigs gain `wireEngineSecuritySource(engine, () =>
plugin)` (`engine-security-source.testkit.ts`), the slot
`ObjectQLPlugin.init` fills on a kernel: without it an engine composes
every leaf `$in: []`, and those suites' detail reads and by-id write
probes saw no row (89 reds on the first run, all one cause).
`packages/services/service-analytics/src/__tests__/controlled-by-parent-leaf-three-faces.test.ts`
— a leaf-carrying scope on the three faces: the engine-bound face
composes it and answers the find path's rows (the security service is
asked `canReadObject` / `getReadFilter` of the master), and composes
with the caller's own `where`; the native face declines in `canHandle`
(direct context and the door's `readScopedObjects`) and through the
service the raw-SQL seam is never called; a scope with no leaf is still
served natively (the decline is content-based); the echo refuses in the
withheld `READ_SCOPE_COMPILE_FAILED` / 500 envelope before any
statement, the compiler naming the leaf and the object for the log; an
authored look-alike (`{ "$in": { "object": … } }`) keeps
`INVALID_FILTER` / 400 on every face with or without the leaf-carrying
scope; the deny sentinel keeps serving nothing on the engine face and is
still served natively.
`packages/spec/src/data/readable-ids-conformance.ts` — the nested row
runs in every driver suite: driver-sql (sqlite and live PostgreSQL),
driver-turso (local and remote), driver-memory, driver-sqlite-wasm,
driver-mongodb (refusing), and spec's own.
## Ablations (negative pins put back to the old behaviour, red,
restored, restore proven)
`scripts/pm`-free legs, each: `git restore --source=BASE -- FILES` with
the blob hash proven equal to BASE's, the pins run, `git checkout HEAD
-- FILES` with the blob proven equal to HEAD's and `git diff HEAD` empty
for the leg's files; a trap restores on any exit. BASE `72a302a9d`, HEAD
`2b988305d`.
- **security-plugin.ts at BASE** (the id-list derivation; on disk
`ReadableIds.of(rel.master)` count 1 → 0): 30 pins red — on the SQLite
and the PostgreSQL cells: every statements-per-request pin (the find
reads the master on its own), the 33,000-master read (`DATABASE_ERROR` /
500, `too many SQL variables` on SQLite), the one-statement pins, the
leaf-shape pins (`getReadFilter` answers bound ids), the engine's-cut
pin; in the reworked suites the "no store work", "the scope carries the
leaf", nested-compose "one statement on both paths" and the
delegated-row pins. Restored: blob == HEAD, tree clean.
- **the four analytics files at BASE** (admit count 2 → 0): 3 pins red —
the engine-bound face refuses the leaf (`READ_SCOPE_COMPILE_FAILED`),
`canHandle` answers true and the raw seam is called, the echo's compiler
names no leaf. Restored.
- **turso-driver.ts at BASE** (nested-resolve count 1 → 0): 1 pin red —
the nested conformance row refuses on the remote face. Restored.
## Tests and gates
All under `scripts/pm/os-verify-lock.sh` (shared box; the lock's VERDICT
lines are in the dev report). On head `2b988305d`:
`@objectstack/plugin-security` full suite 208 files / 4275 tests passed,
45 skipped; `@objectstack/service-analytics` 184 files / 4505 passed,
262 skipped; typechecks of plugin-security, service-analytics and
driver-turso exit 0; readable-ids conformance: driver-sql sqlite 18
passed / 2 skipped and live PostgreSQL 36 passed / 1 skipped,
driver-turso 27, driver-memory 15, driver-sqlite-wasm 11, driver-mongodb
13 (refusing), spec 48; the dependency closure built (22 tasks). Gates:
`dispatch-gates --commands` derived 102 families on `2b988305d`; 100 ran
exit 0 and 2 are NOT MEASURED locally (`check:dual-build-cjs-loads`,
`check:i18n`: PREREQUISITE NOT MET, both read every package's `dist/`;
CI's `Lint & Repo Gates` runs them); `--ran` reconciliation: 102
accounted, 0 unrun. `check:nul-bytes` OK. The full dogfood battery is
owed under the lock and reported in the dev report.
Final head `35c4c3951` (one test-only commit after the PR opened at
`2b988305d`):
- CI Test Core shard 5 reddened on `packages/core`'s repo pin
`row-serving-door-exposure`, which reads any non-test source that calls
`.canReadObject(` as a row-serving door, and found
`readable-ids-in-memory.testkit.ts`. Reproduced locally (`pnpm --filter
@objectstack/core run test:repo`: 1 failed / 81 passed).
- Fixed by making the composer take the engine's two halves as injected
predicates (`readAdmission`, `readScope`, ADR-0139 D2 steps 1 and 5),
wired by each suite, so the testkit names no service member. On
`35c4c3951`, `test:repo` is 6 files / 82 passed and the three rewired
suites 3 files / 80 passed. CI on `35c4c3951`: 35 success, 7 skipped,
every Test Core shard green.
- Gate union rerun on `35c4c3951`: 102 derived, 102 run exit 0, 0 NOT
MEASURED (the two PREREQUISITE-NOT-MET families of the first run passed
once every dist was built).
- Dogfood battery on `35c4c3951` under the lock: `Test Files 287 passed
| 3 skipped`, `Tests 2395 passed | 18 skipped`, `VERDICT command-exit 0`
(held 47m47s).
## Acceptance notes
- `packages/spec/src/data/readable-ids.ts:50-53` ("The engine's filter
seam is the one caller that passes it") and
`packages/objectql/src/filter-comparand-shape.ts:109` ("This is the one
caller that passes `admitReadableIds`") say the sentence this PR
corrects in `filter-comparand-shape.ts`'s docblock; both are outside the
claim's bound (the first is a second spec file, the second is objectql).
Prose only; carrier: the seat.
- ADR-0139 D6's clause "§2's mechanism (b) for `controlled_by_parent`
stands, set-size ceiling included" is false since this PR; the dated
parenthetical added beside it points at ADR-0055's amendment rather than
rewriting the accepted sentence. Tier H; carrier: the seat.
- The cloud seat's staging read of a two-level chain on Turso remote
(measurement condition 4) is owed at its pin move; the remote-face fix
and its conformance row are in this PR.
- The `/analytics/sql` echo refuses for `controlled_by_parent` objects
(Deviation 1); if the maintainer wants the echo to render for them, that
is the raw-SQL compiler learning the engine's composition (ruling's
option B for Q2), a separate card.
- `readable-ids-in-memory.testkit.ts` is a test double of the engine's
five composition steps for the fake-engine suites; parity with the
engine's own composition is pinned on the real stack in this package and
in objectql's suites. If a later change to the engine's composition is
meant to reach those suites, the testkit must move with it.
## 维护者速读(草稿)
**改了什么:** 主从对象(`controlled_by_parent`)的读取范围不再预先把「能读的主记录 id 列表」查出来再绑进
SQL,而是交给引擎用子查询(ADR-0139 的 `ReadableIds` 叶子)在一条语句里完成;分析报表的原生 SQL
通道遇到这种范围主动让给引擎通道,`/analytics/sql` 试运行回显对主从对象改为响亮拒绝;Turso
远程端补上两级链;ADR-0055 与 ADR-0139 按裁决逐句更正。
**为什么改:** HotCRM 规模下主记录超过约 3.3 万条时,子对象读取直接 500(SQL
变量数超限),且每次请求扫两遍主表。切换后一条语句、不随主表增长,实测 3.3 万主记录返回 200 且行集正确;16 个仪表盘小组件在
SQLite 上总耗时为切换前的 75%。
**风险与代价(含回滚):**
行集实测不变(普通、受限、全部可见、两级链、空外键、悬空外键、无主对象读权限);一处代理读取的行集按已裁定的「子对象跟随主对象的代理读取」变宽(上文
Deviation 2,该主记录本来就对该调用者可见),请你拍板是否接受;唯一可见变化是试运行回显改为拒绝。回滚是一次
revert;#25144 的启动期拒绝独立成立。
**席位意见:** (留空)
**你要做的:** 本 PR 触及 `docs/adr/**`(Tier H),需要你的批准后由席位落地;并请对 Deviation 2 表态。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UnHcTADjeg3VwxF21QHj7W)_
---------
Co-authored-by: os-bill <bill@objectstack.ai>
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8010b64 commit 0b8bce4
29 files changed
Lines changed: 1788 additions & 359 deletions
File tree
- .changeset
- docs/adr
- packages
- drivers/driver-turso/src
- plugins/plugin-security/src
- services/service-analytics/src
- __tests__
- strategies
- spec/src/data
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
72 | 73 | | |
73 | 74 | | |
74 | 75 | | |
| 76 | + | |
75 | 77 | | |
| 78 | + | |
76 | 79 | | |
77 | 80 | | |
78 | 81 | | |
| |||
118 | 121 | | |
119 | 122 | | |
120 | 123 | | |
| 124 | + | |
121 | 125 | | |
122 | 126 | | |
123 | 127 | | |
| 128 | + | |
124 | 129 | | |
125 | 130 | | |
126 | 131 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
241 | 241 | | |
242 | 242 | | |
243 | 243 | | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
244 | 259 | | |
245 | 260 | | |
246 | 261 | | |
| |||
249 | 264 | | |
250 | 265 | | |
251 | 266 | | |
252 | | - | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
253 | 270 | | |
254 | 271 | | |
255 | 272 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3388 | 3388 | | |
3389 | 3389 | | |
3390 | 3390 | | |
| 3391 | + | |
| 3392 | + | |
| 3393 | + | |
| 3394 | + | |
| 3395 | + | |
| 3396 | + | |
| 3397 | + | |
| 3398 | + | |
| 3399 | + | |
| 3400 | + | |
3391 | 3401 | | |
3392 | 3402 | | |
3393 | 3403 | | |
3394 | 3404 | | |
3395 | 3405 | | |
3396 | 3406 | | |
3397 | 3407 | | |
3398 | | - | |
| 3408 | + | |
3399 | 3409 | | |
3400 | 3410 | | |
3401 | 3411 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
63 | 63 | | |
64 | 64 | | |
65 | 65 | | |
| 66 | + | |
66 | 67 | | |
67 | 68 | | |
68 | 69 | | |
| |||
446 | 447 | | |
447 | 448 | | |
448 | 449 | | |
449 | | - | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
450 | 459 | | |
451 | 460 | | |
452 | 461 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
70 | 71 | | |
71 | 72 | | |
72 | 73 | | |
| |||
281 | 282 | | |
282 | 283 | | |
283 | 284 | | |
| 285 | + | |
| 286 | + | |
284 | 287 | | |
285 | 288 | | |
286 | 289 | | |
| |||
0 commit comments