Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,17 @@ $container->registerService(AppRoot::class, new AppRoot(__DIR__ . '/..'));
the graph resolves and in what order plugins would boot, and what enabling one
*would* do — named provider by provider — without enabling it.

### Catalogue relationships

`plugins.list` declares the shape of its actual result: a `plugins` array with registry names,
metadata, activation state and a nullable installation timestamp. `plugins.simulate.plugin` and
`plugins.verify.plugin` name `plugins.list.name` through `x-milpa-source`, so a catalogue reader
can locate their producer. Call the list without filters to discover the initial values.

Registry names are not source directory names. A vendor plugin can be registered without any
local source directory, and local code can exist before registration. These relationships are
discovery hints; they grant no permission and do not select or register a plugin.

### Declared in code, switched at runtime

A host has plugins from two places, and `ActivePlugins` decides which of them
Expand Down
39 changes: 37 additions & 2 deletions src/Operations/PluginOperations.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,33 @@ public function operations(): array
'default' => false,
],
],
'required' => [],
],
// Registry names feed management operations; they are not local source directories.
// Greenhouse decisions/0324, evidence/0640.
outputSchema: [
'type' => 'object',
'properties' => [
'plugins' => [
'type' => 'array',
'items' => [
'type' => 'object',
'properties' => [
'name' => ['type' => 'string'],
'version' => ['type' => 'string'],
'author' => ['type' => 'string'],
'site' => ['type' => 'string'],
'type' => ['type' => 'string'],
'installed' => ['type' => 'boolean'],
'enabled' => ['type' => 'boolean'],
'source' => ['type' => 'string'],
'installedAt' => ['type' => ['string', 'null']],
],
'required' => ['name', 'version', 'author', 'site', 'type', 'installed', 'enabled', 'source', 'installedAt'],
],
],
],
'required' => ['plugins'],
],
scopes: ['plugins:read'],
path: '/plugins',
Expand Down Expand Up @@ -298,7 +325,11 @@ public function operations(): array
handler: fn (array $input): array => $this->inspection()->simulate($input),
inputSchema: [
'type' => 'object',
'properties' => ['plugin' => ['type' => 'string', 'description' => 'Plugin name, e.g. "MailPlugin".']],
'properties' => ['plugin' => [
'type' => 'string',
'description' => 'Plugin name, e.g. "MailPlugin".',
'x-milpa-source' => ['tool' => 'plugins.list', 'key' => 'name'],
]],
'required' => ['plugin'],
],
scopes: ['plugins:read'],
Expand Down Expand Up @@ -355,7 +386,11 @@ public function operations(): array
handler: fn (array $input): array => $this->inspection()->verify($input),
inputSchema: [
'type' => 'object',
'properties' => ['plugin' => ['type' => 'string', 'description' => 'Plugin name, e.g. "MailPlugin".']],
'properties' => ['plugin' => [
'type' => 'string',
'description' => 'Plugin name, e.g. "MailPlugin".',
'x-milpa-source' => ['tool' => 'plugins.list', 'key' => 'name'],
]],
'required' => ['plugin'],
],
scopes: ['plugins:read'],
Expand Down
26 changes: 26 additions & 0 deletions tests/Operations/PluginOperationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,32 @@ private function operations(?PluginInstallerInterface $installer = null, array $
return $byName;
}

/** The catalogue describes the list's real rows and points management reads at registry names. */
public function testTheListPublishesItsResultsForManagementConsumers(): void
{
$this->registry->register($this->record('Catalogued'));
$operations = $this->operationsWithRoot(sys_get_temp_dir());
$list = $operations['plugins.list'];
self::assertSame([], $list->inputSchema['required'] ?? null);
self::assertNotNull($list->outputSchema);
$rowSchema = $list->outputSchema['properties']['plugins']['items'];
$rows = ($list->handler)([])['plugins'];
self::assertCount(1, $rows);
self::assertEqualsCanonicalizing(array_keys($rows[0]), $rowSchema['required']);
self::assertEqualsCanonicalizing(array_keys($rows[0]), array_keys($rowSchema['properties']));
self::assertSame(['string', 'null'], $rowSchema['properties']['installedAt']['type']);
self::assertNull($rows[0]['installedAt']);
self::assertSame('Catalogued', $this->call('plugins.show', ['name' => $rows[0]['name']])['name']);
foreach (['plugins.simulate', 'plugins.verify'] as $name) {
self::assertSame(
['tool' => 'plugins.list', 'key' => 'name'],
$operations[$name]->inputSchema['properties']['plugin']['x-milpa-source'] ?? null,
);
}
// Registration can name a new, unregistered class: registry membership is not its source.
self::assertArrayNotHasKey('x-milpa-source', $operations['plugins.register']->inputSchema['properties']['name']);
}

/**
* Las mismas, pero con una raíz de app — para ver aparecer las dos que tocan disco.
*
Expand Down
Loading