feat(scripts): manage_script reads who wrote each version, so an author outlives a transfer of the script's ownership (#1639) - #1641
Merged
Conversation
…or outlives a transfer of the script's ownership (#1639) `manage_script command=versions name=<script>` returns the version history newest first: the version number, its author, the roles that author held at that save, the status, when it was written, and the descriptive fields the snapshot carried (display_name, description, category, tags). The response also carries the script's current owner_email, so the two facts are answered side by side. They are two facts. owner_email is where the script is filed now, and an administrator can move a script to another owner (#1404), after which it names somebody who may never have written a line of it. The author is recorded per version and does not move, so the oldest entry still names whoever created the script. The roles on an entry are the authority a run of that version presents. The history carries no source. A version holds the whole body, and returning every version's would turn one call into the complete edit history of the file; an earlier version's code is read with command=diff. Who may read the history is who may read the script — its owner, and an administrator on any script — refused in the words command=get uses, and a deployment whose store keeps no versions is refused in the words command=diff uses rather than answered with an empty list. The tool description and command=help name the command, and docs/scripts/running.md states which command answers which question. The in-memory store fake answered versions oldest-first while the PostgreSQL store answers ORDER BY version DESC, so it now reverses on the way out and fills the Category and CreatedAt a real row carries. Claude-Session: https://claude.ai/code/session_01X6eWaeUVdP569e3hMn7GXP
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1641 +/- ##
==========================================
- Coverage 91.58% 91.58% -0.01%
==========================================
Files 777 777
Lines 77813 77837 +24
==========================================
+ Hits 71266 71287 +21
- Misses 4275 4278 +3
Partials 2272 2272 ☔ 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.
Closes #1639
An agent asked who wrote a script had one field to answer with, and it was the wrong one.
manage_script command=getreturnsowner_email, which is where the script is filed; an administrator can move a script to another owner (#1404), and after a move that field names somebody who may never have written a line of it. The authors were recorded the whole time — every version carries its author and the roles that author held at the save — and that history was readable on the script's page in the portal and nowhere else.What
command=versionsanswersmanage_script command=versions name=<script>returns the version history newest first, each entry carrying the version number, its author, the roles captured at that save, the status, when it was written, and the descriptive fields the snapshot held:{ "name": "daily-sales-report", "owner_email": "sam@example.com", "count": 3, "versions": [ {"version": 3, "author": "admin@example.com", "author_roles": ["admin"], "status": "applied", "created_at": "2026-08-20T09:12:00Z", "display_name": "Daily Sales", "description": "…", "category": "reporting", "tags": ["sales"]}, {"version": 2, "author": "jane@example.com", "author_roles": ["analyst"], "status": "applied", "created_at": "2026-08-14T16:04:00Z", "display_name": "Daily Sales", "description": "…", "category": "reporting", "tags": ["sales"]}, {"version": 1, "author": "jane@example.com", "author_roles": ["analyst"], "status": "applied", "created_at": "2026-08-13T14:30:00Z", "display_name": "Daily sales", "description": "…", "category": "reporting", "tags": []} ] }The response carries the current
owner_emailbeside the history because the two are different facts and the question is usually about both at once. The oldest entry names whoever created the script, so "who wrote this?" survives the transfer that madeowner_emailsomebody else; the transfer's own version is in the history too, authored by the administrator who made it, which is why a run of it presents that administrator's roles.The roles on an entry are not decoration. They are the authority a run of that version presents (
script.Author), so the record answers what the code can reach as well as who wrote it.What it does not return
The source. A history entry carries the whole body, and returning every version's would turn one call into the complete edit history of the file; an earlier version's code is read with
command=diff, as it was before. Whether an old version's body should also be fetchable on its own is a separate question and is not part of this.Who may call it
The visibility
getalready applies, through the samereadablegate: the owner reads their own script's history, an administrator reads any script's, and everybody else receives the refusalgetgives for that script, word for word — naming the difference would confirm the script exists to a caller who may not see it.A deployment whose store does not implement
script.VersionStoreis refused in the termscommand=diffrefuses in, rather than being handed an empty history that reads as a script nobody has ever saved.The fake that did not model its store
memStoreininternal/platform/scriptlayer/scriptlayer_test.goreturned versions oldest-first while the PostgreSQL store returnsORDER BY version DESC(internal/platform/scriptstore/version.go:168), so a test asserting "newest first" against it would have passed on the fake's own ordering. It now reverses on the way out, and fills theCategoryandCreatedAta real row carries.Documentation
docs/scripts/running.mdgains a "Who owns it and who wrote it" section stating that the owner and a version's author are different facts and which command answers which, plus a row in the command table.docs/server/tools.mdanddocs/llms.txtcarry the same distinction, and both the tool description andcommand=help's dialect contract name the command.Verification
authorand theauthor_rolesrecorded at that saveTestIssue1639_EveryVersionNamesItsAuthorNewestFirstgetreports the new owner andversionsstill reports the original author on the first versionTestIssue1639_TheAuthorSurvivesAnOwnerTransfergetgivesTestIssue1639_ARefusalIsTheOneGetGivesowner_emailTestIssue1639_AnAdministratorReadsAnybodysHistoryscript.VersionStorerefuses indiff's termsTestVersions_WithoutAVersionStoreRefusesLikeDiff(unit)The acceptance tests ran as a real MCP client against the local stack, as the people the criteria are about: the script created and edited by an ordinary person, the move made by an administrator over
PUT /api/v1/portal/scripts/{id}/owner, and the refusal collected by somebody who owns nothing here. The transcript is atbuild/1639/acceptance.md, with the wire forms it sent.The last criterion is the one that cannot be executed behind the real surface: the dev stack runs the PostgreSQL store, which implements versioning, so a store without it cannot be stood up there. It is asserted against a store fake that deliberately does not promote the version methods, comparing the refusal against the one
diffgives rather than asserting a message of its own.make verifygreen. Patch coverage 31/31 executable changed lines (100%),make lint0 issues, gosec clean.