Repository navigation
Commit 57c2b73
Part of #20020. This PR covers the four `driver-sql` refusal doors and
their twins in the Turso remote transport. The three engine doors the
card also names are not changed: measured, they see no provenance mark
on any host except the analytics face, so applying the rule there would
hide every CRUD caller's own diagnostic. That needs a decision; see "The
engine half" below.
Clause-②: no
## What changed
A read scope is the RLS, sharing or tenant predicate that
`plugin-security` (ordinary reads) and `service-analytics`
(`ObjectQLStrategy.withReadScope`) AND into the caller's `where`. Both
merges mark the scope `'policy'` and the caller's own predicate
`'author'` (#8220,
`packages/spec/src/data/filter-subtree-provenance.ts`). The contract for
`'policy'`: "A refusal raised from inside it keeps the #7929 redaction:
identity (`INVALID_FILTER` / 400) and capability statement on the wire,
operands in the server log." And the fail direction: "Unmarked or
ambiguous ⇒ withheld."
Four `SqlDriver` refusal doors did not read the mark. They now go
through the seam the cross-field (#7929) and target-field (#8197)
refusals already use: `withheldFilterError`, resolved in `applyFilters`
/ `withWithheldFilterLog` with `resolveFilterSubtreeProvenance`.
- **Retired or unknown operator** (the emitter's `default:` arm):
`retiredFilterOperatorError` and the new
`unsupportedFilterOperatorError` hand over the field's operator map as
the node.
- **Non-boolean `$null` / `$exists`** (`classifyFilterKey`): the field's
operator map is the node.
- **`$and` / `$or` whose operand is not a list**
(`assertFilterNodeList`): the operand is the node when it is an object.
A primitive operand is judged by the node that carries the key. The
shared walk in `@objectstack/spec` hands that hook only the operand, the
key and the path, so `sqlFilterVerdictHooks` rebuilds the node from the
path. It records every node the walk announces through `assertNode`,
keyed by paths made only of combinator keys and list indices. The spec
hook signature is unchanged.
- **A WHERE column the table does not have**
(`unresolvableFilterColumnRefusal`, raised after the statement ran): the
dialect reports only a column name. `unresolvableColumnProvenance` finds
every node in the query's `where` whose key (as written, through
`mapSortField`, or through an external column map) or `{ $field }` names
that column. The name reaches the wire only when there is at least one
such node and every one resolves `'author'`. Otherwise the refusal uses
the unnamed wording the method already had for a dialect message it
could not parse. The four callers (`find`, `count`, `aggregate`,
`distinct`) now pass their `where`.
Code and status are unchanged on every path: `INVALID_FILTER` / 400. The
set of refused filters is unchanged. An `'author'`-marked subtree gets
the text the door answered before. The source templates are the same
literals, now passed as the diagnostic and the author text.
**Turso remote transport (the claim's conditional arm).**
`RemoteTransport` has its own copies of three of these doors, and they
did not read the mark either:
- `unsupportedOperator`, for a retired or unknown operator. The same
method's arm for a non-operator key in an operator map names the target
the same way and is converted with it.
- `nonListCombinator`.
- `nonBooleanNullComparand` / `nonBooleanExistsComparand`.
They now go through `withheldInvalidFilterError`, and the new
`withheldRefusal` helper sends the diagnostic to the sink. The transport
has no refusal for an unresolvable WHERE column: `find` answers `[]`.
That door is not touched here. `pushLike` is not touched.
## Measured: what each 400 body carried, before → after
Probe (scratch, not committed): a real `ObjectQL` over
`SqliteWasmDriver` (a `SqlDriver` subclass). The analytics face went
through the real `POST /api/v1/analytics/query` route:
`AnalyticsService` in ObjectQL-only mode, with `getReadScope` returning
the scope. The CRUD face went through `ql.find` under a middleware that
copies `plugin-security`'s merge and marking code. Before = base
`26550c660`. After = this branch, with `driver-sql` dist rebuilt. The
scope used distinctive names (`policy_stage`, `PSECRET…`), and a row
counts as a leak when one of them appeared in the response message.
| refusal class | door | analytics face, policy scope | CRUD face,
policy scope |
|---|---|---|---|
| column the table does not have | driver-sql | field → nothing | field
→ nothing |
| retired operator (`$regex`) | driver-sql | field, operator → nothing |
field, operator → nothing |
| unknown operator | driver-sql | field, operator → nothing | field,
operator → nothing |
| combinator, object operand | driver-sql | field, comparand → nothing |
field, comparand → nothing |
| combinator, primitive operand | driver-sql | comparand → nothing |
comparand → nothing |
| non-boolean `$null` | driver-sql | field, comparand → nothing | field,
comparand → nothing |
| non-boolean `$exists` | driver-sql | field, comparand → nothing |
field, comparand → nothing |
| text operator on a number field | engine | field → field (not changed)
| no refusal: the door runs before the merge, and the scope matched 0
rows |
| unreadable temporal comparand | engine | field, comparand → same (not
changed) | no refusal, 0 rows |
| unknown `{placeholder}` | `@objectstack/core` `resolveFilterTokens`
via engine `resolveWhereTokens` | token → token (not changed) | no
refusal: compared as a literal, 0 rows |
- Every withheld row kept `INVALID_FILTER` / 400. The server log carried
the field and the comparand.
- On the CRUD face, a caller's own filter vouched `'author'` by the
merge returned the same message before and after for all ten classes
(first 220 characters compared). On the analytics face, the caller's own
`where` answers were unchanged.
- With the real `SecurityPlugin`, a CEL rule `policy_unsynced ==
'PSECRET1u'` on a field that is declared but has no column: before, a
400 naming `policy_unsynced`; after, the unnamed wording. A CEL rule on
an undeclared field answered 0 rows with no refusal, before and after.
## Host × mark: which marks a refusal door sees
How each column was read:
- **Driver columns:** measured with the real `SecurityPlugin` (a CEL
`using` rule) over a real `ObjectQL` + `SqliteWasmDriver`, reading the
`where` the driver received.
- **Engine-doors column:** measured with a recording middleware
registered first on the simulated-merge engine. The doors run before any
middleware, and the real-`SecurityPlugin` rows were not recorded
separately at that stage.
- **Analytics row:** read from `withReadScope`, and consistent with the
after-probe: the caller's arm discloses and the scope withholds.
| host / path | engine doors see | driver: caller's `where` | driver:
injected scope |
|---|---|---|---|
| no security plugin | no mark | unmarked | none |
| `plugin-security`, member, plain `where` | no mark | `'author'` |
`'policy'` |
| `plugin-security`, member, no `where` | no mark | none | `'policy'` |
| `plugin-security`, system context | no mark | unmarked | none |
| `plugin-security`, anonymous | no mark | unmarked | none |
| `plugin-security`, `where` holding `{current_user_id}` | no mark |
unmarked (the engine rewrote it before the merge) | `'policy'` |
| `plugin-security`, FilterArray `where` | no mark | `'author'` (the
lowered copy is vouched) | `'policy'` |
| analytics `withReadScope` | caller arm `'author'`, scope `'policy'` |
`'author'` | `'policy'` |
| Turso REMOTE mode through `TursoDriver` | not measured | unmarked:
`toRemoteFilter` rebuilds every node, measured by an `'author'`-marked
`where` getting the withheld text | unmarked |
`plugin-sharing` was not measured end to end.
**What an unmarked caller loses at the four driver doors:** its own
field, operator, comparand and path. It gets the withheld wording with
the same code and status. Measured cases: no security plugin, system
context, anonymous, a `where` holding a placeholder token, and Turso
remote mode. A member's plain `where` under `plugin-security` keeps the
full text.
## The engine half: not in this PR
`text-operator-declared-type-door.ts`, `temporal-comparand-door.ts` and
the unknown-placeholder refusal (`UnknownFilterTokenError` from
`packages/core/src/utils/filter-tokens.ts`, called by the engine's
`resolveWhereTokens`) all run on the caller's `where` before the
middleware chain. The first column of the host table is the measurement:
on every host except the analytics face, the `where` these doors see
carries no mark at all. Under "unmarked ⇒ withheld", every REST, SDK and
flow caller would lose what these three doors name for its OWN filter:
- the field and its declared type (the #15661 ruling asks for both);
- the comparand and the remedy (#8690);
- the placeholder and its "did you mean" suggestion.
On the CRUD face a read scope never reaches these doors (measured
above), so they disclose policy content only on the analytics face.
Options and a recommendation are in the `os-dev-report` on #20020. The
seat took option C in claim amendment 5823437510: the analytics-face
pre-judge belongs to the services lane (pointer 5823452098 on #19995),
and this PR does not change these three doors.
## Compile surfaces (per `references/compile-surfaces.md`)
| # | face | this PR |
|:--|:--|:--|
| 1 | `driver-sql` `applyFilterCondition` (and `driver-sqlite-wasm` /
Turso local, by inheritance) | CHANGED: four doors |
| 2 | Turso `RemoteTransport` `buildWhereSQL` | CHANGED: three door
classes. `pushLike` is untouched, and `unsupportedOperator`'s `$between`
arm still names its target, which cannot be reached from a policy
through `TursoDriver` |
| 3 | service-analytics `compileScopedFilterToSql` | not touched
(outside the claim); its refusals are the withheld
`READ_SCOPE_COMPILE_FAILED` / 500 |
| 4 | service-analytics `lowerAnalyticsWhere` | not touched (outside the
claim); its non-boolean `$null` / `$exists` is #20040 |
| 5 | `formula` `matchesFilterCondition` | not touched, not measured |
| half | objectql `applyHaving` / `matchesHaving` | not touched, not
measured |
| — | `driver-memory` / `driver-mongodb` | not touched. They do not read
the mark (grep); disclosure there is not measured |
## Tests
- **New:**
`packages/drivers/driver-sql/src/sql-driver-refusal-door-provenance.test.ts`
(34 cases) and
`packages/drivers/driver-turso/src/remote-transport-refusal-door-provenance.test.ts`
(29 cases). Per door, they cover:
- policy-marked: same code and status, and no secret in the message; the
secrets are in the log or sink.
- author-marked: the full text, equal to what the withheld case logged.
- unmarked: byte-identical to policy.
- both arm orders of a merged `$and`.
- the column door's lookup by name, and the combinator door's node
lookup under nesting and `$not`.
- **Existing pins marked `'author'`:** these pins read a door's
author-facing text on an unmarked `where`, so their `where` (or a
shallow copy of a shared case constant) is now marked `'author'`, as a
merge boundary marks a caller's own predicate. `driver-sql`: 11541,
17857, backend-fault-envelope, boolean-identity, filter-no-silent-drop,
filter-refusal-envelope, icontains-and-retired-operators, null-operators
(two assertions moved to code and status), out-of-contract-filter-input,
text-case-conformance, unresolvable-where-column-refusal.
`driver-turso`: bare-date-routing, comparand-refusal,
node-operator-refusal, not-operator, null-comparand-refusal,
text-predicates. `driver-sqlite-wasm`: icontains-and-retired-operators,
which is outside the claim's file list, because `SqliteWasmDriver`
inherits these doors.
- **Rewritten:** `sql-driver-target-field-provenance.test.ts` "an
UNDECLARED operator still names its operator and field". It stood so
that this widening would be a deliberate act, and it now pins both
halves.
- **Turso remote mode:** `remote-transport-text-predicates.test.ts`
(`$regex` through `TursoDriver` remote) and
`turso-local-remote-text-parity.test.ts` now assert the withheld wording
on the remote face, because `toRemoteFilter` drops the mark. A withheld
remote answer must name none of the case's mentions.
Suites at head `3bf7a68ce` (after merging `main`, which brought #20019):
- `driver-sql`: 183 files passed, 11 skipped; 2771 tests passed, 170
skipped.
- `driver-turso`: 65 files, 1473 tests passed.
- `driver-sqlite-wasm`: 31 files, 582 tests passed.
Before that merge, at `2e36f756d` (the same code as `8e3c49fbc`, which
only adds the changeset):
- `service-analytics`: 118 files, 2560 tests passed.
- `plugin-security`: 130 files, 2561 tests passed.
- `plugin-sharing`: 37 files, 913 tests passed.
`typecheck` exits 0 for all three driver packages at `3bf7a68ce`. The
two new pin files and the changed `driver-sqlite-wasm` file are in their
packages' `tsc` programs (`--listFiles`, 1 each).
**Consumer pin changed (patch round 1, `244f5321e`):
`packages/plugins/plugin-auth/src/auth-contains-filter.test.ts`, face 3
(#5893).** It read the retired-operator door's full text through an
unmarked `engine.find` on a real `SqlDriver`, and it went red at
`b6f40dc95` (CI Test Core 6/6). It is re-pinned to the production
behaviour:
- **Unmarked leg**, renamed "… and withholds the operator from an
unmarked caller": `INVALID_FILTER` / 400, the message contains "is
RETIRED" and names neither `$regex` nor `$icontains`, and the spied
driver log names both.
- **New `'author'`-marked leg:** the full text, `$regex` and the
`$icontains` prescription, is kept for a vouched caller.
- **The file's header note** is restated to match. No plugin-auth source
changed.
A repo-wide sweep found no other consumer pin on these four doors'
wording. The old-wording grep over `packages/`, `examples/` and `apps/`
tests gave 101 files, 19 of which reference a SQL-family driver, each
read at its hit line. A trigger-token grep over the 311 test files
outside the three drivers that use a real SQL driver found nothing more.
Patch-round suites (`244f5321e`, the same code as `5fb4b25b1`, which
only edits the changeset), each with its own exit code 0:
- `plugin-auth`: 114 files, 2440 tests passed.
- `service-storage`: 40 files, 627 tests passed.
- `cli` unit project: 224 files; 3129 tests passed, 29 skipped. Two
published-subpath pin files needed `cli` built first, and passed after
the build.
- `cloud-connection` 29/374, `plugin-dev` 8/80, `connector-mcp` 3/23 and
`hono` 5/122.
- At `5fb4b25b1`: `driver-sql`, `driver-turso` and `driver-sqlite-wasm`
give the same counts as above.
These are the rest of CI Test Core shard 6/6, which may not have run
once plugin-auth failed, because the step runs turbo without
`--continue`. `example-embed-objectql` is NOT MEASURED: it depends only
on driver-memory, objectql and spec.
**Changeset (patch round 1, `5fb4b25b1`):** the frontmatter gains
`'@objectstack/driver-sqlite-wasm': patch`, and one sentence states the
inheritance: "`SqliteWasmDriver` (`@objectstack/driver-sqlite-wasm`) and
`TursoDriver` in local mode extend `SqlDriver`, so they inherit this
change from it: the same four doors answer the same way there."
**Ablations** (committed state, restored and proved by blob hash plus an
empty `git diff HEAD`):
1. `sql-driver.ts` reverted to base: the new driver-sql pin file had 32
of 34 failing. The 2 that passed are author-disclosure cases that base
satisfies by disclosing to everyone.
2. The combinator door's node lookup replaced with `undefined` (through
`scripts/ablation-replace.mjs`, anchor 1 → 0): 4 failing, exactly the
primitive-operand author cases.
3. `remote-transport.ts` reverted to base: 29 of 29 failing.
## Gates
- `dispatch-gates --commands` derived 62 families from this diff at
`3bf7a68ce`. All 62 exited 0. `check:dual-build-cjs-loads` and
`check:type-check-debt` first exited 3 (PREREQUISITE NOT MET) and exited
0 after `turbo run build --filter=./packages/* --filter=./packages/*/*`.
`--ran` reconciliation: 62 derived, 62 run, 0 NOT-MEASURED, derived from
recorded exit codes.
- After the last changeset edit (`b6f40dc95`), the changeset and prose
gates were re-run and exited 0: ADR-0087 registration, no-major,
empty-changeset, deadline census, objectui-changeset, nul-bytes,
doc-authoring.
- `check-issue-citations --base 9d81af7`: exit 0, 28 citations
resolve.
- `check:driver-conformance`: "50 covered cell(s), 0 in the DEBT ledger,
0 exempt" before and after.
- ESLint on the 24 changed `.ts` files: 24 files, 0 errors, 0 warnings.
`eslint.config.mjs` sets no `parserOptions.project`, so this diff cannot
change a verdict on an untouched file. The repo-wide run is CI's.
## Acceptance notes
- **Eight more `driver-sql` doors relay policy content on the CRUD
face.** Measured with the same probe and a policy-marked scope, at head:
- an empty `$icontains`;
- a non-string `$like`;
- a `$like` pattern with a trailing escape;
- an object `$contains` comparand;
- an object `$in` member;
- an `undefined` comparand;
- a non-node element of `$or`;
- an undeclared node combinator.
Each 400 named the field or the comparand. They are outside this card's
seven and the claim's file surface, so they are reported to the seat as
a finding rather than fixed here. The seat filed them as #20039.
- **Analytics caller `where`.** On `POST /api/v1/analytics/query`, a
caller's `{ f: { $null: 'x' } }` or `{ f: { $exists: 'x' } }` answered
200. The driver received `{ f: { $ne: null } }`. Reported to the seat,
which filed it as #20040 (services lane).
- **Stale prose, not pins:** two test comments still say `driver-sql`
refuses `$regex` "by name", which is now true only for an
`'author'`-marked `where`: plugin-auth
`auth-where-operator-coverage.test.ts` (header note) and runtime
`undeclared-field-write-driver-split.integration.test.ts`. Neither
asserts that wording.
- **Turso remote mode.** `TursoDriver.toRemoteFilter` rebuilds every
filter node, so no mark reaches `RemoteTransport` in remote mode, and
every mark-reading refusal there withholds for every caller. This fails
closed and discloses nothing.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7465eeb commit 57c2b73
26 files changed
Lines changed: 1096 additions & 126 deletions
File tree
- .changeset
- packages
- drivers
- driver-sqlite-wasm/src
- driver-sql/src
- driver-turso/src
- plugins/plugin-auth/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 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
62 | 62 | | |
63 | 63 | | |
64 | 64 | | |
| 65 | + | |
65 | 66 | | |
66 | 67 | | |
67 | 68 | | |
| |||
140 | 141 | | |
141 | 142 | | |
142 | 143 | | |
143 | | - | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
144 | 153 | | |
145 | 154 | | |
146 | 155 | | |
| |||
383 | 392 | | |
384 | 393 | | |
385 | 394 | | |
386 | | - | |
| 395 | + | |
| 396 | + | |
387 | 397 | | |
388 | 398 | | |
389 | 399 | | |
| |||
413 | 423 | | |
414 | 424 | | |
415 | 425 | | |
416 | | - | |
| 426 | + | |
417 | 427 | | |
418 | 428 | | |
419 | 429 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| 86 | + | |
86 | 87 | | |
87 | 88 | | |
88 | 89 | | |
| |||
182 | 183 | | |
183 | 184 | | |
184 | 185 | | |
185 | | - | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
186 | 193 | | |
187 | 194 | | |
188 | 195 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
86 | 86 | | |
87 | 87 | | |
88 | 88 | | |
| 89 | + | |
89 | 90 | | |
90 | 91 | | |
91 | 92 | | |
| |||
110 | 111 | | |
111 | 112 | | |
112 | 113 | | |
113 | | - | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
114 | 121 | | |
115 | 122 | | |
116 | 123 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| 57 | + | |
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
| |||
199 | 200 | | |
200 | 201 | | |
201 | 202 | | |
202 | | - | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
203 | 208 | | |
204 | 209 | | |
205 | 210 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | | - | |
| 45 | + | |
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| |||
134 | 134 | | |
135 | 135 | | |
136 | 136 | | |
137 | | - | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
138 | 141 | | |
139 | 142 | | |
140 | 143 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
34 | 35 | | |
35 | 36 | | |
36 | 37 | | |
| |||
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
77 | | - | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
78 | 85 | | |
79 | 86 | | |
80 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
79 | 79 | | |
80 | 80 | | |
81 | 81 | | |
82 | | - | |
| 82 | + | |
83 | 83 | | |
84 | 84 | | |
85 | 85 | | |
| |||
141 | 141 | | |
142 | 142 | | |
143 | 143 | | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
144 | 154 | | |
145 | 155 | | |
146 | 156 | | |
| |||
232 | 242 | | |
233 | 243 | | |
234 | 244 | | |
235 | | - | |
| 245 | + | |
236 | 246 | | |
237 | 247 | | |
238 | 248 | | |
| |||
341 | 351 | | |
342 | 352 | | |
343 | 353 | | |
344 | | - | |
| 354 | + | |
345 | 355 | | |
346 | 356 | | |
347 | 357 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
78 | 78 | | |
79 | 79 | | |
80 | 80 | | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
81 | 85 | | |
82 | | - | |
| 86 | + | |
83 | 87 | | |
84 | 88 | | |
85 | 89 | | |
| |||
134 | 138 | | |
135 | 139 | | |
136 | 140 | | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
137 | 144 | | |
138 | | - | |
| 145 | + | |
139 | 146 | | |
140 | 147 | | |
141 | 148 | | |
| |||
161 | 168 | | |
162 | 169 | | |
163 | 170 | | |
| 171 | + | |
164 | 172 | | |
165 | 173 | | |
166 | | - | |
| 174 | + | |
167 | 175 | | |
168 | 176 | | |
169 | 177 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| 61 | + | |
61 | 62 | | |
62 | 63 | | |
63 | 64 | | |
| |||
185 | 186 | | |
186 | 187 | | |
187 | 188 | | |
188 | | - | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
189 | 194 | | |
190 | 195 | | |
191 | 196 | | |
| |||
253 | 258 | | |
254 | 259 | | |
255 | 260 | | |
256 | | - | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
257 | 266 | | |
258 | 267 | | |
259 | 268 | | |
| |||
0 commit comments