Repository navigation
Commit 6440cf1
fix(plugin-security): a permission-set assignment is titled by the person's name, not the raw user id (#24885)
Fixes #24775
Clause-②: no
This PR covers one of the card's two objects, `sys_user_permission_set`
(`plugin-security`). The other, `sys_notification_preference`
(`service-messaging`), needs a ruling first, so #24775 remains open
after this merges. Open choice 1 below gives that ruling's options.
Related: #24701 (Record Shares recipient, closed; its seat ruling
`6282390889` is the precedent read for this card).
## What changes
`sys_user_permission_set.display_title` is the object's `nameField`. It
composed the stored foreign key, `record.user_id + ' → ' +
record.permission_set`. So Setup's list of a user's permission sets and
the assignment's record page read like `ISu3jHPIrdgD45L1rrC2FuE7DIcsk2ap
→ qa_fls_rep` (HotCRM pass, F-25).
It now names the person:
```
(isBlank(trim(record.user_id.name)) ? record.user_id.email : record.user_id.name) + ' → ' + record.permission_set
```
- The user's `name`, or their `email` where the name is blank. That is
the platform's own `name || email` rule for a person's handle
(`auth-manager.ts`, `approval-service.ts`).
- The value is read one hop through `user_id`. That is the formula read
path's one-hop traversal (ADR-0079 addendum, 2026-10-18), read under the
reader's own authority.
- No stored column, no spec change, no objectui change, no console name
mapping.
- `user_id` is read only through the hop. A bare read beside the
traversal would be the `traversal-bare-and-traversed` refusal.
- `titleFormat` (`{user_id} → {permission_set}`) is unchanged. It is the
rung below the declared pointer in the console's H1 ladder: objectui
`packages/components/src/renderers/layout/containers.tsx:2542` at the
pin `616df6598b` ("The declared pointer OUTRANKS `titleFormat`"). A
renderer reaches it only when the title has no value.
- The field's label and description are unchanged, so no translation
bundle moves.
Files:
-
`packages/plugins/plugin-security/src/objects/sys-user-permission-set.object.ts`:
the expression and its comments.
-
`packages/plugins/plugin-security/src/objects/sys-user-permission-set-person-title.test.ts`
(new): the pins.
-
`packages/plugins/plugin-security/src/objects/sys-security-assignment-display-title.test.ts`:
`sys_user_permission_set` leaves its case table, since the title no
longer equals the `titleFormat` text. `sys_user_position` keeps every
assertion it had.
- `.changeset/24775-permission-set-assignment-person-title.md`:
`@objectstack/plugin-security` patch.
## Premises, measured (on `main` at `6b9e6df909`, then merged to
`b1676441ba`)
- **H1, holds.** Both objects declare `nameField: 'display_title'`, a
text formula with no stored column. The read door serves it: on a real
engine (driver-sql, better-sqlite3), `findOne`, a `find` projecting only
`display_title`, and the insert response all carry it.
- **H2, read.** `packages/spec/src/data/display-name.ts` lists `lookup`
and `user` in `TITLE_INELIGIBLE_TYPES`. So the pointer stays on the text
formula; no pointer moves.
- **H3.** The console already names the person in a `lookup` column's
chip. For the title, though, the platform's server-side resolvers read
the title field's value: `resolveRecordDisplayName`, and
`servedRecordTitle` in `record-title.ts`. So the person's name has to be
in the formula's value. The formula's one hop is the platform's own way
to put it there.
- **H4, holds (it was NOT MEASURED in #24701's report).** A formula
reads one hop through a reference. This shipped with `d537a318b` and
`1bf5f6e17c`, and the ADR-0079 addendum records it. Measured on the real
engine with the real `sys_user`, the hop formula gives `Alice Liddell →
crm_edit` on the insert response, on `findOne` and on a projected
`find`. A grant whose user does not exist reads `null`.
- **`sys_notification_preference` cannot take the same route.** Its
`user_id` is `Field.text` (maxLength 255). It holds a `sys_user.id` or
the literal `*`, the admin-global default `preference-resolver.ts` reads
as `WILDCARD`. The read path serves a hop only through a reference type
(`REFERENCE_VALUE_TYPES`: lookup, master_detail, user, tree, in
`field-value.zod.ts`). Measured: a text `user_id` read as
`record.user_id.name` gives `null`. See open choice 1.
## Open choice 1: how `sys_notification_preference` can name the person
No route names the person without one of three things: re-typing
`user_id`, a stored copy, or a formula-language feature. The dispatch's
hard stops forbid the last two in this lane.
- **A. Re-type `user_id` as a `user` reference, and spell the
admin-global default as an empty reference instead of `*`.** That is
ADR-0118 D1's representation of a non-user in a user column: null, never
a sentinel; the display is a render rule. The title then reads the
person one hop, as here, and shows the empty reference as the default's
label. The Setup form gets a user picker instead of a raw-id text box,
and the list column a person chip.
- Business need: the Setup grid is the measured producer (HotCRM F-25).
Zero code producers write `*` rows: `git grep` finds the resolver's
constant, its tests and one driver-sql test.
- Long-term: contract-first. It removes a sentinel from a user column.
- AI mistakes: a typed reference refuses a non-user id at the write
door. Today any string is stored as a "user id" and silently matches
nobody.
- Startup scope: no new column or capability. But it narrows the accept
set (`*` and any non-user string refused), so it is `Clause-②: yes
(narrowing)` and BREAKING. It owes a stored-row conversion (`*` to
null), the resolver change, and a NULL-safe user key part in the unique
index (ADR-0120 D3 makes only the organization key part NULL-safe).
Effort M, as its own card.
- **B. A console display rule for the column only** (a widget hint on
`user_id`). No such widget exists at the pin. The shipped hints are
`recipient-picker`, `object-ref`, `position-ref`, `permission-ref` and
`filter-condition`. And a hint on `user_id` cannot reach the composite
title, so the H1 and the list's title column stay raw. It does not close
the finding.
- **C. A stored name column:** refused by the dispatch (it goes stale on
rename).
- **D. A by-id lookup function over a text column:** the spec and
formula lane's. It would be a second traversal dialect beside the
one-hop rule.
- **Recommendation: A, as its own `service-messaging` card.** The
long-term and AI-safety axes both favour typing the column. The cost is
a breaking narrowing with a data conversion, and that is the
maintainer's to rule.
## Open choice 2: the hop costs a read on every authenticated request of
a grant holder
The authorization prelude reads the caller's grants with no field
projection: `tryFind(ql, 'sys_user_permission_set', { user_id: userId },
100)` in `packages/core/src/security/resolve-authz-context.ts:1457`. A
read with no projection computes every formula, so `display_title`'s hop
now costs one batched `sys_user` read there.
Measured with `auth-prelude-query-count.pin.test.ts`'s own rig (real
engine, real `AuthManager`, `resolveAuthzContext`), old formula against
new. The probe was a scratch file and is not committed:
| principal | old formula | this PR |
| --- | --- | --- |
| no direct grant | 8 reads, 4 round trips | 8 reads, 4 round trips |
| one direct grant | 8 reads, 4 round trips | 9 reads, 5 round trips
(`sys_user` 2 to 3) |
The committed pin measures a principal with no direct grant, so it stays
green and cannot see this. The grants cache that would amortise it is
off by default (`OS_AUTHZ_GRANTS_CACHE_TTL_MS=0`). Other reads of the
table that name no projection pay the same single read per page: the
explain engine, the platform-admin scan, the last-admin guard and the
auth manager's grant reads. They are admin-time or boot-time paths.
- **A. Land as is.** Every request of an organization admin or platform
admin pays one more indexed read and one more round trip, for a title
nobody on that path reads.
- **B. Land after (or with) a `core`-lane change that projects the
prelude's grant read to the columns the resolver reads,** plus a
grant-holder case in the prelude pin. The title is still declared once,
on the object, and the hot path reads only what it uses. Which columns
the resolver reads is that lane's to measure. A wrong projection would
drop a column a security decision reads.
- **C. Withdraw the hop.** The title stays the raw id.
- **Recommendation: B.**
- Business need: the title serves Setup pages, while the cost lands on
every request.
- Long-term: B avoids a hidden coupling, where a display formula costs
authorization a round trip.
- AI mistakes: the extended pin makes the next such cost visible.
- Startup scope: one projection and one pin case, and no new gate.
## Tests (head `2deb9e32e6`; every run under
`scripts/pm/os-verify-lock.sh`, exit read from its `VERDICT` line)
- Closure: `pnpm --workspace-concurrency=2 --filter
'@objectstack/plugin-security^...' --filter
'@objectstack/service-messaging^...' build` gave `VERDICT command-exit
0`. Then `pnpm --filter @objectstack/plugin-security build` gave
`VERDICT command-exit 0`; `check-dts-emitted`: 1/1, and `dist/index.mjs`
carries the new expression (count 1).
- `pnpm --filter @objectstack/plugin-security exec vitest run
--maxWorkers=2` (whole package, code tree of `07c9bf2315`): `Test Files
196 passed (196)`, `Tests 4049 passed | 45 skipped (4094)`.
- `pnpm --filter @objectstack/plugin-security typecheck` (on
`2deb9e32e6`'s tree): `VERDICT command-exit 0`. It includes
`check:test-typecheck: OK`, whose `tsconfig.test.json` includes
`src/**/*`, the new pin file among them.
- The two title files on `2deb9e32e6`'s tree: `Test Files 2 passed (2)`,
`Tests 14 passed (14)` (8 in the new file, 6 for `sys_user_position`).
- The new pins, with real `sys_user` and the real assignment definition
on driver-sql:
- the registered pointer, and the formula reads `permission_set`,
`user_id.email` and `user_id.name`, nothing bare;
- the person's name on the insert response, on `findOne` and on a `find`
projecting only `display_title`. `resolveRecordDisplayName` and
`servedRecordTitle` agree, and the row still holds the foreign key;
- the control: a user whose stored name is empty, or only spaces, is
titled by their email;
- read as the reader, under a `sys_user` row-scope middleware in the
shape the security plugin composes:
- a readable user is named;
- an unreadable one leaves the title `null`, and neither the name nor
the email appears in the served row;
- SYSTEM reads the name;
- a missing user reads `null`;
- the write door refuses a row missing either title column;
- no stored column and no search companion.
- `sys_notification_preference` is untouched, so no `service-messaging`
test or typecheck is owed.
## Ablation (one leg: the one pinned behaviour this PR changes)
- The mutation: `node scripts/ablation-replace.mjs` replaced the new
expression with the old one, inside a script carrying `trap 'git
checkout HEAD -- "$ABS"' EXIT INT TERM` over an absolute path.
- Proof it landed on disk: anchor count 1 to 0, replacement count 0 to
1, blob `72b5b4afc1` to `712f8d6419`.
- The subject is imported from `src/` by a relative path, so no `dist/`
was involved.
- Mutated run of `sys-user-permission-set-person-title.test.ts`: `Tests
6 failed | 2 passed (8)`. That is the expected direction, red. The two
still green pin unchanged behaviour: the missing-column refusal and no
stored column.
- Restored with `git checkout HEAD -- "$ABS"`: blob after restore
`72b5b4afc1`, equal to HEAD's blob; `git diff HEAD` empty; `git status
--porcelain` empty.
## Gates
- **Derivation.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) on `2deb9e32e6` derived 68
commands. That is 4 changed paths against merge base `b1676441b`, 264
changed lines. The seat's planned-surface count was 51.
- **First sweep on `9ffef3b9a4`:** 65 commands exit 0.
- `pnpm check:query-options-erasure` exit 1: the new pin erased three
option bags (236 to 239 test sites). Typed in `2deb9e32e6`; the re-run
on that tree exits 0 at 236.
- `pnpm check:dual-build-cjs-loads` exit 3: PREREQUISITE NOT MET, every
package's `dist/` (`pnpm build`). NOT MEASURED locally.
- `pnpm check:i18n` exit 3: PREREQUISITE NOT MET, the CLI plus the
closure of ten packages. NOT MEASURED locally. No label or description
moved, so no bundle should.
- **Re-run of the union on `2deb9e32e6`:** in the report on the card.
CI's check-runs are the record for every family.
## Acceptance notes
- Same family, not in this card's file surface. The seat should fold
these into the family's close-out card, not file single cards:
- `sys_user_position.display_title` (`record.user_id + ' → ' +
record.position`, `sys-user-position.object.ts:76`). `user_id` is a
lookup there, so this PR's route applies. It is pinned today, on the
engine read door, as `usr_alice → sales_manager`.
- `sys_notification_delivery` (`record.channel + ' → ' +
record.recipient_id`) and `sys_notification_subscription`
(`record.principal + ' · ' + record.topic`). Both are text columns, so
they share open choice 1's fork.
- The prelude pin's blind spot (a principal with no direct grant) is
open choice 2's. Noted, not filed.
> **Seat's append** (`domain:services` seat 1,
`session_011TdZ8GjixNZFLz1xJD9zzK`):
> - **First line changed** from `Part of #24775` to `Fixes #24775`, by
seat ruling `6291928519` and resume note `6298203362`. #24896 landed (PR
#24941, `644e4b2c54`), and the card's other object,
`sys_notification_preference`, is carried by #24895.
> - `sys_user_position` does not ride this PR: the prelude reads it
unprojected, `resolve-authz-context.ts:1484`.
> - Below is the dev's round-2 markdown from os-dev report `6299091948`
on #24775, verbatim. The dev writes the body once and never PATCHes it.
Where the sections above say this PR is `Part of` or that #24775 stays
open, this append supersedes them.
## Round 2 (head `66a590fd88`)
**Merge.** `origin/main` at `865d2c8cf4` is merged in as `9474db510e`,
with no rebase and no force-push. It brings #24941 (`644e4b2c54`): the
authz prelude's grant read now names `permission_set`,
`organization_id`, `valid_from` and `valid_until`. Git reported no
conflict and no `os-regen` deferral.
**One semantic conflict, settled in one line** (`894860ed7a`). #24941's
pin,
`packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts`,
has a pair of direct-grant cases: "as declared" and "with a title that
reads one hop through user_id". Before it counts anything, the "as
declared" case checks the value of the declared title, and it expected
the raw user id. On this branch the declared title is the person-named
one. So on the merge commit that check read `Prelude → prelude_editor`,
and the case went red (`Expected: "USER_ID → prelude_editor"`,
`Received: "Prelude → prelude_editor"`). The count assertions after it
never ran. The line now expects `Prelude → prelude_editor` in both
cases. No count, rig or other assertion changed.
**One sentence the merge made false** (`66a590fd88`). The changeset's
Cost bullet said the prelude rises to 9 reads in 5 round trips for a
grant holder. It now says the prelude's grant read names its columns, so
it computes no title and stays at 8 reads in 4 round trips. The rest of
the changeset is unchanged.
**The prelude, re-measured on the merged tree.** This uses this PR's
real `sys_user_permission_set` definition, on the rig of the pin above,
by round 1's method. The probe was a scratch file, deleted and never
committed.
| read, for a principal holding one direct grant | reads | round trips |
| --- | --- | --- |
| the prelude (`resolveAuthzContext`), round 1 on `b1676441ba` | 9 | 5 |
| the prelude (`resolveAuthzContext`), now | 8 | 4 |
| control: the same grant read, no projection | 2
(`sys_user_permission_set` 1, `sys_user` 1) | 2 |
| control: the same grant read, projected to the four columns | 1 | 1 |
- The prelude's reads match the pin's `PRELUDE_READS` exactly:
`sys_session` 1, `sys_user` 2, `sys_member` 2, `sys_user_position` 1,
`sys_user_permission_set` 1, `sys_metadata_activation` 1.
- The unprojected control returns `Prelude → prelude_editor`. So the hop
is live on this engine, and the instrument counts it. The projected
control returns exactly the four columns.
- So on `main` now, the hop adds no read and no round trip to the
prelude.
- After the one-line change, the pin passes on the merged tree: all 7
cases, both direct-grant cases at 8 reads in 4 round trips.
**Tests on `66a590fd88`.** Every run went through
`scripts/pm/os-verify-lock.sh`, and each exit is read from its VERDICT
line.
- Closure build: `pnpm --workspace-concurrency=2 --filter
'@objectstack/plugin-security^...' --filter
'@objectstack/plugin-auth^...' build` gave VERDICT command-exit 0 (30 of
85 projects).
- `pnpm --filter @objectstack/plugin-security exec vitest run
--maxWorkers=2`: `Test Files 197 passed (197)`, `Tests 4077 passed | 45
skipped (4122)`.
- `pnpm --filter @objectstack/plugin-security typecheck`: VERDICT
command-exit 0, `check:test-typecheck: OK`.
- The two title files: `Test Files 2 passed (2)`, `Tests 14 passed
(14)`.
- `plugin-auth` is now a touched package:
- `pnpm --filter @objectstack/plugin-auth exec vitest run
--maxWorkers=2`: `Test Files 172 passed (172)`, `Tests 3289 passed | 10
skipped (3299)`.
- `pnpm --filter @objectstack/plugin-auth typecheck`: VERDICT
command-exit 0, after `pnpm --filter @objectstack/plugin-auth build`.
The first attempt stopped at TS2307 in `examples/basic-usage.ts` because
the package's own `dist/` was missing, so it measured nothing.
- No ablation this round, because no tested behaviour changed. Round 1's
ablation of the title stands.
**Gates on `66a590fd88`.**
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 69 commands: 5 paths against merge
base `865d2c8cf`, 267 changed lines.
- They ran as one runner under the lock. 67 exited 0 on the first pass.
`pnpm check:dual-build-cjs-loads` and `pnpm check:i18n` exited 3
(PREREQUISITE NOT MET, no `dist/`). Both exited 0 on a second pass,
after the union's `check:type-check-debt` had built the workspace.
- `--ran` exited 0: `Run reconciliation — 69 derived, 69 run, 0
NOT-MEASURED, 0 UNRUN.`
- CI on `66a590fd88`: `Lint & Repo Gates`, `TypeScript Type Check`,
`Build Core` and `Governed Surface Queue Guard` succeeded. `Test Core
(1/6)` through `(6/6)` succeeded; the aggregate `Test Core` was
in_progress at report time.
**Superseded in the body above.**
- "Files" should also list
`packages/plugins/plugin-auth/src/auth-prelude-query-count.pin.test.ts`
(the one line above).
- The opening paragraph's "#24775 remains open after this merges", and
open choice 1: the preference half is now carried by #24895.
- Open choice 2: ruled B in seat ruling `6291928519` and landed by
#24941. The table above is its measurement on `main`.
- "Tests" and "Gates": these are round-1 figures on `2deb9e32e6`. The
round-2 figures are above.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011TdZ8GjixNZFLz1xJD9zzK)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 693352e commit 6440cf1
5 files changed
Lines changed: 242 additions & 25 deletions
File tree
- .changeset
- packages/plugins
- plugin-auth/src
- plugin-security/src/objects
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
281 | 281 | | |
282 | 282 | | |
283 | 283 | | |
284 | | - | |
| 284 | + | |
285 | 285 | | |
286 | 286 | | |
287 | 287 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
19 | | - | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
20 | 22 | | |
21 | 23 | | |
22 | 24 | | |
| |||
46 | 48 | | |
47 | 49 | | |
48 | 50 | | |
49 | | - | |
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| |||
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | 62 | | |
68 | 63 | | |
69 | 64 | | |
| |||
| 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 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
46 | 47 | | |
47 | 48 | | |
48 | 49 | | |
| |||
57 | 58 | | |
58 | 59 | | |
59 | 60 | | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
67 | 74 | | |
68 | 75 | | |
69 | 76 | | |
70 | | - | |
| 77 | + | |
71 | 78 | | |
72 | 79 | | |
73 | 80 | | |
| |||
0 commit comments