Repository navigation
Conversation
| if request.plugin is not None: | ||
| target = await _runner_target(http_request, request) | ||
| elif request.status is not None: | ||
| raise InvalidBaseAccessRequestError("status", "is recorded only for a runner's collection target") |
There was a problem hiding this comment.
[P1] Enforce target ownership independently of optional plugin metadata
Omitting both plugin and status skips _runner_target and sends the same target binding through the generic commit path. Against a real Server in enforced access mode, a different Scope contributor gets 403 with plugin present but 200 without it; the owning runner then syncs the injected checkpoint. This also lets that contributor change a reserved server target's checkpoint, although PATCHing that target correctly returns 403. A caller can therefore skip collection or poison its binding without runner/admin authority. Resolve collection-target bindings server-side and enforce their ownership/version checks regardless of which optional fields the caller supplies.
| elif record.record_type == "source_collect_target": | ||
| if payload["runner"] != "server" and not contains("source_runner", {"runner": payload["runner"]}): | ||
| raise BundleFormatError("collection target references a missing runner") |
There was a problem hiding this comment.
[P2] Keep accepted unsynced-runner targets exportable
create_source_target accepts a device runner that has never synced (the new e2e test does this too), so a persisted target can legitimately have no pc_source_runners row. This dependency check then rejects the entire Scope export. Reproduced by creating a ticket-chat target for never-synced through HTTP (201), then calling PortableBundleService.export: it raises BundleFormatError: collection target references a missing runner. A newly configured/offline device thus prevents backing up unrelated data in the same Scope. Make target admission and bundle dependencies agree so every accepted configuration can be exported and restored.
| result = await application.ingestion.commit(mapping.commit_connector_checkpoint_request(request)) | ||
| if target is not None and request.status is not None: | ||
| from powercontext.builtin.persistence.source_collection import SourceCollectionRepository | ||
|
|
||
| status = request.status | ||
| async with _require_plugin_manager(http_request).database.transaction() as connection: |
There was a problem hiding this comment.
[P2] Commit the checkpoint and target status in one transaction
application.ingestion.commit durably commits the checkpoint before this separate status transaction starts, violating RFC #2617's atomic checkpoint/status contract. With a real SQLite Server and a trigger that rejects only the status UPDATE, the request returns 500 but the next sync already contains the new checkpoint ({"cursor":42}), while all target status fields remain untouched. Removing the trigger and retrying with the original expected checkpoint returns 409, so the failed request cannot recover its status write. Keep the checkpoint CAS and target status update in the same transaction so they commit or roll back together.
d057191 to
faa5d6b
Compare
… and change their own Artifacts Implements the Artifact plugin part of RFC oceanbase#2549 and the plugin APIs and Dashboard pages of RFC oceanbase#2579, and replaces the managed Artifact plugin framework (oceanbase#2246). - A plugin is a directory with plugin.py assigning an ArtifactPlugin. Server checks it in a child process; generate runs in Supervisor Workers, and render_artifact, search_artifacts, user changes, Source writes of the plugin's own types, and plugin routes run in a per-plugin host process. - Plugins present (GET .../rendering) and search their own families: generic search passes the complete request to search_artifacts, and Prepared Context sections may list plugin families. Core builds no index for them. - Revision v1_5_0_005 removes the previous framework's storage only when it is empty, adds pc_artifacts.metadata (plugin name and code digest), and accepts plugin: bindings; frozen revision resources are unchanged. Portable revision records move to schema version 3 and still restore version 2. - Plugin APIs mount at /v1/plugins/{name}/api with a configurable body ceiling, and plugin pages run in a sandboxed Dashboard frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cancel only timed-out calls - Each plugin keeps one host process and starts more while every process is full and requests queue, up to `host_processes` (overridable per plugin with POWERCONTEXT_SERVER_PLUGIN_HOST_PROCESSES, capped in total by POWERCONTEXT_SERVER_PLUGIN_TOTAL_HOST_PROCESSES); extra processes stop after 5 idle minutes. - A call past its time limit is cancelled in the host and returns 504 `plugin_timeout`; other calls continue. A host whose heartbeat stops for 5 seconds is terminated, its other calls return 503, and it restarts after a backoff. - Prepared Context leaves out a plugin section whose plugin is unavailable and records the failed search in the receipt. - `Cron` without a time zone uses the machine's time zone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d lifecycle writes Plugin route writers act as the caller, but set_tags, bind_handle, and the direct lifecycle transition (retirement, deprecation with a replacement) wrote through repositories without the shared Artifact access check, so a Scope contributor could retag or retire another user's Artifact through a plugin route. These writes now run the same artifact.write check as content changes; Worker writers keep their unchanged security. The new acceptance test also restores the read-side assertions removed with the old plugin authorization suite: a viewer of one Scope gets 403 for plugin Artifacts, renderings, search, and Prepared Context sections of another. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s and report the committed revision The host checked expected revisions and governance versions before calling the plugin, but the writer then decided forget, merge, and update against the rows it reread, so a write committed after the precheck could be forgotten or merged by a stale request. User changes now carry the caller's expectations into the transaction, which rejects a changed target with 409 (412 for updates) whether or not the plugin repeats the check. Responses no longer reread the head after the plugin returns: the writer keeps the snapshot and records its agreed write committed, and the Server builds replace, create, forget, and merge responses from them, so a later write cannot appear as the result of an earlier request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on plugin writes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema version 2 artifact_revision records were verified against their archived digest and then staged as version 3 with that same digest, so restoring any archive exported by v1.4.1 through v1.5.1 failed with "record digest does not match content", and the restored revisions would have been reported as immutable conflicts on a repeat restore. A verified earlier-schema record is now re-identified at the current schema for staging and target comparison, while the bundle total digest still covers the archived digests. The regression test restores, re-restores, and re-exports an archive produced by the released v1.5.1 runtime. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it for the plugin API contract The OpenAPI changes removed the managed plugin operations and custom family schemas and opened Artifact families to loaded plugins, but the Dify tool declarations and contract, the Desktop IPC types, and the Bub access audit still described the old contract. Regenerate them, and classify the new plugin listing, detail, and Scope progress operations as returning no Scope content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A plugin family's create and replace bodies accept any content object, so a built-in family's request also matched the plugin branch and failed oneOf's exactly-one rule in JSON Schema validators; the Dify tools rejected every built-in Artifact edit as an invalid request. The Server selects the branch by family name, which anyOf states accurately. Generated Python models keep the same unions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ct request bodies Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The host process groups added tzlocal to the builtin extras without relocking the projects that depend on powercontext by path, so their --locked checks failed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odies Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ness and fix new test typing The Prepared Context client drops an unused per-section search so an older Server accepts the request; the Bub compatibility check still compared the payload with the full model dump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n runtime and its Dashboard titles Point plugin imports at powercontext.builtin.authz, pass the section to the Prepared Context budget evaluation's section builder, and title plugin pages through the Dashboard's document_title. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e after rebasing onto master Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, and expose routes as OpenAPI and MCP tools
- Each Server process rescans the plugin directory every POWERCONTEXT_SERVER_PLUGINS_RESCAN_SECONDS
(default 60, 0 off). A directory whose content is stable for two scans is copied to a read-only
snapshot, checked, and added, updated, or removed without a restart. Code is imported only from
snapshots; a new version's host process takes requests once ready while the old one drains, and
Workers launch from the version current at dispatch.
- `upgrade(ctx)` runs every time a version loads, before setup, as the plugin itself; instances without
host processes run it in a short-lived child. A failed check or upgrade keeps the old version and is
reported as `last_check_failure`.
- GET /v1/plugins/{name}/openapi.json describes a plugin's routes; routes with `mcp=True` are MCP tools
named <plugin>__<handler>, listed from the plugins loaded now.
- /v1/plugins reports version and declaration digests, activation time, routes, and pages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…support Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Read-only directories made the data directory impossible to remove with ordinary tools; snapshot files stay read-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cription operation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the review fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ess audit get_plugin_openapi is authorized for any authenticated caller, as the Access API is, but returns only a plugin's route descriptions and no Scope content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-in runtime Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e after rebasing onto master Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… description operation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r and device runners Implements RFC oceanbase#2617 and replaces the previous Source plugin framework. - `SourcePlugin` (version, min_compatible_version, definitions, collect, target, setup, api, pages, upgrade) loads from the plugin directory alongside Artifact plugins. A plugin with only definitions extends the Source write API; one with collect runs through runners. - Revision v1_5_0_006 adds pc_source_runners and pc_source_collect_targets and drops pc_source_write_plugins, refusing the upgrade while it still lists plugins. Portable bundles carry runners (name and bound subject) and targets without runner status. - Target API and MCP tools: pc_source_runners, pc_source_add, pc_source_status, pc_source_target_set_enabled, pc_source_target_delete; runners sync through POST /v1/source-runners/{runner}/sync. Targets of the server runner need server.admin; identity parameters are declared, normalized, and immutable. - The server runner collects in Supervisor Workers and commits Sources, checkpoints, and target status in one transaction; CollectResult.fail reports one target. - `powercontext source-agent` collects on devices: it submits Sources and then checkpoints with status. Runner requests carry the plugin version, accepted within the Server plugin's compatible range; the Server's plugin resolves and projects them. - Removed: /v1/source-management/*, `powercontext source load`, the source management settings, and the old examples, which are rewritten as Source plugins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…document the new restore tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unner deletion in the access audit The Source collection contract removed the old source management operations and added delete_source_runner, which returns no Scope content. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…audit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e after rebasing onto master Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on operation tables Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… in their own section Rebasing onto master's grouped navigation dropped the closing tag of the group loop, so every Dashboard page with plugin pages failed to render. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
faa5d6b to
737484d
Compare
Which issue or RFC does this PR close?
Implements RFC #2617 (Source plugins: collection runners and targets). Builds on #2638 (RFC #2579), which builds on #2616 (RFC #2549): this branch contains their commits, so review the commits after
e1107039and merge after #2638.Rationale for this change
The previous Source plugin framework kept collection targets on each device behind a management service with a reverse channel. RFC #2617 keeps targets on the central Server, lets runners pull them, and makes Source plugins ordinary directory plugins next to Artifact plugins.
What changes are included in this PR?
SourcePlugin(version, min_compatible_version, definitions, collect, target, scopes, setup, api, pages, upgrade)loads fromPOWERCONTEXT_SERVER_PLUGINS_DIRlike Artifact plugins, including reloading andupgrade. A plugin with onlydefinitionsextends the Source write API;targetdeclares a parameter model and identity parameters with fixed normalizers. Artifact and Source plugin declarations sharePluginSpec.v1_5_0_006(SQLite and MySQL) addspc_source_runnersandpc_source_collect_targetsand dropspc_source_write_plugins; its domain step refuses the upgrade while the old registry still lists plugins. Target checkpoints reusepc_connector_checkpoints. Portable bundles addsource_runner(name and bound subject) andsource_collect_targetrecords; runner reports and target status restart empty.GET /v1/source-runners(pc_source_runners),POST /v1/source-runners/{runner}/sync,DELETE /v1/source-runners/{runner}, and/v1/scopes/{scope_id}/source-targets(pc_source_add,pc_source_status,pc_source_target_set_enabled,pc_source_target_delete). Targets of theserverrunner needserver.admin; a runner name is bound to the subject of its first sync; identity parameters cannot change.POST /v1/source-observationsandPOST /v1/connector-checkpoints/commitaccept the runner's plugin name and version (and the commit a target status); the Server checks the version against its plugin's compatible range (plugin_version_incompatible), and its plugin resolves and projects the observation. Device calls to plugin routes carryX-PowerContext-Plugin-Version.servertargets and commits the Sources, checkpoints, and target status in one transaction.CollectResult.failfails one target while the others commit. New targets are collected right away.powercontext source-agent --server ... --plugins ...syncs every 30 seconds, collects on each plugin's schedule and new targets right away, submits Sources and then each checkpoint with its status, and retries on the next sync afterplugin_version_incompatible.ctx.center.call_pluginreaches the plugin's own routes on the Server./v1/source-management/*,powercontext source load, theSOURCE_PLUGINS_DIRandSOURCE_MANAGEMENT_*settings, andpowercontext.source_management. Thetickets,ticket-chat, andimagesexamples are rewritten as Source plugins; integration tool lists and the DSH secret scan follow the new MCP tools.Are there any user-facing changes?
Yes, breaking for the previous Source plugin framework: its API, CLI command, settings, and MCP tools (
pc_source_capabilities,pc_source_instances,pc_source_instance_set_enabled) are removed, and the upgrade is refused while write plugins are still loaded. New API and MCP tools as listed above; the Desktop contract is regenerated and qualified.release/upgrade-support.jsonrecords the schema review.How was this change tested?
c7fb4b8c:uv run pytest -n 4— 10378 passed, 984 skipped, 10 failed. All 10 are fixed ind057191dand pass on the VM (44 passed with the plugin e2e tests): the CLI imported server dependencies throughsource-agent, the OceanBase restore layer docs lacked the new tables, and two upgrade-history tests listed the domain steps withoutsource_collection.make docs-test: verified 1106 public pages and their internal links.make js-test,make opencode-test, andmake cindy-testpass, with committed bundles current.uv run prek run) anduv run ty checkpass locally;tests/test_upgrade_release_gate.pypasses with the recorded schema review.sqlite-source-plugins-a80648a8-v1: anonymous loopback, static Bearer, HTTPS with CA/base path, and enforced Provider modes passed against the wheel built froma80648a8;bundled_product_qualification_covers_every_desktop_operationpassed.tests/e2e/test_source_plugins.pyruns a real Server with an identity map and a realpowercontext source-agentsubprocess: onlyserver.adminaddsservertargets; duplicate and conflicting targets; invalid parameters; collection on add; a failing target keeps its checkpoint while another target in the call commits; identity parameters are immutable; the device runner submits Sources and checkpoints and resubmits nothing next time; another credential cannot sync as the runner; a newer device plugin is rejected withplugin_version_incompatible; disabled targets are not synced; deleting keeps collected Sources.v1_5_0_006is refused whilepc_source_write_pluginsholds a row and otherwise adds the new tables; registered SQLite fingerprints of earlier revisions are unchanged; the MySQL layout resources pass the shared layout validation in the suite but were not run against a native OceanBase or seekDB.tests/builtin/portability/test_bundle.pyround-trips targets and their runner without status.AI usage statement
Implemented with Claude Code (Claude Opus 5.5) under the author's direction; the author made the design decisions in the RFC review. Claude Code ran the tests and checks listed above.
🤖 Generated with Claude Code