Skip to content

feat(scripts): manage_script reads who wrote each version, so an author outlives a transfer of the script's ownership (#1639) - #1641

Merged
cjimti merged 1 commit into
mainfrom
issue-1639-script-version-authors
Sep 6, 2026
Merged

feat(scripts): manage_script reads who wrote each version, so an author outlives a transfer of the script's ownership (#1639)#1641
cjimti merged 1 commit into
mainfrom
issue-1639-script-version-authors

Conversation

@cjimti

@cjimti cjimti commented Sep 6, 2026

Copy link
Copy Markdown
Member

Closes #1639

An agent asked who wrote a script had one field to answer with, and it was the wrong one. manage_script command=get returns owner_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=versions answers

manage_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_email beside 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 made owner_email somebody 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 get already applies, through the same readable gate: the owner reads their own script's history, an administrator reads any script's, and everybody else receives the refusal get gives 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.VersionStore is refused in the terms command=diff refuses 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

memStore in internal/platform/scriptlayer/scriptlayer_test.go returned versions oldest-first while the PostgreSQL store returns ORDER 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 the Category and CreatedAt a real row carries.

Documentation

docs/scripts/running.md gains 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.md and docs/llms.txt carry the same distinction, and both the tool description and command=help's dialect contract name the command.

Verification

Criterion Executed by Result
Two applied versions return two entries, newest first, each with a non-empty author and the author_roles recorded at that save TestIssue1639_EveryVersionNamesItsAuthorNewestFirst PASS
After an administrator transfer, get reports the new owner and versions still reports the original author on the first version TestIssue1639_TheAuthorSurvivesAnOwnerTransfer PASS
A caller who is neither the owner nor an administrator gets the refusal get gives TestIssue1639_ARefusalIsTheOneGetGives PASS
An administrator reads a script they do not own, addressed by owner_email TestIssue1639_AnAdministratorReadsAnybodysHistory PASS
A store without script.VersionStore refuses in diff's terms TestVersions_WithoutAVersionStoreRefusesLikeDiff (unit) PASS

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 at build/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 diff gives rather than asserting a message of its own.

make verify green. Patch coverage 31/31 executable changed lines (100%), make lint 0 issues, gosec clean.

…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

codecov Bot commented Sep 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.58%. Comparing base (f56c59f) to head (6470d72).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cjimti
cjimti merged commit a218914 into main Sep 6, 2026
10 checks passed
@cjimti
cjimti deleted the issue-1639-script-version-authors branch September 6, 2026 02:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

manage_script cannot read who wrote a version, so after an owner transfer the author is unreachable over MCP

1 participant