mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-27 06:47:07 +00:00
c17865462f
* feat: built-ins record their origin, and an item made from one can be offered Pad's current text (TASK-3462 U1+U2)
U1: every convention and playbook Pad ships has a stable key and a content
hash; template seeding and the new server-side POST /library/activate record
{key, seed hash, seed text} in item_builtin_origin (migration 128 / pg 102).
CLI, remote MCP and the web Library page activate through the endpoint;
seeded and activated copies of an entry store the same fields. Export/import
carry the origin; copies get none; a request body cannot claim one.
U2: GET /items/{ref}/builtin derives current / update_available / diverged /
unknown_origin / unknown_entry; POST /items/{ref}/builtin/update takes the
library text through the ordinary PATCH path (expected_seq required, status
and title untouched, a forced version row keeps a diverged item's edits);
GET /builtins lists states in one join, showing only fully visible
collections.
* fix: the origin's seed text is NUL-protected (trigger migration 129); the MCP activate test asserts the endpoint call; activation follows the declared kind server-side (TASK-3462)
* fix: the built-in seed moves in the update's own transaction; a NUL-bearing imported origin is skipped; the listing computes visibility once; numbers hash by value (TASK-3462, codex r1)
* fix: on the applier path the built-in seed is recorded only after the apply confirms; a library change that only drops a field is offered (TASK-3462, codex r2)
* fix: a field the library added that the user already set reads diverged; an update clears convention metadata the library dropped (TASK-3462, codex r3)
* test: the lifecycle test finds the imported item by slug (the copy shares its title); ScanNUL's cost figures count the two new columns (TASK-3462)
* test(e2e): Library activate posts the entry to /library/activate and the item records its built-in origin (TASK-3462)
55 lines
2.0 KiB
Go
55 lines
2.0 KiB
Go
package mcp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/PerpetualSoftware/pad/internal/collections"
|
|
"github.com/PerpetualSoftware/pad/internal/models"
|
|
)
|
|
|
|
// --- library activate ---
|
|
|
|
// dispatchLibraryActivate reproduces `pad library activate <title>`. The
|
|
// server's POST /workspaces/{ws}/library/activate does the work since
|
|
// TASK-3462: it builds the fields, picks the collection that declares the
|
|
// entry's artifact kind (BUG-2702), and records the item's built-in origin, so
|
|
// this door, the CLI and the web create the same item.
|
|
//
|
|
// The title is checked against the libraries in-process first, so an unknown
|
|
// one answers with the not-found payload this tool has always returned rather
|
|
// than the server's 404. The OAuth-scope hook (d.Apply) still runs on the
|
|
// POST, so this isn't a scope bypass.
|
|
func (d *HTTPHandlerDispatcher) dispatchLibraryActivate(
|
|
ctx context.Context,
|
|
input map[string]any,
|
|
user *models.User,
|
|
) (*CallToolResult, error) {
|
|
const cmdKey = "library activate"
|
|
workspace, _ := input["workspace"].(string)
|
|
if workspace == "" {
|
|
return noWorkspaceResult(ctx, d.Lister), nil
|
|
}
|
|
title, _ := input["title"].(string)
|
|
if title == "" {
|
|
return validationFailedResult(cmdKey, "title is required",
|
|
"Pass `title=<library-item-title>` matching an entry in the convention or playbook library."), nil
|
|
}
|
|
if collections.GetLibraryConvention(title) == nil && collections.GetLibraryPlaybook(title) == nil {
|
|
return NewErrorResult(ErrorPayload{
|
|
Code: ErrNotFound,
|
|
Message: fmt.Sprintf("%s: %q not found in convention or playbook library", cmdKey, title),
|
|
Hint: "Use `pad_library action=list` to enumerate available titles.",
|
|
}), nil
|
|
}
|
|
body, err := json.Marshal(map[string]string{"title": title})
|
|
if err != nil {
|
|
return dispatcherErrorResult(cmdKey, "encode body", err), nil
|
|
}
|
|
urlPath := "/api/v1/workspaces/" + url.PathEscape(workspace) + "/library/activate"
|
|
return d.executeRequest(ctx, cmdKey, user, http.MethodPost, urlPath, body)
|
|
}
|