Repository navigation
Commit 74832b6
Fixes #18336
Clause-②: yes
Leg **L5** of the accepted #11663 platform-admin re-anchor design — the
**EXIT** of the migration window L4 opened in 17.3.0. Under a **walled**
tenancy posture (`group` / `isolated`), a legacy unscoped
`admin_full_access` grant row no longer confers `PLATFORM_ADMIN`, and
the once-per-process deprecation pointer that announced this retires
with it.
## The ruling this is built against, and the two constraints it did not
touch
The maintainer ruled option **B** on 2026-09-16
(`issuecomment-5695555178`): #11663's design governs over ADR-0131 D13
for these two symbols, and the card stays on the **17.x** line. ADR-0131
was amended accordingly on `origin/main` before this branch was cut —
verified here, not inherited: commit `0b271e2e`,
`docs/adr/0131-total-organization-ownership-no-null-organization-id.md`,
and the amended **D5** states the scope in its own words:
> The legacy unscoped anchor retires with D13 **for the `single` posture
only**: its **walled** half, together with the two symbols that carry
the migration window — `reportLegacyPlatformAdminGrant` and
`resetLegacyPlatformAdminGrantReport` — **left D13 on 2026-09-16** and
retire on the **17.x** line under #11663's design instead (#18336).
Two standing constraints the ruling did **not** touch, and which make
the scope walled-only rather than all-postures:
> The rest of Choice 4A (#11974, 2026-08-25) stands: **retiring the
walled write must not retire the `single` one**
(maintainer, 2026-09-08, #16682 comment `5587754690`)
> 比如临时启动的开发环境,我不可能去配置啊
(maintainer, 2026-09-16, the session that produced the ruling — the
zero-config constraint)
⇒ The `single` posture keeps its zero-config first-user promotion
**and** its grant row. Its disposition is #11979's (Choice 4B), out of
scope here. The all-postures reading was measured on this card before
dispatch and turns **11 pins red**; this branch does not take it, and
the pin `an unscoped admin_full_access grant still confers
PLATFORM_ADMIN with no config at all` is green and kept deliberately.
## What changed
**`packages/core/src/security/resolve-authz-context.ts`**
- §6b: the legacy row-id route to `hasPlatformAdminGrant` is
posture-keyed. A new `legacyGrantAnchorRetired =
postureEnforcesWall(resolveTenancyPosture())`, hoisted once beside the
`unscopedUserPsIds` set, gates the one line that derives standing from
an unscoped `admin_full_access` grant.
- The `else if (hasPlatformAdminGrant)` arm is **removed**. It carried
one thing — the deprecation pointer — and the chain collapses to a bare
`if (configConfersPlatformAdmin)`.
**`packages/core/src/security/platform-admin.ts` / `security/index.ts`**
- `reportLegacyPlatformAdminGrant` and
`resetLegacyPlatformAdminGrantReport` are deleted, along with their
once-per-process latch, and dropped from the re-export list.
**`packages/plugins/plugin-security/src/bootstrap-platform-admin.ts`**
- The boot-side pointer call and the `sys_user` read that fed it are
gone.
- ⭐ **The fail-closed backstop is no longer skipped when a legacy holder
exists.** L4 skipped it on the stated ground that 「the deployment HAS an
administrator, on the old anchor」 and that the pointer carried the
remedy instead. Both premises die with the walled dual read: such a rig
now has **zero** platform administrators. It is told, at `error`, naming
the variable, the row and its holder.
## The boundary this card draws, stated because it is easy to read wider
than it is
The retirement takes the **anchor**, not the **row** and not the
**grant**. A walled holder keeps the `admin_full_access` permission set
they hold — its name and everything downstream resolves from that name —
and loses only platform-admin **standing**: the rung, the built-in
`platform_admin` position, and every consumer of those. Nothing here
writes, deletes or re-owns a grant row; that row's ownership is ADR-0131
**C3**'s, on the v18 line, and the amendment says the drop 「adds no NULL
row, removes none, and re-owns none」. `packages/spec` is untouched (this
lane's standing red line).
That boundary is **pinned**, not merely described: `the retirement takes
the ANCHOR, not the GRANT — the held set still grants what it grants`.
If a later leg widens to the row, that pin is the one to re-author
deliberately.
## Evidence
**Clause-② confirmed on the rebuilt artefact**, not on source alone
(`turbo run build --filter=@objectstack/plugin-security`, 18/18 tasks,
exit 0):
| probe on `packages/core/dist` | `reportLegacy…` | `resetLegacy…` |
positive control | negative control |
|---|---|---|---|---|
| occurrences in `index.d.ts` | **0** | **0** |
`matchesConfiguredPlatformAdmin` 4, `setPlatformAdminConfigSink` 2 | — |
| `name in await import('index.js')` | **false** | **false** | both
`true`, `typeof function` | `notAnExport_xyz` false |
⇒ two symbols leave a published entry. Breaking shape, graded `minor`
under the launch-window convention, on `@objectstack/core` **and**
`@objectstack/plugin-security`.
**Negative-pin dissolution.** Every negative pin this branch relies on
was dissolved: the forbidden behaviour was put back on disk (anchor hit
verified, blob hash before/after recorded), the suite was watched go
red, and the restore was proven by `blob == HEAD` and an empty `git diff
HEAD` — not by an exit code.
| pin family | mutation | result |
|---|---|---|
| walled rigs derive nothing from the legacy row | delete the `&&
!legacyGrantAnchorRetired` gate | **5 failed / 26 passed** — the two
walled-standing arms, the anchor-vs-grant boundary pin, the seeded-read
arm, and the fourth-corner pin |
| the boot fail-closed line is no longer skipped | re-wrap the emission
in `if (!unscopedHolder)` | **1 failed / 23 passed** — `expected
"vi.fn()" to be called 1 times, but got 0 times` |
| the symbol is gone from the module | add a stub `export function
reportLegacyPlatformAdminGrant` to `platform-admin.ts` | **1 failed / 24
passed** — the module pin only; the entry pin stayed green |
| the symbol is gone from the published entry | add the export to
`security/index.ts` | **1 failed / 24 passed** — the entry pin only; the
module pin stayed green |
The last two are deliberately separate: each mutation reds exactly one
of the two absence pins, which is what makes them two measurements
rather than one restated twice.
⛔ Per the security family's disclosure discipline, the evidence above
describes the consequence of a **proposed** mutation to this branch and
carries no reproduction recipe for the shipped product.
**A measurement defect found and fixed in the suite's own double.** The
pin `adds NO read: the recorded query multiset is identical under both
answers of the gate` failed on first run, reporting a `sys_position`
read whose `$in` differed by `platform_admin`. It was not a real
difference: the resolver issues `{ name: { $in: grants.positions } }` —
a live reference to an array §6c mutates **after** the query is issued —
and the double stored `opts.where` by reference, so the comparison read
the array's final contents instead of the query as issued. The double
now deep-copies at record time, and the two postures are measured
byte-identical. The same latent shape in a **sibling** harness is
reported below as an out-of-scope finding.
**Tests and gates.** `pnpm typecheck` green for `@objectstack/core` and
`@objectstack/plugin-security` (`VERDICT command-exit 0`). Targeted
suites: `resolve-authz-context.platform-admin-config.test.ts` +
`platform-admin.test.ts` **56 passed**,
`bootstrap-platform-admin-walled-owner.test.ts` **24 passed**. Gate
families were re-derived from the real diff with
`scripts/pm/dispatch-gates.mjs --commands` (no paths passed) and
reconciled with `--ran`.
## Acceptance notes
- `docs/qa/platform-checklist/areas/access-security.json` carries a
clause describing the retired behaviour (「the once-per-process [authz]
pointer names the OS_PLATFORM_OWNER_EMAIL config line」) and names
`reportLegacyPlatformAdminGrant` in an anchor's parenthetical. It is
**outside this dispatch's declared file surface**, so it is not touched
here; the anchor symbol itself is `parsePlatformAdminEmails`, which
still resolves. Noted for whoever owns the checklist.
- `PlatformAdminConfigSink.warn` now has no caller inside
`@objectstack/core`. It is left in place deliberately: removing it would
be a **third** published-surface removal, wider than this card, and it
would break every consumer that passes an object literal with a `warn`
member.
- ADR-0131's own text is untouched — it was amended for this leg by
#18413 before this branch existed.
## Patch round 1 — the contract review's findings 1, 2 and 4
The at-tier review (`issuecomment-5737292414`) returned FAIL on one
blocking finding and two cheap ones. All three are addressed in this
branch; nothing that PASSED was touched.
### Finding 1 (blocking) — the retirement was done at one of the
anchor's TWO readers
`packages/plugins/plugin-auth/src/last-admin-guard.ts` enumerated
platform administrators from the same unscoped, in-window
`admin_full_access` row on **every** posture, while §6b had stopped
deriving standing from it under a wall. One anchor, two readers,
disagreeing — and the disagreement is introduced by this diff, so it is
fixed here rather than sequenced.
Grade 1 of `resolveAdminUserIds` is now keyed on the identical
expression the derivation site uses
(`postureEnforcesWall(resolveTenancyPosture())`), hoisted once per
enumeration; under a wall the grade's `sys_permission_set` scan is
skipped outright, so that path also issues one read fewer. `single` is
untouched, and the two zero-population refusals gained a conditional
clause naming `OS_PLATFORM_OWNER_EMAIL`, because 「restore the
`admin_full_access` row」 stopped being a remedy that ends the emptiness
on a walled rig.
Direction, stated because it reads backwards easily: under a wall the
guard now refuses **more** (it protects the config-anchored
administrator a write used to be allowed to take away) and refuses
**wrongly** less (revoking the inert row is no longer reported as
removing the last administrator). Both are narrowings toward the
already-declared model.
**Walled coverage was as much the finding as the logic.**
`last-admin-guard.config-anchor.test.ts` gains a seven-case `[#11663
L5]` suite that drives both walled postures and pairs every walled arm
with a `single` control on the identical fixture. The two posture-blind
sibling suites (`last-admin-guard.test.ts`,
`last-admin-guard.re-pricing.test.ts`) now pin `single` explicitly
instead of inheriting whatever the box exports, and the pre-L5 pin
「ALLOWS it when the same user also holds the legacy grant」 says `single`
in its title.
Dissolution, both new pin families:
| mutation | result |
|---|---|
| remove the grade-1 posture gate | **2 failed / 16 passed** — the
blocking arm fails with `promise resolved "{ id: 'usr_owner', …
email_verified: false }" instead of rejecting`, i.e. the write that ends
the last config-anchored standing goes through; the inverse arm fails
with it. Every `single` control stayed green. |
| force the walled remedy clause empty | **1 failed / 17 passed** — only
the remedy arm; the refusal itself fired unchanged, which is what makes
the clause additive and conditional rather than a new verdict. |
### Finding 2 — the ② naming gap
Closed by naming, not by stating a precedent.
`packages/runtime/src/index.ts:247` and
`packages/plugins/plugin-hono-server/src/adapter.ts:4` are `export *
from '@objectstack/core'` (re-verified here; a repo-wide grep finds
exactly those two), so the two removed names leave those published
surfaces too. Both are now in the changeset, alongside
`@objectstack/plugin-auth`, whose behaviour this round changes. The
level is unaffected — all publishable packages sit in one `fixed` group
— and the reason for naming them is the CHANGELOG tombstone an upgrading
consumer of *those* packages greps. This follows the `ApiRegistry`
precedent rather than the core-only one.
### Finding 4 — prose
`admin-standing-surface.ts`'s `sys_user_permission_set` reason is
posture-qualified, and so is the module header's account of where
`hasPlatformAdminGrant` comes from. ⚠️ The first attempt cited the
tracker ids and `check:doc-authoring` refused it — that `reason` is a
runtime string that reaches customers, and the baseline is shrink-only.
The sentence now says the same thing without them.
### Finding 3
Not touched here, by instruction — the seat filed it separately as
#19145.
<sub>⚠️ This section was appended by the `domain:services` seat, ⛔ not
by the delivering dev: the dev writes a PR body once at creation and
never PATCHes it, so it handed the text over in its report. The seat
verified the load-bearing claims against the tree before appending — the
guard is posture-keyed at `last-admin-guard.ts:941` (control on
`origin/main`: 0 occurrences) and the changeset names all five packages
— and ⛔ did not verify every figure quoted above, which is the next
reviewer's to test.</sub>
## Patch round 2 — the last unkeyed reader of the anchor
The at-tier review of `61a951c97` (`issuecomment-5737610351`) returned
FAIL on one blocking finding: the retirement was still done at only some
of the anchor's readers. This round closes the class rather than one
instance.
### The reader set, re-measured here and now closed
#11663's design P3 enumerates the in-repo consumers of 「who are the
platform admins?」. Their state on this head, each verified in this
branch rather than inherited:
| reader | how it answers | state |
|---|---|---|
| `resolve-authz-context.ts` §6b | derives | keyed (round 0) |
| `plugin-security/src/bootstrap-platform-admin.ts` | reads the row at
boot | keyed (round 0) |
| `plugin-auth/src/last-admin-guard.ts` | counts the population | keyed
(round 1) |
| `plugin-security/src/explain-engine.ts` | **consumes the verdict** | ✅
needs nothing |
| `plugin-auth/src/ensure-default-organization.ts` | **confers** on the
population | keyed (**this round**) |
`explain-engine.ts` was checked, not assumed: at `:562` it sets
`hasPlatformAdminGrant: grants.posture === 'PLATFORM_ADMIN'`, reading
the resolver's one verdict back, and its only `sys_user_permission_set`
read is `collectGrantProvenance`, which reports *why grants were
dropped* and derives no standing. It moved with §6b in round 0.
**An independent sweep says the set is closed.** A grep for source reads
of an *unscoped* `admin_full_access` grant (`organization_id: null`
beside the admin permission set, non-test) returns exactly two files in
the whole tree — `bootstrap-platform-admin.ts` and
`ensure-default-organization.ts` — both now keyed. The remaining files
that merely name `ADMIN_FULL_ACCESS` were read: a config-validation
refusal, the spec declaration, the seeded set definition, a CLI
formatter, sandbox type assertions, and the two already-keyed derivation
sites.
### What changed
**`ensureDefaultOrganization` step 2** — its legacy fallback is keyed on
the same expression the derivation uses. This reader is worse than the
guard: the guard *counted* a holder, this one **confers** — it writes
the account it picks a `sys_member { role: 'owner' }` row on the Default
Organization and hands them the org's seeded rows through
`claimSeedOwnership`. Unkeyed, a walled rig carrying a legacy row bound,
as an organization owner, an account the derivation grades `MEMBER` and
the bootstrap logs at `error` as non-existent. Reachable despite
plugin-auth skipping the helper under a wall (`auth-plugin.ts:1100`),
because `@objectstack/organizations` — which a walled posture requires —
wraps and calls it.
Under a wall with no verified declared owner, the answer is now
`no_admin`: **a wait, not a silent mis-binding.** The declared owner's
verifying update re-runs the helper through the trigger predicate, and
the binding happens then.
**The trigger predicate's grant-insert arm** retires with it under a
wall — cost only, since the helper answered `no_admin` from that grant
either way. ⛔ The `sys_user` arms are deliberately untouched: on a
walled rig the declared owner's verifying update is the only write that
ever grows the population, so keying that off would strand the
deployment forever. The sibling predicate `shouldReplayBootstrapFor` in
`plugin-security` already spells the same walled exclusion.
**Coverage.** `ensure-default-organization.test.ts` had 0 posture-env
sites; it now pins `single` for the whole file instead of inheriting the
ambient environment, and gains a seven-case `[#11663 L5]` walled suite
in which every walled arm is paired with a `single` control on the
identical fixture — including the zero-config default (both tenancy
inputs unset), a no-added-read pin, and the config anchor still binding
under a wall.
Dissolution of the two new pin families:
| mutation | suite | result |
|---|---|---|
| remove the step-2 posture gate | `ensure-default-organization.test.ts`
| **3 failed / 38 passed** — the walled binding arm fails with `expected
{ defaultOrgCreated: true, … } to match object { defaultOrgCreated:
false, … }`, i.e. the organization really is created and the legacy
holder really is bound; the unverified-owner wait and the no-added-read
pin fail with it. Every `single` control stayed green. |
| the same mutation | `walled-default-org-self-registrant.pin.test.ts` |
**1 failed / 4 passed** — the re-authored pin, and only it. The
surviving positive control stayed green, so the inverted assertion is
falsifiable rather than vacuous. |
| delete the trigger's walled exclusion |
`ensure-default-organization.test.ts` | **1 failed / 40 passed** — only
the trigger arm; the helper's own answers were untouched, which is what
makes it a cost fix and not a verdict change. |
### The one declared out-of-lane change
`packages/plugins/organizations/src/walled-default-org-self-registrant.pin.test.ts`
pinned the pre-L5 model: its positive control asserted that under
`isolated` a legacy grant still anchors the bootstrap, saying in its own
words that the negative cases were 「green only because objectstack#11211
stopped minting that row on a walled box, not because this path is
inert」. This diff makes that path inert, so the pin is **inverted rather
than deleted** — the fixture is cloud#1509's reported shape and it is
the only place in the tree that pins what a walled rig does with a
legacy row at this seam. The suite's method survives: the operator case
above it (a declared address reading verified) carries the
positive-control role, and the header now records that clause 2 rests on
the keying rather than on what gets minted.
⚠️ `packages/plugins/organizations` is **not** in this lane's named
package set. This is a deliberate, declared, **test-only** widening
forced by CI — its runtime source is untouched.
### Changeset
`@objectstack/organizations` is added at `minor`. ⛔ Not because a test
was re-authored — that earns nothing. It is named because the behaviour
an operator gets *from that package* moves: it is the runtime that
performs the default-organization bootstrap on a walled deployment, and
after this change it no longer creates a Default Organization for a
legacy grant holder, nor binds them as its owner. The tombstone belongs
in the CHANGELOG of the package whose behaviour changed, which is the
same reasoning that named `@objectstack/runtime` and
`@objectstack/plugin-hono-server` in round 1.
### One thing named rather than left to be found
`plugin-approvals/src/approval-service.ts:1402` decides its override
actor with `posture === 'PLATFORM_ADMIN' ||
perms.includes(ADMIN_FULL_ACCESS)`. Its first arm moved with §6b in
round 0; its second reads the permission-set NAME, which a walled legacy
holder still carries — the declared 「the retirement takes the ANCHOR,
not the GRANT」 boundary this PR pins. ⚠️ Measured: this diff does
**not** change that function's answer for such an account (before, both
arms were true; after, the second still is), so it is not a dual-track
this PR opens and it is not fixed here. It belongs to whatever leg
disposes of the row itself — ADR-0131 C3 on the v18 line, and #11979 for
`single`.
<sub>⚠️ Appended by the `domain:services` seat, ⛔ not by the delivering
dev, which writes a body once at creation and never PATCHes it. The seat
independently re-ran the closure sweep with a **different, cruder**
instrument (file-level co-occurrence of the token with an
`organization_id: null` spelling) and reached the same conclusion by a
different route: its extra hits — `sys-user-position.object.ts`,
`runtime/src/security/actor-user.ts`,
`spec/src/identity/eval-user.zod.ts` — were each read and are a
refusal's explanatory comment, a docstring example, and the spec's own
constant declaration. ⛔ None derives the population. The seat also
verified the surface (16 files, nothing outside the authorised set, zero
`packages/spec`, the `organizations` touch test-only). The dissolution
figures above are the dev's and are the next reviewer's to test.</sub>
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5eebc9e commit 74832b6
16 files changed
Lines changed: 1045 additions & 388 deletions
File tree
- .changeset
- packages
- core/src/security
- plugins
- organizations/src
- plugin-auth/src
- plugin-security/src
Lines changed: 24 additions & 0 deletions
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
99 | 99 | | |
100 | 100 | | |
101 | 101 | | |
102 | | - | |
103 | | - | |
104 | | - | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
105 | 106 | | |
106 | 107 | | |
107 | 108 | | |
| |||
130 | 131 | | |
131 | 132 | | |
132 | 133 | | |
133 | | - | |
134 | | - | |
135 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
136 | 142 | | |
137 | 143 | | |
138 | 144 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
216 | 220 | | |
217 | 221 | | |
218 | 222 | | |
| |||
221 | 225 | | |
222 | 226 | | |
223 | 227 | | |
224 | | - | |
225 | | - | |
226 | 228 | | |
227 | 229 | | |
228 | 230 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
| 14 | + | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
22 | | - | |
23 | 21 | | |
24 | 22 | | |
25 | 23 | | |
| |||
41 | 39 | | |
42 | 40 | | |
43 | 41 | | |
44 | | - | |
45 | 42 | | |
46 | 43 | | |
47 | 44 | | |
| |||
50 | 47 | | |
51 | 48 | | |
52 | 49 | | |
53 | | - | |
54 | 50 | | |
55 | 51 | | |
56 | 52 | | |
| |||
244 | 240 | | |
245 | 241 | | |
246 | 242 | | |
247 | | - | |
248 | | - | |
249 | | - | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | | - | |
254 | | - | |
255 | | - | |
256 | | - | |
257 | | - | |
258 | | - | |
259 | | - | |
260 | | - | |
261 | | - | |
262 | | - | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
263 | 276 | | |
264 | 277 | | |
265 | | - | |
266 | | - | |
267 | | - | |
268 | | - | |
269 | | - | |
270 | | - | |
271 | | - | |
272 | | - | |
273 | | - | |
274 | | - | |
275 | | - | |
276 | | - | |
277 | | - | |
278 | | - | |
279 | | - | |
280 | | - | |
281 | | - | |
282 | | - | |
283 | | - | |
284 | | - | |
285 | | - | |
286 | | - | |
287 | | - | |
288 | | - | |
289 | | - | |
290 | | - | |
291 | | - | |
292 | | - | |
293 | | - | |
294 | | - | |
295 | | - | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
296 | 286 | | |
297 | 287 | | |
298 | 288 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
331 | 331 | | |
332 | 332 | | |
333 | 333 | | |
334 | | - | |
| 334 | + | |
| 335 | + | |
335 | 336 | | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
340 | | - | |
341 | | - | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
342 | 342 | | |
343 | | - | |
344 | | - | |
345 | | - | |
346 | | - | |
347 | | - | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
348 | 351 | | |
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 | | - | |
0 commit comments