Skip to content

Commit 6bf7f1c

Browse files
docs(permissions,qa): the unscoped admin_full_access anchor confers platform-admin standing under single only (#19876)
Part of #19145 Clause-②: no Carriers 1 and 2 of #19145 (the two documentation carriers), plus one more carrier of the same claim that the sweep found in the same tree. Carriers 3 (`packages/runtime/src/domains/automation.ts` `RUN_LIFECYCLE_DENY_MESSAGE`) and 4 (`packages/platform-objects/src/identity/sys-user.object.ts` `role.description`) are **not touched here**. They belong to other lanes, and a split has been requested from triage. #19145 remains open for them. ## The rule as the code decides it (all at `2bbb4623`) - `packages/core/src/security/resolve-authz-context.ts:865`: `legacyGrantAnchorRetired = postureEnforcesWall(resolveTenancyPosture())`. This reads the **requested** posture (the environment), not the effective one, so a walled deployment that is running degraded is still treated as walled. - `packages/spec/src/security/tenancy-posture.ts:54`: `postureEnforcesWall` returns `posture !== 'single'`. The walled postures are `group` and `isolated`. - `resolve-authz-context.ts:958`: an unscoped `admin_full_access` row sets `hasPlatformAdminGrant` only when `!legacyGrantAnchorRetired`, so only under `single`. The set's name and capabilities are still pushed on every posture, so the anchor is retired but the grant is not (pinned at `resolve-authz-context.platform-admin-config.test.ts:404`). - `resolve-authz-context.ts:1009`: the config anchor (`OS_PLATFORM_OWNER_EMAIL`, verified stored row) has no posture gate and confers on every posture. - The request path has no per-request pointer any more. `reportLegacyPlatformAdminGrant` and `resetLegacyPlatformAdminGrantReport` are gone (absence pinned at `platform-admin.test.ts:267`, with positive controls). The only operator-facing line left is the boot-time fail-closed error at `packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:897`, which names the legacy holder at `:912`. - The `single` half is described as it ships. Its fate is #11979's (Choice 4B), and nothing here anticipates that decision. ## Per carrier: old text, new text, code site **1. `content/docs/permissions/authorization.mdx` (combination semantics, step 3)** - Old: "That posture is derived at one site from **two anchors**, either of which is sufficient: the configured administrator list … and an **unscoped `admin_full_access` grant** row." - New: The anchors depend on the tenancy posture. The configured list is sufficient under every posture. The unscoped grant row is sufficient only under `single`. Under `group` / `isolated` it is no longer an anchor: the set still grants what any held set grants, but it confers no `PLATFORM_ADMIN` standing, so standing comes from configuration alone. Adds a link to the tenancy-modes page. - Code: `resolve-authz-context.ts:865`, `:958`, `:1009`; `tenancy-posture.ts:54`. **1b. `content/docs/permissions/permission-sets.mdx` (who holds `admin_full_access`). The sweep found this; it was not in the card.** - Old: "derived at a single site from **two anchors**, and either one is sufficient" and, for the grant row, "It still confers standing, and it is **deprecated**: a deployment resolving through it logs a pointer, once per process, at the configuration line that re-anchors it." - New: Which anchor is sufficient depends on posture. The config anchor is read under every posture. The grant row confers standing under `single` only. Under walled postures it is not an anchor, and nothing is logged about it per request. If a walled deployment holding such a row has no usable configured administrator, the boot log reports zero administrators, names the account holding the row, and points at `OS_PLATFORM_OWNER_EMAIL`. - Code: as above, plus `bootstrap-platform-admin.ts:897` / `:912` for the boot line. **2. `docs/qa/platform-checklist/areas/access-security.json`, item `access-security.platform-owner-email-anchor`, revision 1 to 2 (history appended; revision 1 untouched)** - Step 6. Old: "grep the process log for the once-per-process legacy-grant re-anchor pointer". New: run the #11663 L5 posture-split unit pins named in clause 6 and cite their output. The step states that there is no live log line to grep and that the only operator-facing line left is the boot-time fail-closed error. - Clause 6. Old: "the legacy anchor is honoured and loudly re-pointed … the once-per-process [authz] pointer names the OS_PLATFORM_OWNER_EMAIL config line … (reportLegacyPlatformAdminGrant, platform-admin.ts)", oracle `log`. New: the legacy grant is an anchor under `single` only, and a silent one. Under a walled posture (`postureEnforcesWall`, the requested posture) it confers no `PLATFORM_ADMIN` standing, although the held set keeps its capabilities. The config anchor still confers on that rig. The request path logs nothing. The oracle is now `test` (the L5 describes in `resolve-authz-context.platform-admin-config.test.ts` and `platform-admin.test.ts`), and the clause records that a change to the `single` half is #11979's to make. - Source parenthetical. Old: "… matchesConfiguredPlatformAdmin verified-only, reportLegacyPlatformAdminGrant". New: "… resolvePlatformAdminEmails loud-once resolve, matchesConfiguredPlatformAdmin verified-only" (all live symbols). Adds two anchors: `resolve-authz-context.ts#legacyGrantAnchorRetired` and `tenancy-posture.ts#postureEnforcesWall`. - The same claim appeared elsewhere in the item, so these parts were fixed to keep the item true: - Step 1 booted `isolated` with the variable unset. That boot is refused today (`packages/plugins/plugin-auth/src/auth-plugin.ts:551`). - Under a walled posture the seeded admin holds no grant-anchored standing, because no row is written and an existing row confers nothing. That made clause 1, clause 3 and the seeded-admin both-sides control false under `isolated`. - The live legs now boot `single` (the zero-config default) and keep it across restarts. Clause 1, the persona and the knownGap name the posture. A walled variant was added. `automated.ref` now says clause 6 is test-only. - Code: as above, plus `auth-plugin.ts:551`. ## Verification (head `2505ca61`) - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 45 commands. All 45 were run and each exit code was recorded. All 45 exited 0. `check:skill-examples` first exited 3 (PREREQUISITE NOT MET: client-react not built). That is not a measurement. It exited 0 after `@objectstack/client-react` / `@objectstack/client` were built. `--ran` reconciles as 45 derived / 45 run / 0 unrun. - `pnpm check:platform-checklist`: OK. Symbol anchors went from 620/638 to 622/640 resolved. The `areas/access-security.json` anchor census went from **44 to 46** against its floor of 44, and the floor and ledger are unchanged. - The control-byte self-scan over the three files found 0 hits. - Not measured locally, and declared to CI: the Build Docs job, the Test Core shards and the type-check lanes that the derivation lists as CI-only. ## Changeset None. `content/docs/**` and `docs/qa/**` are not in any published package's `files[]` (checked over every non-private `package.json`), so nothing publishes. This is `skip-changeset` territory, and no label was written by this seat, per the dispatch. ## Acceptance notes - **Sweep** (`content/docs/**`, `docs/qa/**`, excluding the auto-generated `references/` and `releases/`) for `either of which`, `two anchors`, `once-per-process`, `reportLegacyPlatformAdminGrant`, `unscoped admin_full_access`, `re-anchor` and `admin_full_access`: - The only other carrier of the claim was `permission-sets.mdx:167-183`, fixed above. - `authorization.mdx` (validity-window and deactivation sections): "an expired / deactivated unscoped `admin_full_access` grant no longer derives `platform_admin`" is still true on every posture, so it was left alone. - `deployment/self-hosting.mdx` first-boot section: already posture-correct. - **File surface beyond the claim:** `content/docs/permissions/permission-sets.mdx` is outside the claim's declared file surface. It was fixed in place because it is the same defect class, a mechanical prose fix in the same tree, and no other claim holds it. The claim's surface needs to be widened by the seat. - **Step 1 of the checklist item** could not run as written, independently of #19136, because an `isolated` boot with the variable unset is refused. It was fixed in the same edit because the item's other clauses depended on the boot posture. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f3a3afc commit 6bf7f1c

3 files changed

Lines changed: 43 additions & 22 deletions

File tree

‎content/docs/permissions/authorization.mdx‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,16 @@ implementation detail:
111111
`viewAllRecords` / `modifyAllRecords` (super-user bypass, posture-gated)
112112
short-circuit the object's *business* RLS. Crossing the **tenant wall**,
113113
though, requires the **`PLATFORM_ADMIN` posture** (ADR-0099 D1). That posture
114-
is derived at one site from **two anchors**, either of which is sufficient:
115-
the deployment's **configured administrator list** — an address declared in
114+
is derived at one site, and which anchors it reads depends on the
115+
deployment's [tenancy posture](/docs/deployment/tenancy-modes). The
116+
deployment's **configured administrator list** — an address declared in
116117
`OS_PLATFORM_OWNER_EMAIL`, matched against the caller's own stored and
117-
**email-verified** `sys_user` row — and an **unscoped `admin_full_access`
118-
grant** row. See
118+
**email-verified** `sys_user` row — is sufficient under every posture. An
119+
**unscoped `admin_full_access` grant** row is sufficient only under
120+
`single`; under the walled postures (`group` / `isolated`) it is no longer
121+
an anchor — the set it names still grants what any held set grants, but it
122+
confers no `PLATFORM_ADMIN` standing, so there standing comes from
123+
configuration alone. See
119124
[who holds `admin_full_access`](/docs/permissions/permission-sets#built-in-permission-sets)
120125
for both. A *scoped* grant, or
121126
piecemeal platform capabilities (`studio.access`, `manage_users`, …), grant

‎content/docs/permissions/permission-sets.mdx‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,9 @@ Auto-seeded on boot by `plugin-security`
165165
| `viewer_readonly` | Per-org | Read access only | Auditors / read-only stakeholders |
166166

167167
**Who holds `admin_full_access` is not (only) a grant row.** `PLATFORM_ADMIN`
168-
standing is derived at a single site from **two anchors**, and either one is
169-
sufficient:
168+
standing is derived at a single site from **two anchors**. Which of them is
169+
sufficient depends on the deployment's
170+
[tenancy posture](/docs/deployment/tenancy-modes):
170171

171172
- **Configuration — `OS_PLATFORM_OWNER_EMAIL`.** The deployment declares its
172173
administrators as one email address, or a comma-separated list of them. A
@@ -177,10 +178,16 @@ sufficient:
177178
compared trimmed and lower-cased, so `Ada@Example.com` and `ada@example.com`
178179
are one administrator, not two half-matches. Nothing is stored and nothing is
179180
granted — the answer is recomputed from configuration on every resolution.
181+
This anchor is read under every posture.
180182
- **An unscoped `admin_full_access` grant row** — a `sys_user_permission_set`
181-
row with `organization_id = NULL`, the original anchor. It still confers
182-
standing, and it is **deprecated**: a deployment resolving through it logs a
183-
pointer, once per process, at the configuration line that re-anchors it.
183+
row with `organization_id = NULL`, the original anchor. It confers standing
184+
under the `single` posture only. Under the walled postures (`group` /
185+
`isolated`) the row is no longer an anchor: the set it names still grants
186+
what any held set grants, but it confers no `PLATFORM_ADMIN` standing, and
187+
nothing is logged about it per request. If a walled deployment holding such a
188+
row ends up with no usable configured administrator, the boot log says it has
189+
zero platform administrators, names the account that holds the row, and points
190+
at `OS_PLATFORM_OWNER_EMAIL` as the way to restore that account's standing.
184191

185192
Which anchor a fresh deployment gets depends on its
186193
[tenancy posture](/docs/deployment/tenancy-modes). Under `single` the first

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,11 +2403,11 @@
24032403
"title": "OS_PLATFORM_OWNER_EMAIL platform-admin anchor: only a VERIFIED stored-email match confers PLATFORM_ADMIN — unset, unverified match, non-match and malformed lists each confer nothing, fail closed",
24042404
"since": "v17",
24052405
"status": "active",
2406-
"revision": 1,
2406+
"revision": 2,
24072407
"priority": "P1",
24082408
"surface": "api",
24092409
"personas": [
2410-
"admin (seeded dev admin — grant-anchored via the unscoped admin_full_access row, the ADR-0068 D2 contrast; its address is seed-stamped verified per #11343)",
2410+
"admin (seeded dev admin — its address is seed-stamped verified per #11343. Under single, the posture this item boots, it is grant-anchored via the unscoped admin_full_access row that first-registrant promotion writes, the ADR-0068 D2 contrast; under a walled posture no such row is written and an existing one confers no platform-admin standing (#11663 L5), so it would stand only if OS_PLATFORM_OWNER_EMAIL named its address)",
24112411
"fresh member M (the config-anchor subject; a fresh sign-up's sys_user row is UNVERIFIED, which is exactly the unverified-match fixture)"
24122412
],
24132413
"fixtures": {
@@ -2417,20 +2417,20 @@
24172417
"a manage_platform_settings-floor probe route: GET /api/v1/datasources answers 401/403 BEFORE any service resolution (packages/services/service-datasource/src/admin-routes.ts; DATASOURCE_ADMIN_CAPABILITY), and admin_full_access carries that capability (packages/spec/src/identity/eval-user.zod.ts) — so the probe discriminates platform-admin standing cleanly"
24182418
],
24192419
"knownGaps": [
2420-
"the ENTITLED live leg needs a VERIFIED second account, and no stock HTTP path verifies one: email_verified is statically readonly at the user-context API (stripped — the write-path-guards class), a fresh sign-up reads unverified, and the seeded dev admin — whose address IS stamped verified at seed time (#11343, auth-plugin.ts) — also holds the unscoped admin_full_access grant, so its 200s can never attribute to the config anchor. Verify M's address via a verification-enabled boot with mail capture (identity-auth.email-verification-loop's fixture, itself a knownGap there) or a system-context stamp through the same isSystem doorway the seed itself uses; without either, clause 5's live leg scores blocked(fixture) and the entitled side rests on the unit pin"
2420+
"the ENTITLED live leg needs a VERIFIED second account, and no stock HTTP path verifies one: email_verified is statically readonly at the user-context API (stripped — the write-path-guards class), a fresh sign-up reads unverified, and the seeded dev admin — whose address IS stamped verified at seed time (#11343, auth-plugin.ts) — also holds the unscoped admin_full_access grant under single (the posture this item boots), so its 200s can never attribute to the config anchor. Verify M's address via a verification-enabled boot with mail capture (identity-auth.email-verification-loop's fixture, itself a knownGap there) or a system-context stamp through the same isSystem doorway the seed itself uses; without either, clause 5's live leg scores blocked(fixture) and the entitled side rests on the unit pin"
24212421
]
24222422
},
24232423
"steps": [
2424-
"boot showcase isolated with the variable UNSET; admin session; sign up fresh member M and record M's email; as M: GET /api/v1/datasources and GET /api/v1/auth/me/permissions (read systemPermissions) — the baseline refusals",
2424+
"boot showcase single (OS_TENANCY_POSTURE=single — also what a deployment with no tenancy configuration resolves to) with the variable UNSET — a walled posture (group / isolated) refuses to boot with it unset (plugin-auth init; out of scope here, see negative) — and keep that posture for every restart below; admin session; sign up fresh member M and record M's email; as M: GET /api/v1/datasources and GET /api/v1/auth/me/permissions (read systemPermissions) — the baseline refusals",
24252425
"restart with OS_PLATFORM_OWNER_EMAIL=<M's email> (M's row is unverified); as M repeat both probes",
24262426
"restart with OS_PLATFORM_OWNER_EMAIL=<an address matching no sys_user row>; repeat M's probes",
24272427
"restart with a malformed list, e.g. OS_PLATFORM_OWNER_EMAIL='<M's email>, not an address'; capture the [authz] refusal from the process log (it prints once, on the first derivation after the value is seen); as M repeat the probes — the WHOLE variable is refused, so even the well-formed entry confers nothing",
24282428
"entitled side (see knownGaps for the verified-member fixture): with M's row verified and the variable naming M, as M repeat the probes and read /api/v1/auth/me/permissions — expect the declared admin envelope; where the fixture is unavailable, run the unit pin and cite its output instead",
2429-
"with the variable unset again, drive one admin-standing derivation as the seeded admin (any authenticated request) and grep the process log for the once-per-process legacy-grant re-anchor pointer"
2429+
"the walled half of the legacy grant anchor: run the #11663 L5 posture-split unit pins named in clause 6 and cite their output. There is no live log line to grep — the once-per-process legacy-grant re-anchor pointer was retired with the walled anchor (#19136), and nothing replaces it on the request path; the only operator-facing line left is plugin-security's boot-time fail-closed error, which fires when a walled rig has no usable configured administrator"
24302430
],
24312431
"acceptance": [
24322432
{
2433-
"clause": "unset = ZERO config-derived administrators, and the ADR-0068 D2 unscoped-grant anchor stays the only door: M's datasource probe is refused server-side (401/403 per identity, never 200) and M's systemPermissions lacks manage_platform_settings, while the seeded admin's identical probe answers 200 (both sides of the gate)",
2433+
"clause": "unset = ZERO config-derived administrators, and under single the ADR-0068 D2 unscoped-grant anchor stays the only door: M's datasource probe is refused server-side (401/403 per identity, never 200) and M's systemPermissions lacks manage_platform_settings, while the seeded admin's identical probe answers 200 (both sides of the gate)",
24342434
"oracle": "api",
24352435
"verify": "M's GET /api/v1/datasources >=400 with the declared code; M's /auth/me/permissions systemPermissions array excludes manage_platform_settings; admin's same GET is 200",
24362436
"evidence": "both personas' traces"
@@ -2460,10 +2460,10 @@
24602460
"evidence": "test output (and, when the fixture exists, M's entitled traces)"
24612461
},
24622462
{
2463-
"clause": "additive, never subtractive — the legacy anchor is honoured and loudly re-pointed: with the variable unset, standing resting on the unscoped admin_full_access grant alone still resolves PLATFORM_ADMIN, and the once-per-process [authz] pointer names the OS_PLATFORM_OWNER_EMAIL config line that re-anchors it (reportLegacyPlatformAdminGrant, platform-admin.ts)",
2464-
"oracle": "log",
2465-
"verify": "seeded admin's request succeeds AND the process log carries the legacy-grant pointer naming the variable; it appears once — grep the whole log, not the tail",
2466-
"evidence": "the log excerpt + the admin trace"
2463+
"clause": "the legacy grant is an anchor under single ONLY, and silently so (#11663 L5): under single, standing resting on the unscoped admin_full_access grant alone still resolves PLATFORM_ADMIN (clauses 1 and 3 observe it live); under a walled posture — postureEnforcesWall, i.e. every posture except single, read from the REQUESTED posture — the same row confers no PLATFORM_ADMIN standing (the held set still grants its own capabilities; only the rung and the built-in platform_admin position go), while the config anchor keeps conferring on that same walled rig; and the request path logs nothing about the row on any posture",
2464+
"oracle": "test",
2465+
"verify": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts — the '[#11663 L5] the legacy grant row is an anchor under `single`, and NOWHERE else', '[#11663 L5] the legacy anchor confers only on the rigs Choice 4A keeps it for' and '[#11663 L5] standing across postures' describes; packages/core/src/security/platform-admin.test.ts — '[#11663 L5] the legacy-grant deprecation pointer is retired' (both pointer symbols absent from the module and from the @objectstack/core security entry, with positive controls). The single half's own fate is #11979's (Choice 4B): if that lands, this clause is re-authored, not read as a regression",
2466+
"evidence": "test output"
24672467
}
24682468
],
24692469
"negative": [
@@ -2478,19 +2478,22 @@
24782478
"unverified match (nothing)",
24792479
"non-match (nothing)",
24802480
"comma-separated list — duplicates collapsed, trim+lowercase normalization (platform-admin.ts)",
2481-
"malformed entry (whole-variable refusal, loud once)"
2481+
"malformed entry (whole-variable refusal, loud once)",
2482+
"walled posture — the unscoped admin_full_access grant alone confers no PLATFORM_ADMIN standing (unit pin, clause 6)"
24822483
],
24832484
"traps": [
24842485
"wrong-persona",
24852486
"auth-state-leak"
24862487
],
24872488
"automated": {
24882489
"kind": "unit",
2489-
"ref": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts (the acceptance criterion, all four fail-closed arms, the ⭐ stored-row-not-seed pin, the legacy-grant pointer loudness) + packages/core/src/security/platform-admin.test.ts (parse/refusal/memo) + packages/core/src/security/admin-standing-surface.test.ts. STILL MANUAL: the live-HTTP arms over a real boot (clauses 1-4 and 6) — the pins run the derivation in-process; this item's live legs prove the same verdicts through the mounted server"
2490+
"ref": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts (the acceptance criterion, all four fail-closed arms, the ⭐ stored-row-not-seed pin, the #11663 L5 posture split of the legacy grant anchor) + packages/core/src/security/platform-admin.test.ts (parse/refusal/memo) + packages/core/src/security/admin-standing-surface.test.ts. STILL MANUAL: the live-HTTP arms over a real boot (clauses 1-4; clause 6 is test-only) — the pins run the derivation in-process; this item's live legs prove the same verdicts through the mounted server"
24902491
},
24912492
"source": [
24922493
"packages/core/src/security/resolve-authz-context.ts#hasPlatformAdminStanding (§6b-config — the config anchor inside the ONE derivation site; additive, never subtractive) + (hasPlatformAdminStanding, the id-shaped projection)",
2493-
"packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails (parsePlatformAdminEmails whole-variable refusal, loud-once resolve, matchesConfiguredPlatformAdmin verified-only, reportLegacyPlatformAdminGrant)",
2494+
"packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails (parsePlatformAdminEmails whole-variable refusal, resolvePlatformAdminEmails loud-once resolve, matchesConfiguredPlatformAdmin verified-only)",
2495+
"packages/core/src/security/resolve-authz-context.ts#legacyGrantAnchorRetired (#11663 L5 — the unscoped admin_full_access row derives PLATFORM_ADMIN only when the requested posture enforces no wall)",
2496+
"packages/spec/src/security/tenancy-posture.ts#postureEnforcesWall (walled = every posture except single)",
24942497
"packages/core/src/security/admin-standing-surface.ts (the env declared a non-table derivation input — no break-glass write can reach it)",
24952498
"packages/types/src/env.ts#PLATFORM_OWNER_EMAIL_ENV,200 (PLATFORM_OWNER_EMAIL_ENV / resolvePlatformOwnerEmail)",
24962499
"packages/plugins/plugin-auth/src/auth-plugin.ts (#11343 — the dev seed stamps its admin's address verified)",
@@ -2503,6 +2506,12 @@
25032506
"date": "2026-08-30",
25042507
"change": "new — the #11663 L2 deployment-config platform-admin anchor landed (#13146, 2026-08-29) with no checklist coverage. Env-conferred standing is a security gate and needs both sides asserted live: confer on a VERIFIED stored-row match only; nothing on unset / unverified / non-match / malformed (whole-variable fail-closed, loud once); plus the legacy-grant re-anchor pointer. Written as guard assertion per the shipped-guard posture; the entitled live leg is fixture-gapped (verified second account) and rests on the unit pin meanwhile",
25052508
"ref": "#13146"
2509+
},
2510+
{
2511+
"revision": 2,
2512+
"date": "2026-09-23",
2513+
"change": "re-anchored on the walled retirement of the legacy grant anchor (#19136, issue #19145): step 6 and clause 6 told the runner to grep the process log for the once-per-process re-anchor pointer and cited reportLegacyPlatformAdminGrant, both removed — clause 6 now states the posture split (an anchor under single only, silent everywhere) with the #11663 L5 unit pins as its oracle, and the source parenthetical names live symbols. The item's live legs move from isolated to single: an isolated boot with the variable unset refuses to start (plugin-auth init), and under a walled posture the seeded admin holds no grant-anchored standing, so clauses 1 and 3 and the seeded-admin both-sides control only hold under single. Persona and knownGap state the posture. The single half is described as it ships; its fate is #11979's",
2514+
"ref": "#19145"
25062515
}
25072516
]
25082517
},

0 commit comments

Comments
 (0)