feat(portal): rename save_artifact/manage_artifact to save_asset/manage_asset (#1029)#1031
Merged
Conversation
…ge_asset (#1029) The portal toolkit's object is an asset everywhere else in the system: the tables are portal_assets and portal_asset_versions, the tool result field is asset_id, the REST API and portal UI say asset. The two tool names were the only place it was called an artifact, so save_artifact returned an asset_id. The tools are now save_asset and manage_asset. This is a hard rename: the old names are not registered. MCP tools are discovered at tools/list time, so a second registration of the same capability under a retired name is discovery surface with no compensating benefit. Provenance harvesting takes the save tool's name from the toolkit constant (re-exported through portalstore) rather than a literal, so the harvest list and the registered name cannot drift; a mismatch there stops provenance capture silently. A guard test asserts the constant against a real tools/list response. Migration 000087 rewrites the exact old names in database-backed persona tools_allow and tools_deny. The deny direction is why the migration exists: a stale deny entry stops denying and fails open. Glob entries such as save_* still match the new names and are left alone. Deployments must update two things by hand: persona allow/deny lists held in a YAML config file, and saved prompt bodies that name the tools in their text. The old names are recorded in scripts/retired-tools.txt, so doc-check now hard -fails if either reappears in the documentation. Adjacent corrections this exposed: comments in thread_actions.go and validation_handler.go attributed the feedback actions to the asset tool, but #618 moved them to manage_feedback; the pending-feedback error path reported "failed to gather your assets" when the collection gather failed. portal.s3_prefix keeps its "artifacts/" value. It addresses stored blobs, not the tool surface. Closes #1029
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1031 +/- ##
=======================================
Coverage 89.38% 89.38%
=======================================
Files 475 475
Lines 52040 52028 -12
=======================================
- Hits 46514 46505 -9
+ Misses 3667 3665 -2
+ Partials 1859 1858 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
The portal toolkit's object is an asset everywhere in the system except the two tool names. The tables are
portal_assets,portal_asset_versions,portal_asset_thumbnails. The tool result field isasset_id. The REST API, the portal UI, and the docs all say asset. Only the tools said artifact, sosave_artifactreturned anasset_id.The tools are now
save_assetandmanage_asset.save_artifactsave_assetmanage_artifactmanage_assetHard rename, no aliases
The old names are not registered. MCP tools are discovered at
tools/listtime rather than pinned by a versioned client contract, so registering the same capability twice adds discovery surface, pollutes theplatform_find_toolsembedding corpus, and presents one capability as two, without buying anything a client actually needs.scripts/retired-tools.txtnow carries both old names, somake doc-checkhard-fails if either reappears anywhere indocs/, the bench doc tree, orREADME.md.Provenance harvesting
MCPProvenanceMiddlewaredecides when to harvest the accumulated tool-call chain onto a saved asset by matching the called tool name against a configured list. That list was a hardcoded[]string{"save_artifact"}inpkg/platform/platform.go. If the string and the registered name disagree, nothing fails: provenance capture just stops.The list now comes from the toolkit's own constant, re-exported through
internal/platform/portalstoresopkg/platformdoes not need a new import edge intopkg/toolkits/portal.TestSaveToolNameIsRegisteredasserts the constant against a realtools/listresponse over an in-memory session, rather than againstTools()(which returns the same constant and so could never fail).Persona tool lists
Persona
tools.allow/tools.denyare glob lists matched against live tool names, and in at least one deployment personas are database-backed rather than file-backed. Migration000087rewrites the exact old names inpersona_definitions.tools_allowandtools_deny, preserving array order and leaving glob entries such assave_*alone (they still match the new names).The deny direction is why the migration exists. A stale allow entry stops granting the tool, which is visible immediately. A stale deny entry stops denying it, which fails open and is visible to nobody.
Two things a deployment must update by hand:
Explicitly out of scope
portal.s3_prefixkeeps its"artifacts/"default. It addresses stored blobs, not the tool surface.The tools were not renamed to
create_file/manage_file. An asset is versioned with revert, thumbnailed in light and dark, provenance-harvested, collection-organized, feedback-threaded, and publicly shareable; "file" communicates none of that and collides withs3_put_object, which writes actual objects.Assets and resources were not merged into one tool. They differ in origin (agent-generated vs human-uploaded), direction (session output vs session input), versioning, provenance, MCP exposure, and write path. A merged tool would need a
kinddiscriminator under which roughly half the actions are invalid for either value.Also in this change
pkg/toolkits/portal/thread_actions.goandpkg/portal/validation_handler.gonow attribute the feedback-thread actions tomanage_feedback, where Feedback: dedicated manage_feedback MCP tool (review/respond across all artifacts + general channel) #618 put them.failed to gather your collectionswhen the collection gather fails.pkg/platform/config.goand the docs state why the S3 prefix is exempt from the rename.Verification
make verifygreen.Live-tested against the
make dev-upstack (PostgreSQL + SeaweedFS, real HTTP MCP transport, API-key auth, the full assembled middleware chain):tools/listadvertisessave_assetandmanage_asset, advertises neither old name, and no portal tool's name, title, description, or input schema contains the word "artifact".save_assetcreated aportal_assetsrow and returned itsasset_id.searchcall, the stored asset carriedprovenance.tool_callsof length 1 withtool_name = search. Not a unit test of the harvest function, a real call through the real chain.manage_asset action=getread the asset back with matching name, content type, and size.toolkit_kind = portal, so registry lookup routes the new names correctly.000087applied to a probe persona row rewrote bothtools_allowandtools_deny, lefttrino_*untouched, and preserved ordering.Two automated guards were added rather than left to review discipline:
TestToolsListVocabulary(the advertised surface never says "artifact") andTestSaveToolNameIsRegistered(the harvest constant matches a registered tool).Closes #1029