RFC Meeting: Service Locator for MediaWiki core (2016-03-23, #wikimedia-office)

Hosted by daniel on Mar 23 2016, 9:00 PM - 10:00 PM.


See the Architecture meetings page for more general information about this meeting (also: Phab query: list of upcoming RFC meetings, Phab query: list of all RFC meetings).

Recurring Event

Event Series
This event is an instance of E66: ArchCom RFC Meeting Wxx: <topic TBD> (<see "Starts" field>, #wikimedia-office), and repeats every week.

Event Timeline

RobLa-WMF updated the event description. (Show Details)
RobLa-WMF renamed this event from RFC Meeting: <topic TBD> (<see "Starts" field>, #wikimedia-office) to RFC Meeting: Service Locator for MediaWiki core (2016-03-23, #wikimedia-office).

Meeting log captured here:

1Copied from https://tools.wmflabs.org/meetbot/wikimedia-office/2016/wikimedia-office.2016-03-23-21.03.log.txt
221:03:20 <DanielK_WMDE_> #startmeeting RFC office hour
321:03:21 <wm-labs-meetbot> Meeting started Wed Mar 23 21:03:20 2016 UTC and is due to finish in 60 minutes. The chair is DanielK_WMDE_. Information about MeetBot at http://wiki.debian.org/MeetBot.
421:03:21 <wm-labs-meetbot> Useful Commands: #action #agreed #help #info #idea #link #topic #startvote.
521:03:21 <wm-labs-meetbot> The meeting name has been set to 'rfc_office_hour'
621:03:34 <gwicke> next is #topic
721:03:40 <DanielK_WMDE_> #topc Service Locator for MediaWiki core https://phabricator.wikimedia.org/T124792
821:03:47 <DanielK_WMDE_> #link https://phabricator.wikimedia.org/T124792
921:03:54 <DanielK_WMDE_> \o/
1021:03:55 <bd808> DanielK_WMDE_: spelling
1121:03:57 <gwicke> #topic Service Locator for MediaWiki core https://phabricator.wikimedia.org/T124792
1221:04:20 <gwicke> heh, you need to do it as the chair
1321:04:22 <DanielK_WMDE_> bd808: hm?
1421:04:25 <bd808> only DanielK_WMDE_ can do that unless he adds another admin
1521:04:35 <bd808> DanielK_WMDE_: "#topc"
1621:04:47 <DanielK_WMDE_> hehe
1721:04:52 <DanielK_WMDE_> #topic Service Locator for MediaWiki core https://phabricator.wikimedia.org/T124792
1821:04:57 <DanielK_WMDE_> how do i add an admin?
1921:05:05 <DanielK_WMDE_> anyway. quick summary:
2021:05:20 <DanielK_WMDE_> since the last meeting about this topic, I have been working on code experiments.
2121:05:49 <DanielK_WMDE_> There is a patch that would introduce a serivce locator into core: https://gerrit.wikimedia.org/r/#/c/264403/
2221:06:13 <DanielK_WMDE_> Some important points about the Service Locator (aka DI container):
2321:06:17 <DanielK_WMDE_> * no auto-wiring
2421:06:23 <MaxSem> +1115, -127 - not that bad :P
2521:06:23 <DanielK_WMDE_> * no 3rd party lib
2621:06:40 <DanielK_WMDE_> MaxSem: yea, well, there's more in the follow-ups
2721:06:51 <DanielK_WMDE_> * closures in php files for wiring
2821:07:07 <DanielK_WMDE_> * extensible: extensions can add, replace, and wrap services
2921:07:25 <DanielK_WMDE_> Any questions/comments so far?
3021:07:48 <Scott_WUaS> thanks, not yet
3121:07:57 <RoanKattouw> DanielK_WMDE_: Are there usage examples anywhere?
3221:07:57 <DanielK_WMDE_> i have one:
3321:08:02 <SMalyshev> DanielK_WMDE_: so the clients interact with the container by calling static methods?
3421:08:10 <DanielK_WMDE_> RoanKattouw: in the follow-up patches, yes
3521:08:19 <RoanKattouw> I see
3621:08:44 <DanielK_WMDE_> SMalyshev: one static method to get the main instance. application code should have the services injected though.
3721:09:04 <DanielK_WMDE_> SMalyshev: only "static" code (like hook handlers) would access the main service locator through a static method
3821:09:12 <SMalyshev> DanielK_WMDE_: ah, so ServiceLocator::getInstance()->doStuff() ?
3921:09:16 <DanielK_WMDE_> RoanKattouw: the ticket has a rough overview of the patches
4021:09:38 <DanielK_WMDE_> SMalyshev: MediaWikiServices::getInsteance()->getFooThings()->doFoo()
4121:10:16 <DanielK_WMDE_> SMalyshev: but that's not typical. You'd rather see: new Thingy( MediaWikiServices::getInsteance()->getFooThing() )
4221:10:59 <SMalyshev> DanielK_WMDE_: aha, I see. how would you make "getFooThing" extendable? i.e. add new "FooBarThing"?
4321:11:09 <SMalyshev> would extension be able to do it?
4421:11:23 <DanielK_WMDE_> SMalyshev: there are getters for well known services, and a generic one for all other services.
4521:11:37 <DanielK_WMDE_> getFooThingy() is a shorthand for getService( 'FooThingy' )
4621:11:45 <matt_flaschen> DanielK_WMDE_, what's the real benefit of the convenience getter, type-safety?
4721:11:45 <SMalyshev> I see.
4821:11:50 <DanielK_WMDE_> services defined by extensions wouldn't have conveniance methods
4921:12:03 <DanielK_WMDE_> matt_flaschen: yes, type safety and typo safety
5021:12:11 <RoanKattouw> DanielK_WMDE_: So the area I'm most interested in right now is ServiceWiring.php. Do you have an example of what the wiring would look like for a service that depends on another service?
5121:12:55 <MaxSem> one thing current code is missing is teardown. this functionality should be present for tests
5221:13:12 <matt_flaschen> RoanKattouw, MainConfig at https://gerrit.wikimedia.org/r/#/c/250150/20/ServiceWiring.php
5321:13:23 <matt_flaschen> Depends on ConfigFactory.
5421:13:23 <DanielK_WMDE_> RoanKattouw: sure, pretty much all wiring in the initial patch already depends on other services. The closure gets the service locator as a param, so it has access to other services.
5521:13:29 <RoanKattouw> Oh I see
5621:13:48 <matt_flaschen> It's pretty similar to how Flow uses Pimple. We just don't have the convenience getters.
5721:14:00 <MaxSem> also, I don't like the concept of wiring files tht just get randomly included
5821:14:33 <DanielK_WMDE_> MaxSem: "randomly included"?
5921:14:55 <MaxSem> "randomly" is probably not the best word
6021:15:03 <DanielK_WMDE_> a, i see. well, it's not very pretty, but i don't think it's a problem
6121:15:09 <MaxSem> but do you realise you're just reimplementing autoloader?
6221:15:14 <RoanKattouw> OK and the service objects are cached and lazy-instantiated
6321:15:22 <matt_flaschen> MaxSem, how's that?
6421:15:25 <DanielK_WMDE_> the alternatives are: make it all declarative (and come up with a DSL for this), like spring
6521:15:32 <matt_flaschen> autoloader doesn't handle services depending on other services?
6621:15:33 <RoanKattouw> Yeah, I was mostly trying to make sure that the nice parts from Pimple are present
6721:15:50 <DanielK_WMDE_> or wrap all of this in closes. that means a lot of overhead, and potential performance issues
6821:16:03 <DanielK_WMDE_> using plain arrays with plain closures means loading no classes and creating no objects at init time
6921:16:36 <DanielK_WMDE_> MaxSem: this is very different from autoloader. autoloader is class -> file. This is name -> object.
7021:16:39 <MaxSem> matt_flaschen, autoloader is class => file mapping. this wiring thingie is a service => file mapping
7121:16:56 <DanielK_WMDE_> MaxSem: this doesn't map to files. it maps to instances.
7221:17:26 <matt_flaschen> MaxSem, no it's not. It's a "how to make a service recipe". It's not about files.
7321:17:40 <MaxSem> hmm oadWiringFiles( array $wiringFiles )
7421:17:42 <DanielK_WMDE_> RoanKattouw: yea, it's all lazy init and caches. the caching is actually a problem, i'll come to that in a minute if there are no further questions.
7521:17:49 <SMalyshev> yeah I don't think inventing another DSL is a good idea. We'll just end up having mini-PHP inside PHP
7621:17:50 <MaxSem> *loadWiringFiles( array $wiringFiles )
7721:17:57 <matt_flaschen> MaxSem, that's just how the recipes are loaded.
7821:18:02 <DanielK_WMDE_> MaxSem: yes, extensions can add more wiring files. this was requested by tim last time.
7921:18:36 <legoktm> DanielK_WMDE_: is there going to be actual code in these files or just arrays?
8021:18:41 <DanielK_WMDE_> SMalyshev: indeed. dependency tracking would be the only advantage. i don't think it's worth the pain
8121:19:11 <DanielK_WMDE_> legoktm: no code on the top level. just arrays of closures, and in each closure the code to instantiate a service. that may be trivial or not so much....
8221:19:17 <MaxSem> DanielK_WMDE_, why not set it in extension.json?
8321:19:31 <DanielK_WMDE_> Here's a later version of the wiring file, with more "meat" to it: https://gerrit.wikimedia.org/r/#/c/267692/28/ServiceWiring.php
8421:19:40 <DanielK_WMDE_> most of the "meat" is actually B/C stuff with old style config
8521:20:14 <DanielK_WMDE_> MaxSem: that's exactly what this is for. in extension.json, you register your wiring file by adding it to the global
8621:20:15 <SMalyshev> DanielK_WMDE_: do you have an example of overriding service, e.g. for test mocking?
8721:20:35 <DanielK_WMDE_> SMalyshev: yes, but it'S WIP
8821:20:39 <DanielK_WMDE_> let me dig up the link...
8921:21:04 <SMalyshev> that looks like very frequent use case for replacing services
9021:21:45 <DanielK_WMDE_> SMalyshev: well, only for B/C. For new style code, you'd just inject the mock. no need to do anything with the global service locator
9121:21:54 <DanielK_WMDE_> but sure, we will need a mechanism for this.
9221:22:34 <DanielK_WMDE_> SMalyshev: https://gerrit.wikimedia.org/r/#/c/267692/28/tests/phpunit/MediaWikiTestCase.php
9321:23:06 <DanielK_WMDE_> there I introduce MediaWikiTestCase::overrideMwServices
9421:23:20 <DanielK_WMDE_> my version is still fialing some tests, but i'm getting there
9521:23:25 <DanielK_WMDE_> it's not as bad as i feared
9621:24:09 <SMalyshev> ah,I see, thanks
9721:24:24 <DanielK_WMDE_> the problem is that services depend on each other, and we do not track how.
9821:24:53 <DanielK_WMDE_> so if you replace one service, other services may use the old or the new instance.
9921:25:08 <DanielK_WMDE_> this is rather tricky to get right. the only safe way to do this is to reset *everything*
10021:25:31 <DanielK_WMDE_> I cose not to do this during testing (test code has to care about avoiding inconsistencies)
10121:25:44 <DanielK_WMDE_> but in production, if any services needs resetting, all services have to be reset
10221:26:04 <DanielK_WMDE_> and that's actually the biggest issue i'd like to discuss today:
10321:26:50 <DanielK_WMDE_> during initialization, extensions max access services (like the config factory), but they may also change configuration. Which in turn would impact services. So initialization order matters.
10421:27:47 <DanielK_WMDE_> One nasty case: an extension adds a new config in wgConfigRistry. But the ConfigFactory is already instantiated (the extension loader needs it, because it needs access to caches). So the change to wgConfigRegistry has no effect on the existing ConfigFactory.
10521:28:19 <DanielK_WMDE_> For now, I solve this by resetting all services after the initialization phase. this is not very pretty though. Any ideas how to do this more nicely?
10621:28:58 <RoanKattouw> Where can I read more about resetting services?
10721:28:59 <DanielK_WMDE_> (look at "Accessing Services During Initialization" in the ticket for more info)
10821:29:04 <RoanKattouw> Like, what is it, when is it used, why is it needed, etc
10921:29:07 <RoanKattouw> Oh OK
11021:29:11 <matt_flaschen> DanielK_WMDE_, if wgConfigRegistry is modified using extension.json, then the extension loader could mark that, and only do the full reset if it was actually modified. Though if this is always done it's a moot point.
11121:29:32 <DanielK_WMDE_> RoanKattouw: also the section before that, called "Resetting the Service Locator".
11221:30:01 <DanielK_WMDE_> matt_flaschen: but other configuration may affect other services, and we don't know which or how
11321:30:25 <RoanKattouw> Interesting
11421:30:35 <DanielK_WMDE_> SMalyshev: btw, the RFC also has a section called "Forcing Services During Testing" :D
11521:30:43 <bd808> I doubt there is going to be a more graceful solution that to dump the instance cache once $wgFullyInitialised === true
11621:31:06 <legoktm> [14:28:19] <DanielK_WMDE_> For now, I solve this by resetting all services after the initialization phase. this is not very pretty though. Any ideas how to do this more nicely? <-- don't allow extensions to add config late
11721:31:11 <bd808> Until $wgFullyInitialised === true a lot of things are sketchy
11821:31:36 <DanielK_WMDE_> bd808: well, i can think of a more craceful one, but that has other issues: fully declarative wiring. that way, you can track all dependencies, resetting only exactly what is needed. but it's maintenance hell, and not possible with legacy code.
11921:31:58 <bd808> DanielK_WMDE_: crawl, walk, run :)
12021:31:58 <DanielK_WMDE_> legoktm: yes, that is true. it's actually intentional.
12121:32:12 <SMalyshev> declarative means no IDE help when configuring service ctors
12221:32:15 <RoanKattouw> I hadn't considered these cases because they don't arise in Flow's use of DI, but they arise here because you're using DI for things like DB connections
12321:32:25 <DanielK_WMDE_> legoktm: global state is evil. changing global state with the expectation to change behavior of pre existing objects is more evil.
12421:32:33 <SMalyshev> i.e. if I add an argument, who knows if I add it in the same place in declarative config
12521:33:14 <DanielK_WMDE_> SMalyshev: yea, i'm not a fan of declarative. well, in theory i am, actually. but not in practice. not here, not now. let's keep things simple and explicit: closures that return objects.
12621:34:32 <DanielK_WMDE_> RoanKattouw: yea, DB connections are a fun can of worms, especially with the fun unittest fake database tables that are bound to a connection. if you close it, you have to re-create the tables... that kind of fun is why the patches are all > 1000 lines :/
12721:35:16 <matt_flaschen> RoanKattouw, we do use container resets in a few places in Flow. Mostly just in tests, but also one trick we do with PurgeAction.
12821:35:17 <legoktm> DanielK_WMDE_: so I don't see why it's a case we have to worry about...we should just explictly not support anything registering things late, nor should we permit things to access it early either
12921:35:31 <DanielK_WMDE_> bd808: so, if we do reset all service instances once, should we track for which ones that would be particularly bad? I mean, we should really avoid re-connecting to the database, or the memcached server.
13021:35:41 <DanielK_WMDE_> In the ticket, i proposed a coupld of possible solutions:
13121:36:04 <DanielK_WMDE_> 1) Allow services to declare that they do not need to be re-created when configuration changes, perhaps by using a marker interface. Such services would need to use an entire Config object, or some kind of "promise" object, to keep track of changing configuration.
13221:36:06 <DanielK_WMDE_> 2) Blacklist some services from being accessed during initialization. One way to do this is to have a separate service locator for "primordial" services, with a separate wiring file. This assumes that services that were not blacklisted can be kept alive through config changes. This is tricky, since it's already not true for the ConfigFactory (extensions can register new configurations), and pretty much everything depends on using
13321:36:07 <DanielK_WMDE_> configuration.
13421:37:04 <matt_flaschen> DanielK_WMDE_, could we blacklist all configs except 'main' during initialization?
13521:37:14 <DanielK_WMDE_> matt_flaschen, RoanKattouw: forking maintenance scripts is another case that needs a full reset. and the installer! took me a while to figure that one out.
13621:37:53 <DanielK_WMDE_> matt_flaschen: i suppose so. i'd rather want to blacklist LoadBalancer, though...
13721:39:23 <DanielK_WMDE_> legoktm: "nor should we permit things to access it early either" <-- that doesn't work for things the extension loader itself needs. also, it will break compat with some extensions.
13821:39:35 <bd808> DanielK_WMDE_: hmm.. yeah by the time that $wgFullyInitialised === true a lot of things will have been built. main cache, message cache, parser cache, sessionmanager, possibly db connections for authn, ...
13921:39:40 <SMalyshev> DanielK_WMDE_: maybe not blacklist but whitelist? I.e. if we say we only should access some very basic services during init, maybe not allow to access others then
14021:39:40 <DanielK_WMDE_> legoktm: it would be really nice if we had a clean init phase with no service access, but that's not how it is.
14121:40:13 <DanielK_WMDE_> bd808: loading the message cache twice would suck. so maybe whitelist some services that could be kept?
14221:41:05 <DanielK_WMDE_> SMalyshev: yea, perhaps. i see two kinds of whitelist: a list of things that it's ok to access, and a list of things that can be kept and doesn't need a reset.
14321:41:14 <bd808> that might work. "locking" some services as soon as ExtensionRegistry is done loading maybe
14421:41:20 <DanielK_WMDE_> actually, the latter should be a marker interface, i suppose
14521:41:50 <MaxSem> DanielK_WMDE_, lists of exceptions are a sure ways to feet shot off
14621:41:51 <SMalyshev> DanielK_WMDE_: yeah the second looks like anybody should be able to claim it
14721:42:06 <DanielK_WMDE_> MaxSem: propose a better way
14821:42:27 <DanielK_WMDE_> SMalyshev: i think i'll experiment with this, yea
14921:42:29 <SMalyshev> i.e. if my service does not keep anything special in the instance, I should be able to say "just use the same object always"
15021:43:04 <DanielK_WMDE_> SMalyshev: if it doesn't use anything special, re-creating it doesn't hurt
15121:43:14 <DanielK_WMDE_> it's the edge cases that make the fun
15221:43:14 <SMalyshev> DanielK_WMDE_: except performance :)
15321:43:17 <DanielK_WMDE_> MessageCache...
15421:43:35 <DanielK_WMDE_> SMalyshev: creating a trivial object is no problem. fetching stuff from the database twice is a problem
15521:44:26 <MaxSem> and then someone overrides this trivial object with something that does stuff on init...
15621:45:08 <DanielK_WMDE_> MaxSem: the override would thake effect after the init phase. nothing can override anything before the init phase.
15721:45:17 <DanielK_WMDE_> anyway, we are reaching the last quarter of this... so let me ask:
15821:45:26 <bd808> The problem I see with early init and locking is that until $wgExtensionFunctions are run you really don't know what the state of the global config is
15921:45:27 <DanielK_WMDE_> who if you is going to be at the hackathon?
16021:45:34 <DanielK_WMDE_> and who's interested in a session on this?
16121:45:38 <bd808> and that happens very late in Setup.php
16221:45:41 <MaxSem> not me:P
16321:45:46 <DanielK_WMDE_> (and ho is NOT going to be at the hackathong, but still interested?)
16421:46:13 <SMalyshev> I'll be in the hackathon and may be interested
16521:46:32 <bd808> I'll be at the hackathon and willing to talk. I think the real proof of this is going to be more code though.
16621:46:33 <SMalyshev> need to read more about it before it I guess :)
16721:46:52 <DanielK_WMDE_> bd808: do you think this is the right place? https://gerrit.wikimedia.org/r/#/c/270020/25/includes/Setup.php
16821:46:57 <DanielK_WMDE_> bd808: if not, please comment there
16921:47:13 <DanielK_WMDE_> SMalyshev: that would be appreciated :)
17021:47:26 <SMalyshev> I agree, the only way to know how it works is trying to write a code with it. The theory looks fine, but the devil is always in the details :)
17121:47:51 <DanielK_WMDE_> bd808: there is already quite a lot of code, approaching 10k lines. I'd like a bit more fedback before i steal more hours from wmde working on this :P
17221:48:49 <DanielK_WMDE_> bd808, SMalyshev: we (ArchCom) are considering setting up a "working group" to tackle this (and similar tasks). Would you be interested?
17321:49:06 <SMalyshev> DanielK_WMDE_: yes, I think so
17421:49:33 <bd808> DanielK_WMDE_: you're not going to trick me that easily ;)
17521:49:40 <bd808> but yeah I'm interested
17621:49:47 <DanielK_WMDE_> hehe :P
17721:49:49 <DanielK_WMDE_> awesome
17821:50:12 <DanielK_WMDE_> so... anyone else interested in joining a group like that?
17921:50:23 * bd808 nominates legoktm
18021:50:52 <legoktm> I'm interested
18121:50:56 <DanielK_WMDE_> cool :D
18221:51:08 <Scott_WUaS> (I/WUaS are not planning to go to the hackathon at this point)
18321:51:26 <DanielK_WMDE_> one request to everyone: please review the patches and leave comments. the more i can do before the hackathon, the more we can get done at the hackathon.
18421:51:40 <Scott_WUaS> :)
18521:51:49 <DanielK_WMDE_> by "the patches" i mean the ones linked on https://phabricator.wikimedia.org/T124792
18621:52:39 <DanielK_WMDE_> cool. so, now we have 5 minutes to go through the backlog and add #info and #action tags :D
18721:52:49 <DanielK_WMDE_> any favorite lines to #info?
18821:53:28 <gwicke> honestly, I find writing 1-2 paragraphs as a tl;dr on the task more useful
18921:53:44 <DanielK_WMDE_> gwicke: go ahead, then :P
19021:53:55 <gwicke> heh ;)
19121:54:09 <gwicke> I missed half of the discussion, so that wouldn't be very accurate
19221:54:19 <gwicke> any volunteers?
19321:54:41 <Scott_WUaS> to do what specifically, gwicke?
19421:54:43 <DanielK_WMDE_> i'll summarize tomorrow, one way or the other
19521:55:08 <gwicke> DanielK_WMDE_: I could include a line or two in the update today
19621:55:21 <DanielK_WMDE_> gwicke: that sounds good
19721:55:48 <gwicke> please write them ;)
19821:56:11 <DanielK_WMDE_> so my main take-aways for today is that i seem to be on the right track.
19921:56:55 <DanielK_WMDE_> some experimentation/investigation is needed for resetting services after the init phase
20021:57:10 <gwicke> "I think the real proof of this is going to be more code though."
20121:57:46 <DanielK_WMDE_> gwicke: that's always true. i'd appreciate some help with that, though. would be nice if people could just play with the code, and try what it can or can't do
20221:58:00 <DanielK_WMDE_> what issues they run into
20321:58:08 <gwicke> yeah, could say that
20421:58:33 <DanielK_WMDE_> matt_flaschen: would you be interested in a code experiment which replaces Pimple with MediaWikiServices?
20521:58:41 <gwicke> General support, but a desire to write more code with it before making a final call.
20621:58:43 <DanielK_WMDE_> just to see how it goes, if it's nice to use, and if anything is missing
20721:58:58 <DanielK_WMDE_> gwicke: "more code" is a bit unspecific though
20821:59:07 <DanielK_WMDE_> i'd be greatful for concrete suggestions
20921:59:?? <gwicke> Tentative working group forming, aiming to discuss at Jerusalem Hackathon.
21021:59:?? <DanielK_WMDE_> Anomie asked be to showcase refactoring LoadBalancer and friends. so i did - and learned a lot in the process.
21121:59:?? <bd808> DanielK_WMDE_: what are the blockers to landing the main patch to introduce the components? If it is softly introduced into core it may be easier to hammer out some of the edge details.
21221:59:?? <matt_flaschen> DanielK_WMDE_, it needs to be done, but I don't know if I'll be able to get to it. I can at least help with code review and troubleshooting, though.
21322:00:?? <DanielK_WMDE_> bd808: the blocker is "someone has to review and merge it"
21422:00:?? <bd808> classic problem :)
21522:00:?? <DanielK_WMDE_> :P
21622:00:?? <gwicke> https://etherpad.wikimedia.org/p/di-summary
21722:01:?? <DanielK_WMDE_> ok, thanks everyone! this was very helpful!
21822:01:?? <DanielK_WMDE_> #endmeeting

daniel renamed this event from RFC Meeting: Service Locator for MediaWiki core (2016-03-23, #wikimedia-office) to ArchCom RFC Meeting Wxx: <topic TBD> (<see "Starts" field>, #wikimedia-office).Nov 21 2016, 6:11 PM
daniel changed the host of this event from RobLa-WMF to daniel.
daniel invited: TechCom.
daniel updated the event description. (Show Details)
daniel renamed this event from ArchCom RFC Meeting Wxx: <topic TBD> (<see "Starts" field>, #wikimedia-office) to RFC Meeting: Service Locator for MediaWiki core (2016-03-23, #wikimedia-office).