Repository navigation
Commit 41dc302
feat(plugin-auth): a platform administrator can mint and revoke SCIM connection credentials (#30991)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DJjeMCH9mdodCQWD68YLVEE)_
domain:server#3 · os-elon-musk · session_016WV4cycHNJyS5MXHqaGsFE
Fixes #30958
Clause-②: yes (widening)
Metadata-protocol: none
Skills-docs: updated here —
`content/docs/permissions/authentication.mdx` ("SCIM Connection
Credentials" under Admin User Management); `skills/**` teaches no SCIM
setup, so no skill changes
## 维护者速读
- **改了什么**:plugin-auth 新增两个仅平台管理员可用的端点 `POST
/api/v1/auth/admin/scim/mint-credential` 和 `POST
/api/v1/auth/admin/scim/revoke-credential`。前者为一个 SCIM 连接签发
bearer,明文只在这一次响应里出现(`Cache-Control:
no-store`),库里只存带密钥的单向摘要;后者把凭证置为失效,下一次 SCIM 请求即 401。轮换 = 同一连接再签一张、IdP
换上新的、再吊销旧的。每次签发和吊销各写一行 `sys_audit_log`,记管理员和连接,不记令牌也不记摘要。
- **为什么改**:`mintScimConnectionCredential`
写好了却没有调用方,`sys_scim_connection_credential` 在数据 API 上又拒绝创建,所以任何 IdP 都拿不到
bearer,SCIM 实际上接不通。
- **风险与回滚**:纯新增两条路由,不改现有行为;门禁复用 `/admin/create-user` 同一个
`judgePlatformAdmin`(匿名 401、非管理员 403、挂起会话 403)。SCIM 未开启时签发答 503。回滚即
revert。
- **先看**:①
`packages/plugins/plugin-auth/src/scim-connection-admin.ts`(两个处理函数、组织归属、审计行);②
`auth-plugin.ts` 里新挂的两条路由,`gateAdmin` 在读 body 之前;③
`scim-connection-service.ts` 末尾追加的 `revokeScimConnectionCredential`。
- **两分钟验证**:`pnpm --filter @objectstack/plugin-auth exec vitest run
src/scim-connection-admin.test.ts`,预期 9 个测试全绿(签发后 SCIM `POST /Users`
201、非管理员 403、吊销后 401、令牌和摘要不出现在审计行和日志里)。
- **需要你做的**:无,席位做合约审查并落地。只有一点请留意:签发权限只给平台管理员,若要放给组织管理员,需要你另行裁决,本 PR 不做。
## Why
ObjectStack owns the SCIM credential lifecycle: the stable
`@better-auth/scim` plugin keeps no bearer of its own, and
`verifyScimBearerToken` resolves every connection from a
`sys_scim_connection_credential` row. The mint
(`mintScimConnectionCredential`) existed with no caller, and the object
refuses create on the data API (`enable.apiMethods: ['list']`,
engine-owned), so no identity provider could be handed a bearer. Door
choice (route, platform admin only) recorded on the card before
building: comment 6687604379.
## What changes
1. **Two raw auth mounts** in `auth-plugin.ts`, beside the other
`/admin/*` mounts: `POST ${basePath}/admin/scim/mint-credential` and
`POST ${basePath}/admin/scim/revoke-credential`. Each runs the shared
`gateAdmin` (`judgePlatformAdmin`, ADR-0068 D2 + ADR-0069 D3
held-session) before reading the body, then hands off to
`scim-connection-admin.ts`. Both rows are in `auth-route-ledger.ts`
(mint `requires: 'scim'`) and in the conformance test's hand-written
objectstack set.
2. **Mint** (`runMintScimCredential`): body `{ connectionId, label?,
provisioningDomainId?, organizationId?, expiresAt? }`. `connectionId` is
required, at most 246 characters (a SCIM connection's own writes are
audited as `svc:scim:<connectionId>` (#30960) in `sys_audit_log.actor`,
which holds 255), no surrounding whitespace; `expiresAt` must be an ISO
instant in the future; anything else is 400 `INVALID_REQUEST`. SCIM not
wired is 503 `SERVICE_UNAVAILABLE`. The row is written under
`withSystemContext(engine)` inside `runAttributedToUser(admin)`, with
the row's `user_id` = the admin (the service's `mintedByUserId` input).
The response is 200 `{ success: true, data: { credentialId,
connectionId, token, expiresAt } }` with `Cache-Control: no-store`; it
is the only place the plaintext appears. A storage failure answers 500
without the driver's cause text, because a driver error can echo the
inserted row and that row carries the digest.
3. **Organization ownership.** A system insert must carry an
organization (ADR-0131 D9). A named `organizationId` must exist (else
400); with none named, the Default Organization
(`findDefaultOrganization`); if that cannot be told apart, 400 asking
for `organizationId`. This decides only which organization owns the
credential row.
4. **Revoke** (`runRevokeScimCredential`): body `{ credentialId }`. It
calls the new `revokeScimConnectionCredential` (appended to
`scim-connection-service.ts`, nothing above it edited), which sets
`active: false`; `verifyScimBearerToken` already refuses inactive rows,
so the bearer answers 401 at the next SCIM request. Unknown id 404
`RESOURCE_NOT_FOUND`, already revoked 409 `RESOURCE_CONFLICT`, missing
id 400. Revoke does not require SCIM to be on, so a credential can still
be switched off after SCIM is disabled. The row stays (its digest is
one-way and the audit trail points at it).
5. **Audit.** Each mint and each revoke writes one `sys_audit_log` row
directly (as `/admin/create-user` does): admin as `user_id` / `actor`,
`object_name` `sys_scim_connection_credential`, `record_id` the
credential, `metadata` `{ event: 'scim_credential_minted' |
'scim_credential_revoked', connectionId }` (a closed type with no field
for a secret). A refused audit write does not undo the operation; it
warns once, naming no secret. With plugin-audit absent there is no
`sys_audit_log` object and the write is skipped.
6. **Docs.** `content/docs/permissions/authentication.mdx` gets "SCIM
Connection Credentials": the curl call, the once-shown token, the 503,
revoke and its 404 / 409, rotation, the audit row.
## Tests
- `packages/plugins/plugin-auth/src/scim-connection-admin.test.ts` (9
tests): a real ObjectQL engine on the SQL driver (sqlite), the real
`AuthManager` with SCIM and organizations on, the real
`registerAuthRoutes` on Hono. Admin mints, then the bearer creates a
user through SCIM `POST /Users` (201); the row's owner, `user_id` (the
admin), label and `active`; one audit row. Rotation: a second mint for
the same connection, revoke the first, old bearer 401, new bearer 201,
second revoke 409, two audit rows. Non-admin 403 and anonymous 401 on
both mounts, with nothing written. Malformed bodies 400 (including a
past or unparseable expiry); a 246-character connection id mints and a
247-character one is refused 400 with nothing stored; a future expiry
stored and answered, SCIM off 503, unknown 404, missing 400. Every case
runs `expectSecretNowhereElse`: the token and its digest are absent from
every `sys_audit_log` row and every captured console line, the at-rest
row (driver read) holds the digest and never the token, and an engine
read under system context does not serve the digest.
- `packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts`
(new case W5): on the composed runtime with plugin-audit and SCIM on,
the admin mints over HTTP (`no-store` asserted), the bearer creates a
SCIM user (201), revoke answers 200 and the bearer then 401; exactly two
door audit rows (minted, revoked) naming the admin and the connection;
the token and the digest appear in no `sys_audit_log` row, no
`sys_activity` row, and not in `GET
/data/sys_scim_connection_credential` (200, lists the credential). The
`/export` door answers 403 `EXPORT_NOT_PERMITTED` even to the platform
admin (measured: no permission set grants `allowExport` on this
engine-owned object); the pin asserts that refusal and that its body
carries neither value, so a future export grant has to come back to this
test.
-
`packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts`:
both mounts join the derived `/admin/` sweep in the objectstack-gate
bucket (non-admin 403, admin 200 / 404).
## Acceptance notes
- **Out of scope: a Setup button.** No UI calls the door yet; the seat
files that follow-up card after landing. Nothing under `ui/` is touched.
- **Who may mint.** Platform administrator only, as the card and the
dispatch require. Letting an organization administrator mint for their
own organization would be a maintainer decision and is not proposed
here.
- **Two kinds of audit row.** With plugin-audit installed, its generic
mirror also records the credential row's create and update as ordinary
data-change rows; plugin-audit omits `internal` fields, so
`token_digest` is not in them (the dogfood case asserts the token and
digest are absent from every `sys_audit_log` and `sys_activity` row).
The door's own row is the one that names the event and the connection.
- **Tenant-audit census.** The two new engine writes (the door's audit
insert, the revoke update) are new application-surface write call sites,
so `check-tenant-audit-census` asked for its artefacts to be
regenerated: `content/docs/permissions/tenant-audit-census.mdx` and
`docs/audits/2026-08-tenant-audit-write-call-sites.counts.md` (`node
scripts/tenant-audit-census.mjs --write`), plus the page's hand-written
figures (261 → 263 sites, 184 → 186 decidable, 144 → 146 elevated, 68 →
69 const-named). Both writes spell `{ context: { isSystem: true } }` at
the call, so the census reads them as decidably elevated, and the
"provably no tenant context" figure stays 2. These two files are outside
the card's file surface; they change only because the gate requires it.
- **#30960 (SCIM audit attribution, server seat 5)** may also edit
`scim-connection-service.ts`; this PR only appends below the existing
code there.
## Verification
**Delta `306a19fc8e`** (test-only; after the merge queue ejected
`677f474461`): the queue's full dogfood shard 3 failed in
`admin-platform-admin-standing.dogfood.test.ts`, whose derived `/admin/`
sweep requires every served route to be classified; the two new mounts
were not (summary issue #31012, which this delta fixes; the same failure
ejected #31002 from that speculative stack). `79a87832` adds both to
`ADMITTED` with probe bodies (SCIM is on at that boot, so the mint
answers 200; the revoke of an unknown id answers 404), and `306a19fc`
merges `origin/main` `8ae0ab9b`. At `306a19fc8e`: that file plus
`admin-identity-audit-trail`, `admin-route-nonadmin-refusal`,
`admin-ledger-decision-metadata`, `route-ledger-live-mount-parity` and
`discovery-auth-families`: `Test Files 6 passed (6)`, `Tests 45 passed
(45)`. Ablation (the two `ADMITTED` lines removed): exit 1, the
unclassified assertion names exactly the two SCIM routes, restored
clean.
**Delta `677f474461`** (after the seat's review started on
`6c7e436129`): the connection-id cap of 246 above, its pin, and one line
each in the docs and the changeset. At `677f474461`: `vitest run
src/scim-connection-admin.test.ts` `Tests 9 passed (9)`; plugin-auth
`tsc --noEmit` exit 0; ablation (cap back to 255 via
`ablation-replace.mjs`): exit 1, 2 red (the malformed-request case and
the new 246/247 pin, both `expected 200 to be 400`), restored clean.
Re-run at `677f474461`, each exit 0: `check:doc-anchors`,
`check:doc-authoring`, `check:doc-frontmatter`, `check:docs-single-h1`,
`check-doc-route-spelling --advisory`, `check-docs-section-name`,
`docs-audit/check-affected-docs`, `check:doc-security-posture`,
`check-empty-changeset`, `check-changeset-no-major`,
`check-tenant-audit-census`, `check:nul-bytes`, `check:issue-citations`,
`check-issue-citations`, `check:keyed-text-bounds`,
`check:error-status-conformance`. The red "TypeScript Type Check" rollup
on `6c7e436129` was its `typecheck-consumers` lane concluding
`cancelled` (that run was cancelled by this push), not a type error; the
other three lanes were green.
Tests and gates at `fc4a6a7801` (`origin/main` `1aeeafd9` merged, which
brought #30879's plugin-auth change). The final head `6c7e436129` merges
`origin/main` `000a4c0d` (#30927, #30938, #30952, #30956, #30961,
#30969), none of which touches a file in this diff; at `6c7e436129`
`check-tenant-audit-census`, `check:auth-mount-ledger` and
`check-empty-changeset --base origin/main` were re-run, each exit 0.
#30960 had not landed when this was opened.
- `pnpm --filter @objectstack/plugin-auth typecheck` (tsc, examples,
`check:test-typecheck`): exit 0. `vitest run`: `Test Files 230 passed
(230)`, `Tests 4155 passed | 10 skipped (4165)`.
- Dogfood, after building the closure: `vitest run
test/admin-identity-audit-trail.dogfood.test.ts
test/admin-route-nonadmin-refusal.dogfood.test.ts`: `Test Files 2 passed
(2)`, `Tests 11 passed (11)`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (exit 0) derived 96 commands; all 96 ran,
every one exit 0. Two needed a prerequisite first:
`check-plugin-teardown-shape --self-test` exited 3 until its pinned
fixture commit was fetched, and `check:skill-examples` /
`check:dual-build-cjs-loads` exited 3 until the packages they read were
built. `check-tenant-audit-census` first exited 1 on this diff's own two
write sites; fixed in `c8e10b4f` (see Acceptance notes).
`dispatch-gates.mjs --ran`: exit 0, "96 derived famil(ies) accounted for
— 96 run, 0 NOT-MEASURED".
<details><summary>Ablations: seven legs, each red for its own reason,
each restored clean</summary>
At `6c7e436129`, the final head. Each leg through `node
scripts/ablation-replace.mjs` (anchor must drop by exactly one, restore
verified against the HEAD blob), running `vitest run
src/scim-connection-admin.test.ts`. Every leg: exit 1, then `git diff
HEAD` empty; the run ended RESTORED-CLEAN.
| leg | mutation | red tests | a failing assertion |
| :--- | :--- | :--- | :--- |
| gate | the mint mount skips `gateAdmin` and takes any caller |
mint→201; non-admin/anon refusal | `expected 200 to be 403` (a non-admin
got a bearer) |
| revoke-write | the revoke never writes `active: false` |
revoke/rotation; unknown/missing/SCIM-off | `expected 201 to be 401`
(revoked bearer still provisions) |
| audit-row | the mint writes no audit row | mint; refusal;
revoke/rotation | `expected [] to have a length of 1` |
| audit-secret | the mint's audit metadata carries the bearer | mint;
revoke/rotation | audit rows `not to contain 'oss_scim_…'` |
| log-secret | the mint logs the bearer at `warn` | mint;
revoke/rotation | log text `not to contain 'oss_scim_…'` |
| scim-off | the 503 refusal while SCIM is off is removed | SCIM-off |
`expected 200 to be 503` |
| secret-binding | the mint digests with a key the verifier does not use
| mint; refusal; revoke/rotation | SCIM `POST /Users` `expected 401 to
be 201` |
</details>
<details><summary>96 gates, exit codes at fc4a6a7</summary>
```text
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dev-prereqs.mjs --self-test :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/check-tenant-audit-census.mjs :: exit 0
node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:auth-mount-ledger :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
```
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4cycHNJyS5MXHqaGsFE
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4cycHNJyS5MXHqaGsFE)_
<!-- signed: domain:server#3 · os-elon-musk ·
session_016WV4cycHNJyS5MXHqaGsFE -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 7d9f1e9 commit 41dc302
14 files changed
Lines changed: 1117 additions & 30 deletions
File tree
- .changeset
- content/docs/permissions
- docs/audits
- packages
- plugins/plugin-auth/src
- qa/dogfood/test
| 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 | |
|---|---|---|---|
| |||
1166 | 1166 | | |
1167 | 1167 | | |
1168 | 1168 | | |
| 1169 | + | |
| 1170 | + | |
| 1171 | + | |
| 1172 | + | |
| 1173 | + | |
| 1174 | + | |
| 1175 | + | |
| 1176 | + | |
| 1177 | + | |
| 1178 | + | |
| 1179 | + | |
| 1180 | + | |
| 1181 | + | |
| 1182 | + | |
| 1183 | + | |
| 1184 | + | |
| 1185 | + | |
| 1186 | + | |
| 1187 | + | |
| 1188 | + | |
| 1189 | + | |
| 1190 | + | |
| 1191 | + | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
| 1204 | + | |
| 1205 | + | |
1169 | 1206 | | |
1170 | 1207 | | |
1171 | 1208 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
132 | 132 | | |
133 | 133 | | |
134 | 134 | | |
135 | | - | |
| 135 | + | |
136 | 136 | | |
137 | 137 | | |
138 | 138 | | |
| |||
197 | 197 | | |
198 | 198 | | |
199 | 199 | | |
200 | | - | |
| 200 | + | |
201 | 201 | | |
202 | | - | |
203 | | - | |
| 202 | + | |
| 203 | + | |
204 | 204 | | |
205 | 205 | | |
206 | 206 | | |
| |||
211 | 211 | | |
212 | 212 | | |
213 | 213 | | |
214 | | - | |
| 214 | + | |
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | | - | |
| 220 | + | |
221 | 221 | | |
222 | 222 | | |
223 | 223 | | |
224 | | - | |
| 224 | + | |
225 | 225 | | |
226 | 226 | | |
227 | 227 | | |
| |||
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
236 | | - | |
237 | | - | |
| 236 | + | |
| 237 | + | |
238 | 238 | | |
239 | | - | |
| 239 | + | |
240 | 240 | | |
241 | | - | |
| 241 | + | |
242 | 242 | | |
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
246 | | - | |
| 246 | + | |
247 | 247 | | |
248 | 248 | | |
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
252 | | - | |
| 252 | + | |
253 | 253 | | |
254 | 254 | | |
255 | 255 | | |
256 | 256 | | |
257 | | - | |
258 | | - | |
| 257 | + | |
| 258 | + | |
259 | 259 | | |
260 | 260 | | |
261 | 261 | | |
| |||
307 | 307 | | |
308 | 308 | | |
309 | 309 | | |
310 | | - | |
| 310 | + | |
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
315 | | - | |
316 | | - | |
317 | | - | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
318 | 318 | | |
319 | 319 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
| 36 | + | |
| 37 | + | |
38 | 38 | | |
39 | | - | |
| 39 | + | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| |||
90 | 90 | | |
91 | 91 | | |
92 | 92 | | |
93 | | - | |
| 93 | + | |
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
101 | 101 | | |
102 | 102 | | |
103 | 103 | | |
| |||
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
| 169 | + | |
169 | 170 | | |
| 171 | + | |
170 | 172 | | |
171 | 173 | | |
172 | 174 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
218 | 218 | | |
219 | 219 | | |
220 | 220 | | |
| 221 | + | |
221 | 222 | | |
222 | 223 | | |
223 | 224 | | |
| |||
6194 | 6195 | | |
6195 | 6196 | | |
6196 | 6197 | | |
| 6198 | + | |
| 6199 | + | |
| 6200 | + | |
| 6201 | + | |
| 6202 | + | |
| 6203 | + | |
| 6204 | + | |
| 6205 | + | |
| 6206 | + | |
| 6207 | + | |
| 6208 | + | |
| 6209 | + | |
| 6210 | + | |
| 6211 | + | |
| 6212 | + | |
| 6213 | + | |
| 6214 | + | |
| 6215 | + | |
| 6216 | + | |
| 6217 | + | |
| 6218 | + | |
| 6219 | + | |
| 6220 | + | |
| 6221 | + | |
| 6222 | + | |
| 6223 | + | |
6197 | 6224 | | |
6198 | 6225 | | |
6199 | 6226 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3874 | 3874 | | |
3875 | 3875 | | |
3876 | 3876 | | |
| 3877 | + | |
| 3878 | + | |
| 3879 | + | |
| 3880 | + | |
| 3881 | + | |
| 3882 | + | |
| 3883 | + | |
| 3884 | + | |
| 3885 | + | |
| 3886 | + | |
| 3887 | + | |
| 3888 | + | |
| 3889 | + | |
| 3890 | + | |
| 3891 | + | |
| 3892 | + | |
| 3893 | + | |
| 3894 | + | |
| 3895 | + | |
| 3896 | + | |
| 3897 | + | |
| 3898 | + | |
| 3899 | + | |
| 3900 | + | |
| 3901 | + | |
| 3902 | + | |
| 3903 | + | |
| 3904 | + | |
| 3905 | + | |
| 3906 | + | |
| 3907 | + | |
| 3908 | + | |
| 3909 | + | |
| 3910 | + | |
| 3911 | + | |
| 3912 | + | |
| 3913 | + | |
| 3914 | + | |
| 3915 | + | |
| 3916 | + | |
| 3917 | + | |
| 3918 | + | |
| 3919 | + | |
| 3920 | + | |
| 3921 | + | |
3877 | 3922 | | |
3878 | 3923 | | |
3879 | 3924 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
246 | 246 | | |
247 | 247 | | |
248 | 248 | | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
249 | 256 | | |
250 | 257 | | |
251 | 258 | | |
| |||
0 commit comments