Repository navigation
Commit 49144fc
Fixes #20135
Clause-②: no
`apiOperations`, the operation set `/auth/me/permissions` (and
`ISecurityService.getEffectiveObjectPermissions`) attaches to each
entry, now offers exactly what the REST door serves this subject. It
used to disagree with the door in two places, both in the direction of
offering an operation the door refuses:
- **`enable.apiEnabled: false`.** The door answers `404
OBJECT_API_DISABLED` for every verb. The annotation ignored the switch:
it carried the object's whole closure, or no annotation at all (read by
a client as default-allow) when the subject's export stays allowed on an
otherwise unrestricted object.
- **The export slot** (seat pointer 5831905897).
`annotateEffectiveApiOperations` fell back to the MERGED `'*'` export
bit (`acc.allowExport ?? wildExport`). So a private object reached only
through a plain `'*': { allowExport: true }` was annotated `export`, and
so was an object the exporting set itself names without the grant. The
export door answers both `403 EXPORT_NOT_PERMITTED`.
The fix is in the producer, `annotateEffectiveApiOperations` in
`@objectstack/core`
(`packages/core/src/security/effective-object-permissions.ts`), as
triage directed: 「read the same resolver `enforceApiAccess` reads, so
the two cannot diverge」. The REST door, `packages/spec`
(`resolveEffectiveApiMethods`, `effectiveOperationsArray`,
`apiExposureDenialReason`), `checkObjectPermission`, the seed and the
folds are all untouched. No exported name or type changes.
## What changes
The annotation asks the door's own two questions, entry by entry:
- **The object half** is `canServeApiOperation`
(`@objectstack/spec/data`). It is the boolean face of
`apiExposureDenialReason`, the function `apiAccessDenialFromEnable`
(`enforceApiAccess`) turns into its 404 and 405. The served set is the
closure filtered through it. It judges `apiEnabled === false` first and
for every operation, so an API-disabled object is annotated `[]`. For
any other `enable` the filter is the identity, because the closure
already is what the door admits.
- **The export half** is `objectPermissionGrants(entry, 'allowExport')`:
read and `allowExport` on the entry itself. That is the export door's
conjunction, and `PermissionEvaluator`'s `export` branch documents it as
what the merged per-object entry answers. For the export bit, since
#20083 and #20134 the coverage passes put each set's `'*'` grant only on
the entries that set reaches, per posture, so the entry's `allowExport`
already is the per-set, per-posture answer and the merged-bit fallback
is gone. The read half of the conjunction is the entry's read bit, which
still carries the merged fold's read over-grant pre-registered for
#20136. It moved no cell of the parity column below.
Which entries carry an annotation keeps the #18931 / #18990 rule: an
unrestricted object whose every operation is still served, `export`
included, gets none. What moved is the evaluation of "still served": an
API-disabled object never qualifies.
## H4: `apiOperations: []`, and the entry stays
- **What the client reads.** Nothing in this repo's SDK reads
`apiOperations`. `git grep apiOperations -- packages/client
packages/client-react` finds 0 lines; `@objectstack/client` only
re-exports the `GetEffectivePermissionsResponse` type. The reader is
objectui, which is **UNMEASURED** here because the sibling repo is not
in this container. From the spec contract
(`EffectiveObjectPermissionSchema.apiOperations`: "absent =
default-allow") and the objectui code quoted on #18931 (`effectiveApiOps
? effectiveApiOps.includes('export') : true`), an array hides every
operation it does not list and an absent one hides nothing. `[]` is also
the shape a deny-all (`apiMethods: []`) object already carries, so the
client meets no new shape.
- **Why not drop the entry.** The entry carries the CRUD bits that
`current_user.can()` reads on the server, and it reads an absent entry
as "no grant". `apiEnabled` closes the API, not data access
(`enforceApiAccess`' docblock: "`apiEnabled` controls automatic API
exposure, not data access"). Dropping the entry would change a
server-side `can()` answer, which H5 rules out, and would contradict the
#18990 ruling that every reachable object gets an entry.
## Measurement
**Door semantics used below.** An operation is "served" when neither
door refuses it: the object door (`404 OBJECT_API_DISABLED` / `405
OBJECT_API_METHOD_NOT_ALLOWED`) and, for `export`, the export door (`403
EXPORT_NOT_PERMITTED`). The operations measured are the ones the REST
door gates by name: `get`, `list`, `create`, `update`, `delete`, `bulk`,
`import`, `export`. `bulk` counts as served when any of `createMany` /
`updateMany` / `deleteMany` passes the door. "Offered" means the entry's
`apiOperations` lists the operation, or the entry has no annotation
(default-allow).
### Real stack (H1, H2): the real `GET /auth/me/permissions` against
real REST requests
This used a throwaway dogfood probe that is **not committed**. It ran
`bootStack` with the real `SecurityPlugin`, `orgContext: true`, 61
registered objects, and one authored app. The app holds `pq_open`
(public), `pq_exposed` (`apiEnabled: true`), `pq_hidden` (`apiEnabled:
false`), `pq_hidden_subset` (`apiEnabled: false` + `apiMethods:
['get','list']`), `pq_subset` (`apiMethods: ['get','list']`),
`pq_private` (`access.default: 'private'`) and `pq_private_hidden`
(private + `apiEnabled: false`). There are 0 such `apiEnabled: false`
objects in `examples/`, so the reach is authored objects only. Every
subject × every registered object × the 10 door requests above went
through the real REST routes, with bodies that cannot mutate. Base is
`16c5a33fdd`; head is this branch.
| subject (resolved sets) | offered but refused, present entries: base →
head | served but hidden: base → head | no-entry cells (unchanged) |
route == member |
|---|---|---|---|---|
| seeded platform admin (`admin_full_access`,
`organization_admin_no_bypass`, `member_default`) | 16 → **0** | 0 → 0 |
0 | yes |
| `admin_full_access` + `member_default` + a plain `'*': { allowExport
}` | 21 → **0** | 0 → 0 | 0 | yes |
| `member_default` | 0 → 0 | 0 → 0 | 134 | yes |
| member + explicit read on the app objects | 16 → **0** | 0 → 0 | 101 |
yes |
| … + a plain `'*': { allowExport }` | 20 → **0** | 0 → 0 | 101 | yes |
| member + explicit read and export on the app objects | 19 → **0** | 0
→ 0 | 101 | yes |
| member + a plain `'*'` with every bit and export | 11 → **0** | 0 → 0
| 15 | yes |
| member + `'*': { viewAllRecords, allowExport }` | 19 → **0** | 0 → 0 |
0 | yes |
- **H1 holds.** At base, `pq_hidden` read `[get, list, create, update,
delete, upsert, bulk, aggregate, search, import]` for the platform
admin, and had no annotation at all for export-allowed subjects.
Meanwhile every REST verb on it answered `404 OBJECT_API_DISABLED`. At
head every API-disabled object reads `[]`.
- **H2 holds.** For `admin_full_access` beside a plain export-only
`'*'`, `sys_secret` read `[get, list, aggregate, search, export]` and
`GET /data/sys_secret/export` answered `403 EXPORT_NOT_PERMITTED`. The
same holds for the authored private `pq_private`, which had no
annotation (default-allow) and a 403 export. The member with explicit
read beside the plain export wildcard shows the same on `pq_private`.
- **Entry-level diff, base-equivalent leg → head, all 8 subjects** (the
base-equivalent leg is the ablation below; its door readings equal true
base in all 8 subjects): 0 entries added or removed, 0 `allow*` bits
changed, **0 operations added to any annotation**, 74 operations
removed, 11 annotations added (9 × `[]`, 2 × the closure minus `export`
on `pq_private`).
- **No-entry cells** are objects the subject's map carries no entry for
(e.g. `member_default` on the `sys_*` objects it cannot read). Whether
an entry exists is the seed's question, and this PR leaves it alone. See
the Acceptance notes.
### Unit fixture (H3): the committed parity column
`plugin-security`'s `get-effective-object-permissions.test.ts` table
covers PR #20145's 16 subjects plus 2 export-slot subjects. The 2 new
subjects are `admin_full_access` beside a plain export-only `'*'`, and
one set whose exporting `'*'` sits beside an explicit `crm_lead` entry
without the grant. Each subject is checked against every registered
object its map carries (the 11-object `REGISTERED` fixture) and every
door-gated operation. The oracle is `canServeApiOperation` for the
object half, the door's decision function, since plugin-security takes
no dependency on `@objectstack/rest`. For `export` it is the real
registered `svc.canExport`. The subjects also run through the existing
`can()` ↔ `checkObjectPermission` rows, green with `KNOWN_OVER_GRANT`
untouched.
| subject | offered but refused: base → head | served but hidden: base →
head |
|---|---|---|
| wall-less org admin · viewer · one set: plain `'*'` + narrower entry ·
platform admin · platform admin + wall-less · walled org admin · bare
modify-all · super-read + plain bits · one set: super-user `'*'` +
narrower entry | 7 → **0** each (`crm_hidden` × 7) | 0 → 0 |
| explicit entry beside a plain `'*'` · super-user `'*'` + export ·
super-read `'*'` + export · explicit entry beside a super-user `'*'` | 8
→ **0** each (`crm_hidden` × 8, `export` included) | 0 → 0 |
| **platform admin beside a plain export-only `'*'`** | 10 → **0**
(`crm_hidden` × 8, `crm_secret.export`, `sys_secret.export`) | 0 → 0 |
| **one set: exporting `'*'` + explicit `crm_lead` without the grant** |
9 → **0** (`crm_hidden` × 8, `crm_lead.export`) | 0 → 0 |
| member · export-only `'*'` beside a reader · a `'*'` granting nothing
| 0 → 0 | 0 → 0 |
Entry-level over these 18 subjects: 0 entries added or removed, 0
`allow*` bits changed, 0 operations added to any annotation, 92 removed,
7 annotations added. The core pins also hold an export grant without
read to no `export`, and read and export arriving from two different
sets to `export`.
## For `domain:cli` (the route) and `domain:services` (plugin-security):
cross-lane
No code in `plugin-hono-server` or `plugin-security` changes, only their
pins. The bytes `/auth/me/permissions` serves change for the affected
objects, and `getEffectiveObjectPermissions` returns the same map:
- an object declaring `enable.apiEnabled: false` reads `apiOperations:
[]` in every entry. That includes an unrestricted one whose export stays
allowed, which used to carry no annotation;
- `export` leaves the annotation where the export door refuses it: a
private object reached only through a plain wildcard export grant, an
object named without the grant by the set whose wildcard carries it, and
an entry granting export without read. A private, unrestricted object in
that position gains an annotation, its closure minus `export`.
Nothing else moves: no entry, no `allow*` bit, no added operation.
Shape, keys and route are unchanged, and so is every server decision.
`can()` reads the same bits.
## Clause-②
`no`, as the claim carries it. The served annotation narrows only toward
what the door already refuses (0 operations added, measured above), and
the door is untouched, so no request that succeeded now fails. No
exported name or type changes. `annotateEffectiveApiOperations` keeps
its signature. As an exported helper called standalone, it no longer
reads the map's `'*'`, a behaviour change named in the changeset. There
are 0 non-test callers in this repo: every hit of `git grep
annotateEffectiveApiOperations` outside tests is its definition, its
re-export, the composition and comments. `check-changeset-no-major
--base origin/main` exits 0 ("This diff introduces no `major` bump"),
and `check-adr-0087-registration --base origin/main` exits 0 ("1
non-breaking changeset(s) seen"). The changeset grades
`@objectstack/core` `patch`.
## Tests run, at head `f2904cd05b` (core `dist/` rebuilt from this
source)
Each suite ran under the verify lock, and each line quotes its `VERDICT
command-exit 0`:
- `pnpm --filter @objectstack/core test`: 53 files, 1353 tests passed.
`typecheck` exit 0, test layer included.
- `pnpm --filter @objectstack/plugin-hono-server test`: 27 files, 323
passed. `typecheck` exit 0.
- `pnpm --filter @objectstack/plugin-security test`: 135 files, 2718
passed. `typecheck` exit 0.
- Dogfood, 18 files, against the `dist/` closure built from this code
(`turbo run build --filter='@objectstack/dogfood^...'`, 64 tasks), in
two runs:
- 126 passed and 1 skipped: `organization-update-door`,
`me-apps-and-everyone-baseline`, `showcase-permission-projection`,
`showcase-permission-seeding`, `showcase-permission-zoo`,
`two-doors-permission`, `comments-permission-matrix`,
`attachments-permission-matrix`, `authz-conformance`;
- 135 passed: `showcase-private-owd`, `owner-anchor-and-bulk-writes`,
`showcase-crud-persona-matrix`, `showcase-client-liaison-fixtures`,
`showcase-fls-read-mask-strip`, `showcase-scope-depth`,
`showcase-scope-depth-write`, `showcase-scope-depth-fallback`,
`showcase-anonymous-deny-surfaces`.
- New and changed pins:
- core `effective-object-permissions.test.ts`: 7 new cases in a
`[#20135]` block;
- plugin-security `get-effective-object-permissions.test.ts`: 2
subjects, the `apiOperations` column (one case per subject) and a
reported-cases case;
- plugin-hono-server `current-user-endpoints-effective-objects.test.ts`:
a route byte-equality case over an API-disabled and a private object;
- plugin-hono-server `effective-api-operations.test.ts`: 4 API-disabled
cases. Four existing cases were rewritten: one is **inverted on
purpose**, since it pinned the merged-`'*'` fallback itself, and three
called annotate on a map that never went through the per-set passes.
Those three now go through `buildEffectiveObjectPermissions` and assert
the same outcomes.
## Ablation
The mutation restored the base reading in one block: the merged-`'*'`
export bit back, and the door filter off. A string marker kept the dist
reading exact.
- It went through `scripts/ablation-replace.mjs` (anchor 1 → 0, blob
`da81b319` → `33501e91`). Core's `dist/` was rebuilt, and
`ablation-dist-preflight` found the marker in 2 built files.
- **Observed direction: red.**
- core: 5 failed, 22 passed. The two right-direction controls stayed
green.
- plugin-hono-server: 6 failed, 27 passed, over its two pin files
together (`effective-api-operations.test.ts`, 29 cases, and
`current-user-endpoints-effective-objects.test.ts`, 4 cases).
- plugin-security: 16 failed, 36 passed. These are the 15 subject rows
with an API-disabled or export mismatch, and the reported-cases case.
The three subjects with no mismatch stayed green, as did every `can()`
row.
- The stack probe on the ablated build reproduced true base's door
readings in all 8 subjects.
- **Restore.** The blob equals HEAD, and `git diff HEAD` is empty (the
tool's own verdict). Core was rebuilt, and `ablation-dist-preflight
--absent` reports the marker absent from all 14 built files and a clean
tree. All three suites are green again: 27, 33 and 52 passed. A second
ablation leg, run to dump both legs' maps for the tables above, restored
the same way.
## Gates, at head `f2904cd05b`
- The list comes from `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack`: 64 commands, derived from 6 paths
against merge base `16c5a33fd`. I ran each one and recorded its exit
code. **64 of 64 exit 0.**
- One needed a second run. `pnpm check:dual-build-cjs-loads` first
answered `PREREQUISITE NOT MET` (exit 3, not measured) because 8
packages had no `dist/`. After `turbo run build` of those 8, all cache
hits, it answered: "105 published require entry point(s) across 67
package(s) load; 660 emitted CommonJS file(s) parse".
- `dispatch-gates --ran`: "64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN".
- `node scripts/check-issue-citations.mjs --base 16c5a33`: exit 0, 3
citations resolve.
- **Lint, as a proven narrowing.** `eslint --no-inline-config --format
json` over the 5 changed TypeScript files reports 5 files, 0 errors and
0 warnings. `eslint --print-config` resolves a config for each of them,
and eslint ignores the changeset. `eslint.config.mjs` never enables
type-aware linting (no `parserOptions.project`, no typed rules, as its
own comment states), so this diff cannot move any untouched file's
verdict. The repo-wide `pnpm lint` is CI's.
- **NOT MEASURED locally** (CI owns them): Test Core, Dogfood Regression
Gate, Build Core, Temporal Conformance, the type-check lanes, and the
families `dispatch-gates` names outside its 64.
## DELIBERATE CORRECTION: three pending changesets (seat amendment 2,
5851881633)
Each change rewrites ONE sentence of a pending release note that this PR
makes false, following the #20132 / #20145 precedent. Every other line
of each file is byte-identical: `git diff --numstat f2904cd HEAD`
reads `1 1` on each file, and with the changed line deleted from both
sides `diff` exits 0. Check Changeset turns red on the foreign-changeset
rule by design, naming exactly these three files.
### 1.
`.changeset/18931-me-permissions-unrestricted-export-annotation.md`,
line 13 (blob `6eac3716` → `d49f56e3`)
- **Before:** 「an object that is unrestricted **and** keeps `export`
gets none.」
- **After:** 「an object that is unrestricted **and** keeps `export` gets
none — unless its `enable.apiEnabled` is `false`, which is annotated
`[]` since #20135 because the REST door answers 404 for every verb on
it.」
### 2. `.changeset/18990-viewall-only-permissions-seed.md`, line 12
(blob `261e412f` → `727fbffa`)
- **Before:** 「… an unrestricted object whose export stays allowed still
gets no `apiOperations` (the entry itself is seeded since #20134),
because for it the client's default-allow path is already right.」
- **After:** 「… an unrestricted object whose export stays allowed still
gets no `apiOperations` (the entry itself is seeded since #20134),
because for it the client's default-allow path is already right — except
an object with `enable.apiEnabled: false`, annotated `[]` since #20135
because the REST door refuses every verb on it.」
### 3. `.changeset/20134-super-user-entries-every-bit.md`, line 15 (blob
`a113da40` → `d2ee6a58`)
- **Before:** 「That entry carries no `apiOperations`, exactly as the
operation channel said nothing about the object before, so a client's
default-allow path for the operation set is unchanged.」
- **After:** 「That entry carries no `apiOperations` — unless the object
declares `enable.apiEnabled: false`, which is annotated `[]` since
#20135 — so for every other such object the operation channel says what
it said before and a client's default-allow path is unchanged.」
**The new sentences are TRUE at this head.** An object with
`enable.apiEnabled: false` is annotated `[]` in every entry, and the
REST door answers `404 OBJECT_API_DISABLED` for every verb on it; both
were measured on the stack above. Every other unrestricted object whose
export stays allowed still carries no annotation. That holds for the
controls `pq_open` and `pq_exposed`, and for `crm_account` in the core
pins.
**This card's own changeset follows.** Its line 21 said those notes
"otherwise describe [such an object] as carrying none", which the
corrections make false. That one sentence now reads 「That includes an
unrestricted object whose export stays allowed, which used to carry no
annotation at all; this release's notes for #18931, #18990 and #20134
name that exception.」 `git diff --numstat` reads `1 1`, and every other
line is byte-identical.
## Patch round 1, at head `ba66a482b2` (contract review 5851878434,
prose only)
Two commits sit on top of `f2904cd05b`: the three corrections, then this
card's changeset sentence. They touch only those four `.changeset`
files. Every code and test blob is identical to `f2904cd05b`: `git diff
--stat f2904cd HEAD -- . ':(exclude).changeset'` is empty, and the
five files carry blobs `da81b3192b`, `cd0367c631`, `ab113b718a`,
`070606df33` and `0ea90cbfed` at both heads. So the suites, ablation,
stack and gate readings above stand for the code. The branch was not
merged with `main`, and its merge base is still `16c5a33fdd`.
Gates re-run at `ba66a482b2`, with `--base 16c5a33` (the merge base)
and this body as the `--event`:
- `node scripts/check-empty-changeset.mjs --base 16c5a33`: **exit 1,
as expected**. It names exactly the three corrected files, each as
"present on the merge base and CHANGED by this PR -- this is somebody
else's release note":
`.changeset/18931-me-permissions-unrestricted-export-annotation.md`,
`.changeset/18990-viewall-only-permissions-seed.md` and
`.changeset/20134-super-user-entries-every-bit.md`. Its first line is "✓
No empty-frontmatter changeset introduced by this diff (4 declaring
changeset(s) added)". The class is DELIBERATE CORRECTION, not COLLISION,
so the base text must **not** be restored; a same-head contract-review
PASS is what confirms it.
- `node scripts/check-changeset-no-major.mjs --base 16c5a33 --event`
(this body): exit 0. It prints "✓ This diff introduces no `major` bump."
and "✓ LEVEL AXIS: this PR declares clause-② `no`, so no package here is
declared to have grown a published surface.", with declaration line
`Clause-②: no` and no direction arm.
- `node scripts/check-adr-0087-registration.mjs --base 16c5a33
--event` (this body): exit 0, "✓ check-adr-0087-registration: this PR
adds no declared-breaking changeset (4 non-breaking changeset(s) seen)."
- `node scripts/check-issue-citations.mjs --base 16c5a33`: exit 0, "✅
check-issue-citations: every citation this change adds resolves (or is a
declared cross-repo reference)." (3 judged, 3 resolve).
- `origin/main` has since moved one commit, to `8d1f7ab785` (#20125). It
touches none of the six files in this diff, and in
`packages/core/src/security` it touches only `operation-private-keys.ts`
and its pin. The branch was not merged, as the seat directed.
## Acceptance notes
- **Entry presence is untouched, and it has its own mismatches.** On the
stack, a subject whose map has no entry for an object (the "no-entry
cells" column) leaves the client on default-allow for that object's
operations while the door refuses some of them. Two examples:
`member_default` on `sys_metadata` `create`, and `export` on objects it
cannot read. Those counts are identical at base and head. Which entries
exist is the seed's and the folds' question: it is excluded from this
claim, and #20136 is next in this file. It is not filed here. `can()`
reads such an object as "no grant" on every verb, which is the
entry-presence ruling's territory (#18990).
- **Pending release notes, corrected in this PR.** Seat amendment 2
(5851881633 on #20135) ruled Q1 → B after contract review 5851878434.
The review measured `resolveEffectiveApiMethods({ apiEnabled: false
}).mode === 'unrestricted'`, so an API-disabled object with no
`apiMethods` is "unrestricted" in the resolver's own vocabulary, and all
three pending sentences read false for it at this head. Each gets a
one-clause DELIBERATE CORRECTION; see the section below.
- **Spec describe string.**
`EffectiveObjectPermissionSchema.apiOperations`' `.describe()` says
"Present only when the object tightens exposure via apiMethods". That
has been inexact since the export axis (#3544), and is now inexact for
`apiEnabled: false` too. `packages/spec` is out of scope, so this is
noted only.
- **`history` / `restore` / `purge` / `search` are outside the parity
column** by construction. No REST route gates them by name. The spec's
`apiExposureDenialReason` admits every operation on an unrestricted
object, while `resolveEffectiveApiMethods` withholds the flag-gated
ones, and that difference lives in `packages/spec`. It is unchanged, and
the annotation keeps the closure's answer.
- The stack probe and the ablation dump were throwaway files and are not
in this diff. The tree was proven clean against HEAD after both.
Session `session_01Bvd69VPa6puiNzzPUroDBx` (the `domain:engine` seat 1
dispatch, round 22), on branch
`claude/issue-20135-api-operations-parity`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9401b84 commit 49144fc
9 files changed
Lines changed: 376 additions & 44 deletions
File tree
- .changeset
- packages
- core/src/security
- plugins
- plugin-hono-server/src
- plugin-security/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
13 | 13 | | |
14 | 14 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
283 | 283 | | |
284 | 284 | | |
285 | 285 | | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 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 | + | |
0 commit comments