mcp: the catalogue is a query — delete the 116 committed tool files
The fleet's agent door answered from plugin/<app>/mcp.json: the tool array each
app's binary projected when it was BUILT, embedded by plugin/embed.go and handed
to zip as Plugin.Tools. 116 files, 49,865 lines, and a second source for a fact
every child already knows.
A second source can only be stale or accidentally correct. This one was stale in
the way no gate in this repository could see: o11y's 353 missing ops live in
github.com/hanzoai/o11y, so a go.mod bump in ANOTHER repo invalidated an artifact
in this one with nothing in the diff to say so. Regenerating it more often is not
the fix — a generator on a hook is still two sources with a race between them,
and the trigger is in a different repository. (593aa309 did regenerate it, which
is why the file reads 365 today. The next cross-repo bump silently un-fixes it.)
So the host asks. POST /v1/mcp is the HOST's own handler now (zip's is Disabled,
so exactly one handler holds the address). A tools/list forwards the CALLER's own
message to every composed subsystem's own /mcp over its private ZAP socket, in
parallel, and unions the replies — zip.App.Start resolves a cold child on the
same single-flighted path a prefix request takes, so the first list pays one
start per app and nothing after it does. A tools/call goes to the app that listed
the name, verbatim; the child's own registry decides whether the tool exists.
A SUBSYSTEM THAT DOES NOT ANSWER IS NAMED, in result._meta["hanzo.ai/unavailable"],
because a silently-short list and a stale file are the same defect: the caller
cannot tell an app that serves nothing from one that did not answer. Measured on
the built binaries — host + real o11y child, kms pointed at a dead address:
tools=364, unavailable=[{kms, connection refused}]
364 and not 365 because o11y projects get_v1_o11y_logs twice; the door serves the
first and logs the collision. THAT DUPLICATE IS WHY cmd/cloud's tests were red on
main — zip refused the Load ("tool is already served by plugin o11y"), a boot
failure. One name still has one owner; it is no longer fatal to the fleet.
Also gone with the mechanism they configured: manifest.App.Open and zip's
one-open-plugin rule. The host forwards the caller's own request to EVERY
subsystem now, so each answers for this caller out of its own rows, and being
asked per caller is no longer a privilege one app holds.
The release gate moved with the door. Car 3 compared the live tool count against
`jq -s length` over the committed files — both sides were the same bytes, so it
proved only that the image carried its own tree, and it passed while o11y's
catalogue held 12 of 365. It asks the better question now: did every subsystem
answer. A broken deployment used to match the files exactly.
plugin/<app>/openapi.json SURVIVES, for the one reason the catalogue could not:
the weave carries each subsystem's prose, and that prose is lifted from the app's
SOURCE at describe time (openapi.Synopsis). A running child has no comment to
read and would answer with its deployment's brand blurb, which the weave would
publish as the description of every product tag. Deleting that half waits on the
synopsis becoming a declared value.
Tests are against RUNNING subsystems (fleet/mcp_test.go): real zip children on
real ZAP sockets, exact sets, bodies never status codes. Mutation-checked three
ways — unmount a child, silence the outage report, ask only the first app — all
three go red.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
This commit is contained in:
+25
-11
@@ -600,20 +600,34 @@ jobs:
|
||||
- name: Every published address is routed
|
||||
run: go run ./cmd/reach openapi.yaml https://api.hanzo.ai openapi/unreachable.txt
|
||||
|
||||
# THE MCP PROJECTION. The door serves exactly the tools in the committed
|
||||
# plugin/*/mcp.json, so "the MCP tool list was refreshed" is not a claim to
|
||||
# make — it is a number to check. It needs no car of its own: the subsets
|
||||
# are regenerated by car 0, baked into the image by car 1 and deployed by
|
||||
# car 2. This is the assertion that all three happened.
|
||||
- name: The MCP tool list is the one this release committed
|
||||
# THE MCP DOOR, AND WHETHER EVERY SUBSYSTEM ANSWERED IT.
|
||||
#
|
||||
# This step used to compare the live tool count against `jq -s length` over
|
||||
# the committed plugin/*/mcp.json. Both sides of that comparison came from
|
||||
# the same files — the door was SERVING those bytes — so it could only ever
|
||||
# prove the image carried the tree it was built from. It passed for months
|
||||
# while plugin/o11y/mcp.json held 12 tools and the o11y binary served 365.
|
||||
#
|
||||
# The door composes itself by asking every subsystem now, so the release
|
||||
# question is a different and much better one: DID THEY ALL ANSWER. A
|
||||
# subsystem that is down, mis-rolled or wedged is named in the reply's
|
||||
# _meta, which is the one thing the old check could never see — a broken
|
||||
# deployment matched the files exactly.
|
||||
- name: Every subsystem answered the MCP door
|
||||
run: |
|
||||
set -euo pipefail
|
||||
WANT=$(jq -s '[.[]|length]|add' plugin/*/mcp.json)
|
||||
GOT=$(curl -fsS -X POST https://api.hanzo.ai/v1/mcp \
|
||||
BODY=$(curl -fsS -X POST https://api.hanzo.ai/v1/mcp \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools|length')
|
||||
echo "MCP: committed ${WANT}, live ${GOT}"
|
||||
[ "$WANT" = "$GOT" ] || { echo "::error::the MCP door serves ${GOT} tools and this release committed ${WANT}"; exit 1; }
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}')
|
||||
TOOLS=$(echo "$BODY" | jq '.result.tools|length')
|
||||
DOWN=$(echo "$BODY" | jq -r '.result._meta["hanzo.ai/unavailable"] // [] | length')
|
||||
echo "MCP: ${TOOLS} tools live, ${DOWN} subsystems unavailable"
|
||||
if [ "$DOWN" != "0" ]; then
|
||||
echo "$BODY" | jq -r '.result._meta["hanzo.ai/unavailable"][] | " \(.app): \(.error)"'
|
||||
echo "::error::${DOWN} subsystems did not answer the fleet's agent door"
|
||||
exit 1
|
||||
fi
|
||||
[ "$TOOLS" -gt 0 ] || { echo "::error::the MCP door serves ZERO tools"; exit 1; }
|
||||
|
||||
# ══ CAR 4 ══ FAN OUT. One release, one document, every projection.
|
||||
#
|
||||
|
||||
@@ -2640,18 +2640,37 @@ migration silently strips request shapes from every generated CLI and SDK.
|
||||
two structs — 1246 packages for a DTO. One definition, neither end importing the
|
||||
other. Measured: `plugin/admin` 2261 → 1115 packages, `apps/billing` 1246 → 945,
|
||||
and two boards that had been reporting zeros started reporting the truth.
|
||||
- **THE FLEET HAS ONE MCP DOOR: `POST /v1/mcp`, on the HOST.** zip serves it
|
||||
(`zip.MCPConfig{Path:"/v1/mcp"}` in `cmd/cloud`), and its tool list is the union
|
||||
of every mounted plugin's BUILD-TIME catalogue — `plugin/<app>/mcp.json`, written
|
||||
by the same `<app> describe` run that writes that app's `openapi.json`, embedded
|
||||
by the leaf `plugin/embed.go` and handed to zip as `Plugin.Tools`. So `tools/list`
|
||||
is a memcpy of a constant and starts NO child; only a `tools/call` wakes one — the
|
||||
single plugin that owns the name — over ZAP on its private socket, where the
|
||||
child's OWN registry answers. There were THREE hand-rolled registries for this one
|
||||
concept (`apps/tools/http.go`, `apps/tools/builtin.go`, `apps/automations/mcp.go`)
|
||||
and the public one exposed none of the typed ops; all three are deleted. Type an
|
||||
- **THE FLEET HAS ONE MCP DOOR: `POST /v1/mcp`, on the HOST, AND IT IS A QUERY.**
|
||||
The host serves it itself (`fleet.Mount` in `cmd/cloud`; zip's own door is
|
||||
`Disabled` there so exactly one handler holds the address). A `tools/list`
|
||||
forwards the CALLER's own message to every composed subsystem's own `/mcp` over
|
||||
its private ZAP socket, in parallel, and unions the replies — so what the door
|
||||
carries is what the subsystems serve at that instant, and a subsystem whose tools
|
||||
depend on the tenant answers for THIS caller out of its own rows. `zip.App.Start`
|
||||
resolves a cold child, which is the same single-flighted path a prefix request
|
||||
takes, so the first list pays one start per app and nothing after it does.
|
||||
**A subsystem that does not answer is NAMED** in `result._meta["hanzo.ai/unavailable"]`,
|
||||
because a silently-short list and a stale file are the same defect. A `tools/call`
|
||||
goes to the app that listed the name, verbatim; a name nobody has listed costs one
|
||||
discovery, then `-32602`.
|
||||
It used to read a BUILD-TIME catalogue — `plugin/<app>/mcp.json`, embedded by
|
||||
`plugin/embed.go` and handed to zip as `Plugin.Tools` — and `tools/list` was a
|
||||
memcpy. **Those 116 files are deleted (49,865 lines).** They were a second source
|
||||
for a fact each child already knows, and they were wrong: `plugin/o11y/mcp.json`
|
||||
held 12 tools while the o11y binary at the same commit served 365, because the
|
||||
missing 353 ops live in `github.com/hanzoai/o11y` and a `go.mod` bump in ANOTHER
|
||||
repository invalidated an artifact in this one with nothing in the diff to say so
|
||||
— no generator on a hook here could ever have seen that trigger. There were also
|
||||
THREE hand-rolled registries for this one concept (`apps/tools/http.go`,
|
||||
`apps/tools/builtin.go`, `apps/automations/mcp.go`); all three are deleted. Type an
|
||||
op and it IS a tool — do not write a second JSON-RPC envelope, and note
|
||||
`manifest/mcp_test.go` turns one red (no served path may end in `/mcp`).
|
||||
`plugin/<app>/openapi.json` SURVIVES, for the one reason the catalogue could not:
|
||||
the fleet weave carries each subsystem's PROSE, and that prose is lifted from the
|
||||
app's SOURCE at describe time (`openapi.Synopsis`) — a running child has no comment
|
||||
to read and would answer with its deployment's brand blurb, which the weave would
|
||||
publish as the description of every product tag. Making the synopsis a declared
|
||||
value is what deleting that half is waiting on.
|
||||
- **The per-tenant tool plane is ONE typed op, and callable in-process.** An org's
|
||||
connectors, functions, agents, authored skills and external MCP servers are ROWS,
|
||||
not code, so no build-time catalogue can hold them: they are reached through
|
||||
@@ -3324,9 +3343,15 @@ The file may only shrink: a 404 not listed fails the release, and a listed line
|
||||
that starts answering must be deleted in the same commit. Today it holds 14
|
||||
`/v1/pricing/*` lines, all owned by a Cloudflare worker that exists in no repo.
|
||||
|
||||
**The projections are gated, not hoped for.** MCP needs no car — the door serves
|
||||
exactly the tools in the committed `plugin/*/mcp.json`, so car 3 checks the
|
||||
number rather than claiming it (833 = 833 at v1.801.350). The eight clients and
|
||||
**The projections are gated, not hoped for.** MCP needs no car — but the gate
|
||||
changed with the door. Car 3 used to compare the live tool count against
|
||||
`jq -s length` over the committed `plugin/*/mcp.json`; both sides came from the
|
||||
same files (the door was SERVING those bytes), so it proved only that the image
|
||||
carried the tree it was built from, and it passed for months while o11y's
|
||||
catalogue held 12 of 365. The door composes itself by asking now, so car 3 asks
|
||||
the better question: **did every subsystem answer** — any name in
|
||||
`result._meta["hanzo.ai/unavailable"]` fails the release. A broken deployment used
|
||||
to match the files exactly. The eight clients and
|
||||
the docs each run hanzoai/ci's `client:` lane, which fetches `openapi.yaml` at
|
||||
the release's sha, **refuses on a digest mismatch**, regenerates, compiles itself
|
||||
and its examples, writes `.spec-lock` and cuts a patch.
|
||||
|
||||
@@ -265,9 +265,10 @@ test-fast: ## Everything `test` runs except the spec drift gate. Inner loop only
|
||||
# prose and examples reach the document. `-run zipdoc` picks the directives
|
||||
# out of ./... by name, so a typed op added anywhere is covered and no
|
||||
# unrelated generator fires.
|
||||
# 2. each app describes ITSELF: `<app> openapi` mounts that one subsystem and
|
||||
# 2. each app describes ITSELF: `<app> describe` mounts that one subsystem and
|
||||
# projects its own router into plugin/<app>/openapi.json (mk/fleet.mk — one lean
|
||||
# binary per app, no fused build and no mega link).
|
||||
# binary per app, no fused build and no mega link). It no longer writes an MCP
|
||||
# catalogue beside it: the door asks the subsystems (package fleet).
|
||||
# 3. the weave composes those subsets into openapi.yaml (openapi/weave.go),
|
||||
# refusing when two apps claim one path or one schema name. There is no
|
||||
# monolith left to read: the woven document IS the published spec.
|
||||
@@ -293,7 +294,7 @@ describe: ## Regenerate every app's projections, then weave them into openapi.ya
|
||||
$(GO) generate -run zipdoc ./...
|
||||
$(MAKE) -f mk/fleet.mk describe-apps
|
||||
$(MAKE) -f mk/fleet.mk openapi-weave OUT=openapi.yaml
|
||||
@echo ">> openapi.yaml — $$(grep -c '^ /' openapi.yaml) paths, $$(cat plugin/*/mcp.json | grep -c '\"name\":') MCP tools"
|
||||
@echo ">> openapi.yaml — $$(grep -c '^ /' openapi.yaml) paths. The MCP tool list is NOT an artifact: POST /v1/mcp asks every subsystem."
|
||||
|
||||
test-cgo: ## Prove the cgo build works too — forces the fork's pure-Go backend via -tags sqlite_purego so the embedded modernc importers don't double-register "sqlite".
|
||||
$(TEST_ENV) CGO_ENABLED=1 $(GO) test -tags "sqlite_purego $(TEST_TAGS)" ./...
|
||||
|
||||
+62
-94
@@ -15,18 +15,18 @@ package ai
|
||||
// and an inventory nobody can read is how a door that serves nothing passes for
|
||||
// a healthy one.
|
||||
//
|
||||
// So this file adds exactly one op, and it reports THREE numbers that are three
|
||||
// different questions, never one number standing in for all of them:
|
||||
// So this file adds exactly one op, and it reports what THIS PROCESS's door
|
||||
// actually carries — read from the live registry, never from a description of it.
|
||||
//
|
||||
// published — every tool this BUILD can serve: the union of every subsystem's
|
||||
// committed catalogue, the same plugin/<app>/mcp.json bytes the host
|
||||
// hands zip at Load. A property of the artifact, true in any process.
|
||||
// served — what THIS PROCESS's door actually composed. Read from the LIVE
|
||||
// composition (App.Plugins + App.MCPTools), so a subsystem that did
|
||||
// not mount is missing from it. This is the number that can be zero
|
||||
// while `published` is nine hundred, and saying so is the whole point.
|
||||
// local — the part of `served` this process registered itself, as opposed to
|
||||
// composing from a child's catalogue.
|
||||
// IT USED TO REPORT A THIRD NUMBER, `published`: every tool the BUILD could
|
||||
// serve, summed over the committed plugin/<app>/mcp.json catalogues. Those files
|
||||
// are gone. They were a second source for a fact each child already knows, and
|
||||
// they were wrong — plugin/o11y/mcp.json held 12 tools while the o11y binary at
|
||||
// the same commit served 365 — so the fleet-wide question is answered by ASKING
|
||||
// the fleet now, at the one door, which also NAMES every subsystem it could not
|
||||
// reach (package fleet). No process but the host can ask that question, and a
|
||||
// subsystem inventing an answer to it is precisely the green surface this file
|
||||
// was written against.
|
||||
//
|
||||
// A deployment manifest answers what was INTENDED; only the process answers what
|
||||
// it LOADED, and during a rolling upgrade the two disagree by design (scope.go
|
||||
@@ -39,20 +39,16 @@ package ai
|
||||
// the PATH-derived id (get_v1_o11y_logs, get_v1_analytics_top), and a path lives
|
||||
// under the subtree the manifest grants exactly one subsystem, so two defaults
|
||||
// cannot meet; an op that DOES name itself escapes that, so the name is checked
|
||||
// instead — zip refuses a Load whose catalogue claims a name another plugin
|
||||
// already owns (a boot failure), and manifest's TestEveryCatalogueToolIsAnOpOfItsOwnApp
|
||||
// turns that boot failure into a red build. A hand-written id in this package
|
||||
// therefore carries its subsystem: aiMCPTools, never mcpTools.
|
||||
// instead — the fleet door refuses to serve one name from two apps and logs both
|
||||
// (fleet/mcp.go). A hand-written id in this package therefore carries its
|
||||
// subsystem: aiMCPTools, never mcpTools.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/hanzoai/cloud/apps/principal"
|
||||
"github.com/hanzoai/cloud/manifest"
|
||||
"github.com/hanzoai/cloud/plugin"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
@@ -90,52 +86,49 @@ func mountMCP(app *zip.App) {
|
||||
zip.Get(app, "/v1/ai/mcp/tools", o.tools, zip.WithOperationID("aiMCPTools"))
|
||||
}
|
||||
|
||||
// aiMCPQuery narrows the answer to one subsystem.
|
||||
// aiMCPQuery asks for the tool names as well as the counts.
|
||||
//
|
||||
// A typed op's Go type name IS its schema name and the fleet's schema namespace
|
||||
// is FLAT, so every name in this file carries the product prefix.
|
||||
type aiMCPQuery struct {
|
||||
// App names one subsystem whose tool NAMES to list. Empty answers counts
|
||||
// only: nine hundred names is a page no operator reads and no model can
|
||||
// afford to be handed by accident.
|
||||
App string `json:"app"`
|
||||
// Names asks for this process's tool NAMES and not only how many there are.
|
||||
// Off by default: a list of names is a page, and the question this op exists
|
||||
// to answer ("is the door up and does it have anything behind it") is answered
|
||||
// by the count.
|
||||
Names bool `json:"names"`
|
||||
}
|
||||
|
||||
// aiMCPSurface is what the one MCP door carries, from this process's vantage.
|
||||
type aiMCPSurface struct {
|
||||
// Published is every tool this BUILD can serve — the union of every
|
||||
// subsystem's committed catalogue, which is a property of the artifact and
|
||||
// therefore the same answer in every process.
|
||||
Published int `json:"published"`
|
||||
// Served is what THIS PROCESS's door actually composed. It is the number that
|
||||
// can be far smaller than Published — a host that mounted nothing serves
|
||||
// nothing — and the only one that describes the door a client is talking to.
|
||||
Served int `json:"served"`
|
||||
// Local is the part of Served this process registered ITSELF, rather than
|
||||
// composing from a mounted child's catalogue.
|
||||
Local int `json:"local"`
|
||||
// Apps is one row per subsystem the build publishes, in manifest order.
|
||||
Apps []aiMCPApp `json:"apps"`
|
||||
}
|
||||
|
||||
// aiMCPApp is one subsystem's contribution to the door.
|
||||
type aiMCPApp struct {
|
||||
// Name is the subsystem, as the manifest names it.
|
||||
Name string `json:"name"`
|
||||
// Tools is how many tools its committed catalogue publishes.
|
||||
// Tools is how many tools THIS PROCESS's door carries: its own typed-op
|
||||
// registry, projected. It is the only number a subsystem can state honestly —
|
||||
// what the FLEET's door carries is a question only the host can ask, and it
|
||||
// asks it by asking every subsystem (POST /v1/mcp, tools/list).
|
||||
Tools int `json:"tools"`
|
||||
// Served reports that THIS process actually mounted it, so its tools are on
|
||||
// the door a client can call rather than only in the build.
|
||||
Served bool `json:"served"`
|
||||
// Names are its tool names, present only for the subsystem the query named.
|
||||
// Apps is one row per subsystem this deployment composes, in manifest order.
|
||||
Apps []aiMCPApp `json:"apps"`
|
||||
// Names are this process's own tool names, present only when the query asked
|
||||
// for them.
|
||||
Names []string `json:"names,omitempty"`
|
||||
}
|
||||
|
||||
// Tools reports what this binary's MCP door carries: every tool the build
|
||||
// publishes, how many of them this process actually serves, and which subsystem
|
||||
// each belongs to. It is the answer to "is the door up and does it have anything
|
||||
// behind it" — a question a status code cannot answer, since an empty door and a
|
||||
// full one are both 200.
|
||||
// aiMCPApp is one subsystem, as this process sees it.
|
||||
type aiMCPApp struct {
|
||||
// Name is the subsystem, as the manifest names it.
|
||||
Name string `json:"name"`
|
||||
// Served reports that THIS process mounted it, so its tools are on this
|
||||
// process's door rather than behind a sibling this process only knows the name
|
||||
// of.
|
||||
Served bool `json:"served"`
|
||||
}
|
||||
|
||||
// Tools reports what THIS PROCESS's MCP door carries: how many tools its own
|
||||
// registry projects, optionally their names, and which subsystems this process
|
||||
// composed. It is the answer to "is this door up and does it have anything behind
|
||||
// it" — a question a status code cannot answer, since an empty door and a full
|
||||
// one are both 200. What the FLEET's door carries is the fleet door's own answer:
|
||||
// POST /v1/mcp, tools/list, which asks every subsystem and names the ones that
|
||||
// did not reply.
|
||||
func (o mcpOps) tools(ctx context.Context, in *aiMCPQuery) (*aiMCPSurface, error) {
|
||||
// A TOOL CALL IS AN API CALL. The gate is the op's own, read from the bit
|
||||
// cloud.Bridge parked, so it holds identically over REST and over MCP — and
|
||||
@@ -143,56 +136,31 @@ func (o mcpOps) tools(ctx context.Context, in *aiMCPQuery) (*aiMCPSurface, error
|
||||
if !principal.ValidatedFrom(ctx) {
|
||||
return nil, zip.ErrForbidden(mcpGate)
|
||||
}
|
||||
return surface(o.app, in.App), nil
|
||||
return surface(o.app, in.Names), nil
|
||||
}
|
||||
|
||||
// surface reads the door. The published half comes from the committed
|
||||
// catalogues — the same bytes the host hands zip — and the served half from the
|
||||
// live composition, never from a list of what was meant to mount.
|
||||
func surface(app *zip.App, only string) *aiMCPSurface {
|
||||
cat := published()
|
||||
// surface reads the door: this process's own registry, and which subsystems it
|
||||
// actually composed.
|
||||
//
|
||||
// Both halves come from the LIVE app — App.MCPTools and App.Plugins — never from
|
||||
// a list of what was meant to mount, and never from an artifact. There is no
|
||||
// build-time half left to disagree with them.
|
||||
func surface(app *zip.App, names bool) *aiMCPSurface {
|
||||
tools := app.MCPTools()
|
||||
mounted := map[string]bool{}
|
||||
for _, p := range app.Plugins() {
|
||||
mounted[p.Name] = true
|
||||
}
|
||||
out := &aiMCPSurface{
|
||||
Local: len(app.MCPTools()),
|
||||
Apps: make([]aiMCPApp, 0, len(manifest.Apps)),
|
||||
}
|
||||
out.Served = out.Local
|
||||
out := &aiMCPSurface{Tools: len(tools), Apps: make([]aiMCPApp, 0, len(manifest.Apps))}
|
||||
for _, a := range manifest.Apps {
|
||||
row := aiMCPApp{Name: a.Name, Tools: len(cat[a.Name]), Served: mounted[a.Name]}
|
||||
if row.Served {
|
||||
out.Served += row.Tools
|
||||
out.Apps = append(out.Apps, aiMCPApp{Name: a.Name, Served: mounted[a.Name]})
|
||||
}
|
||||
if names {
|
||||
out.Names = make([]string, 0, len(tools))
|
||||
for _, t := range tools {
|
||||
out.Names = append(out.Names, t["name"].(string))
|
||||
}
|
||||
if only != "" && only == a.Name {
|
||||
row.Names = cat[a.Name]
|
||||
}
|
||||
out.Published += row.Tools
|
||||
out.Apps = append(out.Apps, row)
|
||||
sort.Strings(out.Names)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// published is the build's catalogue — subsystem → its tool names — parsed ONCE.
|
||||
// The bytes are the artifact each app's own binary projected from its own
|
||||
// registry at build time (plugin/embed.go), so this reads what the door serves
|
||||
// rather than a description of it.
|
||||
var published = sync.OnceValue(func() map[string][]string {
|
||||
out := make(map[string][]string, len(manifest.Apps))
|
||||
for _, a := range manifest.Apps {
|
||||
var tools []struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if json.Unmarshal(plugin.Tools(a.Name), &tools) != nil {
|
||||
continue // an app that has not been described yet publishes nothing
|
||||
}
|
||||
names := make([]string, 0, len(tools))
|
||||
for _, t := range tools {
|
||||
names = append(names, t.Name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
out[a.Name] = names
|
||||
}
|
||||
return out
|
||||
})
|
||||
|
||||
+98
-231
@@ -2,22 +2,24 @@
|
||||
|
||||
package ai
|
||||
|
||||
// mcp_test.go drives the REAL door, never a description of it.
|
||||
// mcp_test.go drives ai's REAL door, never a description of it.
|
||||
//
|
||||
// The fleet's door is composed, not written: the host Loads every subsystem
|
||||
// with the catalogue that subsystem's own binary projected from its own typed-op
|
||||
// registry, and zip serves the union at one JSON-RPC endpoint. So the honest way
|
||||
// to test it is to compose it — every manifest row, its real committed
|
||||
// catalogue — and then ASK it. A remote mount (Plugin.Addr set) records the
|
||||
// plugin and installs its catalogue without spawning anything (zip load.go), so
|
||||
// the composition under test is the production one and the test costs no
|
||||
// processes.
|
||||
// It used to compose the WHOLE fleet here — every manifest row, loaded as a
|
||||
// remote mount carrying that app's committed plugin/<app>/mcp.json — and assert
|
||||
// the union. That composition is gone with the artifact: the fleet's door is no
|
||||
// longer the concatenation of files this package can read, it is what the
|
||||
// subsystems answer when the host asks them, and the only honest place to test
|
||||
// that is against subsystems that are RUNNING (fleet/mcp_test.go, which starts
|
||||
// real children on real sockets and goes red on a short list).
|
||||
//
|
||||
// Every assertion below reads a BODY. A tools/list that 200s with an empty array
|
||||
// is the exact failure this fleet has shipped, and a status code cannot tell it
|
||||
// from a full one.
|
||||
// What remains here is what belongs here: ai's own op, on ai's own door, and the
|
||||
// property that a tool call IS an API call — the same gate, the same words, both
|
||||
// projections. Every assertion reads a BODY. A tools/list that 200s with an empty
|
||||
// array is the exact failure this fleet has shipped, and a status code cannot
|
||||
// tell it from a full one.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -31,7 +33,6 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
"github.com/hanzoai/cloud/manifest"
|
||||
"github.com/hanzoai/cloud/plugin"
|
||||
)
|
||||
|
||||
// door is the framework's default MCP path. This file asserts what is BEHIND the
|
||||
@@ -39,64 +40,17 @@ import (
|
||||
// owns, and pinning it here would be a second place for it to be written down.
|
||||
const door = "/mcp"
|
||||
|
||||
// fleet composes a host exactly as cmd/cloud does — every manifest row, its real
|
||||
// catalogue — minus the subsystems named in `without`, which is how a test
|
||||
// UNMOUNTS one. Nothing is spawned: every plugin is a remote mount at an address
|
||||
// no request in this file ever reaches, because none of these tests calls a tool
|
||||
// that belongs to a child.
|
||||
func fleet(t *testing.T, without ...string) *zip.App {
|
||||
// served is ai's own op, mounted on its own door, with the identity boundary's
|
||||
// carrier installed exactly as cloud.Listen installs it.
|
||||
func served(t *testing.T) *zip.App {
|
||||
t.Helper()
|
||||
skip := map[string]bool{}
|
||||
for _, n := range without {
|
||||
skip[n] = true
|
||||
}
|
||||
app := zip.New(zip.Config{AppName: "cloud", Logger: luxlog.New("aimcptest"), DisableStartupMessage: true})
|
||||
for i, a := range manifest.Apps {
|
||||
// A co-resident app routes no prefix of its own, so there is nothing to
|
||||
// mount — the same skip cmd/cloud's mount() makes.
|
||||
if a.Coresident || skip[a.Name] {
|
||||
continue
|
||||
}
|
||||
p := zip.Plugin{
|
||||
Name: a.Name,
|
||||
Addr: fmt.Sprintf("127.0.0.1:%d", 1+i), // never dialled: no test here calls a child's tool
|
||||
Tools: plugin.Tools(a.Name),
|
||||
}
|
||||
if err := app.Add(zip.Load(p, a.Prefixes...)); err != nil {
|
||||
t.Fatalf("compose %s: %v", a.Name, err)
|
||||
}
|
||||
}
|
||||
app := zip.New(zip.Config{AppName: "ai", Logger: luxlog.New("aimcptest"), DisableStartupMessage: true})
|
||||
app.Use(cloud.Bridge())
|
||||
mountMCP(app)
|
||||
app.Prepare()
|
||||
return app
|
||||
}
|
||||
|
||||
// list asks the door for its tools and returns their names, in the order the
|
||||
// door served them.
|
||||
func list(t *testing.T, app *zip.App) []string {
|
||||
t.Helper()
|
||||
body := rpc(t, app, `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`, "", "")
|
||||
var env struct {
|
||||
Result struct {
|
||||
Tools []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
} `json:"tools"`
|
||||
} `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(body), &env); err != nil {
|
||||
t.Fatalf("tools/list is not an MCP envelope: %v\nbody: %s", err, trunc(body))
|
||||
}
|
||||
out := make([]string, 0, len(env.Result.Tools))
|
||||
for _, tl := range env.Result.Tools {
|
||||
if tl.Name == "" || len(tl.InputSchema) == 0 {
|
||||
t.Errorf("a tool arrived with no name or no inputSchema: %+v", tl)
|
||||
}
|
||||
out = append(out, tl.Name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// rpc posts one JSON-RPC message to the door, optionally as a validated caller
|
||||
// (the headers SanitizeIdentity mints), and returns the body.
|
||||
func rpc(t *testing.T, app *zip.App, msg, user, org string) string {
|
||||
@@ -122,6 +76,40 @@ func rpc(t *testing.T, app *zip.App, msg, user, org string) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// list asks the door for its tools and returns their names, in the order the
|
||||
// door served them.
|
||||
func list(t *testing.T, app *zip.App) []string {
|
||||
t.Helper()
|
||||
body := rpc(t, app, `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`, "", "")
|
||||
var env struct {
|
||||
Result struct {
|
||||
Tools []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
} `json:"tools"`
|
||||
} `json:"result"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(body), &env); err != nil {
|
||||
t.Fatalf("tools/list is not an MCP envelope: %v\nbody: %s", err, trunc(body))
|
||||
}
|
||||
out := make([]string, 0, len(env.Result.Tools))
|
||||
for _, tl := range env.Result.Tools {
|
||||
// An op present with an EMPTY description is a SILENT failure: the model
|
||||
// pays context for a nameless tool it cannot choose. That exact bug shipped
|
||||
// once here (zipdoc blind to group prefixes), so it is a gate, not a hope.
|
||||
if strings.TrimSpace(tl.Description) == "" {
|
||||
t.Errorf("tool %q has an EMPTY description — the prose zipdoc lifts IS what a model "+
|
||||
"reads to pick it. Write the doc comment and run: go generate -run zipdoc ./apps/ai/...", tl.Name)
|
||||
}
|
||||
if tl.Name == "" || len(tl.InputSchema) == 0 {
|
||||
t.Errorf("a tool arrived with no name or no inputSchema: %+v", tl)
|
||||
}
|
||||
out = append(out, tl.Name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func trunc(s string) string {
|
||||
if len(s) > 400 {
|
||||
return s[:400] + "…"
|
||||
@@ -129,136 +117,6 @@ func trunc(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// TestTheDoorCarriesEverySubsystemsTools: the aggregation, measured.
|
||||
//
|
||||
// The number is the union of every subsystem's committed catalogue, and it is
|
||||
// asserted EXACTLY — a door that lists nothing, or one that quietly drops an
|
||||
// app, is a different number. The floor beside it is there because "exactly
|
||||
// equal to a thing computed the same way" is satisfiable by two zeros.
|
||||
func TestTheDoorCarriesEverySubsystemsTools(t *testing.T) {
|
||||
app := fleet(t)
|
||||
got := list(t, app)
|
||||
|
||||
want := 0
|
||||
for _, a := range manifest.Apps {
|
||||
want += len(published()[a.Name])
|
||||
}
|
||||
if len(got) != want {
|
||||
t.Fatalf("the door served %d tools; the fleet's catalogues publish %d", len(got), want)
|
||||
}
|
||||
if want < 900 {
|
||||
t.Fatalf("the fleet publishes only %d tools — a catalogue is missing or empty; "+
|
||||
"regenerate: make -f mk/fleet.mk describe-apps", want)
|
||||
}
|
||||
// A name is dispatch, so two owners make it unroutable.
|
||||
seen := map[string]bool{}
|
||||
for _, n := range got {
|
||||
if seen[n] {
|
||||
t.Errorf("the door lists %q twice — a tool name is dispatch and cannot have two owners", n)
|
||||
}
|
||||
seen[n] = true
|
||||
}
|
||||
// Spot-check that a real subsystem's real op actually arrived, so this
|
||||
// cannot pass on a list of the right size made of the wrong things.
|
||||
for _, want := range []string{"get_v1_o11y_logs", "get_v1_analytics_top", "createOrganization"} {
|
||||
if !seen[want] {
|
||||
t.Errorf("the door does not carry %q", want)
|
||||
}
|
||||
}
|
||||
t.Logf("the one door carries %d tools across %d subsystems", len(got), len(manifest.Apps))
|
||||
}
|
||||
|
||||
// TestUnmountingASubsystemLeavesTheDoor: THE MUTATION, as a property.
|
||||
//
|
||||
// A registry that silently lists nothing is the defect this estate has shipped
|
||||
// twice, and the reason it survived is that no test could tell a full door from
|
||||
// an empty one. This one can: unmount o11y and its tools must be GONE — by count
|
||||
// and by name — while every other subsystem's stay.
|
||||
func TestUnmountingASubsystemLeavesTheDoor(t *testing.T) {
|
||||
const gone = "o11y"
|
||||
full := list(t, fleet(t))
|
||||
cut := list(t, fleet(t, gone))
|
||||
|
||||
n := len(published()[gone])
|
||||
if n == 0 {
|
||||
t.Fatalf("%s publishes no tools, so unmounting it proves nothing — pick a subsystem that does", gone)
|
||||
}
|
||||
if len(full)-len(cut) != n {
|
||||
t.Fatalf("unmounting %s changed the door by %d tools; its catalogue holds %d",
|
||||
gone, len(full)-len(cut), n)
|
||||
}
|
||||
left := map[string]bool{}
|
||||
for _, s := range cut {
|
||||
left[s] = true
|
||||
}
|
||||
for _, name := range published()[gone] {
|
||||
if left[name] {
|
||||
t.Errorf("%s is unmounted but the door still lists its tool %q", gone, name)
|
||||
}
|
||||
}
|
||||
// And the rest of the fleet is untouched: an unmount must not take a sibling
|
||||
// with it.
|
||||
for _, name := range published()["analytics"] {
|
||||
if !left[name] {
|
||||
t.Errorf("unmounting %s also lost analytics' tool %q", gone, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTheInventoryAgreesWithTheDoor: the anti-green-surface gate.
|
||||
//
|
||||
// ai's op reports what the door carries. If it can report a number the door does
|
||||
// not serve, it is exactly the instrument this fleet keeps mistaking for the
|
||||
// mechanism. So it is measured AGAINST the door, on the same app, twice — whole,
|
||||
// and with a subsystem unmounted.
|
||||
func TestTheInventoryAgreesWithTheDoor(t *testing.T) {
|
||||
for _, without := range [][]string{nil, {"o11y"}, {"o11y", "iam", "admin"}} {
|
||||
app := fleet(t, without...)
|
||||
if got, want := surface(app, "").Served, len(list(t, app)); got != want {
|
||||
t.Errorf("without %v: the inventory says %d tools are served; the door serves %d",
|
||||
without, got, want)
|
||||
}
|
||||
}
|
||||
// Published is a property of the BUILD, so unmounting cannot move it — that
|
||||
// is the whole reason the two numbers are two fields.
|
||||
whole, cut := surface(fleet(t), ""), surface(fleet(t, "o11y"), "")
|
||||
if whole.Published != cut.Published {
|
||||
t.Errorf("unmounting a subsystem moved `published` (%d → %d) — it reports what the "+
|
||||
"BUILD can serve, and only `served` reports what this process did",
|
||||
whole.Published, cut.Published)
|
||||
}
|
||||
if whole.Served == cut.Served {
|
||||
t.Errorf("unmounting a subsystem did NOT move `served` (%d) — then it is not reading "+
|
||||
"the live composition", whole.Served)
|
||||
}
|
||||
// The row for an unmounted subsystem says so, and still reports what it would
|
||||
// have contributed.
|
||||
for _, row := range cut.Apps {
|
||||
if row.Name != "o11y" {
|
||||
continue
|
||||
}
|
||||
if row.Served {
|
||||
t.Error("o11y is unmounted and its row says served")
|
||||
}
|
||||
if row.Tools != len(published()["o11y"]) {
|
||||
t.Errorf("o11y's row publishes %d tools; its catalogue holds %d", row.Tools, len(published()["o11y"]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── the gate ────────────────────────────────────────────────────────────────
|
||||
|
||||
// served is ai's own op, mounted on its own door, with the identity boundary's
|
||||
// carrier installed exactly as cloud.Listen installs it.
|
||||
func served(t *testing.T) *zip.App {
|
||||
t.Helper()
|
||||
app := zip.New(zip.Config{AppName: "ai", Logger: luxlog.New("aimcptest"), DisableStartupMessage: true})
|
||||
app.Use(cloud.Bridge())
|
||||
mountMCP(app)
|
||||
app.Prepare()
|
||||
return app
|
||||
}
|
||||
|
||||
// get drives the REST projection of the same op.
|
||||
func get(t *testing.T, app *zip.App, user string) (int, string) {
|
||||
t.Helper()
|
||||
@@ -343,19 +201,17 @@ func TestAToolCarriesItsOpsGateExactly(t *testing.T) {
|
||||
if err := json.Unmarshal([]byte(text), &got); err != nil {
|
||||
t.Fatalf("the tool result is not the op's Out: %v\ntext: %s", err, trunc(text))
|
||||
}
|
||||
want := surface(app, "")
|
||||
if got.Published != want.Published || got.Served != want.Served || got.Local != want.Local {
|
||||
t.Fatalf("the tool answered {published:%d served:%d local:%d}; the op answers {published:%d served:%d local:%d}",
|
||||
got.Published, got.Served, got.Local, want.Published, want.Served, want.Local)
|
||||
if want := surface(app, false); got.Tools != want.Tools || len(got.Apps) != len(want.Apps) {
|
||||
t.Fatalf("the tool answered {tools:%d apps:%d}; the op answers {tools:%d apps:%d}",
|
||||
got.Tools, len(got.Apps), want.Tools, len(want.Apps))
|
||||
}
|
||||
if got.Published < 900 || len(got.Apps) != len(manifest.Apps) {
|
||||
t.Fatalf("the tool's body is not the fleet's inventory: published=%d over %d rows",
|
||||
got.Published, len(got.Apps))
|
||||
if len(got.Apps) != len(manifest.Apps) {
|
||||
t.Fatalf("the inventory names %d subsystems; the manifest holds %d", len(got.Apps), len(manifest.Apps))
|
||||
}
|
||||
// This process registered ai's op and nothing else, so its door serves
|
||||
// exactly what it declared — and says so.
|
||||
if got.Local != 1 || got.Served != 1 {
|
||||
t.Errorf("this process registered 1 typed op; it reports local=%d served=%d", got.Local, got.Served)
|
||||
if got.Tools != 1 {
|
||||
t.Errorf("this process registered 1 typed op; it reports tools=%d", got.Tools)
|
||||
}
|
||||
|
||||
// And the same op over HTTP, validated, answers the same body — one op, two
|
||||
@@ -368,9 +224,43 @@ func TestAToolCarriesItsOpsGateExactly(t *testing.T) {
|
||||
if err := json.Unmarshal([]byte(body), &rest); err != nil {
|
||||
t.Fatalf("the REST body is not the op's Out: %v", err)
|
||||
}
|
||||
if rest.Published != got.Published || rest.Served != got.Served {
|
||||
t.Errorf("REST answered {published:%d served:%d}, MCP answered {published:%d served:%d}",
|
||||
rest.Published, rest.Served, got.Published, got.Served)
|
||||
if rest.Tools != got.Tools || len(rest.Apps) != len(got.Apps) {
|
||||
t.Errorf("REST answered {tools:%d apps:%d}, MCP answered {tools:%d apps:%d}",
|
||||
rest.Tools, len(rest.Apps), got.Tools, len(got.Apps))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTheInventoryReadsTheLiveRegistry: the anti-green-surface gate, at the only
|
||||
// scope a subsystem can honestly answer for.
|
||||
//
|
||||
// The op must report what THIS PROCESS's door actually carries, so registering a
|
||||
// second typed op has to move the number. If it does not, the op is reading
|
||||
// something other than the registry — which is precisely the instrument this
|
||||
// fleet keeps mistaking for the mechanism, and the shape of the artifact that was
|
||||
// just deleted.
|
||||
func TestTheInventoryReadsTheLiveRegistry(t *testing.T) {
|
||||
app := served(t)
|
||||
one := surface(app, true)
|
||||
if got := len(list(t, app)); one.Tools != got {
|
||||
t.Fatalf("the inventory says %d tools; the door serves %d", one.Tools, got)
|
||||
}
|
||||
if len(one.Names) != one.Tools {
|
||||
t.Fatalf("names=%v does not match tools=%d", one.Names, one.Tools)
|
||||
}
|
||||
if len(one.Names) == 0 || one.Names[0] != "aiMCPTools" {
|
||||
t.Fatalf("names=%v, want the op this process registered", one.Names)
|
||||
}
|
||||
|
||||
// A SECOND op on the same app: the number moves, or nothing is being read.
|
||||
type probeIn struct {
|
||||
X string `json:"x"`
|
||||
}
|
||||
zip.Get(app, "/v1/ai/mcp/probe", func(context.Context, *probeIn) (*probeIn, error) { return nil, nil },
|
||||
zip.WithOperationID("aiMCPProbe"), zip.WithSummary("a second op, to prove the count is read and not remembered"))
|
||||
two := surface(app, true)
|
||||
if two.Tools != one.Tools+1 {
|
||||
t.Fatalf("registering an op moved the inventory from %d to %d — it is not reading the live registry",
|
||||
one.Tools, two.Tools)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,26 +278,3 @@ func TestAiIsOnItsOwnDoor(t *testing.T) {
|
||||
"path-derived namespace and must name its owner", names[0])
|
||||
}
|
||||
}
|
||||
|
||||
// TestOneNameOneOwner: the namespace holds across the whole fleet.
|
||||
//
|
||||
// zip refuses a Load whose catalogue claims a name another plugin already owns —
|
||||
// a BOOT failure. fleet() performs that composition for real, over every
|
||||
// manifest row, so a collision fails this file before any assertion runs. This
|
||||
// test states the invariant the composition proves, and names the count so a
|
||||
// silently emptied catalogue cannot satisfy it.
|
||||
func TestOneNameOneOwner(t *testing.T) {
|
||||
owner := map[string]string{}
|
||||
for _, a := range manifest.Apps {
|
||||
for _, name := range published()[a.Name] {
|
||||
if held, dup := owner[name]; dup {
|
||||
t.Errorf("tool %q is claimed by both %q and %q", name, held, a.Name)
|
||||
}
|
||||
owner[name] = a.Name
|
||||
}
|
||||
}
|
||||
if len(owner) < 900 {
|
||||
t.Fatalf("only %d distinct tool names across the fleet", len(owner))
|
||||
}
|
||||
t.Logf("%d distinct tool names, one owner each", len(owner))
|
||||
}
|
||||
|
||||
+7
-10
@@ -8,17 +8,14 @@ import (
|
||||
|
||||
func init() {
|
||||
zip.Describe("GET /v1/ai/mcp/tools", zip.Doc{
|
||||
Description: "Tools reports what this binary's MCP door carries: every tool the build\npublishes, how many of them this process actually serves, and which subsystem\neach belongs to. It is the answer to \"is the door up and does it have anything\nbehind it\" — a question a status code cannot answer, since an empty door and a\nfull one are both 200.",
|
||||
Description: "Tools reports what THIS PROCESS's MCP door carries: how many tools its own\nregistry projects, optionally their names, and which subsystems this process\ncomposed. It is the answer to \"is this door up and does it have anything behind\nit\" — a question a status code cannot answer, since an empty door and a full\none are both 200. What the FLEET's door carries is the fleet door's own answer:\nPOST /v1/mcp, tools/list, which asks every subsystem and names the ones that\ndid not reply.",
|
||||
Fields: map[string]string{
|
||||
"aiMCPApp.name": "Name is the subsystem, as the manifest names it.",
|
||||
"aiMCPApp.names": "Names are its tool names, present only for the subsystem the query named.",
|
||||
"aiMCPApp.served": "Served reports that THIS process actually mounted it, so its tools are on\nthe door a client can call rather than only in the build.",
|
||||
"aiMCPApp.tools": "Tools is how many tools its committed catalogue publishes.",
|
||||
"aiMCPQuery.app": "App names one subsystem whose tool NAMES to list. Empty answers counts\nonly: nine hundred names is a page no operator reads and no model can\nafford to be handed by accident.",
|
||||
"aiMCPSurface.apps": "Apps is one row per subsystem the build publishes, in manifest order.",
|
||||
"aiMCPSurface.local": "Local is the part of Served this process registered ITSELF, rather than\ncomposing from a mounted child's catalogue.",
|
||||
"aiMCPSurface.published": "Published is every tool this BUILD can serve — the union of every\nsubsystem's committed catalogue, which is a property of the artifact and\ntherefore the same answer in every process.",
|
||||
"aiMCPSurface.served": "Served is what THIS PROCESS's door actually composed. It is the number that\ncan be far smaller than Published — a host that mounted nothing serves\nnothing — and the only one that describes the door a client is talking to.",
|
||||
"aiMCPApp.name": "Name is the subsystem, as the manifest names it.",
|
||||
"aiMCPApp.served": "Served reports that THIS process mounted it, so its tools are on this\nprocess's door rather than behind a sibling this process only knows the name\nof.",
|
||||
"aiMCPQuery.names": "Names asks for this process's tool NAMES and not only how many there are.\nOff by default: a list of names is a page, and the question this op exists\nto answer (\"is the door up and does it have anything behind it\") is answered\nby the count.",
|
||||
"aiMCPSurface.apps": "Apps is one row per subsystem this deployment composes, in manifest order.",
|
||||
"aiMCPSurface.names": "Names are this process's own tool names, present only when the query asked\nfor them.",
|
||||
"aiMCPSurface.tools": "Tools is how many tools THIS PROCESS's door carries: its own typed-op\nregistry, projected. It is the only number a subsystem can state honestly —\nwhat the FLEET's door carries is a question only the host can ask, and it\nasks it by asking every subsystem (POST /v1/mcp, tools/list).",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
+74
-14
@@ -44,6 +44,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/credz/launch"
|
||||
"github.com/hanzoai/cloud/fleet"
|
||||
"github.com/hanzoai/cloud/manifest"
|
||||
"github.com/hanzoai/cloud/openapi"
|
||||
"github.com/hanzoai/cloud/plugin"
|
||||
@@ -96,23 +97,24 @@ func forward(kv map[string]string) {
|
||||
}
|
||||
|
||||
func run(addr, zapAddr, enable string) error {
|
||||
// THE FLEET'S ONE AGENT DOOR, at POST /v1/mcp. zip serves it: initialize, ping,
|
||||
// tools/list and tools/call are its handleMCP, and the tool list is the union
|
||||
// of every mounted plugin's build-time catalogue (mount below), rendered once
|
||||
// as bytes. So tools/list — the method an MCP client calls constantly — is a
|
||||
// memcpy and starts NO child; only a tools/call wakes one, the single plugin
|
||||
// that owns the named tool, over ZAP on its private socket.
|
||||
// THE FLEET'S ONE AGENT DOOR is served BY THIS HOST, at POST /v1/mcp, and
|
||||
// zip's is switched off so that exactly one handler holds the address.
|
||||
//
|
||||
// The host is the only process that can own it. MCPTools() is in-process, so a
|
||||
// plugin cannot enumerate a lazy sibling, and a plugin-hosted door would cost
|
||||
// its own wake on the very first list.
|
||||
// zip's door answers out of an app's own typed-op registry plus the build-time
|
||||
// catalogues a host hands it. This host has neither: it registers no op, and
|
||||
// the catalogues are deleted. What it has is CHILDREN, and the honest content
|
||||
// of the fleet's door is what they serve RIGHT NOW — so the host asks them
|
||||
// (fleet.Mount, below, after the mount loops have built the plugin table).
|
||||
//
|
||||
// The host is still the only process that can own it: a plugin's MCPTools() is
|
||||
// in-process, so no subsystem can enumerate a lazy sibling.
|
||||
//
|
||||
// The address comes from manifest, not from a literal here: the console's
|
||||
// terminal handler has to know it too (to refuse to answer a machine door with
|
||||
// the SPA shell, and to send an agent that guessed zip's default to the real
|
||||
// one), and when those two were written down separately the second one was
|
||||
// simply missing — GET /mcp answered 200 text/html for as long as that lasted.
|
||||
app := zip.New(zip.Config{AppName: "cloud", MCP: zip.MCPConfig{Path: manifest.MCPPath}})
|
||||
app := zip.New(zip.Config{AppName: "cloud", MCP: zip.MCPConfig{Disabled: true}})
|
||||
|
||||
// Mint this host's child-signing secret and take the KMS root key OUT of the
|
||||
// host's own environment — both BEFORE the first Load spawns an eager child.
|
||||
@@ -203,6 +205,16 @@ func run(addr, zapAddr, enable string) error {
|
||||
// whole fleet and no plugin can see past itself.
|
||||
spec(app, composed)
|
||||
|
||||
// THE AGENT DOOR, at POST /v1/mcp — composed by ASKING, at the moment of
|
||||
// asking. Registered after the mount loops so the plugin table it starts from
|
||||
// is the finished one, and before anything listens.
|
||||
//
|
||||
// It is the composed set minus the CORESIDENT apps: a coresident app is
|
||||
// middleware on a sibling's router (zen on ai's), so it is not a child this
|
||||
// host can start and its ops are already in the sibling's registry — asking
|
||||
// for it by name would report a permanent outage for an app that is serving.
|
||||
fleet.Mount(app, manifest.MCPPath, routed(composed), locate(app))
|
||||
|
||||
// The bare /mcp needs no route here. webui's terminal handler answers it from
|
||||
// manifest.MCPPath (webui/mcp.go) — one rule, in the one place that can tell a
|
||||
// machine door from a client-side console route. A route registered here would
|
||||
@@ -292,10 +304,13 @@ func mount(app *zip.App, a manifest.App, eager bool, secret, rootKey string, abs
|
||||
}
|
||||
p := a.Plugin()
|
||||
p.Lazy = !eager
|
||||
// This app's MCP tools, from the artifact its own binary wrote when it was
|
||||
// built. Given them, zip serves this app's tools on the host's door and
|
||||
// forwards a tools/call to this app alone — without ever running it to ask.
|
||||
p.Tools = plugin.Tools(a.Name)
|
||||
// NO BUILD-TIME TOOL CATALOGUE. zip.Plugin.Tools took the array this app's
|
||||
// binary projected when it was BUILT (plugin/<app>/mcp.json) so the host could
|
||||
// answer tools/list without running anything. That artifact was a second
|
||||
// source for a fact the child already knows, and it was wrong: o11y's held 12
|
||||
// tools while the o11y binary at the same commit served 365. The door asks the
|
||||
// child now (fleet.Mount), so there is nothing to hand over here.
|
||||
//
|
||||
// Per-plugin, on the plugin's OWN Env, which zip appends to that ONE child's
|
||||
// environment: a scoped token for every child, and — for the broker alone —
|
||||
// the launch secret and the root key. A token or key placed in the host's
|
||||
@@ -337,6 +352,51 @@ func mount(app *zip.App, a manifest.App, eager bool, secret, rootKey string, abs
|
||||
return nil
|
||||
}
|
||||
|
||||
// routed drops the CORESIDENT apps from a composed set: the ones that mount as
|
||||
// middleware on a sibling's router and are therefore not children this host can
|
||||
// reach by name. Their ops are registered on the sibling's app, so the sibling
|
||||
// already answers for them; asking for one by name would report a permanent
|
||||
// outage for a subsystem that is serving perfectly.
|
||||
func routed(composed []string) []string {
|
||||
co := map[string]bool{}
|
||||
for _, a := range manifest.Apps {
|
||||
if a.Coresident {
|
||||
co[a.Name] = true
|
||||
}
|
||||
}
|
||||
out := make([]string, 0, len(composed))
|
||||
for _, name := range composed {
|
||||
if !co[name] {
|
||||
out = append(out, name)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// locate is how the door reaches ONE app: the child this host started, or the
|
||||
// instance an operator pointed CLOUD_<NAME>_ADDR at.
|
||||
//
|
||||
// Both are needed because they are reached differently and only the composition
|
||||
// root knows which is which. zip.App.Start covers a spawned child and is the
|
||||
// right door for it — idempotent, and the same single-flighted path a request to
|
||||
// the app's prefix takes, so a burst of askers still produces one process. A
|
||||
// remotely mounted app is never started, so Start has nothing to report about it
|
||||
// and would name it unavailable forever.
|
||||
func locate(app *zip.App) fleet.At {
|
||||
remote := map[string]string{}
|
||||
for _, a := range manifest.Apps {
|
||||
if addr := a.Plugin().Addr; addr != "" {
|
||||
remote[a.Name] = addr
|
||||
}
|
||||
}
|
||||
return func(name string) (string, error) {
|
||||
if addr := remote[name]; addr != "" {
|
||||
return addr, nil
|
||||
}
|
||||
return app.Start(name)
|
||||
}
|
||||
}
|
||||
|
||||
// draining flips true when this host is shutting down, and is read lock-free by
|
||||
// /readyz. It is the HOST's copy of the contract drain.go states for the root
|
||||
// package: the host is the process K8s signals and probes, and it does not link
|
||||
|
||||
+20
-16
@@ -1,12 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hanzoai/cloud/fleet"
|
||||
"github.com/hanzoai/cloud/manifest"
|
||||
"github.com/hanzoai/cloud/webui"
|
||||
"github.com/zap-proto/zip"
|
||||
@@ -33,28 +34,27 @@ func doMethod(t *testing.T, app *zip.App, method, path, body string) (int, strin
|
||||
return resp.StatusCode, resp.Header.Get("Content-Type"), resp.Header.Get("Location"), string(b)
|
||||
}
|
||||
|
||||
// app308 builds the host the way run() does: the MCP door at manifest.MCPPath,
|
||||
// then the console catch-all LAST. There is no second registration in between,
|
||||
// and that is the point — webui's handler is TERMINAL, so it answers the bare
|
||||
// /mcp precisely because nothing else claimed it. This test is therefore a test
|
||||
// of the COMPOSED host and not of a helper: neuter mcpDoor (webui/mcp.go) and
|
||||
// every case below fails with the console shell.
|
||||
// app308 builds the host the way run() does: the fleet's own MCP door at
|
||||
// manifest.MCPPath, then the console catch-all LAST. There is no second
|
||||
// registration in between, and that is the point — webui's handler is TERMINAL,
|
||||
// so it answers the bare /mcp precisely because nothing else claimed it. This
|
||||
// test is therefore a test of the COMPOSED host and not of a helper: neuter
|
||||
// mcpDoor (webui/mcp.go) and every case below fails with the console shell.
|
||||
//
|
||||
// The typed op is not decoration. zip installs the door only when the app has at
|
||||
// least one op, plugin tool or caller (installMCP), so a host with an empty
|
||||
// registry serves no /v1/mcp at all — and a redirect onto a door that was never
|
||||
// installed is the very bug this file is about. One op is the smallest thing that
|
||||
// makes the target real.
|
||||
// The door is the HOST's, registered by fleet.Mount, and zip's is disabled — the
|
||||
// same pair run() sets. It fronts no app here (an empty composed set), which is
|
||||
// exactly right for this file: what is being tested is that the ADDRESS is
|
||||
// reachable and answers JSON-RPC, not what is behind it. What is behind it is
|
||||
// tested against running subsystems in fleet/mcp_test.go.
|
||||
func app308(t *testing.T) *zip.App {
|
||||
t.Helper()
|
||||
app := zip.New(zip.Config{
|
||||
AppName: "cloud",
|
||||
DisableStartupMessage: true,
|
||||
MCP: zip.MCPConfig{Path: manifest.MCPPath},
|
||||
MCP: zip.MCPConfig{Disabled: true},
|
||||
})
|
||||
type ping struct{ Ok bool }
|
||||
zip.Get(app, "/v1/ping", func(context.Context, *ping) (*ping, error) {
|
||||
return &ping{Ok: true}, nil
|
||||
fleet.Mount(app, manifest.MCPPath, nil, func(string) (string, error) {
|
||||
return "", errNoFleetHere
|
||||
})
|
||||
if err := webui.Mount(app); err != nil {
|
||||
t.Skipf("console embed unavailable in this build: %v", err)
|
||||
@@ -63,6 +63,10 @@ func app308(t *testing.T) *zip.App {
|
||||
return app
|
||||
}
|
||||
|
||||
// errNoFleetHere: this host composes no subsystem, so nothing is reachable. A
|
||||
// door that answers anyway is the property under test.
|
||||
var errNoFleetHere = errors.New("this host composes no subsystems")
|
||||
|
||||
// TestBareMCPBeatsTheConsoleCatchAll is the defect, as a test.
|
||||
//
|
||||
// Measured on api.hanzo.ai before the fix: POST /mcp = 405 "method not allowed",
|
||||
|
||||
+33
-46
@@ -5,16 +5,21 @@ package cloud
|
||||
// This is the ONE producer of a per-app artifact. An app's projections are not
|
||||
// sliced out of the fleet's by prefix (that would make the fleet the source and
|
||||
// the app a derivative, exactly backwards); they are generated from the app's OWN
|
||||
// live router by the SAME openapi.FleetSpec and the SAME zip.App.MCPTools the
|
||||
// whole fleet is generated from, over an app with only that subsystem mounted.
|
||||
// live router by the SAME openapi.FleetSpec the whole fleet is generated from,
|
||||
// over an app with only that subsystem mounted.
|
||||
// Compose upward, never carve downward — see openapi/weave.go for the other half.
|
||||
//
|
||||
// TWO projections, ONE mount, one instant, one registry: openapi.json (what the
|
||||
// app's addresses are) and mcp.json (what its typed ops are as MCP tools). They
|
||||
// are written together and cannot be generated apart, so a tool cannot exist
|
||||
// without its op, and a schema cannot go stale on one surface while the other
|
||||
// moves. That is the whole honesty argument for the door: the reverse direction —
|
||||
// "every typed op is a tool" — is not tested, it is unfalsifiable by construction.
|
||||
// ONE projection, from ONE mount of ONE registry: openapi.json, what the app's
|
||||
// addresses are.
|
||||
//
|
||||
// It used to write mcp.json beside it — the same registry projected as MCP tools
|
||||
// — and argued that writing them together made them agree. They did agree, and
|
||||
// they were BOTH WRONG BY THE SAME 353 OPS: coupling two derived files to each
|
||||
// other makes neither true, because nothing in that coupling forces either back
|
||||
// to the registry. The tool catalogue is not generated any more; the host asks
|
||||
// the child for it (package fleet), so a tool list cannot be stale because there
|
||||
// is no tool list. This document survives only because the fleet weave needs the
|
||||
// app's SOURCE synopsis, which no running process can hand back.
|
||||
//
|
||||
// It lives on Serve because Serve is the single entry every app binary shares:
|
||||
// plugin/<app>/main.go is generated as one cloud.Listen call, so putting the mode
|
||||
@@ -34,25 +39,21 @@ import (
|
||||
// describeArg is the argv word that switches a binary from serving to describing.
|
||||
const describeArg = "describe"
|
||||
|
||||
// SpecFile and ToolsFile are the two artifacts a describe run writes into the
|
||||
// app's own plugin/<app> directory. Named here because the host EMBEDS ToolsFile
|
||||
// (plugin/embed.go) and the weave READS SpecFile — one name each, so a rename
|
||||
// cannot leave a reader looking for a file no writer produces.
|
||||
const (
|
||||
SpecFile = "openapi.json"
|
||||
ToolsFile = "mcp.json"
|
||||
)
|
||||
// SpecFile is the artifact a describe run writes into the app's own plugin/<app>
|
||||
// directory. Named here because the host EMBEDS it (plugin/embed.go) and the
|
||||
// weave READS it — one name, so a rename cannot leave a reader looking for a file
|
||||
// no writer produces.
|
||||
const SpecFile = "openapi.json"
|
||||
|
||||
// DescribeRequested reports whether argv asks this binary to describe itself, and
|
||||
// the DIRECTORY it named: `<binary> describe <dir>`. Read before any flag parsing
|
||||
// — the mode is a mode, not an option.
|
||||
//
|
||||
// A DIRECTORY and not stdout, and the argument is required. There are two
|
||||
// artifacts, so there is no single stream to write; and a subsystem's own
|
||||
// dependencies write to stdout anyway (hanzoai/commerce prints a sqlite-vec
|
||||
// warning and GORM debug lines at mount), which a `> file` redirect splices into
|
||||
// the front of the document and turns into 71KB of invalid JSON. A writer whose
|
||||
// output an unrelated library can corrupt is not a writer.
|
||||
// A DIRECTORY and not stdout, and the argument is required. A subsystem's own
|
||||
// dependencies write to stdout at mount (hanzoai/commerce prints a sqlite-vec
|
||||
// warning and GORM debug lines), which a `> file` redirect splices into the front
|
||||
// of the document and turns into 71KB of invalid JSON. A writer whose output an
|
||||
// unrelated library can corrupt is not a writer.
|
||||
func DescribeRequested() (string, bool) {
|
||||
if len(os.Args) > 1 && os.Args[1] == describeArg {
|
||||
if len(os.Args) > 2 {
|
||||
@@ -87,21 +88,17 @@ func SpecConfig() (*Config, func(), error) {
|
||||
func() { os.RemoveAll(dir) }, nil
|
||||
}
|
||||
|
||||
// Describe writes app's TWO projections into dir: the OpenAPI document and the
|
||||
// MCP tool catalogue, from the one live router, in one pass.
|
||||
// Describe writes app's projection into dir: the OpenAPI document, from the one
|
||||
// live router.
|
||||
//
|
||||
// JSON for both, because JSON is what they ARE — the document is the same bytes
|
||||
// served at /v1/openapi.json and dropped into hanzoai/openapi, and the catalogue
|
||||
// is the same bytes the host hands zip as Plugin.Tools. Encoding to YAML would
|
||||
// put a yaml library in the graph of every app binary to write a file only the
|
||||
// weave reads. Indented so a subset reviews as a diff.
|
||||
// JSON, because JSON is what it IS — the same bytes served at /v1/openapi.json
|
||||
// and dropped into hanzoai/openapi. Encoding to YAML would put a yaml library in
|
||||
// the graph of every app binary to write a file only the weave reads. Indented so
|
||||
// a subset reviews as a diff.
|
||||
//
|
||||
// Each artifact is rendered whole before its file is touched, so a projection
|
||||
// failure leaves the previous one intact rather than truncating it into an app
|
||||
// that appears to serve nothing. mcp.json is written for EVERY app, including the
|
||||
// ones with no typed ops yet (an empty array), so the host's embed pattern is
|
||||
// always satisfiable and "this app got its first typed op" shows as a diff in a
|
||||
// file that already exists rather than as a new one nobody reviews.
|
||||
// It is rendered whole before the file is touched, so a projection failure leaves
|
||||
// the previous one intact rather than truncating it into an app that appears to
|
||||
// serve nothing.
|
||||
func Describe(dir string, app *zip.App) error {
|
||||
if dir == "" {
|
||||
return fmt.Errorf("usage: %s %s <dir>", filepath.Base(os.Args[0]), describeArg)
|
||||
@@ -144,17 +141,7 @@ func Describe(dir string, app *zip.App) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// MCPTools is already sorted by name (zip mcp.go), so this artifact is a
|
||||
// function of the op set and not of registration order — an edit that moved
|
||||
// nothing a client can see produces no diff.
|
||||
tools, err := json.MarshalIndent(app.MCPTools(), "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("mcp: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(dir, SpecFile), append(spec, '\n'), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(filepath.Join(dir, ToolsFile), append(tools, '\n'), 0o644)
|
||||
return os.WriteFile(filepath.Join(dir, SpecFile), append(spec, '\n'), 0o644)
|
||||
}
|
||||
|
||||
// describe mounts specs into a throwaway app and writes its projections.
|
||||
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
// Copyright © 2026 Hanzo AI. MIT License.
|
||||
|
||||
// Package fleet is how the light host answers a question about a subsystem: it
|
||||
// ASKS the subsystem.
|
||||
//
|
||||
// The host links no subsystem (cmd/cloud), so it cannot read a registry it does
|
||||
// not hold. For a long time it read a COMMITTED PROJECTION instead —
|
||||
// plugin/<app>/mcp.json, the tool array each app's binary wrote when it was
|
||||
// built, embedded into the host and handed to zip as Plugin.Tools. That file was
|
||||
// a second source for a fact the child already knows, and a second source can
|
||||
// only be stale or accidentally correct. It was stale: plugin/o11y/mcp.json held
|
||||
// 12 tools while the o11y binary at the same commit served 365, because the 353
|
||||
// missing ops live in github.com/hanzoai/o11y and a dependency bump in another
|
||||
// repository invalidated an artifact in this one with nothing in the diff to say
|
||||
// so.
|
||||
//
|
||||
// Regenerating that file more often does not fix it. A generator on a hook is
|
||||
// still two sources with a race between them, and the trigger here is in a
|
||||
// different repository, so no hook in this one can see it. The fix is that the
|
||||
// file stops existing and the host asks.
|
||||
//
|
||||
// # What asking costs, and why it is affordable
|
||||
//
|
||||
// A child answers on its OWN door — zip's default /mcp, which cloud.Serve
|
||||
// deliberately leaves where the framework puts it (manifest.FrameworkMCPPath) —
|
||||
// over the private ZAP socket the host started it on. [At] resolves the name to
|
||||
// that address, and zip.App.Start is exactly that function: idempotent, and the
|
||||
// SAME single-flighted path a request to the app's prefix takes, so a burst of
|
||||
// concurrent askers still produces one child.
|
||||
//
|
||||
// So the first ask of a cold app pays that app's start. That is the cost of the
|
||||
// answer being true, and it is paid once per app per host: the child stays up
|
||||
// afterwards, and every later ask is a unix round trip. Nothing is cached
|
||||
// between requests, because a cache of a catalogue IS the file this package
|
||||
// exists to delete.
|
||||
//
|
||||
// # A subsystem that does not answer is REPORTED
|
||||
//
|
||||
// [Answer.Err] is never swallowed. A catalogue that silently drops the app it
|
||||
// could not reach is indistinguishable from one whose app serves nothing, and
|
||||
// those are the same defect the stale file was: the caller cannot tell. Every
|
||||
// caller here reports its failures by name — see [Door] for the wire shape.
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
zaphttp "github.com/zap-proto/http"
|
||||
)
|
||||
|
||||
// At resolves one app to the address its own door answers on, starting it if it
|
||||
// is cold.
|
||||
//
|
||||
// It is a function rather than a *zip.App because there are two ways an app is
|
||||
// reachable and only one of them is a child of this host: zip.App.Start covers
|
||||
// the spawned ones, and a deployment that points CLOUD_<NAME>_ADDR at an instance
|
||||
// running elsewhere is mounted, never started, so Start does not know it. The
|
||||
// composition root holds both facts (cmd/cloud), and this package holds neither.
|
||||
type At func(app string) (addr string, err error)
|
||||
|
||||
// Answer is one subsystem's reply to one question, or the reason there is none.
|
||||
// Exactly one of Body and Err is meaningful.
|
||||
type Answer struct {
|
||||
// App is the subsystem asked, as the manifest names it.
|
||||
App string
|
||||
// Body is what its own door replied, verbatim.
|
||||
Body []byte
|
||||
// Err is why there is no reply: it would not start, or it did not answer.
|
||||
Err error
|
||||
}
|
||||
|
||||
// Ask puts req to every named app's own door at path, in PARALLEL, and returns
|
||||
// one Answer per app in the order asked.
|
||||
//
|
||||
// req is the CALLER's own request and is copied per child rather than rebuilt,
|
||||
// so a child answers as itself for the caller who asked: its headers ride along,
|
||||
// which is how a subsystem whose tools depend on the tenant (the tool plane's
|
||||
// connectors, skills and enabled servers) contributes rows no projection could
|
||||
// have held. That is the mechanism zip already used for its single "open"
|
||||
// plugin, applied to every app, which is what makes the one-open-plugin rule
|
||||
// unnecessary.
|
||||
//
|
||||
// Order is the order given — the manifest's mount order, which is the fleet's
|
||||
// routing order — so a caller that resolves a collision by taking the first
|
||||
// resolves it the way the router would.
|
||||
func Ask(at At, apps []string, req *fasthttp.Request, path string) []Answer {
|
||||
out := make([]Answer, len(apps))
|
||||
var wg sync.WaitGroup
|
||||
for i, name := range apps {
|
||||
wg.Add(1)
|
||||
go func(i int, name string) {
|
||||
defer wg.Done()
|
||||
out[i] = ask(at, name, req, path)
|
||||
}(i, name)
|
||||
}
|
||||
wg.Wait()
|
||||
return out
|
||||
}
|
||||
|
||||
// ask is one hop: resolve, copy the caller's request onto the child's own path,
|
||||
// and read the reply.
|
||||
//
|
||||
// The failure is returned, never logged-and-dropped. "This app would not start"
|
||||
// and "this app serves nothing" are different answers and the difference is the
|
||||
// whole point of this package.
|
||||
func ask(at At, name string, req *fasthttp.Request, path string) Answer {
|
||||
addr, err := at(name)
|
||||
if err != nil {
|
||||
return Answer{App: name, Err: err}
|
||||
}
|
||||
r := fasthttp.AcquireRequest()
|
||||
defer fasthttp.ReleaseRequest(r)
|
||||
resp := fasthttp.AcquireResponse()
|
||||
defer fasthttp.ReleaseResponse(resp)
|
||||
req.CopyTo(r)
|
||||
// The child's own address, on the child's own path. Host is the app name so a
|
||||
// child's logs and its caller-attribution name the asker's target rather than
|
||||
// the socket path.
|
||||
r.SetHost(name)
|
||||
r.URI().SetPath(path)
|
||||
if err := clientFor(addr).Do(r, resp); err != nil {
|
||||
return Answer{App: name, Err: fmt.Errorf("%s at %s: %w", name, addr, err)}
|
||||
}
|
||||
if code := resp.StatusCode(); code < 200 || code > 299 {
|
||||
return Answer{App: name, Err: fmt.Errorf("%s answered %d for %s", name, code, path)}
|
||||
}
|
||||
return Answer{App: name, Body: append([]byte(nil), resp.Body()...)}
|
||||
}
|
||||
|
||||
// clients is one pooled transport per ADDRESS, for the same reason zip keeps one
|
||||
// Conn per peer name (zip ask.go): a transport holds a connection pool, so
|
||||
// dialing per ask turns every hop into a fresh connect. Keyed by address rather
|
||||
// than by name because a reloaded child gets a new socket and must not be
|
||||
// reached through the old one's pool.
|
||||
var clients sync.Map // addr -> *zaphttp.Transport
|
||||
|
||||
func clientFor(addr string) *zaphttp.Transport {
|
||||
if c, ok := clients.Load(addr); ok {
|
||||
return c.(*zaphttp.Transport)
|
||||
}
|
||||
c, _ := clients.LoadOrStore(addr, zaphttp.Dial(network(addr), addr))
|
||||
return c.(*zaphttp.Transport)
|
||||
}
|
||||
|
||||
// network reads the plumbing off the address shape, the same rule zip's
|
||||
// transport registry applies (zip networkOf): a filesystem path is a unix
|
||||
// socket, anything else is host:port. A child of this host is always the former
|
||||
// — zip starts it on a private socket — and a CLOUD_<NAME>_ADDR mount may be
|
||||
// either.
|
||||
func network(addr string) string {
|
||||
if strings.HasPrefix(addr, "/") || strings.HasPrefix(addr, "./") || strings.HasPrefix(addr, "@") {
|
||||
return "unix"
|
||||
}
|
||||
return "tcp"
|
||||
}
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
// Copyright © 2026 Hanzo AI. MIT License.
|
||||
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/hanzoai/cloud/manifest"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// The fleet's ONE agent door, composed AT THE MOMENT OF ASKING.
|
||||
//
|
||||
// zip serves this door for an app out of that app's own typed-op registry, plus
|
||||
// whatever build-time catalogues a host handed it. The light host has neither: it
|
||||
// registers no op of its own, and the catalogues are gone. What it has is
|
||||
// CHILDREN, and a door whose whole content is "what my children serve right now"
|
||||
// is a different mechanism from "what I was compiled knowing", so the host owns
|
||||
// it here rather than configuring zip's (cmd/cloud disables that one, so there is
|
||||
// exactly one handler at this address and not two chained by the router).
|
||||
//
|
||||
// Every method below is answered from the children. Nothing is remembered
|
||||
// between requests except which app listed a tool name, and that is a routing
|
||||
// table, not a catalogue: see [Door.owner].
|
||||
|
||||
// protocolVersion is the MCP spec revision this door speaks. It is zip's
|
||||
// (mcpProtocolVersion) — the children answer initialize with the same string,
|
||||
// and a host that claimed a different one would be describing a protocol none of
|
||||
// its subsystems implement.
|
||||
const protocolVersion = "2025-06-18"
|
||||
|
||||
// Unavailable is the _meta key under which tools/list names the subsystems it
|
||||
// could not ask.
|
||||
//
|
||||
// It exists because a short list and a stale file are the SAME defect: the
|
||||
// caller cannot tell a subsystem that serves nothing from one that did not
|
||||
// answer, so it reads a partial catalogue as a complete one. MCP puts extension
|
||||
// data on the result's _meta, so the outage travels with the answer it qualifies
|
||||
// and a client that only reads `tools` still gets every tool that exists —
|
||||
// blanking a working fleet because one app is down would be a worse answer than
|
||||
// a shorter list, but an UNANNOUNCED shorter list is worse than both.
|
||||
const Unavailable = "hanzo.ai/unavailable"
|
||||
|
||||
// Outage is one subsystem that could not be asked, and why.
|
||||
type Outage struct {
|
||||
App string `json:"app"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// Door is the composed agent door: the apps it fronts, how to reach one, and the
|
||||
// tool→app routing it learned from the last time it asked.
|
||||
type Door struct {
|
||||
host *zip.App
|
||||
at At
|
||||
apps []string
|
||||
|
||||
// owner is tool name → the app that LISTED it, written by every gather and
|
||||
// read by tools/call. It is not a catalogue and cannot go stale in a way that
|
||||
// matters: the host only ever NAMES an app to forward to, the child's own
|
||||
// registry decides whether the tool exists, and a name it no longer serves
|
||||
// yields that child's own -32602 rather than a mis-dispatch. A name nobody
|
||||
// listed is discovered by asking, not by guessing.
|
||||
mu sync.RWMutex
|
||||
owner map[string]string
|
||||
}
|
||||
|
||||
// Mount serves the fleet's agent door at path, over apps, reaching one with at.
|
||||
//
|
||||
// apps is the deployment's COMPOSED set (cmd/cloud's `composed`), never the whole
|
||||
// manifest: a deployment that does not run a subsystem must not offer its tools,
|
||||
// for the same reason it must not publish its routes.
|
||||
func Mount(host *zip.App, path string, apps []string, at At) *Door {
|
||||
d := &Door{host: host, at: at, apps: apps, owner: map[string]string{}}
|
||||
host.Post(path, d.serve)
|
||||
return d
|
||||
}
|
||||
|
||||
// message is one JSON-RPC 2.0 envelope, in the shape this door reads it.
|
||||
type message struct {
|
||||
ID json.RawMessage `json:"id"`
|
||||
Method string `json:"method"`
|
||||
Params json.RawMessage `json:"params"`
|
||||
}
|
||||
|
||||
func (d *Door) serve(c *zip.Ctx) error {
|
||||
var req message
|
||||
if err := json.Unmarshal(c.Body(), &req); err != nil {
|
||||
return c.JSON(200, rpcErr(nil, -32700, "parse error"))
|
||||
}
|
||||
switch req.Method {
|
||||
case "initialize":
|
||||
return c.JSON(200, rpcResult(req.ID, map[string]any{
|
||||
"protocolVersion": protocolVersion,
|
||||
"capabilities": map[string]any{"tools": map[string]any{"listChanged": false}},
|
||||
"serverInfo": map[string]any{"name": "cloud", "version": protocolVersion},
|
||||
}))
|
||||
case "tools/list":
|
||||
return d.list(c, req)
|
||||
case "tools/call":
|
||||
return d.call(c, req)
|
||||
case "ping":
|
||||
return c.JSON(200, rpcResult(req.ID, map[string]any{}))
|
||||
default:
|
||||
// notifications/* carry no id and expect no result — ack with 202.
|
||||
if len(req.ID) == 0 {
|
||||
return c.Status(202).JSON(202, map[string]any{})
|
||||
}
|
||||
return c.JSON(200, rpcErr(req.ID, -32601, "method not found: "+req.Method))
|
||||
}
|
||||
}
|
||||
|
||||
// list answers tools/list from the subsystems themselves, and NAMES the ones it
|
||||
// could not reach.
|
||||
func (d *Door) list(c *zip.Ctx, req message) error {
|
||||
tools, down := d.gather(c)
|
||||
result := map[string]any{"tools": tools}
|
||||
if len(down) > 0 {
|
||||
result["_meta"] = map[string]any{Unavailable: down}
|
||||
d.host.Logger().Warn("fleet mcp: tools/list is INCOMPLETE — subsystems did not answer",
|
||||
"unavailable", len(down), "apps", len(d.apps), "tools", len(tools))
|
||||
}
|
||||
return c.JSON(200, rpcResult(req.ID, result))
|
||||
}
|
||||
|
||||
// call runs one tool: find the app that lists the name, hand it the caller's own
|
||||
// message, and return that app's own reply verbatim.
|
||||
//
|
||||
// A name nobody has listed in this process yet costs ONE discovery — the same
|
||||
// ask tools/list makes — rather than a guess or a fan-out per call. If it is
|
||||
// still nobody's, that is a -32602 and not an outage: every app answered, and
|
||||
// none of them serves it.
|
||||
func (d *Door) call(c *zip.Ctx, req message) error {
|
||||
var p struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
_ = json.Unmarshal(req.Params, &p)
|
||||
|
||||
app := d.ownerOf(p.Name)
|
||||
if app == "" {
|
||||
d.gather(c)
|
||||
app = d.ownerOf(p.Name)
|
||||
}
|
||||
if app == "" {
|
||||
return c.JSON(200, rpcErr(req.ID, -32602, "unknown tool: "+p.Name))
|
||||
}
|
||||
// The caller's OWN message, at the child's own door. The child's registry
|
||||
// invokes it, so the host can only ever name a tool and never invoke one the
|
||||
// child did not declare.
|
||||
ans := Ask(d.at, []string{app}, c.Fiber().Request(), manifest.FrameworkMCPPath)[0]
|
||||
if ans.Err != nil {
|
||||
// A hop failure is MCP isError content, per the spec: the model reads "this
|
||||
// tool is not available right now" and reacts, where a 503 body is a
|
||||
// transport failure it cannot interpret.
|
||||
return c.JSON(200, rpcResult(req.ID, map[string]any{
|
||||
"content": []map[string]any{{"type": "text", "text": ans.Err.Error()}},
|
||||
"isError": true,
|
||||
}))
|
||||
}
|
||||
c.SetHeader("Content-Type", "application/json")
|
||||
return c.Bytes(200, ans.Body)
|
||||
}
|
||||
|
||||
// named is one tool with its name lifted out, so the composed list sorts without
|
||||
// re-parsing and each descriptor is carried VERBATIM — the bytes the child's own
|
||||
// registry projected, never a re-encoding.
|
||||
type named struct {
|
||||
name string
|
||||
raw json.RawMessage
|
||||
}
|
||||
|
||||
// gather asks every app what it serves, right now, and returns the union plus
|
||||
// the outages.
|
||||
//
|
||||
// The request it sends is the CALLER's, with the body replaced by a canonical
|
||||
// tools/list: the headers ride along, so a child whose tools depend on who is
|
||||
// asking answers for this caller, while the body cannot be a tools/call the
|
||||
// discovery path would otherwise execute on every child in the fleet.
|
||||
func (d *Door) gather(c *zip.Ctx) ([]json.RawMessage, []Outage) {
|
||||
req := fasthttp.AcquireRequest()
|
||||
defer fasthttp.ReleaseRequest(req)
|
||||
c.Fiber().Request().CopyTo(req)
|
||||
req.Header.SetMethod("POST")
|
||||
req.Header.SetContentType("application/json")
|
||||
req.SetBody([]byte(`{"jsonrpc":"2.0","id":0,"method":"tools/list"}`))
|
||||
|
||||
var all []named
|
||||
var down []Outage
|
||||
owner := map[string]string{}
|
||||
for _, a := range Ask(d.at, d.apps, req, manifest.FrameworkMCPPath) {
|
||||
if a.Err != nil {
|
||||
down = append(down, Outage{App: a.App, Error: a.Err.Error()})
|
||||
continue
|
||||
}
|
||||
tools, err := toolsOf(a.Body)
|
||||
if err != nil {
|
||||
down = append(down, Outage{App: a.App, Error: err.Error()})
|
||||
continue
|
||||
}
|
||||
for _, t := range tools {
|
||||
// One name is one dispatch, so two owners would make it unroutable. The
|
||||
// manifest's order is the router's order, so the first claimant wins here
|
||||
// exactly as it wins a prefix — and the loser is logged rather than
|
||||
// silently dropped, because a tool that vanished into a collision looks
|
||||
// identical to one that was never declared.
|
||||
if held, dup := owner[t.name]; dup {
|
||||
d.host.Logger().Warn("fleet mcp: two subsystems claim one tool name; the first in mount order serves it",
|
||||
"tool", t.name, "serving", held, "shadowed", a.App)
|
||||
continue
|
||||
}
|
||||
owner[t.name] = a.App
|
||||
all = append(all, t)
|
||||
}
|
||||
}
|
||||
sort.Slice(all, func(i, j int) bool { return all[i].name < all[j].name })
|
||||
|
||||
d.mu.Lock()
|
||||
d.owner = owner
|
||||
d.mu.Unlock()
|
||||
|
||||
// Never nil: `"tools": null` is a client-visible difference from an empty
|
||||
// fleet, and JSON has one way to say "no tools".
|
||||
out := make([]json.RawMessage, 0, len(all))
|
||||
for _, t := range all {
|
||||
out = append(out, t.raw)
|
||||
}
|
||||
return out, down
|
||||
}
|
||||
|
||||
func (d *Door) ownerOf(tool string) string {
|
||||
d.mu.RLock()
|
||||
defer d.mu.RUnlock()
|
||||
return d.owner[tool]
|
||||
}
|
||||
|
||||
// toolsOf lifts the descriptors out of one child's tools/list reply.
|
||||
//
|
||||
// A JSON-RPC ERROR reply is an outage and is reported as one: the child is up,
|
||||
// but it did not answer the question, and folding that into "this app has no
|
||||
// tools" is the silent shortening this package exists to remove.
|
||||
func toolsOf(body []byte) ([]named, error) {
|
||||
var env struct {
|
||||
Result struct {
|
||||
Tools []json.RawMessage `json:"tools"`
|
||||
} `json:"result"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &env); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if env.Error != nil {
|
||||
return nil, jsonrpcError(env.Error.Message)
|
||||
}
|
||||
out := make([]named, 0, len(env.Result.Tools))
|
||||
for _, raw := range env.Result.Tools {
|
||||
var hdr struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &hdr); err != nil || hdr.Name == "" {
|
||||
return nil, errNamelessTool
|
||||
}
|
||||
out = append(out, named{name: hdr.Name, raw: raw})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type jsonrpcError string
|
||||
|
||||
func (e jsonrpcError) Error() string { return "tools/list refused: " + string(e) }
|
||||
|
||||
const errNamelessTool = jsonrpcError("a tool descriptor has no name")
|
||||
|
||||
func rpcResult(id json.RawMessage, result any) map[string]any {
|
||||
return map[string]any{"jsonrpc": "2.0", "id": idOrNull(id), "result": result}
|
||||
}
|
||||
|
||||
func rpcErr(id json.RawMessage, code int, msg string) map[string]any {
|
||||
return map[string]any{"jsonrpc": "2.0", "id": idOrNull(id), "error": map[string]any{"code": code, "message": msg}}
|
||||
}
|
||||
|
||||
func idOrNull(id json.RawMessage) any {
|
||||
if len(id) == 0 {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
// Copyright © 2026 Hanzo AI. MIT License.
|
||||
|
||||
package fleet_test
|
||||
|
||||
// The door, end to end, over the wire it actually uses.
|
||||
//
|
||||
// Every test here starts REAL child processes' worth of machinery — a zip app
|
||||
// per subsystem, listening on its own ZAP unix socket, exactly as a plugin child
|
||||
// does — and drives the composed door with JSON-RPC bodies. Nothing is stubbed at
|
||||
// the seam being tested, because the seam being tested is the seam that was wrong:
|
||||
// the old door answered from a committed array and every test of it passed while
|
||||
// the array was missing 353 of o11y's ops.
|
||||
//
|
||||
// So the assertions are BY BODY and never by status code, and they are EXACT
|
||||
// SETS: "the door lists what the children serve" is only a real claim if dropping
|
||||
// a child from the door turns it red. See TestDoorListsExactlyWhatItsChildrenServe.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hanzoai/cloud/fleet"
|
||||
"github.com/zap-proto/fiber/v3"
|
||||
"github.com/zap-proto/zip"
|
||||
)
|
||||
|
||||
// child is one subsystem: a zip app with its own typed ops, serving its own MCP
|
||||
// door on its own socket — the shape cloud.Serve gives every plugin binary.
|
||||
type child struct {
|
||||
name string
|
||||
addr string
|
||||
app *zip.App
|
||||
}
|
||||
|
||||
type thingIn struct {
|
||||
Which string `json:"which"`
|
||||
}
|
||||
type thingOut struct {
|
||||
App string `json:"app"`
|
||||
Which string `json:"which"`
|
||||
}
|
||||
|
||||
// start brings up one child with n typed ops named "<app>_op<i>", and returns it
|
||||
// once its socket accepts.
|
||||
func start(t *testing.T, name string, ops int) *child {
|
||||
t.Helper()
|
||||
sock := filepath.Join(t.TempDir(), name+".sock")
|
||||
app := zip.New(zip.Config{AppName: name, DisableStartupMessage: true})
|
||||
for i := 0; i < ops; i++ {
|
||||
id := opID(name, i)
|
||||
zip.Post(app, "/v1/"+name+"/"+id, func(_ context.Context, in *thingIn) (*thingOut, error) {
|
||||
return &thingOut{App: name, Which: in.Which}, nil
|
||||
}, zip.WithOperationID(id), zip.WithSummary("what "+name+" does at "+id))
|
||||
}
|
||||
go func() { _ = app.Listen(sock) }()
|
||||
t.Cleanup(func() { _ = app.Shutdown() })
|
||||
waitFor(t, sock)
|
||||
return &child{name: name, addr: sock, app: app}
|
||||
}
|
||||
|
||||
func opID(app string, i int) string { return app + "_op" + string(rune('a'+i)) }
|
||||
|
||||
// die stops a child the way zip stops one: the process goes, and its PRIVATE
|
||||
// directory goes with it (zip load.go stop → os.RemoveAll(in.dir)), so the socket
|
||||
// a caller would reach for is not there any more. Reproducing only the first half
|
||||
// — closing the listener while leaving the path dialable through an already
|
||||
// pooled connection — is a fixture that tests nothing, because the in-process
|
||||
// server keeps answering on it.
|
||||
func die(t *testing.T, k *child) {
|
||||
t.Helper()
|
||||
if err := k.app.Shutdown(); err != nil {
|
||||
t.Fatalf("stop %s: %v", k.name, err)
|
||||
}
|
||||
k.addr = k.addr + ".gone"
|
||||
}
|
||||
|
||||
func waitFor(t *testing.T, sock string) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
if c, err := net.Dial("unix", sock); err == nil {
|
||||
_ = c.Close()
|
||||
return
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("%s never accepted", sock)
|
||||
}
|
||||
|
||||
// host composes a door over the named children. at answers from the map, so a
|
||||
// name with no child is an app this host cannot reach — which is exactly the
|
||||
// "deliberately stopped" case.
|
||||
func host(t *testing.T, apps []string, kids map[string]*child) *zip.App {
|
||||
t.Helper()
|
||||
h := zip.New(zip.Config{AppName: "cloud", DisableStartupMessage: true, MCP: zip.MCPConfig{Disabled: true}})
|
||||
fleet.Mount(h, "/v1/mcp", apps, func(app string) (string, error) {
|
||||
k := kids[app]
|
||||
if k == nil {
|
||||
return "", &net.AddrError{Err: "no instance running", Addr: app}
|
||||
}
|
||||
return k.addr, nil
|
||||
})
|
||||
h.Prepare()
|
||||
return h
|
||||
}
|
||||
|
||||
// rpc posts one JSON-RPC message to the door and returns the decoded result.
|
||||
func rpc(t *testing.T, h *zip.App, body string) map[string]any {
|
||||
t.Helper()
|
||||
req, err := http.NewRequest("POST", "http://cloud/v1/mcp", strings.NewReader(body))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := h.Fiber().Test(req, fiber.TestConfig{Timeout: 60 * time.Second, FailOnTimeout: true})
|
||||
if err != nil {
|
||||
t.Fatalf("POST /v1/mcp: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
var env struct {
|
||||
Result map[string]any `json:"result"`
|
||||
Error *map[string]any `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &env); err != nil {
|
||||
t.Fatalf("door answered %d with %q, which is not a JSON-RPC envelope", resp.StatusCode, raw)
|
||||
}
|
||||
if env.Error != nil {
|
||||
return map[string]any{"error": *env.Error}
|
||||
}
|
||||
return env.Result
|
||||
}
|
||||
|
||||
// listed is the tool NAMES the door reports, sorted.
|
||||
func listed(t *testing.T, h *zip.App) []string {
|
||||
t.Helper()
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`)
|
||||
tools, _ := res["tools"].([]any)
|
||||
out := make([]string, 0, len(tools))
|
||||
for _, tl := range tools {
|
||||
m, _ := tl.(map[string]any)
|
||||
n, _ := m["name"].(string)
|
||||
out = append(out, n)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// unavailable is the subsystems tools/list says it could not ask.
|
||||
func unavailable(t *testing.T, h *zip.App) map[string]string {
|
||||
t.Helper()
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":1,"method":"tools/list"}`)
|
||||
meta, _ := res["_meta"].(map[string]any)
|
||||
rows, _ := meta[fleet.Unavailable].([]any)
|
||||
out := map[string]string{}
|
||||
for _, r := range rows {
|
||||
m, _ := r.(map[string]any)
|
||||
app, _ := m["app"].(string)
|
||||
why, _ := m["error"].(string)
|
||||
out[app] = why
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// TestDoorListsExactlyWhatItsChildrenServe is the whole claim, as an EXACT set.
|
||||
//
|
||||
// It is exact on purpose. A subset assertion ("o11y's tools are in there") is the
|
||||
// test the deleted catalogue passed for months while it was missing 353 ops: a
|
||||
// list that is too short satisfies every containment check written against it.
|
||||
// Drop a child from `apps` below and this goes red naming the tools that vanished
|
||||
// — which is the mutation that proves the suite is load-bearing.
|
||||
func TestDoorListsExactlyWhatItsChildrenServe(t *testing.T) {
|
||||
kids := map[string]*child{"alpha": start(t, "alpha", 3), "beta": start(t, "beta", 2)}
|
||||
h := host(t, []string{"alpha", "beta"}, kids)
|
||||
|
||||
want := []string{"alpha_opa", "alpha_opb", "alpha_opc", "beta_opa", "beta_opb"}
|
||||
got := listed(t, h)
|
||||
if strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("door lists %v, the children serve %v", got, want)
|
||||
}
|
||||
if u := unavailable(t, h); len(u) != 0 {
|
||||
t.Fatalf("every child answered, so nothing may be reported unavailable: %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDoorAnswersTheChildsOwnProjection: the descriptors are the child's bytes,
|
||||
// not a re-encoding and not a copy that could differ from them.
|
||||
//
|
||||
// This is the property the file could never have: the host's answer for an app is
|
||||
// EQUAL to what that app's own registry projects, at the instant of asking, so
|
||||
// there is no version of the fleet in which they disagree.
|
||||
func TestDoorAnswersTheChildsOwnProjection(t *testing.T) {
|
||||
kid := start(t, "alpha", 4)
|
||||
h := host(t, []string{"alpha"}, map[string]*child{"alpha": kid})
|
||||
|
||||
var want []string
|
||||
for _, tl := range kid.app.MCPTools() {
|
||||
want = append(want, tl["name"].(string))
|
||||
}
|
||||
sort.Strings(want)
|
||||
if got := listed(t, h); strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("door lists %v; alpha's own MCPTools() is %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestADownChildIsReportedNotSilentlyOmitted is the reason this change is worth
|
||||
// making at all.
|
||||
//
|
||||
// A stale file and a silently-short list are the SAME defect — the caller cannot
|
||||
// tell a subsystem that serves nothing from one that did not answer — so swapping
|
||||
// one for the other would have been a waste. The working child's tools still
|
||||
// arrive (blanking a healthy fleet for one outage is a worse answer), and the
|
||||
// outage is NAMED.
|
||||
func TestADownChildIsReportedNotSilentlyOmitted(t *testing.T) {
|
||||
kids := map[string]*child{"alpha": start(t, "alpha", 2)}
|
||||
// beta is composed into the door and has no instance: the deliberately
|
||||
// stopped child.
|
||||
h := host(t, []string{"alpha", "beta"}, kids)
|
||||
|
||||
if got, want := listed(t, h), []string{"alpha_opa", "alpha_opb"}; strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("the reachable child's tools must still be served: got %v, want %v", got, want)
|
||||
}
|
||||
u := unavailable(t, h)
|
||||
if _, named := u["beta"]; !named {
|
||||
t.Fatalf("beta is down and the door did not say so — the list is short and silent, which is "+
|
||||
"exactly the defect the committed catalogue was. _meta[%q] = %v", fleet.Unavailable, u)
|
||||
}
|
||||
if u["beta"] == "" {
|
||||
t.Error("beta is reported unavailable with no reason; an operator cannot act on that")
|
||||
}
|
||||
if _, wrong := u["alpha"]; wrong {
|
||||
t.Errorf("alpha answered and must not be reported unavailable: %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
// TestAChildThatDIESMidLifeIsReported: the same signal for a child that was up
|
||||
// and stopped, which is the rollout case — the address resolves, the socket does
|
||||
// not answer.
|
||||
func TestAChildThatDIESMidLifeIsReported(t *testing.T) {
|
||||
kids := map[string]*child{"alpha": start(t, "alpha", 2), "beta": start(t, "beta", 1)}
|
||||
h := host(t, []string{"alpha", "beta"}, kids)
|
||||
if got := listed(t, h); len(got) != 3 {
|
||||
t.Fatalf("both children up: want 3 tools, got %v", got)
|
||||
}
|
||||
|
||||
die(t, kids["beta"])
|
||||
if got, want := listed(t, h), []string{"alpha_opa", "alpha_opb"}; strings.Join(got, ",") != strings.Join(want, ",") {
|
||||
t.Fatalf("after beta stopped the door lists %v, want %v", got, want)
|
||||
}
|
||||
if u := unavailable(t, h); u["beta"] == "" {
|
||||
t.Fatalf("beta was stopped and the door reported no outage: %v", u)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolsCallReachesTheOwnersOwnHandler: the tool RUNS, in the child that
|
||||
// declared it, and the child's own reply comes back verbatim.
|
||||
func TestToolsCallReachesTheOwnersOwnHandler(t *testing.T) {
|
||||
kids := map[string]*child{"alpha": start(t, "alpha", 2), "beta": start(t, "beta", 2)}
|
||||
h := host(t, []string{"alpha", "beta"}, kids)
|
||||
|
||||
// No tools/list first: a call must be able to find its owner by asking, or the
|
||||
// door only works for a client that listed in the same process lifetime.
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"beta_opb","arguments":{"which":"x"}}}`)
|
||||
content, _ := res["content"].([]any)
|
||||
if len(content) == 0 {
|
||||
t.Fatalf("tools/call beta_opb returned no content: %v", res)
|
||||
}
|
||||
first, _ := content[0].(map[string]any)
|
||||
text, _ := first["text"].(string)
|
||||
if !strings.Contains(text, `"app":"beta"`) || !strings.Contains(text, `"which":"x"`) {
|
||||
t.Fatalf("tools/call ran somewhere else or lost its arguments: %q", text)
|
||||
}
|
||||
if isErr, _ := res["isError"].(bool); isErr {
|
||||
t.Fatalf("tools/call reported an error: %v", res)
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolsCallOfANameNobodyServesIsRefused: -32602 from the host, after asking.
|
||||
// It is a REFUSAL and not an outage: every child answered and none claims it.
|
||||
func TestToolsCallOfANameNobodyServesIsRefused(t *testing.T) {
|
||||
h := host(t, []string{"alpha"}, map[string]*child{"alpha": start(t, "alpha", 1)})
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"ghost_op","arguments":{}}}`)
|
||||
e, ok := res["error"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("an unknown tool must be refused, got %v", res)
|
||||
}
|
||||
if code, _ := e["code"].(float64); int(code) != -32602 {
|
||||
t.Errorf("code = %v, want -32602", e["code"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestToolsCallOnADownOwnerIsIsErrorNotATransportFailure: per the MCP spec the
|
||||
// model must be able to READ the failure, so a dead hop is isError content.
|
||||
func TestToolsCallOnADownOwnerIsIsErrorNotATransportFailure(t *testing.T) {
|
||||
kids := map[string]*child{"alpha": start(t, "alpha", 1), "beta": start(t, "beta", 1)}
|
||||
h := host(t, []string{"alpha", "beta"}, kids)
|
||||
if got := listed(t, h); len(got) != 2 { // learn the owners while beta is up
|
||||
t.Fatalf("want 2 tools, got %v", got)
|
||||
}
|
||||
die(t, kids["beta"])
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"beta_opa","arguments":{}}}`)
|
||||
if isErr, _ := res["isError"].(bool); !isErr {
|
||||
t.Fatalf("a call to a stopped owner must come back as isError content, got %v", res)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInitializeAndPingAnswerWithoutTouchingAChild: the handshake is the host's,
|
||||
// and a client that only initializes must not wake the fleet.
|
||||
func TestInitializeAndPingAnswerWithoutTouchingAChild(t *testing.T) {
|
||||
h := host(t, []string{"alpha"}, map[string]*child{}) // no child exists at all
|
||||
res := rpc(t, h, `{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}`)
|
||||
if res["protocolVersion"] == nil {
|
||||
t.Fatalf("initialize did not answer a protocolVersion: %v", res)
|
||||
}
|
||||
if res := rpc(t, h, `{"jsonrpc":"2.0","id":2,"method":"ping"}`); res == nil {
|
||||
t.Fatal("ping did not answer")
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ require (
|
||||
github.com/vulcand/oxy/v2 v2.2.0
|
||||
github.com/zap-proto/fiber/v3 v3.2.1
|
||||
github.com/zap-proto/go v1.3.0
|
||||
github.com/zap-proto/http v0.3.1
|
||||
github.com/zap-proto/md v0.1.0
|
||||
github.com/zap-proto/zip v1.18.23
|
||||
golang.org/x/term v0.45.0
|
||||
@@ -98,7 +99,6 @@ require (
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
|
||||
github.com/vultr/govultr/v3 v3.30.0 // indirect
|
||||
github.com/zap-proto/http v0.3.1 // indirect
|
||||
github.com/zap-proto/zap2pb v0.2.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
go.opentelemetry.io/collector/component v1.54.0 // indirect
|
||||
|
||||
+8
-6
@@ -208,12 +208,14 @@ var Apps = []App{
|
||||
{Name: "engine", Prefixes: []string{"/v1/engine"}},
|
||||
{Name: "registry", Prefixes: []string{"/v1/registry"}},
|
||||
{Name: "auto", Prefixes: []string{"/v1/auto"}},
|
||||
// Open: the tool plane also serves the CALLER's own tools — its connectors,
|
||||
// skills, agents, and the external MCP servers it enabled — which are rows and
|
||||
// cannot be in a build-time catalogue. The host asks it per caller on a
|
||||
// tools/list that names one. It is the only open app in the fleet, and zip
|
||||
// refuses a second.
|
||||
{Name: "tools", Open: true, Prefixes: []string{"/v1/mcp/servers", "/v1/plugins", "/v1/skills", "/v1/tools"}},
|
||||
// The tool plane also serves the CALLER's own tools — its connectors, skills,
|
||||
// agents, and the external MCP servers it enabled — which are rows and could
|
||||
// never have been in a build-time catalogue. It used to be the fleet's single
|
||||
// "open" app for that reason, and zip refused a second. Nothing marks it now:
|
||||
// the host forwards the caller's OWN tools/list to EVERY subsystem, so each one
|
||||
// answers for this caller out of its own registry and its own rows, and being
|
||||
// asked per caller is no longer a privilege one app holds.
|
||||
{Name: "tools", Prefixes: []string{"/v1/mcp/servers", "/v1/plugins", "/v1/skills", "/v1/tools"}},
|
||||
{Name: "marketplace", Prefixes: []string{"/v1/marketplace"}},
|
||||
{Name: "referrals", Prefixes: []string{"/v1/admin/referrals/bonuses", "/v1/admin/referrals/sweep", "/v1/referrals"}},
|
||||
{Name: "guide", Prefixes: []string{"/v1/guide"}},
|
||||
|
||||
+21
-78
@@ -2,9 +2,9 @@ package manifest
|
||||
|
||||
// The gates under the fleet's ONE agent door.
|
||||
//
|
||||
// POST /v1/mcp is the host's, served by zip from the composed plugin catalogues
|
||||
// (cmd/cloud/main.go, plugin/embed.go). Three things can silently take it away,
|
||||
// and each one is pinned here:
|
||||
// POST /v1/mcp is the host's, composed by ASKING every subsystem what it serves
|
||||
// (cmd/cloud/main.go, package fleet). Two things can silently take it away, and
|
||||
// each one is pinned here:
|
||||
//
|
||||
// 1. an App row claiming /v1/mcp — a Load registers All(prefix) + All(prefix/*),
|
||||
// and fiber MERGES byte-identical patterns into one route with both handlers
|
||||
@@ -12,7 +12,6 @@ package manifest
|
||||
// run. Silent shadowing, not a panic.
|
||||
// 2. a plugin growing a SECOND hand-rolled door — this fleet had three MCP tool
|
||||
// registries for one concept, and the way back is one route registration.
|
||||
// 3. a catalogue naming a tool no op answers, or two plugins naming one tool.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -50,7 +49,7 @@ func TestNoAppClaimsTheDoor(t *testing.T) {
|
||||
// This is the structural reason a fourth registry cannot grow back. A hand-rolled
|
||||
// JSON-RPC door can only exist as a route; every route an app serves is
|
||||
// regenerated into its own subset by the drift gate (mk/fleet.mk surface-check);
|
||||
// and the one true door is a zip CONTROL route, which is in no subset at all. So
|
||||
// and the one true door is the host's own route, which is in no subset at all. So
|
||||
// the next hand-rolled envelope turns this red and the message names the door it
|
||||
// should have used instead.
|
||||
//
|
||||
@@ -62,8 +61,8 @@ func TestNoSecondMCPDoor(t *testing.T) {
|
||||
for _, p := range served(t, a.Name) {
|
||||
if strings.HasSuffix(p, "/mcp") {
|
||||
t.Errorf("app %q serves %q. The fleet has ONE MCP door: POST /v1/mcp on the host, "+
|
||||
"composed from every plugin's build-time catalogue. A typed op is already a "+
|
||||
"tool there — register one instead of a second JSON-RPC envelope.", a.Name, p)
|
||||
"composed by asking every subsystem. A typed op is already a tool there — "+
|
||||
"register one instead of a second JSON-RPC envelope.", a.Name, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,8 +128,8 @@ func TestNoSecondMCPDoorInSource(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
t.Errorf("%s:%d serves an MCP path: %s\n"+
|
||||
"The fleet has ONE MCP door — POST /v1/mcp on the host, composed from every "+
|
||||
"plugin's build-time catalogue. A typed op is ALREADY a tool there. If this is a "+
|
||||
"The fleet has ONE MCP door — POST /v1/mcp on the host, composed by asking "+
|
||||
"every subsystem. A typed op is ALREADY a tool there. If this is a "+
|
||||
"foreign engine's own surface rather than a projection of our ops, name it in "+
|
||||
"foreignDoors with the reason.", rel, i+1, trimmed)
|
||||
}
|
||||
@@ -139,77 +138,21 @@ func TestNoSecondMCPDoorInSource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestEveryCatalogueToolIsAnOpOfItsOwnApp: soundness of the composed door.
|
||||
// THE CATALOGUE GATE IS GONE, WITH THE CATALOGUE.
|
||||
//
|
||||
// mcp.json and openapi.json are two projections of ONE registry taken in one
|
||||
// process at one instant (`<app> describe`), so this cannot fail while both are
|
||||
// regenerated together — which is precisely why it is worth asserting: it is the
|
||||
// cheap, no-build check that a HAND-EDITED catalogue, or one left behind by a
|
||||
// half-run generator, does not publish a tool the owning app cannot answer.
|
||||
// TestEveryCatalogueToolIsAnOpOfItsOwnApp read plugin/<app>/mcp.json and checked
|
||||
// each name against that app's openapi.json — two artifacts generated in one
|
||||
// process at one instant, so it could only ever catch a hand edit or a half-run
|
||||
// generator. It could not catch the failure that mattered: BOTH files stale by
|
||||
// the same 353 ops, which is exactly how o11y shipped. Two derived things agreeing
|
||||
// with each other is not evidence about the thing they derive from.
|
||||
//
|
||||
// It also refuses two apps claiming one tool name. A name is dispatch, so a
|
||||
// duplicate is unroutable; zip refuses it at Load (App.installTools), which is a
|
||||
// boot failure. Catching it here makes it a red build instead.
|
||||
func TestEveryCatalogueToolIsAnOpOfItsOwnApp(t *testing.T) {
|
||||
owner := map[string]string{}
|
||||
tools := 0
|
||||
for _, a := range Apps {
|
||||
ops := operationIDs(t, a.Name)
|
||||
for _, name := range catalogue(t, a.Name) {
|
||||
tools++
|
||||
if _, ok := ops[name]; !ok {
|
||||
t.Errorf("%s/mcp.json names tool %q, which is not an operationId in "+
|
||||
"%s/openapi.json. The catalogue is a projection of the same typed-op "+
|
||||
"registry the document is — regenerate: make -f mk/fleet.mk describe-apps",
|
||||
a.Name, name, a.Name)
|
||||
}
|
||||
if held, dup := owner[name]; dup {
|
||||
t.Errorf("tool %q is claimed by both %q and %q. A tool name is dispatch, so two "+
|
||||
"owners make it unroutable and zip refuses the composition at boot — rename "+
|
||||
"one op's operationId.", name, held, a.Name)
|
||||
}
|
||||
owner[name] = a.Name
|
||||
}
|
||||
}
|
||||
if tools == 0 {
|
||||
t.Fatal("the fleet's composed MCP door would carry ZERO tools — no plugin/<app>/mcp.json holds any")
|
||||
}
|
||||
t.Logf("%d MCP tools across %d apps on the one door", tools, len(Apps))
|
||||
}
|
||||
|
||||
// catalogue is the tool names in an app's committed MCP catalogue.
|
||||
func catalogue(t *testing.T, app string) []string {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(filepath.Join("..", "plugin", app, "mcp.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v\n\nEvery app publishes its own MCP catalogue beside its subset. "+
|
||||
"Run `make -f mk/fleet.mk describe-apps`.", app, err)
|
||||
}
|
||||
var tools []struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
InputSchema json.RawMessage `json:"inputSchema"`
|
||||
}
|
||||
if err := json.Unmarshal(raw, &tools); err != nil {
|
||||
t.Fatalf("%s/mcp.json: %v", app, err)
|
||||
}
|
||||
out := make([]string, 0, len(tools))
|
||||
for _, tl := range tools {
|
||||
// An op present with an EMPTY description is a SILENT failure: the model
|
||||
// pays context for a nameless tool it cannot choose. That exact bug shipped
|
||||
// once here (zipdoc blind to group prefixes), so it is a gate, not a hope.
|
||||
if strings.TrimSpace(tl.Description) == "" {
|
||||
t.Errorf("%s tool %q has an EMPTY description — the prose zipdoc lifts IS what a "+
|
||||
"model reads to pick it. Write the doc comment and run: go generate -run zipdoc "+
|
||||
"./apps/%s/...", app, tl.Name, app)
|
||||
}
|
||||
if len(tl.InputSchema) == 0 {
|
||||
t.Errorf("%s tool %q has no inputSchema", app, tl.Name)
|
||||
}
|
||||
out = append(out, tl.Name)
|
||||
}
|
||||
return out
|
||||
}
|
||||
// The question it asked — "is every tool on the door an op of its owning app?" —
|
||||
// is now unaskable, and that is the point: the door IS the apps' own registries,
|
||||
// asked at the moment of asking, so a tool that is not an op of its app cannot be
|
||||
// on it. What replaces the gate is a test of the live mechanism, against running
|
||||
// subsystems, which goes red when a subsystem's tools go missing or when two apps
|
||||
// claim one name: fleet/mcp_test.go.
|
||||
|
||||
// operationIDs is every operationId an app's own subset publishes.
|
||||
func operationIDs(t *testing.T, app string) map[string]bool {
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package manifest
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestExactlyOneAppIsOpen: zip refuses a second open plugin at Load, which would
|
||||
// abort a host mid-boot. The manifest is where that is decided, so it is where it
|
||||
// is checked — before a deployment finds out by not starting.
|
||||
func TestExactlyOneAppIsOpen(t *testing.T) {
|
||||
var open []string
|
||||
for _, a := range Apps {
|
||||
if a.Open {
|
||||
open = append(open, a.Name)
|
||||
}
|
||||
}
|
||||
if len(open) != 1 || open[0] != "tools" {
|
||||
t.Fatalf("exactly one app may be open and it is the tool plane, got %v", open)
|
||||
}
|
||||
for _, a := range Apps {
|
||||
if a.Open && a.Coresident {
|
||||
t.Fatalf("%s is open and co-resident: a co-resident app is never Load'ed, so it can never be asked", a.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestOpenReachesTheSpec: the flag is a property of the APP, so it must survive
|
||||
// every rung of the binary-resolution ladder — an operator's address, an
|
||||
// operator's path, the binary on disk, a published release.
|
||||
func TestOpenReachesTheSpec(t *testing.T) {
|
||||
for _, a := range Apps {
|
||||
if a.Open && !a.Plugin().Open {
|
||||
t.Fatalf("%s is open in the manifest and not in the spec the host loads", a.Name)
|
||||
}
|
||||
}
|
||||
t.Setenv("CLOUD_TOOLS_ADDR", "http://127.0.0.1:9999")
|
||||
for _, a := range Apps {
|
||||
if a.Name == "tools" && !a.Plugin().Open {
|
||||
t.Fatal("a remotely-mounted tools app lost its open flag")
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-15
@@ -56,13 +56,6 @@ type App struct {
|
||||
// empty dashboard rather than an error.
|
||||
Eager bool
|
||||
|
||||
// Open means this app also serves tools that depend on WHO is asking, so its
|
||||
// build-time catalogue (plugin/<name>/mcp.json) is incomplete BY CONSTRUCTION
|
||||
// and the host asks it per caller — see zip.Plugin.Open. Exactly one app in
|
||||
// the fleet may be open, because a tool name no catalogue claims has to
|
||||
// resolve somewhere and two candidates would make it ambiguous.
|
||||
Open bool
|
||||
|
||||
// Required means the HOST must not serve without this app. A required app
|
||||
// that will not start aborts the process; every other app degrades to being
|
||||
// absent — its prefixes answer 503 and the rest of the fleet serves.
|
||||
@@ -153,14 +146,7 @@ func Names() []string {
|
||||
// single lean plugin they are editing; a release ships every per-app binary and
|
||||
// the host falls through to the index). A dedicated binary present on disk is
|
||||
// someone's explicit intent, so it wins over the index.
|
||||
func (a App) Plugin() zip.Plugin {
|
||||
p := a.resolve()
|
||||
// Open is a property of the APP — what it serves — and not of where its binary
|
||||
// came from, so it is stamped once here rather than in each of resolve's four
|
||||
// rungs, which is four places for it to be forgotten.
|
||||
p.Open = a.Open
|
||||
return p
|
||||
}
|
||||
func (a App) Plugin() zip.Plugin { return a.resolve() }
|
||||
|
||||
// resolve is the ladder: an operator's address, an operator's path, the binary on
|
||||
// disk beside the host, a published release, and finally the on-disk path again so
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ surface-check: ## Regenerate every subset + the fleet spec FROM SOURCE and fail
|
||||
echo "undocumented, or documented and gone. The SDK repos pull this file, so a route"; \
|
||||
echo "missing here is a route no generated client can reach."; \
|
||||
echo ""; \
|
||||
echo " fix: make describe # then commit openapi.yaml and plugin/*/{openapi,mcp}.json"; \
|
||||
echo " fix: make describe # then commit openapi.yaml and plugin/*/openapi.json"; \
|
||||
echo ""; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
+11
-10
@@ -76,16 +76,17 @@ test: ## Run this app's tests.
|
||||
vet: ## go vet this app and its entrypoint(s).
|
||||
@CGO_ENABLED=$(CGO_ENABLED) $(GO) vet $(APPDIR)/... $(foreach a,$(APPS),$(ROOT)/plugin/$(a))
|
||||
|
||||
# The app's OWN projections, from the app's OWN live router: the binary mounts one
|
||||
# The app's OWN projection, from the app's OWN live router: the binary mounts one
|
||||
# subsystem and projects it through the same openapi.FleetSpec the whole-fleet
|
||||
# golden is projected through AND the same zip MCPTools the host's agent door is
|
||||
# composed from (describe.go). Never sliced out of the fleet spec by prefix — that
|
||||
# would make the fleet the source and the app a derivative, which is backwards and
|
||||
# is exactly how a catch-all silently swallows a neighbour's routes.
|
||||
# golden is projected through (describe.go). Never sliced out of the fleet spec by
|
||||
# prefix — that would make the fleet the source and the app a derivative, which is
|
||||
# backwards and is exactly how a catch-all silently swallows a neighbour's routes.
|
||||
#
|
||||
# ONE invocation writes BOTH openapi.json and mcp.json, from one mount at one
|
||||
# instant over one registry, so the document and the tool catalogue cannot be
|
||||
# generated apart and therefore cannot disagree.
|
||||
# It used to write mcp.json beside it — the same registry projected as MCP tools —
|
||||
# and the argument was that generating them together kept them honest. They were
|
||||
# BOTH stale by the same 353 ops for o11y, because the trigger was a go.mod bump in
|
||||
# another repository. The tool catalogue is not generated any anymore: the host
|
||||
# ASKS each subsystem for its tools at the moment it is asked (package fleet).
|
||||
#
|
||||
# `build` first, because a projection taken from a stale binary is a lie.
|
||||
#
|
||||
@@ -97,13 +98,13 @@ vet: ## go vet this app and its entrypoint(s).
|
||||
# The binary is handed the DIRECTORY, never a redirect: a subsystem's dependencies
|
||||
# write to stdout at mount (hanzoai/commerce prints a sqlite-vec warning and GORM
|
||||
# debug lines), and `> file` splices those into the front of the document.
|
||||
describe: build ## Emit this app's own OpenAPI subset + MCP tool catalogue into plugin/<app>/.
|
||||
describe: build ## Emit this app's own OpenAPI subset into plugin/<app>/.
|
||||
@for a in $(APPS); do \
|
||||
echo ">> describe $$a"; \
|
||||
GIT_SSH_ADDR=127.0.0.1:0 $(BIN)/$$a describe $(ROOT)/plugin/$$a || exit 1; \
|
||||
done
|
||||
|
||||
# Binaries only. plugin/<app>/{openapi,mcp}.json are committed artifacts, like the
|
||||
# Binaries only. plugin/<app>/openapi.json is a committed artifact, like the
|
||||
# fleet's openapi.yaml — `clean` removes what a build wrote, not what it publishes.
|
||||
clean: ## Remove this app's built binary.
|
||||
@rm -f $(foreach a,$(APPS),$(BIN)/$(a))
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "RevokeKey revokes the caller's own API key of the requested class. The class is\nthe same field mint takes — `?type=publishable`, defaulting to secret — so\nrevoking the key that ships in a browser bundle does not sign its holder out of\ntheir own API: the other key keeps working.\n\nRevoking is how a key is replaced when it does not need replacing; minting the\nsame class again rotates it in one step. IAM drops the credential immediately,\nbut the gateway caches keys for a few minutes, so a request that beat the cache\nexpiry may still be served.\n\nFor callers written against the older shape, the class is also accepted in a JSON\nrequest body, read only when `?type=` is absent.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "Type is the key class to act on: \"secret\" (sk-, session-equivalent, belongs\non a server) or \"publishable\" (pk-, org-identifying, safe in a browser\nbundle). Omitted means secret, which is what every existing caller means.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_keys"
|
||||
},
|
||||
{
|
||||
"description": "TopupRails lists the accepted (chain, token, treasury) triples, so a browser can\nrender \"send USDC here\" without the addresses being baked into its bundle.\n\nThis exists because the console previously gated its top-up UI on\nNEXT_PUBLIC_HANZO_HUSD_ADDRESS/_TREASURY — build-time constants. Enabling a rail\ntherefore meant rebuilding and redeploying the frontend, and with them unset the\nUI reported \"not available yet\" no matter what the server could actually accept.\nServing the set at runtime keeps ONE source of truth (the server's config) and\nlets a rail be switched on without shipping a bundle.\n\nEverything here is public on-chain data; no secret is exposed, and the set is\nempty on a deployment that accepts no crypto rail.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_commerce_topup_rails"
|
||||
},
|
||||
{
|
||||
"description": "IssueCSRFToken mints the anti-CSRF token a browser echoes as X-CSRF-Token on\nevery money write (mint/revoke a key, top up, onboard, and the billing/commerce\nwrite verbs). The token is bound to the caller's validated identity and expires,\nso one minted for one identity cannot authorize a write as another.\n\nIt is answered no-store, so it is never cached by a shared proxy. This is the\nsame-origin endpoint the embedded console reads — the Same-Origin Policy is what\nstops a cross-site page from reading the response and forging a write.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_csrf"
|
||||
},
|
||||
{
|
||||
"description": "EmbedStatus reports whether one of this brand's shared embedded apps (cms, erp,\nhelp) may be framed by the caller and is actually running, so a console module\ncan choose between the embed and the provision panel.\n\nIt answers two questions the browser cannot answer for itself. ENTITLEMENT is\nserver-authoritative: each app is a single shared per-BRAND instance, so only a\nmember of the owning brand org — or a SuperAdmin — is given the embed URL; every\nother caller gets phase \"not-entitled\" and no URL. REACHABILITY is a probe of\nthat origin, which a cross-origin page cannot read for itself.\n\nThe probed host is always \u003capp\u003e.\u003cthis deployment's own brand domain\u003e: no part of\nit comes from the request, so this can never be steered into probing an\narbitrary origin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "App is the embedded app to report on: cms (Content Studio), erp or help.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_embed"
|
||||
},
|
||||
{
|
||||
"description": "GetKey returns the caller's own API keys — every type they hold, read\nAUTHORITATIVELY from IAM rather than from the session claim, which lags a key\nminted moments ago. No secret material comes back: a secret key is represented\nby its prefix, and only a publishable key (public by construction) carries its\nfull value.\n\nA transient IAM read failure reports an empty set rather than a 5xx, so the\npage shows the honest empty state and never a fabricated key.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_keys"
|
||||
},
|
||||
{
|
||||
"description": "WalletTopup credits the caller's org for a stablecoin transfer they already sent\nto the treasury. It reads the receipt from that rail's chain, confirms a mined,\nsuccessful ERC-20 Transfer to the rail's treasury, derives USD cents from the\non-chain value using the token's own decimals, records the credit, and returns\nthe amount plus the new balance.\n\nThe credited amount is the ON-CHAIN value, never a number the caller sends, and\nthe credit lands on the caller's own validated org — there is no way to name a\nthird-party subject. Nothing is credited that the chain did not confirm: a\nmissing, failed or non-matching transaction is refused, and a deployment with no\npayment rail enabled says so rather than inventing a credit.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"fromAddress": {
|
||||
"description": "FromAddress is the wallet the transfer was sent from. Optional; when given it\nmust match the transfer's on-chain sender.",
|
||||
"type": "string"
|
||||
},
|
||||
"rail": {
|
||||
"description": "Which accepted rail the transfer was sent on, e.g. \"base-usdc\". The client\nnames it rather than the server guessing from the tx: the same address can\nexist on several chains, so inferring would risk crediting against the wrong\ntreasury. It may be omitted only while exactly one rail is enabled.",
|
||||
"type": "string"
|
||||
},
|
||||
"txHash": {
|
||||
"description": "TxHash is the hash of the ERC-20 transfer that was already sent to the rail's\ntreasury. The receipt is read from that chain; nothing is credited that the\nchain did not confirm.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_commerce_topup_wallet"
|
||||
},
|
||||
{
|
||||
"description": "MintKey creates — or rotates — the caller's API key of the requested type and\nreturns it ONCE. A real IAM failure surfaces as 502, never a fabricated key.\n\nRotating is what creating means here: a user holds one key per type, so the\nendpoint is idempotent by (caller, type) and the superseded credential stops\nworking. Two live secrets for one user would make \"revoke my key\" a lie.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"description": "Type is the key class to act on: \"secret\" (sk-, session-equivalent, belongs\non a server) or \"publishable\" (pk-, org-identifying, safe in a browser\nbundle). Omitted means secret, which is what every existing caller means.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_keys"
|
||||
},
|
||||
{
|
||||
"description": "Onboard creates the caller's organization. Two flows, keyed on whether the caller\nalready has a home org (mirrors app/onboard/route.ts):\n\n - FIRST-RUN (no owner): create + MOVE the user in as admin, so their next JWT\n carries the new owner and the cloud scopes everything to it.\n - ADDITIONAL (owner set): create the org but do NOT move the user — a move\n changes their IAM owner (stripping a SuperAdmin's status + orphaning their\n current org). They reach the new org via the OrgSwitcher, which re-scopes\n X-Org-Id without touching IAM membership. A personal-org request from someone\n who already has an org is meaningless → 409.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the organization's display name. Ignored when personal is true, which\nderives the name from the caller's own username instead.",
|
||||
"type": "string"
|
||||
},
|
||||
"personal": {
|
||||
"description": "Personal asks for the caller's own workspace: the name is derived from their\nusername and the slug auto-suffixes to stay unique. Meaningless — and refused\n— for a caller who already has an organization.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_orgs"
|
||||
}
|
||||
]
|
||||
@@ -1,969 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Is the fleet AI board: LLM generations over gen_ai spans (count, cost,\navg/p95 latency, per-model), per-model usage from the live cloud_usage ledger, and\nthe eval plane (traces, scores, score names, runs, and the average-score trend).\n\nEvery signal degrades INDEPENDENTLY — a table that is absent or errors contributes its\nzero value and the read still succeeds. Generation latency is a SEPARATE query from\ngenerations and cost on purpose: a duration/attribute mismatch there must not zero\nthe two numbers that did read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range is the lower time bound: 24h, 7d or 30d. Anything else reads as the\nboard's own default.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminAIMetrics"
|
||||
},
|
||||
{
|
||||
"description": "Is the SaaS product-analytics board over the caller's tenant window: active\ncustomers, new and churned, retention, MRR, ARPU, the usage trend and the top\ncustomers by spend — every number folded from the commerce ledger, not sampled.\n\nThe window is the caller's, not the fleet's: a SuperAdmin gets every org, a\nwhite-label admin only their own subtree (core.ScopedOrgs, the one scope predicate).\n\nsources[] carries each upstream's freshness so a partial read is VISIBLE rather than\nsilently low: a ledger that answered for only some orgs marks commerce-ledger degraded\ninstead of publishing an undercount as healthy.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range is the lower time bound: 24h, 7d or 30d. Anything else reads as the\nboard's own default.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminAnalytics"
|
||||
},
|
||||
{
|
||||
"description": "Lists IAM applications for one owner org, forwarded VERBATIM from IAM's\nget-applications. These are the platform's OIDC clients — the console reads clientId\noff each row.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"owner": {
|
||||
"description": "Owner is the org whose rows to read. Defaults to the admin org, which owns the\nplatform's roles and applications.",
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"description": "Page is the 1-based page number. Forwarded only when set — IAM applies its own\ndefault otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is rows per page. Forwarded only when set.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminApplications"
|
||||
},
|
||||
{
|
||||
"description": "Reads cloud's tamper-evident audit trail, newest first, with the chain's live\nintegrity attached so a listing can be badged as verified.\n\nWhen cloud has no local store configured it falls back to forwarding IAM's own\nget-records trail verbatim — a DIFFERENT trail, federated so the endpoint never\nregresses to an empty list. Those rows carry no integrity of ours, so the field is\nnull there.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action restricts it to one action name, e.g. \"admin.waitlist.grant\".",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org restricts the trail to one tenant.",
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"description": "Page is the 1-based page number, driving the offset.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is rows per page, default 100.",
|
||||
"type": "string"
|
||||
},
|
||||
"resource": {
|
||||
"description": "Resource restricts it to one resource kind, e.g. \"credit-grant\".",
|
||||
"type": "string"
|
||||
},
|
||||
"resourceId": {
|
||||
"description": "ResourceID restricts it to one resource instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"description": "Result restricts it to \"success\" or \"error\".",
|
||||
"type": "string"
|
||||
},
|
||||
"since": {
|
||||
"description": "Since is the inclusive lower time bound, RFC3339. An unparseable value is\nignored rather than refused — one malformed filter must not hide the trail.",
|
||||
"type": "string"
|
||||
},
|
||||
"sub": {
|
||||
"description": "Sub restricts it to one actor (the validated subject that made the request).",
|
||||
"type": "string"
|
||||
},
|
||||
"until": {
|
||||
"description": "Until is the upper time bound, RFC3339, with the same tolerance.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminAudit"
|
||||
},
|
||||
{
|
||||
"description": "Walks the WHOLE hash chain and reports whether it is intact: how many records\nwere checked, the head hash to pin externally against tail-truncation, and — when the\nchain is broken — the seq of the first bad record and why.\n\nbrokenAt is -1 exactly when ok is true. An unconfigured store is an honest failure\nhere rather than a fabricated pass.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminAuditVerify"
|
||||
},
|
||||
{
|
||||
"description": "Lists the tenant Base instances in the caller's window — a SuperAdmin sees every\ntenant's, anyone else only their own subtree's.\n\nThe scope is enforced TWICE: the upstream is asked for the caller's org, AND every row\nit returns is re-checked against the resolved scope. An upstream that ignored the\nfilter therefore degrades to empty, never to a cross-tenant leak.\n\nThe Base engine is being embedded into cloud; until it lands this proxies\nBASE_ADMIN_URL and, when that is unset, answers 200 with an empty list and msg saying\nso — the honest not-yet state, never fabricated instances.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminBases"
|
||||
},
|
||||
{
|
||||
"description": "Reads one org's usage caps: its spend alerts plus the derived period\nspend, over/warn state and reset time.\n\nThese are the SAME rows the customer edits in their own console — a platform override\nand a customer budget are one model, not two.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the cap to edit or remove, from the path. Unused by the list and create ops.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to act on. Required for a SuperAdmin — they must name their\ntarget; ignored for a white-label admin, who always acts on their own org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCaps"
|
||||
},
|
||||
{
|
||||
"description": "Rolls the fleet's compute usage up to one row per (org, app, project, kind):\nhow many distinct machines ran in the window, how many are still active, what they\nbilled, and when each group last emitted an event. The console folds these into its\norg → app → project tree.\n\nA machine counts as ACTIVE when its LATEST lifecycle event is not a terminal one\n(stop/destroy/terminate/delete/off/shutdown/expire and their past tenses) — the same\nfold the console applies, done in the warehouse so the count is over every machine and\nnot just the page.\n\nHonest-empty when the warehouse is not connected or hanzo.compute_usage is not\nprovisioned yet: an empty list, never a fabricated fleet.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
"description": "Kind narrows to one workload class (bot | machine | cluster | nodepool |\ncontainer | function | …). An OPEN spectrum matched as a plain string, lowercased\nto the warehouse's convention; empty means every kind.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org narrows to one tenant. Empty means every tenant — this board is\ncross-tenant by nature.",
|
||||
"type": "string"
|
||||
},
|
||||
"range": {
|
||||
"description": "Range is the lower time bound: 24h, 7d or 30d. Anything else reads as 30d.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCompute"
|
||||
},
|
||||
{
|
||||
"description": "Marks one cluster node unschedulable — or schedulable again — and can drain\nthe pods already on it.\n\nIt is the ONE infra change that does not go through the run discipline, because there\nis no destructive verdict to check: cordoning is reversible and evicting respects the\ncluster's own PodDisruptionBudgets. It reads the cached board for the same reason.\nThe outcome is audited either way, and the result reports how many pods were evicted.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"cordon": {
|
||||
"description": "Cordon true marks the node unschedulable; false restores it.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"drain": {
|
||||
"description": "Drain additionally evicts the pods already running there.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the node's droplet id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCordonNode"
|
||||
},
|
||||
{
|
||||
"description": "Sets a usage cap on one org — a platform override of a customer budget,\nwritten to the customer's own spend-alert rows. The body is commerce's spend-alert\ncontract, forwarded byte-for-byte.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the cap to edit or remove, from the path. Unused by the list and create ops.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to act on. Required for a SuperAdmin — they must name their\ntarget; ignored for a white-label admin, who always acts on their own org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCreateCap"
|
||||
},
|
||||
{
|
||||
"description": "Mints credit for one org. It is the ONE admin mint surface, and it\ndoes NOT mint in-process: it forwards the request to commerce's already-mint-gated\nPOST /v1/billing/credits, authenticated by the service token and scoped to the\ntarget org, then writes one tamper-evident compliance record. Commerce stays the sole\ncredit ledger; this is a thin, audited relay so there is exactly one place credit is\ncreated.\n\nThe body is commerce's OWN CreateCreditGrant contract, forwarded whole — every field\nit carries reaches commerce. The only two this layer reads are the target org (`org`,\nor `user` as the org-pool alias), which selects the namespace commerce's EdgeAuth\ntrusts, and `idempotencyKey`, which makes a double-clicked grant credit once.\n\nA FAILED grant is audited too, with the request body attached: an attempted mint is\nexactly as interesting to a compliance auditor as a successful one.",
|
||||
"inputSchema": {
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCreateCredit"
|
||||
},
|
||||
{
|
||||
"description": "Answers GET /v1/admin/customers/:org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org is the tenant slug from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCustomer"
|
||||
},
|
||||
{
|
||||
"description": "Lists every customer org at a glance, sorted by slug: owner email, plan,\nsuspend status, member count, balance, month-to-date spend and MRR.\n\nEach row costs one IAM read plus the org's money reads, fanned out under a fixed\nconcurrency ceiling so a large fleet cannot stampede the upstreams. Every read is\nbest-effort per row: an upstream miss degrades THAT field to its honest zero rather\nthan failing the fleet.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminCustomers"
|
||||
},
|
||||
{
|
||||
"description": "Removes one cap by id, lifting the ceiling entirely.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the cap to edit or remove, from the path. Unused by the list and create ops.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to act on. Required for a SuperAdmin — they must name their\ntarget; ignored for a white-label admin, who always acts on their own org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminDeleteCap"
|
||||
},
|
||||
{
|
||||
"description": "Destroys a droplet the board has just proven is NOT a DOKS node. There\nis no snapshot-first undo for a droplet the way there is for a volume: the local disk\ngoes with it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"disk": {
|
||||
"description": "Disk requests a PERMANENT resize that grows the disk. DO can never resize such a\ndroplet down again, so it defaults false — a CPU/RAM-only change, reversible.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the DO droplet id, from the path. Numeric.",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"description": "Size is the target DigitalOcean size slug on resize, e.g. \"s-4vcpu-8gb\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminDeleteDroplet"
|
||||
},
|
||||
{
|
||||
"description": "Destroys a load balancer the board has just proven no live\ntype=LoadBalancer Service in any cluster targets.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DO load balancer id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminDeleteLoadBalancer"
|
||||
},
|
||||
{
|
||||
"description": "Destroys a volume the board has just proven no PersistentVolume in any\ncluster references. Irreversible, so it snapshots first unless explicitly waived —\nthe snapshot IS the undo.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DO volume id, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the snapshot name on the snapshot action. Blank gets a deterministic\n\"\u003cvolume\u003e-predelete-\u003cunix\u003e\" so the undo is findable in the DO console.",
|
||||
"type": "string"
|
||||
},
|
||||
"sizeGiB": {
|
||||
"description": "SizeGiB is the target size on the resize action. A volume only ever grows —\nExpandTo is the verdict that refuses a shrink, so this is not validated here.",
|
||||
"type": "integer"
|
||||
},
|
||||
"snapshot": {
|
||||
"description": "Snapshot is the snapshot-first switch on DELETE. Anything other than the literal\n\"false\" snapshots before destroying — the snapshot IS the undo, so waiving it is\ndeliberate and explicit.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminDeleteVolume"
|
||||
},
|
||||
{
|
||||
"description": "Answers GET /v1/admin/finance. It reads the multi-vendor COGS from commerce\n/v1/costs, the DO promo-credit/burn-down treasury view, and the fleet commerce revenue,\nthen hands them to ComputeFinance. SuperAdmin only.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminFinance"
|
||||
},
|
||||
{
|
||||
"description": "Carries ONE org's current commerce prepaid balance into the native finance\nwallet — the one-time cutover between the two ledgers.\n\nIt is IDEMPOTENT: the deposit uses the fixed ref \"backfill:\u003corg\u003e\", so re-running it\ncredits the wallet at most once. Safe to retry.\n\nThe pre-migration balance is read from the CO-RESIDENT commerce ledger, not over HTTP:\nthe admin HTTP client dials an unroutable in-process address and would read $0, and a\nphantom zero would silently carry nothing while reporting success. When commerce is\nnot co-resident this fails rather than migrating nothing.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org is the tenant to migrate. Required — there is no fleet-wide form of this\ncutover, because each org must be reconciled on its own.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminFinanceBackfill"
|
||||
},
|
||||
{
|
||||
"description": "Reads the platform control-plane board: every runtime launch/release\nswitch (waitlist, public signup, subsystem activation, gateway limits, network ids)\nwith its LIVE value and where that value came from — a stored definition or the\ncompiled-in default.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminFlags"
|
||||
},
|
||||
{
|
||||
"description": "Issues a staff credit grant to the org named in the path — a comp, refund\nor promo — through the ONE credit-write path core.ApplyGrant, which validates the\namount against the per-grant cap, checks the org exists, moves the money and records\nthe tamper-evident audit row.\n\nThe credit lands on the account account.Payer resolves, NOT necessarily the org: name\na member of a pooled org and the pool is credited. The receipt echoes the subject so\nthe caller can see which.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"amountCents": {
|
||||
"description": "AmountCents is the credit, in whole cents. Must be positive and within the\nper-grant cap.",
|
||||
"type": "integer"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code, lower-cased. Empty means usd.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to credit. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason is the operator's justification, recorded on the audit row.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Source is the money bucket: \"trial\" (default) for a non-cash comp that is never\nrefundable, or \"prepaid\" for real money. Anything unknown falls back to trial.",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "User optionally names a MEMBER to credit, by bare IAM username. Empty credits\nthe org. Which of the two the money actually lands on is decided by\naccount.Payer, not here: a pooled org keeps one balance whatever is named.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminGrantCredit"
|
||||
},
|
||||
{
|
||||
"description": "Reads the credit-grant ledger across ALL orgs, newest first — who granted what\nto whom, when, and from which money bucket.\n\nIt is a PROJECTION of the tamper-evident audit trail, not a second store: every grant\nis written there as action \"admin.customer.credit\", so this view cannot drift from\nwhat actually happened, and FAILED grants appear too.\n\nA deployment with no local audit store has no history to project, and says so with an\nempty list and a msg rather than an error.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned. Default 200.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org filters by the ACTOR's org (the staff org that issued the grant), which is\nrarely what a reader wants — the target org is a row field, not a filter.",
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"description": "Result filters by outcome: \"success\" or \"error\". Empty returns both, which is\nthe point of this view — a refused grant is as interesting as a granted one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminGrants"
|
||||
},
|
||||
{
|
||||
"description": "Serves the whole DigitalOcean infrastructure board: droplets, volumes, DOKS\nclusters and load balancers, each cross-referenced against every cluster's live\nKubernetes state so the board can say what is safe to destroy and what is not.\n\nIt is cached for up to a minute because one read is a fan-out over the DO API plus a\nfull pod/PV listing per cluster. Staleness is never load-bearing: every MUTATION\nre-scans from scratch and ignores this cache.\n\nOnly an unusable DO account is a hard failure. A partial read still produces a board,\nwith the failing source named in sources[] — except for clusters and volumes, which\nthe safety verdict depends on; without those the analysis degrades rather than\nclassifying anything it cannot prove.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"refresh": {
|
||||
"description": "Refresh, when present, forces a full re-scan instead of serving the cached\nsnapshot. Every MUTATION re-scans regardless — this is only for the reader.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminInfra"
|
||||
},
|
||||
{
|
||||
"description": "Answers GET /v1/admin/invoices.\n\n\tGET /v1/admin/invoices?org=\u0026status=\u0026limit=",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned. total still reports the full match count.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org filters to one tenant, matched exactly.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status filters on the invoice's LATEST lifecycle status (paid, open, void, …),\nmatched case-insensitively.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminInvoices"
|
||||
},
|
||||
{
|
||||
"description": "Issues a credit grant to any org from the operator Grants view, with the\ntarget named in the body. It funnels through the SAME core.ApplyGrant that\nPOST /v1/admin/customers/:org/credit uses, so there is exactly ONE credit-write path\nand one audit trail behind both.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"amountCents": {
|
||||
"description": "AmountCents is the credit, in whole cents. Must be positive and within the\nper-grant cap.",
|
||||
"type": "integer"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code, lower-cased. Empty means usd.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to credit. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason is the operator's justification, recorded on the audit row.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Source is the money bucket: \"trial\" (default) for a non-cash comp that is never\nrefundable, or \"prepaid\" for real money. Anything unknown falls back to trial.",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "User optionally names a MEMBER to credit, by bare IAM username. Empty credits\nthe org. Which of the two the money actually lands on is decided by\naccount.Payer, not here: a pooled org keeps one balance whatever is named.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminIssueGrant"
|
||||
},
|
||||
{
|
||||
"description": "Answers with the validated operator identity — who the console is signed in as,\nwhich tier they are, and how wide their tenant window is. The fields come from the\nsanitized identity headers the gate just read, so they are authoritative and never\nclient-forgeable; nothing is looked up.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminMe"
|
||||
},
|
||||
{
|
||||
"description": "Answers GET /v1/admin/metrics by aggregating commerce.events directly\n(fleet-wide, no per-org fan-out). SuperAdmin only.\n\n\tGET /v1/admin/metrics?window=30d\u0026limit=20",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the top-customers table.",
|
||||
"type": "string"
|
||||
},
|
||||
"window": {
|
||||
"description": "Window is the movement window the new/churned MRR and the recent feed are\nmeasured over. Anything unrecognised falls back to the board default.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminMetrics"
|
||||
},
|
||||
{
|
||||
"description": "moneyBoardHandler answers GET /v1/admin/money.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminMoney"
|
||||
},
|
||||
{
|
||||
"description": "Is the fleet-wide observability board: LLM usage (requests, tokens, cost,\nerrors, top orgs, top models), trace RED metrics (count, p50/p95/p99 latency in ms,\nerror rate, top services), fleet log volume, and the O11yAI generation rollup — all\naggregated across EVERY tenant, with no org filter applied.\n\nEvery signal degrades INDEPENDENTLY. A table that is absent or errors contributes its\nzero value and the read still succeeds, so the board renders exactly what the\nwarehouse holds rather than failing whole because one of four sources is missing.\nSame when the warehouse is not connected at all: the zero board, never a fabricated\nfleet.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range is the lower time bound: 24h, 7d or 30d. Anything else reads as the\nboard's own default.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminO11y"
|
||||
},
|
||||
{
|
||||
"description": "Lists the tenant directory one row per org, sorted by slug: member count and the\norg's month-to-date spend and credit balance, read live from IAM and commerce.\n\nThe rows are the caller's tenant window, not the fleet: a SuperAdmin gets every org, a\nwhite-label admin only their own subtree. A per-org read that fails degrades THAT row\nto an honest zero — this panel carries no sources[] channel to report freshness on, so\nthe alternative would be a fleet total that silently reads healthy.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminOrgs"
|
||||
},
|
||||
{
|
||||
"description": "Is the Platform Overview tiles: how many orgs and users are in the caller's\ntenant window, the fleet workload counts, and month-to-date spend and credits.\n\nIt ALWAYS answers 200 — a tile board that fails as a whole because one upstream is\ndown is useless. Instead every upstream reports itself in sources[]: ok, degraded, or\nnot-configured. A commerce read that failed for ANY org marks that source degraded,\nbecause the spend/credits totals are then an undercount and must not read healthy.\n\ntokens30d is 0 for the same reason /usage has no series: there is no fleet token\ncounter to read yet.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminOverview"
|
||||
},
|
||||
{
|
||||
"description": "Lists the fleet workload registry: every operator App CR across the platform\nnamespaces with its declared vs running image tag, reconciled health/phase and drift\nverdict. Optionally narrowed by kind, tier or env, each an exact match.\n\nThe rows are the SAME observation /v1/platform/fleet renders — read through the in-process\nplatform seam, not a second k8s client — so the two boards can never disagree about what\nthe fleet is. A PaaS plane that is not co-resident yields an honestly empty registry,\nnever a fabricated row.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"env": {
|
||||
"description": "Env matches the lifecycle namespace (main|test|dev).",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind matches the operator App CR's declared spec.role (sql|kv|generic|ingress).",
|
||||
"type": "string"
|
||||
},
|
||||
"tier": {
|
||||
"description": "Tier matches the derived infra grouping (cloud|data|edge|daemon|paas|app).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminProducts"
|
||||
},
|
||||
{
|
||||
"description": "Reads the current platform plan promo — the singleton discount offer, e.g.\nthe 50%-off launch promo. Commerce stores it in the reserved platform namespace, so\nthe org sent with the read is the admin org and the service token is what passes\ncommerce's own platform-admin gate.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminPromo"
|
||||
},
|
||||
{
|
||||
"description": "Serves GET /v1/admin/providers/credit — the per-provider upstream\ncredit ledger. SuperAdmin-guarded (see Routes).",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminProvidersCredit"
|
||||
},
|
||||
{
|
||||
"description": "Restores access for every member of the org, undoing a suspend. It\nreports the same per-user breakdown.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org is the tenant slug from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminReactivateCustomer"
|
||||
},
|
||||
{
|
||||
"description": "Changes a droplet's plan. Same refusal as delete and for the same\nreason: a DOKS node's size is the node pool's to declare.\n\ndisk=true is a PERMANENT resize — the disk grows and DO can never resize the droplet\nDOWN again. disk=false (the default) changes CPU/RAM only and is reversible. DO\nrequires the droplet to be powered off and applies the change asynchronously, so the\nresponse carries the action to poll, not a completed change.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"disk": {
|
||||
"description": "Disk requests a PERMANENT resize that grows the disk. DO can never resize such a\ndroplet down again, so it defaults false — a CPU/RAM-only change, reversible.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the DO droplet id, from the path. Numeric.",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"description": "Size is the target DigitalOcean size slug on resize, e.g. \"s-4vcpu-8gb\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminResizeDroplet"
|
||||
},
|
||||
{
|
||||
"description": "Grows a volume. GROW ONLY — see Volume.ExpandTo for why the other\ndirection is a data migration this board deliberately refuses to run.\n\nThe MECHANISM follows the volume's owner, because there is exactly one way to grow each\nkind completely. A volume a PVC claims is grown by patching the claim: the CSI driver\nthen resizes the DigitalOcean device AND grows the filesystem on it, leaving claim, PV,\ndevice and filesystem all agreeing. Calling DigitalOcean directly for that volume would\ngrow the device while the PV kept declaring the old capacity and the filesystem never\ngrew at all. One operation, one correct mechanism per owner — not two ways to do it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DO volume id, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the snapshot name on the snapshot action. Blank gets a deterministic\n\"\u003cvolume\u003e-predelete-\u003cunix\u003e\" so the undo is findable in the DO console.",
|
||||
"type": "string"
|
||||
},
|
||||
"sizeGiB": {
|
||||
"description": "SizeGiB is the target size on the resize action. A volume only ever grows —\nExpandTo is the verdict that refuses a shrink, so this is not validated here.",
|
||||
"type": "integer"
|
||||
},
|
||||
"snapshot": {
|
||||
"description": "Snapshot is the snapshot-first switch on DELETE. Anything other than the literal\n\"false\" snapshots before destroying — the snapshot IS the undo, so waiving it is\ndeliberate and explicit.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminResizeVolume"
|
||||
},
|
||||
{
|
||||
"description": "Is the fleet money board: total prepaid balances held, total realized spend,\nMRR, ARPU, a per-customer table sorted highest-revenue first, and a real 30-day spend\ntrend from the usage ledger.\n\nORTHOGONAL to /v1/admin/finance, which is the COGS/margin view of what WE pay vendors.\nThis is the customer side: what each customer holds, spends and subscribes to.\n\narpu divides realized spend by PAYING customers, not by all of them — a fleet of free\nsignups must not deflate the number. A customer counts as paying when it has spend or\nMRR.\n\nAn org whose money did not read degrades to honest zeros and marks the commerce source\ndegraded in sources[], so a partial fleet read is visible instead of quietly low.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminRevenue"
|
||||
},
|
||||
{
|
||||
"description": "Lists IAM roles for one owner org, forwarded VERBATIM from IAM's get-roles.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"owner": {
|
||||
"description": "Owner is the org whose rows to read. Defaults to the admin org, which owns the\nplatform's roles and applications.",
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"description": "Page is the 1-based page number. Forwarded only when set — IAM applies its own\ndefault otherwise.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is rows per page. Forwarded only when set.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminRoles"
|
||||
},
|
||||
{
|
||||
"description": "Sets a node pool's node count — the ONE correct way to change how many\nnodes a DOKS cluster has.\n\nThe response states what the board could NOT prove: DOKS picks which nodes a shrink\nremoves, so no particular pod is shown to survive one. See NodePool.ScaleTo.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"count": {
|
||||
"description": "Count is the node count to set.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the DOKS cluster id, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"pool": {
|
||||
"description": "Pool is the node pool, from the path. Its DO id or its name — both are unique\nwithin a cluster, and an operator reads the name off the board.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminScaleNodePool"
|
||||
},
|
||||
{
|
||||
"description": "Reads the launch board: every hosted service in the registry with its LIVE\nwaitlist mode, evaluated through the flag engine. This is the \"remove the waitlist one\nservice at a time\" view.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminServices"
|
||||
},
|
||||
{
|
||||
"description": "Stores or overwrites ONE platform switch's definition and answers with the\nwhole board as it now stands. The flip is hot: this pod applies it immediately and\npeers converge within one evaluation TTL (15s by default), with no redeploy.\n\nThe body reaches the flag engine BYTE-FOR-BYTE — it is the engine's definition\nformat, not this layer's, so a field the engine understands and admin does not must\nstill arrive intact. setFlagIn names the two fields that matter for documentation; it\nis not a filter.\n\nThe write is recorded in the store's activity log against the caller's email.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "Active is the switch itself: true enables the flag for every evaluation.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"filters": {
|
||||
"description": "Filters is the optional rollout/payload block of a VALUED switch, e.g.\n{\"groups\":[{\"properties\":[],\"rollout_percentage\":100}],\"payloads\":{\"true\":250}}."
|
||||
},
|
||||
"key": {
|
||||
"description": "Key is the switch to write, taken from the path (e.g. \"waitlist.chat\").",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSetFlag"
|
||||
},
|
||||
{
|
||||
"description": "Upserts the platform plan promo — the ONE place the offer is configured.\n\nThe body is commerce's own promo contract and is forwarded BYTE-FOR-BYTE, so no field\ncommerce accepts is dropped in transit. promoIn names its documented fields.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"active": {
|
||||
"description": "Active is the master switch: false parks the offer without deleting it.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"end": {
|
||||
"description": "End is when the offer closes (RFC3339).",
|
||||
"type": "string"
|
||||
},
|
||||
"percentOff": {
|
||||
"description": "PercentOff is the discount, 0-100.",
|
||||
"type": "integer"
|
||||
},
|
||||
"plans": {
|
||||
"description": "Plans are the plan ids the offer applies to.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"start": {
|
||||
"description": "Start is when the offer opens (RFC3339).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSetPromo"
|
||||
},
|
||||
{
|
||||
"description": "Flips ONE service's waitlist switch — the launch lever. Hot: it takes\neffect on this pod immediately and on peers within one evaluation TTL, with no\nredeploy. An unknown service is a 404, not a silent create; onboarding goes through\nupsertService.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"service": {
|
||||
"description": "Service is the slug to flip, taken from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"waitlistMode": {
|
||||
"description": "WaitlistMode is the new mode: true gates the service behind the waitlist, false\nopens it. This is the launch lever.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSetServiceMode"
|
||||
},
|
||||
{
|
||||
"description": "Takes a point-in-time snapshot of one volume — the undo a delete relies\non, available on its own so an operator can take one before any risky change.\n\nIt re-scans the board first (never the cache) so the volume it snapshots is one that\nexists right now, and audits the outcome either way.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DO volume id, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the snapshot name on the snapshot action. Blank gets a deterministic\n\"\u003cvolume\u003e-predelete-\u003cunix\u003e\" so the undo is findable in the DO console.",
|
||||
"type": "string"
|
||||
},
|
||||
"sizeGiB": {
|
||||
"description": "SizeGiB is the target size on the resize action. A volume only ever grows —\nExpandTo is the verdict that refuses a shrink, so this is not validated here.",
|
||||
"type": "integer"
|
||||
},
|
||||
"snapshot": {
|
||||
"description": "Snapshot is the snapshot-first switch on DELETE. Anything other than the literal\n\"false\" snapshots before destroying — the snapshot IS the undo, so waiving it is\ndeliberate and explicit.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSnapshotVolume"
|
||||
},
|
||||
{
|
||||
"description": "Answers GET /v1/admin/subscriptions.\n\n\tGET /v1/admin/subscriptions?org=\u0026status=\u0026limit=",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned. total still reports the full match count.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org filters to one tenant, matched exactly.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status filters on the subscription's LATEST lifecycle status (active, trialing,\ncanceled, …), matched case-insensitively.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSubscriptions"
|
||||
},
|
||||
{
|
||||
"description": "subsystems answers GET /v1/admin/subsystems. ?range=24h|7d|30d bounds the telemetry\nwindow (default 30d) — the same enum, and the same helpers, as the o11y board.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range bounds the telemetry window: 24h, 7d or 30d. Anything else, including\nempty, resolves to the default through the same o11yRange the o11y board uses.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSubsystems"
|
||||
},
|
||||
{
|
||||
"description": "Cuts off every member of the org: IAM refuses a forbidden user at\nlogin AND at token issuance, so a suspended customer can neither sign in nor mint a\nfresh token. Fully reversible with ReactivateCustomer.\n\nThe result names every user updated and every user that was NOT — a partial failure\nleaves the org in a mixed state and says so instead of reporting a clean success.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org is the tenant slug from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSuspendCustomer"
|
||||
},
|
||||
{
|
||||
"description": "Answers the operator's \"Sync now\" button. There is nothing to kick: admin\naggregates LIVE on every read, so the button is just a re-read. It acknowledges\nhonestly with started:true rather than pretending a batch job was queued.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminSync"
|
||||
},
|
||||
{
|
||||
"description": "Edits one cap by id — raise or lower the ceiling, flip enforcement. The\nbody is commerce's spend-alert patch contract, forwarded byte-for-byte.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the cap to edit or remove, from the path. Unused by the list and create ops.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the tenant to act on. Required for a SuperAdmin — they must name their\ntarget; ignored for a white-label admin, who always acts on their own org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminUpdateCap"
|
||||
},
|
||||
{
|
||||
"description": "Onboards a hosted service, or edits one, so a new host comes under the\nlaunch gate WITHOUT a redeploy. Re-registering an existing service PRESERVES its live\nswitch — editing the hosts of a service that is already open must not silently close\nit again.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"hosts": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"service": {
|
||||
"type": "string"
|
||||
},
|
||||
"waitlistMode": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminUpsertService"
|
||||
},
|
||||
{
|
||||
"description": "Returns the month-to-date money totals: one org's when org names one, else the\nfleet sum across every org a SuperAdmin can see.\n\nseries and byProduct are ALWAYS empty. A daily trend and a per-product split are not\nderivable from the commerce billing API — they live in insights/datastore — so this\nanswers with the honest empty arrays rather than fabricating a shape the console would\nthen chart. Same reason tokens and requests are 0: there is no fleet counter to read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org reads ONE tenant's month-to-date total instead of the fleet sum. Honoured\nfor a SuperAdmin only — a white-label admin always reads their own org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminUsage"
|
||||
},
|
||||
{
|
||||
"description": "Splits our upstream AI usage by how it was FUNDED: one row per (provider,\nmodel) over the window, tagged credit (provider grant still remaining), paid (grant\nexhausted) or paid_only (no grant at all).\n\nThe class is resolved at the PROVIDER level from the credit ledger, not per call — the\nper-call split, and the `byo` class, arrive when the metering write stamps a funding\ncolumn on cloud_usage and this can GROUP BY it directly. Until then a provider with\nremaining grant reports all of its usage as credit, which is right in aggregate and\napproximate at the boundary where a grant runs out mid-window.\n\nAn unparseable window falls back to the last 30 days rather than refusing: this is a\ndashboard read, and a typo in a date must not blank the board.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the inclusive start of the window. Unparseable or absent, together with\nTo, falls back to the last 30 days.",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the exclusive end of the window.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminUsageFunding"
|
||||
},
|
||||
{
|
||||
"description": "Lists the user directory across the caller's tenant window, one page at a time.\ntotal is IAM's REAL total, so the console can page through it.\n\nA SuperAdmin may aim the read at one tenant with org; a white-label admin cannot — for\nthem the owner is hard-pinned to their own org and org is ignored, which is what keeps\nthe directory from becoming a cross-tenant read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org narrows the directory to ONE tenant. Honoured for a SuperAdmin only — a\nwhite-label admin is pinned to their own org and this is ignored.",
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"description": "Page is the 1-based page number. Defaults to \"1\"; IAM returns zero rows AND a\nzero total when it is unset, so this layer never leaves it empty.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is rows per page. Defaults to \"200\", the shared admin page size.",
|
||||
"type": "string"
|
||||
},
|
||||
"q": {
|
||||
"description": "Query is a free-text filter, matched by IAM as a \"contains\" over the user name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminUsers"
|
||||
},
|
||||
{
|
||||
"description": "Returns the realtime block-storage board: the DigitalOcean volume fleet\n(count, capacity, monthly list cost, per-volume region and attachment) plus the\nanalytics datastore's OWN fill, read from its system.disks.\n\nA volume's usedGiB and pct are null, always: DO exposes capacity and attachment but no\nfill, so the console renders \"—\" rather than a number nobody measured. The datastore\ncard is the one real fill here, and it is the number to scale on.\n\nThe two sources degrade independently — a DO outage still returns the datastore fill,\nand a disconnected datastore still returns the DO fleet.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminVolumes"
|
||||
},
|
||||
{
|
||||
"description": "Reads one waitlist's leaderboard from the Hanzo waitlist engine — position,\npoints and referral standing per entry — proxied server-authed with the engine secret,\nnever a client credential.\n\nThe engine's payload is forwarded VERBATIM as data; the console normalizes it. When\nthe engine is not configured on this deployment the read still succeeds, with an empty\nobject and a msg saying so, so the panel shows an honest not-wired state instead of an\nerror the operator would chase.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"page": {
|
||||
"description": "Page is the 1-based page number.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is entries per page.",
|
||||
"type": "string"
|
||||
},
|
||||
"waitlist": {
|
||||
"description": "Waitlist is the waitlist slug to read (e.g. \"chat\"). The engine decides what an\nempty slug means.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminWaitlist"
|
||||
},
|
||||
{
|
||||
"description": "Grants a user waitlist points, moving them up toward the access cutoff.\nThis is the access lever: the cutoff itself does not move, the person does.\n\nIt funnels through the engine's verified grant seam (POST /v1/waitlist/award with\nsource=\"grant\" — the ONE path that honours an explicit points amount) and writes a\ntamper-evident audit row either way, so a FAILED grant is recorded too. The reason\nfield goes only to that row.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email identifies the entry to boost. Either this or RefCode is required.",
|
||||
"type": "string"
|
||||
},
|
||||
"points": {
|
||||
"description": "Points is how many points to award. Must be positive — this seam exists to move\nsomeone UP toward the cutoff.",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason is the operator's justification. Not sent to the engine; it is recorded on\nthe audit row, which is the point of asking for it.",
|
||||
"type": "string"
|
||||
},
|
||||
"refCode": {
|
||||
"description": "RefCode identifies the entry by its referral code, when the email is unknown.",
|
||||
"type": "string"
|
||||
},
|
||||
"waitlist": {
|
||||
"description": "Waitlist is the waitlist slug the grant lands on. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminWaitlistBoost"
|
||||
}
|
||||
]
|
||||
@@ -1,15 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "WaitlistMode reports whether ONE host is currently gated by the launch waitlist.\nIt resolves the host to the service that governs it and reads that service's\nwaitlist switch, so a guard sitting in front of a hosted surface can decide in one\ncall whether to show the waitlist or the product. It answers for the ONE host\nasked about and never enumerates the registry, which is why it needs no\ncredential. It FAILS OPEN: an unregistered host, an unmounted registry and a store\nfault all answer known=false with mode=false, so a request is never gated pre-boot\nor on a registry fault.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"description": "Host is the host to resolve, e.g. \"chat.hanzo.ai\". Defaults to the request's\nown Host header when omitted, which is what lets a guard running on the\ngoverned host ask about itself with no argument.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flags_waitlist"
|
||||
}
|
||||
]
|
||||
@@ -1,117 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Removes one of the caller org's campaigns and answers 204 with\nno body. It deletes the stored record only: a campaign already launched keeps\nrunning on the ad network, which must be stopped there. An id another org owns\nreads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_ads_campaigns_id"
|
||||
},
|
||||
{
|
||||
"description": "Returns the caller org's ad campaigns, most recently updated\nfirst, optionally narrowed to one lifecycle status. The listing is bounded by\nthe org: another tenant's campaigns are not reachable from here at all.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many campaigns come back: default 200, maximum 1000. A\nvalue that is not a positive integer reads as the default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status filters to one lifecycle state (draft, active, paused, completed).\nEmpty returns every campaign the org has.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ads_campaigns"
|
||||
},
|
||||
{
|
||||
"description": "Returns one of the caller org's campaigns. An id another org owns\nreads as not found, so the response cannot confirm that it exists.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ads_campaigns_id"
|
||||
},
|
||||
{
|
||||
"description": "Rolls the caller org's ad campaigns up into four numbers: how many\ncampaigns exist, how many are active, and the summed budget and spend across\nall of them. Budget and spend are MINOR units (cents), the same units the\ncampaign rows carry. It counts only this org's campaigns.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ads_summary"
|
||||
},
|
||||
{
|
||||
"description": "Registers a new ad campaign for the caller's org and answers\n201 with the stored row. It only records the campaign — nothing is sent to the\nad network until POST /v1/ads/campaigns/{id}/launch runs it. The org is\nstamped by the server from the validated principal, so a body can never place\na campaign in another tenant.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "Account is the provider ad-account this campaign runs on (Meta act_\u003cid\u003e). Optional.",
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"description": "Budget is the campaign budget in MINOR units (cents). Negative values clamp to 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the campaign's display label. Required; trimmed and bounded to 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"objective": {
|
||||
"description": "Objective is the campaign goal as the provider names it. Optional, bounded to 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"description": "Platform is the ad network: meta, google, tiktok or x. Empty defaults to meta.",
|
||||
"type": "string"
|
||||
},
|
||||
"spend": {
|
||||
"description": "Spend is the amount spent so far in MINOR units (cents). Negative values clamp to 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the lifecycle state: draft, active, paused or completed. Empty defaults to draft.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_ads_campaigns"
|
||||
},
|
||||
{
|
||||
"description": "Replaces the user-owned fields of one of the caller org's\ncampaigns and answers the stored row. It is a full replace, not a patch: every\nfield is written from the request, so an omitted one is cleared. externalId is\nlaunch-owned and is never touched here, so editing a campaign cannot break its\nlink to a live provider execution.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "Account is the provider ad-account this campaign runs on (Meta act_\u003cid\u003e). Optional.",
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"description": "Budget is the campaign budget in MINOR units (cents). Negative values clamp to 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the campaign's display label. Required; trimmed and bounded to 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"objective": {
|
||||
"description": "Objective is the campaign goal as the provider names it. Optional, bounded to 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"description": "Platform is the ad network: meta, google, tiktok or x. Empty defaults to meta.",
|
||||
"type": "string"
|
||||
},
|
||||
"spend": {
|
||||
"description": "Spend is the amount spent so far in MINOR units (cents). Negative values clamp to 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the lifecycle state: draft, active, paused or completed. Empty defaults to draft.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_ads_campaigns_id"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,664 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteAgent removes an agent and every run recorded against it. Answers 204.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"ref": {
|
||||
"description": "Ref is the agent's public id (the agent_… handle create and list return) or\nits org-unique name, from the path. Either resolves the same agent.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_agents_ref"
|
||||
},
|
||||
{
|
||||
"description": "DeleteTarget deregisters one machine. Only its owner, or an org admin, may\nremove it; an unknown id, a cross-org id and a machine owned by someone else\nall answer the same not-found, so a probe learns nothing about what exists.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the target to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_agents_targets_id"
|
||||
},
|
||||
{
|
||||
"description": "ListAgents returns every agent defined in the caller's org, each with the\nnumber of runs recorded against it.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents"
|
||||
},
|
||||
{
|
||||
"description": "AgentActivity serves the org-wide recent-activity feed. Events are REAL: each\nrecorded run is an invoked (ok) or failed (error) event; each agent's own\ncreate/update timestamps are created/updated events. Merged, newest first,\ncapped. Nothing is invented — an org with no agents and no runs gets [].",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_activity"
|
||||
},
|
||||
{
|
||||
"description": "ListBuilds returns the public index of every published build, most recently\nupdated first, so a gallery can link straight to the story behind each product.\nPUBLIC, no tenancy: publishing is the author's act, and only published root\nsessions appear here.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the page. Absent, zero or over 500 reads as 100.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_builds"
|
||||
},
|
||||
{
|
||||
"description": "ReadBuild returns the readable build of one product: the agent session that\nproduced it, turn by turn — the prompts, the reasoning, the commits each turn\nproduced — plus the exact `git log` that re-derives every commit binding from\ngit itself, so nothing here has to be taken on trust.\n\nPUBLIC, no tenancy: it answers only for a session its author explicitly\npublished, which is what makes it safe to be anonymous. An unpublished session\nis invisible here no matter who asks; its owner reads it through the org-scoped\n/v1/agents/sessions routes, which need a validated principal.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"org": {
|
||||
"description": "Org is the org that published the build, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project is the product's slug, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_builds_org_project"
|
||||
},
|
||||
{
|
||||
"description": "AgentMetrics serves the invocations-over-time histogram for the org's Agents\ndashboard. Every point is a REAL count of recorded runs in that time bucket —\none series line per agent that ran in the window. The Resource Usage rollup is\nall-null because this store meters no CPU/memory/storage/cost; the console\nrenders those as \"—\" rather than a fabricated figure. No runs =\u003e empty series\n(an honest \"not connected / no activity yet\"), never a synthesized trend.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range is the window to bucket: 24H, 7D or 30D. Anything else reads as 30D.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_metrics"
|
||||
},
|
||||
{
|
||||
"description": "GetAgent returns one agent with its system prompt and its 20 most recent runs.\nThe ref is the agent's public id or its org-unique name — a created agent is\nimmediately gettable by whatever create handed back.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"ref": {
|
||||
"description": "Ref is the agent's public id (the agent_… handle create and list return) or\nits org-unique name, from the path. Either resolves the same agent.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_ref"
|
||||
},
|
||||
{
|
||||
"description": "ListAgentRuns returns one agent's execution history, newest first — each run's\ninput, its output or its error, and how long it took. Every row is a run that\nactually happened.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many runs come back, newest first. Absent, zero or out of\nrange (1..200) reads as 50.",
|
||||
"type": "integer"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is the agent's public id or its org-unique name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_ref_runs"
|
||||
},
|
||||
{
|
||||
"description": "ListSessions returns the caller org's live sessions, newest first — each with\nits event count, its direct-child count and a one-line preview of its latest\nevent. With no filter it returns ROOT sessions only, so a dashboard shows one\nrow per flow rather than one per subagent; ?root= or ?parent= descends.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the page. Absent, zero or over 500 reads as 100.",
|
||||
"type": "integer"
|
||||
},
|
||||
"parent": {
|
||||
"description": "Parent scopes the page to the direct children of one session. Ignored when\nroot is set; with neither, only ROOT sessions come back.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project filters to the sessions tagged with one product slug.",
|
||||
"type": "string"
|
||||
},
|
||||
"root": {
|
||||
"description": "Root scopes the page to one subagent tree (its root session id).",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status filters to running, paused, done or error.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_sessions"
|
||||
},
|
||||
{
|
||||
"description": "GetSession returns one session with its direct child sessions and its 50 most\nrecent events, oldest of those first.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the session to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_sessions_id"
|
||||
},
|
||||
{
|
||||
"description": "DrainSessionControl returns the steering commands (pause/resume/stop/message)\nrecorded against the caller's own session that are newer than the cursor,\noldest first, with the cursor to poll from next. It is how a locally started\n`hanzo code` session — which is not task-backed, so nothing forwards its\ncommands to an execution engine — consumes what the dashboard posted. Read-only\nand bounded at 200 per poll, so a steady poll is cheap and an applied command is\nnever redelivered.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"after": {
|
||||
"description": "After is the last seq this poller applied; only commands newer than it come\nback. Absent or negative reads as 0, which drains from the beginning.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the session whose commands are being drained, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_sessions_id_control"
|
||||
},
|
||||
{
|
||||
"description": "SessionTree returns the subagent-flow graph rooted at this session: the session,\nits children, their children, each node carrying its own event count. One\nindexed read pulls the whole flow (every node of a flow shares a root id), so\nthe shape is assembled in memory rather than by walking the store per node.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the session to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_sessions_id_tree"
|
||||
},
|
||||
{
|
||||
"description": "ListTargets returns every machine registered to the caller's org, newest\nfirst, each with its live session load.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_targets"
|
||||
},
|
||||
{
|
||||
"description": "GetTarget returns one registered machine, with its live session load.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the target to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_agents_targets_id"
|
||||
},
|
||||
{
|
||||
"description": "UpdateAgent changes an agent in place. Every field is optional; a field the\nrequest omits keeps its stored value. The resulting mode+schedule are\nre-validated together, so a partial update can never leave a long-running\nagent without the cron the scheduler needs to fire it, and a transition INTO\nlong-running counts against the per-org cap on scheduled agents.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"computeRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"executionMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"instructions": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is the agent to update — its public id or org-unique name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
},
|
||||
"serviceAccountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tools": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_agents_ref"
|
||||
},
|
||||
{
|
||||
"description": "PatchSession updates a session's surface-owned truth: its status, its title,\nthe run-target it is dispatched to, and the product it built plus whether that\nbuild's story is public. A FINISHED session stays finished — reopening a\ndone/error run would fabricate liveness — and publishing is refused unless the\nsession names the project it built, because the public build route is keyed on\n(org, project).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the session to update, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project tags the product this session built; Published is the author's\ndecision to let anyone read the story (provenance.go). Both are pointers so\n\"absent\" and \"cleared\" are different requests.",
|
||||
"type": "string"
|
||||
},
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"description": "Target re-dispatches a session to a run-target (the #48 association). \"\" detaches.",
|
||||
"type": "string"
|
||||
},
|
||||
"terminal": {
|
||||
"description": "Terminal publishes (or, with \"\", withdraws) the URL this session's live\nterminal can be watched at. A pointer so \"absent\" and \"withdrawn\" are\ndifferent requests: a session that stops sharing must be able to say so.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_agents_sessions_id"
|
||||
},
|
||||
{
|
||||
"description": "PatchTarget updates one machine in place. Every field is optional; a field the\nrequest omits is left alone. A metrics patch IS a heartbeat — the server stamps\nits own clock, so a client can neither forge nor backdate staleness.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"GPU": {
|
||||
"properties": {
|
||||
"memory": {
|
||||
"description": "VRAM bytes, 0 = unknown",
|
||||
"type": "integer"
|
||||
},
|
||||
"model": {
|
||||
"description": "\"GB10\", \"8060S\", \"RTX 4090\"",
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"description": "nvidia | amd | apple | intel | ...",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Metrics": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "unix seconds, server-stamped",
|
||||
"type": "integer"
|
||||
},
|
||||
"gpuUtil": {
|
||||
"description": "0..1 aggregate utilization",
|
||||
"type": "number"
|
||||
},
|
||||
"load1": {
|
||||
"type": "number"
|
||||
},
|
||||
"load15": {
|
||||
"type": "number"
|
||||
},
|
||||
"load5": {
|
||||
"type": "number"
|
||||
},
|
||||
"memFree": {
|
||||
"description": "bytes",
|
||||
"type": "integer"
|
||||
},
|
||||
"memUsed": {
|
||||
"description": "bytes",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Spec": {
|
||||
"properties": {
|
||||
"arch": {
|
||||
"description": "amd64 | arm64 | ...",
|
||||
"type": "string"
|
||||
},
|
||||
"cpus": {
|
||||
"description": "logical cores",
|
||||
"type": "integer"
|
||||
},
|
||||
"gpus": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/GPU"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"memory": {
|
||||
"description": "total RAM, bytes",
|
||||
"type": "integer"
|
||||
},
|
||||
"os": {
|
||||
"description": "linux | darwin | windows",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"capacity": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the target to update, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"metrics": {
|
||||
"$ref": "#/$defs/Metrics",
|
||||
"description": "present =\u003e a heartbeat; the server stamps its time"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "#/$defs/Spec"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_agents_targets_id"
|
||||
},
|
||||
{
|
||||
"description": "CreateAgent defines an agent in the caller's org: a model, a system prompt\n(instructions) and a set of tool names. The name must be unique in the org and\nmatch ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$. An omitted model takes the\ndeployment's configured default; a named one is checked against the gateway's\nserved catalog, so a model this deployment never serves is refused here rather\nthan failing at run time. A long-running agent must carry a 5-field cron\nschedule (the scheduler would otherwise never fire it) and counts against a\nper-org cap on scheduled agents.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"computeRef": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"executionMode": {
|
||||
"type": "string"
|
||||
},
|
||||
"instructions": {
|
||||
"type": "string"
|
||||
},
|
||||
"model": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"schedule": {
|
||||
"type": "string"
|
||||
},
|
||||
"serviceAccountId": {
|
||||
"type": "string"
|
||||
},
|
||||
"tools": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents"
|
||||
},
|
||||
{
|
||||
"description": "RegisterSession opens a live agent session in the caller's org — the row every\nsurface (the CLI's outer agent, hanzo.bot, the console, chat) hangs its\nactivity off. A session with a parentSessionId becomes a subagent of that\nsession and inherits its root, so one flow is one tree; without one it is\nitself a root. Registering with a terminal status records a session that has\nalready finished.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"type": "string"
|
||||
},
|
||||
"actor": {
|
||||
"type": "string"
|
||||
},
|
||||
"agent": {
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"description": "Execution context — where this session runs (all optional).",
|
||||
"type": "string"
|
||||
},
|
||||
"parentSessionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "The readable build (provenance.go): which product this session builds, and\nwhether its story may be read by the world.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Account tag — the linked AI account this session ran under (login manager).",
|
||||
"type": "string"
|
||||
},
|
||||
"published": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"repo": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"target": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskRunId": {
|
||||
"type": "string"
|
||||
},
|
||||
"taskWorkflowId": {
|
||||
"type": "string"
|
||||
},
|
||||
"terminal": {
|
||||
"description": "Terminal is the URL this session's live terminal is published at, so the\nconsole can watch it. Optional — a session that publishes nothing is still\na session.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents_sessions"
|
||||
},
|
||||
{
|
||||
"description": "RegisterTarget registers a machine as an agent target, or re-links one that is\nalready registered. Re-linking is idempotent and keyed on org+host+owner, so a\nmachine that reconnects refreshes its own row rather than piling up duplicates;\nit answers 200, while a first registration answers 201.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"GPU": {
|
||||
"properties": {
|
||||
"memory": {
|
||||
"description": "VRAM bytes, 0 = unknown",
|
||||
"type": "integer"
|
||||
},
|
||||
"model": {
|
||||
"description": "\"GB10\", \"8060S\", \"RTX 4090\"",
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"description": "nvidia | amd | apple | intel | ...",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Metrics": {
|
||||
"properties": {
|
||||
"at": {
|
||||
"description": "unix seconds, server-stamped",
|
||||
"type": "integer"
|
||||
},
|
||||
"gpuUtil": {
|
||||
"description": "0..1 aggregate utilization",
|
||||
"type": "number"
|
||||
},
|
||||
"load1": {
|
||||
"type": "number"
|
||||
},
|
||||
"load15": {
|
||||
"type": "number"
|
||||
},
|
||||
"load5": {
|
||||
"type": "number"
|
||||
},
|
||||
"memFree": {
|
||||
"description": "bytes",
|
||||
"type": "integer"
|
||||
},
|
||||
"memUsed": {
|
||||
"description": "bytes",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Spec": {
|
||||
"properties": {
|
||||
"arch": {
|
||||
"description": "amd64 | arm64 | ...",
|
||||
"type": "string"
|
||||
},
|
||||
"cpus": {
|
||||
"description": "logical cores",
|
||||
"type": "integer"
|
||||
},
|
||||
"gpus": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/GPU"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"memory": {
|
||||
"description": "total RAM, bytes",
|
||||
"type": "integer"
|
||||
},
|
||||
"os": {
|
||||
"description": "linux | darwin | windows",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"capacity": {
|
||||
"type": "string"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"metrics": {
|
||||
"$ref": "#/$defs/Metrics"
|
||||
},
|
||||
"spec": {
|
||||
"$ref": "#/$defs/Spec"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents_targets"
|
||||
},
|
||||
{
|
||||
"description": "ClaimRoutedRun is the machine's long poll for work: it authenticates the\ndaemon, stamps the liveness the dispatch gate reads (the poll IS the proof a\nrunner is listening), and waits up to 25 seconds for the next run addressed to\nTHIS machine. It answers the run when one arrives and 204 with no body when the\nwindow elapses, on which the daemon re-polls immediately.\n\nTWO independent proofs are required and both fail closed to the same 403: the\ncaller must own this machine (or be an org admin) AND present its claim key in\nX-Target-Key. A run offered to one machine is unreachable from another's claim.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the target to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents_targets_id_claim"
|
||||
},
|
||||
{
|
||||
"description": "MintTargetClaimKey mints (or rotates) the claim key a `hanzo code --serve`\ndaemon presents to claim work for this machine, and returns it ONCE: only its\nSHA-256 hash is stored. Rotating supersedes any prior daemon, so only the\nmachine's owner — or an org admin — may call it; every other caller gets the\nsame not-found an unknown id gets, and learns nothing about what exists.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the target to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents_targets_id_key"
|
||||
},
|
||||
{
|
||||
"description": "ReportRoutedRun completes a claimed run: it delivers the terminal result to the\nrun's durable owner, which is what lets that workflow finish. Scoped to (org,\ntarget, run) and claim-key authenticated, so a machine can only ever report a\nrun it legitimately holds. Idempotent — a report for an unknown or\nalready-finished run answers delivered:false rather than failing, because the\nsession's terminal state was already set by the machine's own stream.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"branch": {
|
||||
"description": "Branch, CommitSha and Diffstat describe what the run produced; Error is the\nfailure when OK is false. Each is clamped, never rejected.",
|
||||
"type": "string"
|
||||
},
|
||||
"changed": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"commitSha": {
|
||||
"type": "string"
|
||||
},
|
||||
"diffstat": {
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the machine reporting, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"ok": {
|
||||
"description": "OK is whether the run succeeded; Changed whether it produced any commit.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"runId": {
|
||||
"description": "RunID is the routed run being completed, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_agents_targets_id_runs_runId_report"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,15 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Tools reports what this binary's MCP door carries: every tool the build\npublishes, how many of them this process actually serves, and which subsystem\neach belongs to. It is the answer to \"is the door up and does it have anything\nbehind it\" — a question a status code cannot answer, since an empty door and a\nfull one are both 200.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "App names one subsystem whose tool NAMES to list. Empty answers counts\nonly: nine hundred names is a page no operator reads and no model can\nafford to be handed by accident.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "aiMCPTools"
|
||||
}
|
||||
]
|
||||
+15
-27
@@ -1417,19 +1417,19 @@
|
||||
"/v1/ai/mcp/tools": {
|
||||
"get": {
|
||||
"operationId": "aiMCPTools",
|
||||
"summary": "Tools reports what this binary's MCP door carries: every tool the build publishes, how many of them this process actually serves, and which subsystem each belongs to.",
|
||||
"description": "Tools reports what this binary's MCP door carries: every tool the build\npublishes, how many of them this process actually serves, and which subsystem\neach belongs to. It is the answer to \"is the door up and does it have anything\nbehind it\" — a question a status code cannot answer, since an empty door and a\nfull one are both 200.",
|
||||
"summary": "Tools reports what THIS PROCESS's MCP door carries: how many tools its own registry projects, optionally their names, and which subsystems this process composed.",
|
||||
"description": "Tools reports what THIS PROCESS's MCP door carries: how many tools its own\nregistry projects, optionally their names, and which subsystems this process\ncomposed. It is the answer to \"is this door up and does it have anything behind\nit\" — a question a status code cannot answer, since an empty door and a full\none are both 200. What the FLEET's door carries is the fleet door's own answer:\nPOST /v1/mcp, tools/list, which asks every subsystem and names the ones that\ndid not reply.",
|
||||
"tags": [
|
||||
"ai"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "app",
|
||||
"name": "names",
|
||||
"in": "query",
|
||||
"required": false,
|
||||
"description": "App names one subsystem whose tool NAMES to list. Empty answers counts\nonly: nine hundred names is a page no operator reads and no model can\nafford to be handed by accident.",
|
||||
"description": "Names asks for this process's tool NAMES and not only how many there are.\nOff by default: a list of names is a page, and the question this op exists\nto answer (\"is the door up and does it have anything behind it\") is answered\nby the count.",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -5399,20 +5399,9 @@
|
||||
"description": "Name is the subsystem, as the manifest names it.",
|
||||
"type": "string"
|
||||
},
|
||||
"names": {
|
||||
"description": "Names are its tool names, present only for the subsystem the query named.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"served": {
|
||||
"description": "Served reports that THIS process actually mounted it, so its tools are on\nthe door a client can call rather than only in the build.",
|
||||
"description": "Served reports that THIS process mounted it, so its tools are on this\nprocess's door rather than behind a sibling this process only knows the name\nof.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"tools": {
|
||||
"description": "Tools is how many tools its committed catalogue publishes.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -5420,22 +5409,21 @@
|
||||
"aiMCPSurface": {
|
||||
"properties": {
|
||||
"apps": {
|
||||
"description": "Apps is one row per subsystem the build publishes, in manifest order.",
|
||||
"description": "Apps is one row per subsystem this deployment composes, in manifest order.",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/aiMCPApp"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"local": {
|
||||
"description": "Local is the part of Served this process registered ITSELF, rather than\ncomposing from a mounted child's catalogue.",
|
||||
"type": "integer"
|
||||
"names": {
|
||||
"description": "Names are this process's own tool names, present only when the query asked\nfor them.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"published": {
|
||||
"description": "Published is every tool this BUILD can serve — the union of every\nsubsystem's committed catalogue, which is a property of the artifact and\ntherefore the same answer in every process.",
|
||||
"type": "integer"
|
||||
},
|
||||
"served": {
|
||||
"description": "Served is what THIS PROCESS's door actually composed. It is the number that\ncan be far smaller than Published — a host that mounted nothing serves\nnothing — and the only one that describes the door a client is talking to.",
|
||||
"tools": {
|
||||
"description": "Tools is how many tools THIS PROCESS's door carries: its own typed-op\nregistry, projected. It is the only number a subsystem can state honestly —\nwhat the FLEET's door carries is a question only the host can ask, and it\nasks it by asking every subsystem (POST /v1/mcp, tools/list).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Overview returns the caller org's analytics KPIs for one time window. Three lenses\nover one warehouse: llm is the live per-org LLM usage ledger (requests, tokens,\nspend, models, providers, errors) and is always real; web (pageviews, visitors,\nsessions) and commerce (orders, revenue, AOV) read the product-event table and\nreport available=false rather than fabricating zeros when it holds nothing yet.\n\nThe org is the validated principal's — never a parameter — so a caller can only\never read its own tenant. 403 without a validated bearer, 400 on an unknown range,\n503 when the warehouse is unreachable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"end": {
|
||||
"description": "End is the exclusive upper bound of a custom window, RFC3339. Requires start.",
|
||||
"type": "string"
|
||||
},
|
||||
"range": {
|
||||
"description": "Range is a relative window: 24h, 7d or 30d. Default 24h. Ignored when both\nstart and end are given. An unknown value is a 400.",
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"description": "Start is the inclusive lower bound of a custom window, RFC3339. Requires end.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_analytics_overview"
|
||||
},
|
||||
{
|
||||
"description": "Timeseries returns the caller org's LLM usage over time as an evenly-spaced series.\nOne point per hour or per day — the bucket the window implies, 24h giving hours and\n7d/30d giving days — carrying requests, total tokens and spend in cents. Empty\nbuckets are filled with zeros so a client charts a continuous line.\n\nThe org is the validated principal's — never a parameter. 403 without a validated\nbearer, 400 on an unknown range, 503 when the warehouse is unreachable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"end": {
|
||||
"description": "End is the exclusive upper bound of a custom window, RFC3339. Requires start.",
|
||||
"type": "string"
|
||||
},
|
||||
"range": {
|
||||
"description": "Range is a relative window: 24h, 7d or 30d. Default 24h. Ignored when both\nstart and end are given. An unknown value is a 400.",
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"description": "Start is the inclusive lower bound of a custom window, RFC3339. Requires end.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_analytics_timeseries"
|
||||
},
|
||||
{
|
||||
"description": "Top returns the caller org's ranked lenses for one window, five of them at once.\nmodels ranks LLM models by spend and is always real; products ranks commerce orders\nby revenue; topPages ranks requested paths, topReferrers the external referrer\ndomains (\"(direct)\" for a missing or same-origin one) and topSources the utm_source\ncampaigns (\"(none)\" when absent), each by pageviews. Every lens carries each row's\nshare of the in-window total, so a top-N honestly shows the long tail.\n\nThe four event lenses report available=false rather than fabricating zeros when the\nproduct-event table holds nothing yet. The org is the validated principal's — never\na parameter. 403 without a validated bearer, 400 on an unknown range, 503 when the\nwarehouse is unreachable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"end": {
|
||||
"description": "End is the exclusive upper bound of a custom window, RFC3339. Requires start.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit bounds every ranked lens in the response. Default 10, maximum 100; a\nvalue at or below zero, or one that is not a number, takes the default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"range": {
|
||||
"description": "Range is a relative window: 24h, 7d or 30d. Default 24h. Ignored when both\nstart and end are given. An unknown value is a 400.",
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"description": "Start is the inclusive lower bound of a custom window, RFC3339. Requires end.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_analytics_top"
|
||||
},
|
||||
{
|
||||
"description": "Errors returns the caller org's most recently captured errors, newest first. The\nerror-tracking read view over event.error — the plane table the write core's error\nfacts land in (errors are DELIBERATELY not on event.event) — each with its captured\nexception surfaced from the attributes map as a first-class field.\n\nThe org is the validated principal's — never a parameter — and this read requires a\nreal bearer, NEVER the write-only publishable key: pk- can attribute a write and can\nread nothing. 403 without a validated bearer, 503 when the warehouse is unreachable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit is how many rows to return, newest first. Default 50, maximum 200; a\nvalue at or below zero, or one that is not a number, takes the default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_errors"
|
||||
},
|
||||
{
|
||||
"description": "Returns the caller org's most recent product events, newest first.\nThe console's raw-event view over event.event — the same table the capture doors\nfill — one row per stored event, with the row's attributes returned as the\nproperties object.\n\nThe org is the validated principal's — never a parameter — and a read requires a\nreal bearer, never the write-only publishable key. 403 without a validated bearer,\n503 when the warehouse is unreachable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit is how many rows to return, newest first. Default 50, maximum 200; a\nvalue at or below zero, or one that is not a number, takes the default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_insights_events"
|
||||
},
|
||||
{
|
||||
"description": "Reports that the unified insights surface is serving. It reads no\ntenant data and consults no dependency, so it answers 200 unconditionally and needs\nno principal — liveness must be probe-able. The warehouse-connectivity probe is a\ndifferent question and lives at GET /v1/analytics/health.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_insights_health"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,47 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "List reads the caller's OWN org audit trail, newest first, with the total the\nfilter matched so a console can page it.\n\nEvery filter is optional and applies WITHIN the caller's org — the org itself is\nthe validated principal's and can never be widened by a request. Fails closed:\nan absent principal is a true \"not signed in\" (401), and a deployment with no\nlocal tamper-evident store answers an honest 501 rather than silently serving\nsomebody else's trail.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action narrows it to one action name, e.g. \"machine.create\".",
|
||||
"type": "string"
|
||||
},
|
||||
"p": {
|
||||
"description": "Page is the 1-based page number, driving the offset. Anything below 2 reads\nthe first page.",
|
||||
"type": "string"
|
||||
},
|
||||
"pageSize": {
|
||||
"description": "PageSize is rows per page, default 100. A value that is not a positive\ninteger falls back to the default.",
|
||||
"type": "string"
|
||||
},
|
||||
"resource": {
|
||||
"description": "Resource narrows it to one resource TYPE, e.g. \"apikey\".",
|
||||
"type": "string"
|
||||
},
|
||||
"resourceId": {
|
||||
"description": "ResourceID narrows it to one resource instance.",
|
||||
"type": "string"
|
||||
},
|
||||
"result": {
|
||||
"description": "Result narrows it to one outcome: \"success\", \"deny\" or \"error\".",
|
||||
"type": "string"
|
||||
},
|
||||
"since": {
|
||||
"description": "Since is the inclusive lower time bound, RFC3339. An unparseable value is\nignored rather than refused — one malformed filter must not hide the trail.",
|
||||
"type": "string"
|
||||
},
|
||||
"sub": {
|
||||
"description": "Sub narrows the trail to one actor — the validated subject that made the\nrequest. Blank means every actor in the org.",
|
||||
"type": "string"
|
||||
},
|
||||
"until": {
|
||||
"description": "Until is the upper time bound, RFC3339, with the same tolerance.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_audit"
|
||||
}
|
||||
]
|
||||
@@ -1,167 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListAuthors returns the platform's whole author program — every org's author\nrecord, not the caller's — with each one's repository and deploy counts and a\nfleet roll-up of the money accrued, pending and paid.\n\nIt is a Hanzo platform operation: a caller who is not a SuperAdmin gets 403. It\nexposes the owning org of each author, which no tenant-facing read ever does.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page. 0 or less means the default of 500; anything above\n1000 is clamped to 1000.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_admin_authors"
|
||||
},
|
||||
{
|
||||
"description": "AuthorRoyaltyBasis returns the audit trail behind ONE author's royalty — the same\npayload the author reads at /v1/authors/basis, from the same builder, so support\nsees exactly what the author sees rather than a parallel view free to drift.\n\nThe data object carries: id, status, asOf, shareBps, platformShareBps,\ndefaultShareBps, shareSource, settlesTo, method (the formula, the rate card and the\nsizing), ledger (every row with its spend, the share applied then, the platform's\nmatching half, whether it satisfies the formula and the attribution edges that\nexplain it), reconciliation (does the ledger foot to the balance) and window (what\nslice was actually returned) — plus period when one was requested.\n\nA Hanzo platform operation: a caller who is not a SuperAdmin gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the author record's handle, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"period": {
|
||||
"description": "Period is the UTC accrual month, YYYY-MM. Empty means every period; any other\nshape is refused with 400.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_admin_authors_id_basis"
|
||||
},
|
||||
{
|
||||
"description": "MyAuthorProgram returns the caller's author-program dashboard: enrolment status,\nlinked forge login, verified repositories and owner-wide claims, recorded deploys,\naccrued / pending / paid royalty, and the payout history.\n\nIt answers ONE OF TWO SHAPES from this address. An org that has never connected\ngets {\"isAuthor\": false, \"defaultShareBps\", \"badgeBase\"} — an honest \"not enrolled\"\nrather than a 404, so the console can render the connect form. An enrolled org gets\nthe dashboard: isAuthor, id, status, githubLogin, verified, verifyCode, verifyFile,\nverifySnippet, shareBps, badgeBase, repos, orgs, deploys, accruedCents,\npendingCents, paidCents, payouts and ledger.\n\nFor an APPROVED author this read ALSO runs the accrual sweep opportunistically, so\nthe dashboard is self-updating. That is why the royalty AUDIT lives at its own\naddress: an audit must not move the money it is auditing.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_authors"
|
||||
},
|
||||
{
|
||||
"description": "MyRoyaltyBasis returns the AUDIT TRAIL behind the caller's own royalty: every\nledger row with the spend it was computed from, the share applied at the time, the\nplatform's matching half, whether each row satisfies the formula, and the\nattribution edges that already existed when the row was written.\n\nIt answers ONE OF TWO SHAPES. An org that has never connected gets\n{\"isAuthor\": false, \"defaultShareBps\"} — never a 404, which would answer \"is this\norg an author?\" for anyone who asked. An enrolled org gets the basis: isAuthor, id,\nstatus, asOf, shareBps, platformShareBps, defaultShareBps, shareSource, settlesTo,\nmethod (the formula, the rate card and the sizing), ledger, reconciliation, window,\nand period when one was requested.\n\nThis read NEVER sweeps, and that is the point of it being a separate address from\nthe dashboard: an audit must not move the money it is auditing, so calling it N\ntimes leaves the balances and the ledger byte-identical.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"period": {
|
||||
"description": "Period is the UTC accrual month, YYYY-MM. Empty means every period; any other\nshape is refused with 400, because the period is echoed back and used as a SQL\nfilter and is only ever accepted in the one form the accrual latch mints.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_authors_basis"
|
||||
},
|
||||
{
|
||||
"description": "ApproveAuthor admits one author to EARNING, optionally on a negotiated royalty\nshare. Until this runs, a connected author accrues nothing however many verified\nrepositories they have.\n\nA share override applies from here forward only — existing ledger rows keep the\nshare that was applied when they were written, because a rate change must never\nrewrite what was already owed.\n\nA Hanzo platform operation: a caller who is not a SuperAdmin gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the author to approve, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"shareBps": {
|
||||
"description": "ShareBps overrides this author's royalty share, in basis points (0–10000).\n0 keeps the platform default. A share change never rewrites history: existing\nledger rows keep the share that was applied when they were written.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_admin_authors_id_approve"
|
||||
},
|
||||
{
|
||||
"description": "PayAuthor records a payout of accrued royalty and settles it.\n\nThe amount is RESERVED against the author's pending royalty atomically before\nanything is paid, so a payout can never exceed what is owed even under concurrent\ncalls. An external author's payout is then BACKED against the platform reserve\nfund — a second, independent guard — and refused with 402 if the reserve cannot\ncover it, with the reservation voided. A \"credits\" method issues the actual wallet\ngrant after both guards; a cash method is record-only. A first-party (treasury)\nauthor's royalty is realized into Hanzo's own reserve instead of an external\nwallet, and every payout row discloses which of the three it was.\n\nA Hanzo platform operation: a caller who is not a SuperAdmin gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"amountCents": {
|
||||
"description": "AmountCents is how much to pay, in cents. Must be positive and can never\nexceed the author's pending royalty (accrued minus paid).",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the author to pay, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"method": {
|
||||
"description": "Method is how it settles: \"credits\" issues a grant into the author's wallet;\nwire, paypal and the like are record-only. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"description": "Reference is the operator's external reference for a cash settlement — a wire\nconfirmation, a PayPal transaction id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_admin_authors_id_payout"
|
||||
},
|
||||
{
|
||||
"description": "SuspendAuthor stops one author earning. Their record, verified claims and ledger\nare untouched — suspension halts future accrual, it does not erase what was already\nowed, and it does not delete the evidence behind it.\n\nA Hanzo platform operation: a caller who is not a SuperAdmin gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the author record's handle, \"aut_\"-prefixed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_admin_authors_id_suspend"
|
||||
},
|
||||
{
|
||||
"description": "SweepAuthorRoyalty runs the accrual sweep across every approved author: for each of\ntheir deploying orgs it computes this period's royalty from that org's metered\nspend and latches it at most once per period.\n\nIt is an OVERRIDE, not the mechanism: a background scheduler runs the same sweep on\nits own, and every author's dashboard read sweeps their own accruals lazily. This\nis the manual trigger for an operator who needs the numbers now. It is idempotent —\nthe per-period latch means running it twice accrues nothing the second time.\n\nA Hanzo platform operation: a caller who is not a SuperAdmin gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_admin_authors_sweep"
|
||||
},
|
||||
{
|
||||
"description": "ConnectAuthor enrols the caller's org in the author program at status \"connected\"\nand returns its enrolment, including the verify code the file method needs. It is\nIDEMPOTENT: a second call returns the same enrolment rather than a conflict.\n\nThe forge login is taken from IAM's LINKED account for the provider when there is\none — that is identity proof, not a claim — and only otherwise from the login in\nthe body, which then has to be proven per repository. Connecting does not admit an\norg to earning: a platform reviewer approves that separately.\n\nAnswers 201 when it enrolled the org and 200 when it found an existing enrolment.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"githubLogin": {
|
||||
"description": "GithubLogin is the account to link. Used only when IAM holds no linked\naccount for the provider — a linked account is stronger proof and always wins.",
|
||||
"type": "string"
|
||||
},
|
||||
"login": {
|
||||
"description": "Login is the provider-neutral alias for GithubLogin, preferred when both are\nsent.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the forge to enrol with: github (the default) or gitlab.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_authors_connect"
|
||||
},
|
||||
{
|
||||
"description": "RecordAuthorDeploy records that the caller's org deployed a project built from a\nsource repository, which is the edge that makes an author's work earn royalty.\n\nIt is deliberately NOT an error for a deploy to attribute to nobody: a project\nbuilt from no repository, or from one no author has verified, answers\n{\"recorded\": false, \"reason\"} so a deploy pipeline can fire this on every deploy\nwithout branching. Attribution resolves per-repository first, then owner-wide, so a\nrepository with its own claim always earns for its own author.\n\nA deploy of a Hanzo-maintained template attributes to the platform treasury, and a\nself-deploy (the author's own org deploying its own repository) is recorded for\nprovenance but excluded from accrual. The edge is idempotent per\nrepository+project+org.\n\nAnswers 201 when it recorded a new edge and 200 otherwise.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"project": {
|
||||
"description": "Project is the deployed project's id. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"repoUrl": {
|
||||
"description": "RepoURL is the source repository the project was built from. Empty means a\nhand-built project with nothing to attribute — an honest no-op, not an error.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_authors_deploys_record"
|
||||
},
|
||||
{
|
||||
"description": "VerifyAuthorRepo proves that the caller owns a repository — or a whole OWNER — and\nrecords the claim, which is what makes deploys of that code earn royalty.\n\nOwnership is proven the SAME two ways in both cases, tried in order: an IAM-linked\nforge token with admin or push permission, or a hanzo.json on the default branch\ncarrying the author's verify code. Claiming an OWNER proves it against that\nowner's \".github\" control repository, and is exactly as strong as a per-repository\nclaim — an owner the caller cannot prove is refused with 422, never assumed.\n\nA per-repository claim wins over an owner-wide one, so a specifically-claimed\nrepository always earns for its own author. A repository another author has\nalready verified is a 409. The org must have connected first.\n\nAnswers 201 when it recorded a new claim and 200 when the claim already existed.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"repoUrl": {
|
||||
"description": "RepoURL is what to claim: a repository (github.com/owner/name) or a whole\nOWNER (github.com/owner, no repository segment). gitlab.com is accepted too.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_authors_repos_verify"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,143 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "FlowDelete deletes one of the caller's flows. A foreign id answers 404 and\ndeletes nothing.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow is the flow's id, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_auto_flows_flow"
|
||||
},
|
||||
{
|
||||
"description": "Flows lists the caller's flows, newest first. The list is scoped by the\nproduct to the caller's org — it can only ever hold the caller's own flows.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_flows"
|
||||
},
|
||||
{
|
||||
"description": "Flow reads one of the caller's flows — the full record, graph included. A\nflow outside the caller's org answers 404, indistinguishable from one that\ndoes not exist.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow is the flow's id, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_flows_flow"
|
||||
},
|
||||
{
|
||||
"description": "Pieces lists the product's built-in piece catalog: the trigger and action\ntypes a flow's nodes can use (webhook, schedule, http, set, branch), each\nwith its input descriptors. The catalog is compiled into the product —\nadding a piece is a product release, not a platform call.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_pieces"
|
||||
},
|
||||
{
|
||||
"description": "Runs lists the caller's run records, newest first — optionally one flow's.\nEach record carries the run's status (queued, running, completed, failed),\nits input, and its output once the run finished.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow narrows the list to one flow's runs when present.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_runs"
|
||||
},
|
||||
{
|
||||
"description": "Run reads one run record: status, input, output (each executed node's\nresult keyed by node id once completed), error detail if it failed, and\ntimestamps. A run outside the caller's org answers 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"run": {
|
||||
"description": "Run is the run's id, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_runs_run"
|
||||
},
|
||||
{
|
||||
"description": "Status reports whether the auto service is reachable — its own health\nendpoint as an honest lens for \"is the automation plane up\".",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_auto_status"
|
||||
},
|
||||
{
|
||||
"description": "FlowUpdate patches one of the caller's flows: the name, the graph, or both\n— only the stated fields move.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data replaces the flow graph when present, verbatim."
|
||||
},
|
||||
"flow": {
|
||||
"description": "Flow is the flow's id, taken from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name renames the flow when present.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_auto_flows_flow"
|
||||
},
|
||||
{
|
||||
"description": "FlowCreate creates a flow in the caller's org. The org is stamped\nserver-side from the validated principal — there is no field by which a\ncaller could place a flow in another org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data is the flow graph — the product's nodes/edges document, verbatim:\nnodes carry a piece type (webhook, schedule, http, set, branch) and its\nconfig; edges wire them. Omit it to create an empty flow."
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the flow's display name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_auto_flows"
|
||||
},
|
||||
{
|
||||
"description": "Publish snapshots the flow's current graph as its next immutable version\nand arms the flow's triggers. Past versions stay addressable in the product\nfor rollback; runs always execute the graph as it was dispatched.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow is the flow's id, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_auto_flows_flow_publish"
|
||||
},
|
||||
{
|
||||
"description": "Start begins one asynchronous run of a flow: the product dispatches the\ngraph to its durable execution engine (the hanzo tasks plane) and answers\nimmediately with the run record in status running. Poll the run until it\nreaches completed — its output then holds each node's result keyed by node\nid — or failed, with the error. A flow whose engine is unreachable answers\nthe product's 503: dispatch is real or it is refused, never queued into the\nvoid.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow is the id of the flow to run.",
|
||||
"type": "string"
|
||||
},
|
||||
"input": {
|
||||
"description": "Input is the trigger payload handed to the run, verbatim JSON object.\nThe run's state starts as {\"trigger\": input}."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_auto_runs"
|
||||
}
|
||||
]
|
||||
@@ -1,403 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteFlow deletes one automation, its versions and its run history. It answers\nno content, and a flow of another org answers not-found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_automations_flows_id"
|
||||
},
|
||||
{
|
||||
"description": "Connectors returns the connector catalogue. Each entry is an external service a\nflow step can invoke, carrying its auth descriptor and the input properties of its\nactions and triggers. The catalogue is the same for every tenant, so the gate is a\nvalidated principal rather than a per-org view.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_connectors"
|
||||
},
|
||||
{
|
||||
"description": "ListFlows returns the caller org's automations, most-recently-updated first. The\noptional `limit` query bounds the page.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page (default 200, maximum 1000).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_flows"
|
||||
},
|
||||
{
|
||||
"description": "GetFlow returns one automation and its latest version. That is the flow record\nplus the step tree the builder edits; a flow of another org answers not-found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_flows_id"
|
||||
},
|
||||
{
|
||||
"description": "ListVersions returns one flow's versions, newest first. The optional `limit`\nquery bounds the page.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow whose versions to list, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit bounds the page (default 200, maximum 1000).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_flows_id_versions"
|
||||
},
|
||||
{
|
||||
"description": "Pieces is the retired-name alias of the connector catalogue. It serves exactly\nwhat GET /v1/automations/connectors serves, under the name this surface used\nbefore \"piece\" (the ActivePieces term) became \"connector\", and stays valid for\nclients pinned to the old path. Prefer /connectors.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_pieces"
|
||||
},
|
||||
{
|
||||
"description": "ListRuns returns the caller org's run history, newest first. The optional\n`flowId` query narrows it to one flow and `limit` bounds the page.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flowId": {
|
||||
"description": "FlowID narrows the history to one flow. Omit it for the whole org's runs.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit bounds the page (default 200, maximum 1000).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_runs"
|
||||
},
|
||||
{
|
||||
"description": "GetRun returns one run. A run that has not reached a terminal status is refreshed\nfrom the durable engine first — scoped to the org's own namespace — so the caller\nsees live progress rather than the last status that happened to be persisted.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the run to read, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_automations_runs_id"
|
||||
},
|
||||
{
|
||||
"description": "UpdateFlow updates one automation's metadata in place. Every field is optional; a\nfield the request omits is left alone. Publishing a version pins which one runs,\nand is refused unless that version belongs to this flow.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"externalId": {
|
||||
"description": "ExternalID sets the caller's own id for this flow.",
|
||||
"type": "string"
|
||||
},
|
||||
"folderId": {
|
||||
"description": "FolderID moves the flow in the builder's tree.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the flow to update, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"description": "Metadata replaces the caller's opaque JSON."
|
||||
},
|
||||
"publishedVersionId": {
|
||||
"description": "PublishedVersionID pins the version runs execute. It must name a version OF\nTHIS FLOW; empty clears the pin, so runs take the latest version again.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_automations_flows_id"
|
||||
},
|
||||
{
|
||||
"description": "Run executes one connector action in-process and answers the outcome. The\ncaller's resolved credential travels in `auth`, delivered to the action\nverbatim — the runtime resolves no credential itself. An action that ran and\nfailed (or an action name the connector does not have) answers ok:false with\nthe failure message, not an HTTP error; an unknown connector is 404 and a\nmissing action 422.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "Action is the name of the connector action to invoke.",
|
||||
"type": "string"
|
||||
},
|
||||
"auth": {
|
||||
"description": "Auth is the caller's resolved credential for the connector, handed to the\naction verbatim. Its shape is whatever the connector's auth descriptor\ndeclares (a token string, an object), so it is opaque here.",
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the connector to run, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"props": {
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
},
|
||||
"description": "Props are the action's input properties, keyed by property name.",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_connectors_id_run"
|
||||
},
|
||||
{
|
||||
"description": "CreateFlow creates an automation and its initial DRAFT version in one call. The\nnew flow is DISABLED — creating it does not arm its trigger; POST\n/v1/automations/flows/{id}/enable does that.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"FlowAction": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nextAction": {
|
||||
"$ref": "#/$defs/FlowAction"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/$defs/StepSettings"
|
||||
},
|
||||
"skip": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"description": "PIECE | CODE | ROUTER | LOOP_ON_ITEMS",
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FlowTrigger": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nextAction": {
|
||||
"$ref": "#/$defs/FlowAction"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/$defs/StepSettings"
|
||||
},
|
||||
"strategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "PIECE_TRIGGER | EMPTY",
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"StepSettings": {
|
||||
"properties": {
|
||||
"actionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"input": {
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"pieceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pieceVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"triggerName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"description": "DisplayName names the flow's initial draft version.",
|
||||
"type": "string"
|
||||
},
|
||||
"externalId": {
|
||||
"description": "ExternalID is the caller's own id for this flow. Optional.",
|
||||
"type": "string"
|
||||
},
|
||||
"folderId": {
|
||||
"description": "FolderID groups the flow in the builder's tree. Optional.",
|
||||
"type": "string"
|
||||
},
|
||||
"trigger": {
|
||||
"$ref": "#/$defs/FlowTrigger",
|
||||
"description": "Trigger is the root of the step tree — how the flow starts, and the action\nchain that follows. Optional: a flow may be created empty and edited later."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_flows"
|
||||
},
|
||||
{
|
||||
"description": "DisableFlow disarms a flow's trigger and marks it DISABLED. Its schedule and its\nevent subscriptions are dropped, so a disabled flow is never a live target; runs\nalready in flight are unaffected, and it can still be started on demand.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_flows_id_disable"
|
||||
},
|
||||
{
|
||||
"description": "EnableFlow arms a flow's trigger and marks it ENABLED. A POLLING trigger gets a\ncron schedule on the durable engine; a WEBHOOK trigger gets a subscription in the\nrouting index, so an inbound event starts it; a MANUAL trigger arms nothing and\nstill runs on demand.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_flows_id_enable"
|
||||
},
|
||||
{
|
||||
"description": "RunFlow starts one durable run of a flow now. It runs the flow's published\nversion if one is pinned, else its latest, and answers the run record it created.\nThe run is bounded by the org's per-minute run-start budget and its in-flight\nconcurrency ceiling; over either, or with the engine not ready, no run is started\nand no run id is burned.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the flow to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_flows_id_run"
|
||||
},
|
||||
{
|
||||
"description": "CreateVersion adds a new DRAFT version to a flow. The version is created invalid\nunless it carries a trigger, and it does not become the running version until it\nis published (PATCH the flow's publishedVersionId) or becomes the latest.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"FlowAction": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nextAction": {
|
||||
"$ref": "#/$defs/FlowAction"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/$defs/StepSettings"
|
||||
},
|
||||
"skip": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"description": "PIECE | CODE | ROUTER | LOOP_ON_ITEMS",
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"FlowTrigger": {
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"nextAction": {
|
||||
"$ref": "#/$defs/FlowAction"
|
||||
},
|
||||
"settings": {
|
||||
"$ref": "#/$defs/StepSettings"
|
||||
},
|
||||
"strategy": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "PIECE_TRIGGER | EMPTY",
|
||||
"type": "string"
|
||||
},
|
||||
"valid": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"StepSettings": {
|
||||
"properties": {
|
||||
"actionName": {
|
||||
"type": "string"
|
||||
},
|
||||
"input": {
|
||||
"additionalProperties": {
|
||||
"type": "object"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"pieceName": {
|
||||
"type": "string"
|
||||
},
|
||||
"pieceVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
"triggerName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"displayName": {
|
||||
"description": "DisplayName names the new version.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the flow to add a version to, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"trigger": {
|
||||
"$ref": "#/$defs/FlowTrigger",
|
||||
"description": "Trigger is the root of the version's step tree. Optional: a version with no\ntrigger is created invalid, and cannot run until one is set."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_automations_flows_id_versions"
|
||||
}
|
||||
]
|
||||
@@ -1,10 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "BaseHealth reports that the base subsystem is serving.\n\nIt is deliberately INDEPENDENT of whether this deployment actually embeds the\nBase engine: the route answers before the CLOUD_BASE_EMBED gate and before the\n/v1/base/* wildcard, so a liveness probe measures the process rather than an\noptional feature, and the wildcard can never shadow it. It reads no tenant, so a\nprober that sends no principal is answered rather than refused.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_base_health"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,18 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Returns every deployable blueprint with its service count and estimated\nmonthly compute cost.\n\nIt is the lightweight index the console renders as a template gallery before\ndrilling into one stack's bill of images — GET /v1/blueprint/sbom?template=\u003cid\u003e\nis the detail view. The cost is the same figure the deploy path meters the\ndeploying org on and the 20% author royalty is taken from, priced from the\nactive rate card (GET /v1/blueprint/health echoes that card).",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_blueprint"
|
||||
},
|
||||
{
|
||||
"description": "Reports blueprint liveness and echoes the compute rate card in force.\n\nThe rate card is the one the estimator actually applies after the operator env\noverlay, so an operator can confirm a tuned knob took effect rather than\ninferring it from a price. Not JWT-gated — a liveness probe must be reachable —\nand it always answers 200 while the subsystem is mounted.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_blueprint_health"
|
||||
}
|
||||
]
|
||||
@@ -1,406 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListAccounts returns the org's chart of accounts — the seeded fixed chart every\nposting key in the ledger refers to.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_accounts"
|
||||
},
|
||||
{
|
||||
"description": "ListBankTransactions returns the org's normalized bank transactions, newest first —\nevery row the import and connector paths have ingested, with its amount in exact cents,\nits direction, and whether it has been matched to a voucher yet.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many rows come back; 500 when absent or not positive.",
|
||||
"type": "integer"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_bank_transactions"
|
||||
},
|
||||
{
|
||||
"description": "ListUnreconciled returns the org's unmatched bank inflows and their open clarifying\nquestions — the queue a human answers so an unexplained deposit is never guessed into\nrevenue.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_bank_unreconciled"
|
||||
},
|
||||
{
|
||||
"description": "ExportPackage returns the complete financial package for the caller's org over\n(from, to]: the trial balance, the P\u0026L, the balance sheet, and the GL detail behind\nthem — the four statements a tax preparer or an investor asks for, assembled from the\none ledger in a single read so they cannot disagree with each other.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"format": {
|
||||
"description": "Format is the export encoding. Only \"json\" is supported; empty means json.",
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the window, exclusive. Empty means all time.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the GL detail rows included as the audit trail; 5000 when absent\nor not positive.",
|
||||
"type": "integer"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the window, inclusive. Empty means up to now.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_export"
|
||||
},
|
||||
{
|
||||
"description": "ListGL returns the org's most recent GL Entry rows, newest first. This is the raw\ndouble-entry detail behind every statement: one row per leg, with its debit, credit,\nposting time and the source that booked it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many rows come back; 500 when absent or not positive.",
|
||||
"type": "integer"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_gl"
|
||||
},
|
||||
{
|
||||
"description": "ListInbox returns the org's open document queue — everything uploaded but not yet\nbooked, newest first, each with its extracted summary and the confidence the scanner\nresolved its category at. A booked document drops out of the queue.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_inbox"
|
||||
},
|
||||
{
|
||||
"description": "Metrics returns the org's deterministic SaaS-metrics snapshot over an optional\n(from, to] window — MRR, ARR, revenue, COGS, burn, gross margin, net income, cash,\ndeferred revenue, monthly burn and runway — as raw int64-cent figures AND the same\nfigures already formatted. Every number is the ledger, aggregated the one way the books\ndefine it, never a guess; it is the grounded read the unified /v1/ask advisor replays.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the window, exclusive. Empty means all time.",
|
||||
"type": "string"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the window, inclusive. Empty means up to now.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_metrics"
|
||||
},
|
||||
{
|
||||
"description": "ProfitAndLoss returns the org's accrual-basis Profit \u0026 Loss over an optional (from, to]\nwindow of RFC3339 posting times: recognized revenue, matched cost, and the net.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the window, exclusive. Empty means all time.",
|
||||
"type": "string"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the window, inclusive. Empty means up to now.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_pnl"
|
||||
},
|
||||
{
|
||||
"description": "BalanceSheet returns the org's Balance Sheet as of `to` (empty = all time), with the\nAssets == Liabilities + Equity equation proof.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 instant the statement is struck as of. Empty means all time.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_position"
|
||||
},
|
||||
{
|
||||
"description": "ListQuestions returns the clarifying questions the caller's own recent GL raises — the\nunusual postings a founder should look at (outliers, reversals, round-offs, uncosted\nrevenue, an overdrawn wallet), sharpest first. An empty list means the books look clean;\nthe detector is deterministic over the ledger and invents nothing.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_questions"
|
||||
},
|
||||
{
|
||||
"description": "ListRules returns the org's auto-categorization rules, highest priority first. A rule\nis a standing instruction — \"anything whose merchant contains X books to category Y\" —\nand it overrides a vendor's default category, so this is the list that decides how a\nfuture bill classifies itself.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_rules"
|
||||
},
|
||||
{
|
||||
"description": "ListTransactions returns the org's booked ledger as a single-line register, newest\nfirst: one row per voucher, with its date, description, vendor, category, source and\namount in exact cents. It is the double-entry ledger projected to the register a human\nreads, filterable by posting-time window, category and vendor. Strictly read-only — it\nrestates the books, it never moves them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"description": "Category filters to one COA account, named by number (\"5300\") or by category\nslug (\"software\").",
|
||||
"type": "string"
|
||||
},
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the posting-time window, inclusive.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps how many rows come back; 200 when absent or not positive.",
|
||||
"type": "integer"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the posting-time window, inclusive.",
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"description": "Vendor filters to rows whose vendor or description contains this text,\ncase-insensitively.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_transactions"
|
||||
},
|
||||
{
|
||||
"description": "TrialBalance returns the org's trial balance over an optional [from, to] window of\nRFC3339 posting times, including the opening/closing columns and the\nTotalDebit == TotalCredit proof that the books balance.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the window, exclusive. Empty means all time.",
|
||||
"type": "string"
|
||||
},
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the window, inclusive. Empty means up to now.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_trial"
|
||||
},
|
||||
{
|
||||
"description": "ListVendors returns the org's vendor book: each canonical vendor, the alias spellings a\nreceipt may print it under, and the expense account new bills from it default to. A\nvendor here is what makes a scanned bill self-classify instead of asking again.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"sandbox": {
|
||||
"description": "Sandbox reads the org's SANDBOX ledger when it is exactly \"true\"; anything else\nreads the live one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_books_vendors"
|
||||
},
|
||||
{
|
||||
"description": "AskBooks answers a plain-language question about the caller's own books — \"what is my\nMRR?\", \"how long is my runway?\" — with figures taken from their ledger, never a guessed\nnumber. A deterministic keyword router picks the intent and reads the real metrics, and\nthose figures, followups and report sources are computed BEFORE any model call and are\nnever altered by one: the optional narration seam only rephrases the sentence, and it\ndegrades silently to the templated answer when no AI plane is wired. It is strictly\nread-only — it restates the books, it never posts to them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the RFC3339 start of the metric window. Empty means all time, treated as a\nsingle reporting period (see monthsBetween).",
|
||||
"type": "string"
|
||||
},
|
||||
"question": {
|
||||
"description": "Question is the plain-language question about the org's books, e.g. \"what is my\nMRR?\". Longer than 2000 characters is truncated, never refused.",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the RFC3339 end of the metric window. Empty means up to now.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_ask"
|
||||
},
|
||||
{
|
||||
"description": "SyncBank pulls every connected bank (Plaid/Teller) for the caller's org, maps each\nfetched transaction to a posting and books it idempotently, then advances that\nconnector's cursor so the next sync resumes where this one stopped. One connector's\noutage is skipped rather than failing the whole sync. It reports the batch: how many\ntransactions were seen, how many vouchers posted, how many inflows reconciled against\nthe processor clearing account, how many raised a question, how many were own-account\ntransfers, and how many were already-processed no-ops. It is READ-ONLY against the\nbank — it ingests, it never sends money.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_bank_sync"
|
||||
},
|
||||
{
|
||||
"description": "UpsertRule creates or updates one auto-categorization rule, keyed by its pattern —\nwriting a pattern that already exists REPLACES that row's category and priority. The\ncategory is normalized to a real COA expense account, and anything unrecognized becomes\n5900 Uncategorized rather than a guessed real account. It answers the row exactly as\nstored, so the caller sees the normalization. A rule overrides a vendor's default\ncategory, so this is the standing instruction that decides how a future bill classifies.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"description": "Category is the COA expense account a matching bill books to. An upsert normalizes\na slug (\"cloud\") to its account number.",
|
||||
"type": "string"
|
||||
},
|
||||
"pattern": {
|
||||
"description": "Pattern is the merchant substring the rule matches on, case-insensitively. It is\nalso the key an upsert writes by.",
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority breaks ties: when several patterns match, the highest wins.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_rules"
|
||||
},
|
||||
{
|
||||
"description": "BookScan posts a reviewed scanned bill to the ledger. It is the scanner's ONLY write:\nthe voucher goes through the same post() choke point every other source uses, so it is\nchecked to balance (Σdebit == Σcredit) and is idempotent by (scan, scanId) — re-booking\nthe same scan answers posted=false and writes nothing. A bill whose economic identity\n(vendor, total, issue date) already posted under a DIFFERENT scan is refused 409 unless\noverride is set, which is what stops the same receipt re-scanned into a new file hash\nfrom double-booking. An unbalanced voucher is refused 400.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Leg": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "Account is the chart-of-accounts number this side posts to, e.g. \"5300\".",
|
||||
"type": "string"
|
||||
},
|
||||
"credit": {
|
||||
"description": "Credit is the leg's credit in exact cents. Set this or Debit, not both.",
|
||||
"type": "integer"
|
||||
},
|
||||
"debit": {
|
||||
"description": "Debit is the leg's debit in exact cents. Set this or Credit, not both.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"Voucher": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Description is the human line for the event, e.g. the vendor a bill came from.",
|
||||
"type": "string"
|
||||
},
|
||||
"legs": {
|
||||
"description": "Legs are the sides of the posting. They must balance: Σdebit == Σcredit, give or\ntake the 2¢ round-off allowance.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Leg"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"postingAt": {
|
||||
"description": "PostingAt is the RFC3339 instant the event posts at — the time every statement\nwindow filters on.",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceId": {
|
||||
"description": "SourceID is the source event's own id within that namespace. Together with\nSourceKind it is the key that makes a repeat posting a no-op.",
|
||||
"type": "string"
|
||||
},
|
||||
"sourceKind": {
|
||||
"description": "SourceKind is the idempotency namespace naming what booked this, e.g. \"scan\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"override": {
|
||||
"description": "Override books this bill even when one of the SAME economic identity\n(vendor, total, issue date) already posted — the explicit human confirmation that a\nsame-looking bill is a genuine second spend, not the same receipt re-scanned.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"scanId": {
|
||||
"description": "ScanID is the scanned document's file hash, as GET /v1/books/inbox and the scan\ndraft report it. It is the idempotency key: re-booking the same scan writes nothing.",
|
||||
"type": "string"
|
||||
},
|
||||
"voucher": {
|
||||
"$ref": "#/$defs/Voucher",
|
||||
"description": "Voucher is the reviewed voucher to post. Its source is FORCED to (scan, scanId)\nserver-side, so it can never be booked under another source's key."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_scan_book"
|
||||
},
|
||||
{
|
||||
"description": "Sync ingests the caller's OWN org from commerce into BOTH ledgers (live and sandbox)\nand reports how many new vouchers posted to each. It is idempotent — money that has\nalready been booked posts nothing on a repeat — and it is read-only against commerce:\nit never mints a deposit, a credit or a payout, only the accounting twin of money that\nalready moved.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_sync"
|
||||
},
|
||||
{
|
||||
"description": "UpsertVendor creates or updates one vendor in the org's vendor book, keyed by its\ncanonical name — writing a canonical name that already exists REPLACES that row's\naliases and default category. A category given as a slug (\"software\") is normalized to\nits real COA expense account, and anything unrecognized becomes 5900 Uncategorized\nrather than a guessed real account. It answers the row exactly as stored, so the caller\nsees the normalization. Recording a vendor is what makes future bills from it\nself-classify instead of asking again.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"aliases": {
|
||||
"description": "Aliases are the other spellings a receipt may print the vendor under; a scan\nmatching any of them resolves to this vendor.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"canonical": {
|
||||
"description": "Canonical is the vendor's one true name, and the key an upsert writes by.",
|
||||
"type": "string"
|
||||
},
|
||||
"defaultCategory": {
|
||||
"description": "DefaultCategory is the COA expense account new bills from this vendor book to.\nAn upsert normalizes a slug (\"software\") to its account number.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_books_vendors"
|
||||
}
|
||||
]
|
||||
@@ -1,10 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Returns the caller org's currently connected bot nodes: what each one\ncalls itself, the platform it runs on, its agent version, when its socket was\nestablished, and the capabilities and commands it reported.\n\nOnly this org's nodes are listed — the org is half of every key in the table it\nreads — and only nodes attached to THIS replica, because the list is of live\nsockets rather than of registrations. The capability and command lists are the\nnode's own self-report: useful to show, never load-bearing, because what a node\nmay actually be asked to do is decided at the socket against the deployment's\nallowlist.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_bot_nodes"
|
||||
}
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "List returns the caller org's live bot runs, read from the bot runtime and projected\ninto the console contract with each run's live session URL derived here.\n\nThe org is ALWAYS the validated principal's org, NEVER a request field, and it is\nwhat scopes the runtime's answer — so one tenant can never enumerate another's\nruns. A runtime that cannot answer is an error, not an empty list: [] would tell\nthe caller \"your org has no runs\", which is a different claim from \"we could not\nask\", and the difference is the whole reason this endpoint exists.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_bots"
|
||||
},
|
||||
{
|
||||
"description": "Stop terminates one of the caller org's own bot runs and reports its terminal state.\n\nThe own-key guard is the org: it is the caller's validated org, never theirs to\nchoose, and the runtime resolves the run id UNDER it. A run belonging to another\ntenant is not among this org's runs, so it answers absent — the same 404 a\nnonexistent id gets, which is what keeps this from being an oracle.\n\nAbsence is honoured ONLY when the runtime answers it. A runtime that does not\nserve stop reports nothing about the run, and reporting \"stopped\" on that basis\nwould be a stop that cannot fail — so it is a 502.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_bots_runId_stop"
|
||||
}
|
||||
]
|
||||
@@ -1,256 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteCampaign removes one campaign of the caller's org and answers 204 with no\nbody. 404 when the org has no campaign with that id.\n\nIt deletes the RECORD, not the executions: a campaign whose channels are live\non a provider should be paused first, or those executions keep running with\nnothing here to report them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign's server-minted handle, \"cmp_\"-prefixed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_campaign_id"
|
||||
},
|
||||
{
|
||||
"description": "RemoveCampaignChannel drops one channel from a campaign and returns the updated\ncampaign. 404 when the campaign carries no channel of that kind.\n\nIt removes the channel from the PLAN. A channel that is live at its provider\nshould be paused first — dropping the row here leaves nothing to pause it with\nafterwards.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is the channel to remove: paid, organic or email.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_campaign_id_channels_kind"
|
||||
},
|
||||
{
|
||||
"description": "ListCampaigns returns the org's campaigns, newest first, optionally narrowed to\none status.\n\nA campaign is the top-level go-to-market object: a value that SPANS channels\n(paid, organic, email) and fans out to the executor for each. The listing is\norg-scoped server-side, so one org can never see another's campaigns.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page. 0 or less means the default of 200; anything above\n1000 is clamped to 1000.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status keeps only campaigns in that state: draft, live, paused or failed.\nEmpty means any.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_campaign"
|
||||
},
|
||||
{
|
||||
"description": "GetCampaign returns one campaign of the caller's org — its name, audience,\ncreatives, channels with their per-channel launch state, schedule, budget and\nstatus. 404 when the org has no campaign with that id.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign's server-minted handle, \"cmp_\"-prefixed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_campaign_id"
|
||||
},
|
||||
{
|
||||
"description": "CampaignMetrics returns a campaign's results over a window: the analytics\nfunnel (impressions, clicks, conversions, revenue, visitors), the spend each\nchannel's connector reports, and the derived growth KPIs — CTR, CVR, CAC and\nROAS.\n\nThere is exactly ONE metrics plane and nothing is stored here: the funnel is an\nanalytics query over the campaign's utm_campaign-tagged events, and the spend is\neach provider's own number read through the org's connector. A warehouse that is\nnot emitting yet degrades to available:false with zeroes — honest-empty, never a\n500 and never a fabricated number. When the campaign runs more than one creative\nand an experiment is wired, abTest carries the A/B analysis.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"end": {
|
||||
"description": "End is an explicit RFC3339 window end.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the campaign to report on, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"range": {
|
||||
"description": "Range is the lookback window: 24h, 7d, 30d or 90d. Anything else, including\nempty, reads as 30d.",
|
||||
"type": "string"
|
||||
},
|
||||
"start": {
|
||||
"description": "Start is an explicit RFC3339 window start. Honored only together with End,\nand only when End is after it.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_campaign_id_metrics"
|
||||
},
|
||||
{
|
||||
"description": "SummarizeCampaigns returns the org's go-to-market roll-up: how many campaigns\nexist, how many are live, their total budget in cents, and which channel\nexecutors this deployment can actually reach.\n\nThe channel list is the deployment's honest capability, not a wish: a kind\nmissing from it is one a launch will record as \"unavailable\" rather than fail\non.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_campaign_summary"
|
||||
},
|
||||
{
|
||||
"description": "CreateCampaign creates a campaign as a DRAFT and returns it.\n\nA draft is inert: nothing is sent, no connector is touched and no budget is\ncommitted until the campaign is launched. The channels named here are validated\nand de-duplicated by kind (one executor per kind), and every channel starts\n\"pending\" whatever the caller claims — a client can never assert a launched\nstate.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"ChannelSpec": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "provider account ref (ad-account/page/list id)",
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"description": "honest last-outcome detail (never a secret)",
|
||||
"type": "string"
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "paid | organic | email",
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"description": "meta | google | x | instagram | (email provider)",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "pending | live | paused | failed | unavailable",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"audience": {
|
||||
"description": "Audience is the segment or audience selector this campaign targets.",
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"description": "Budget is the campaign's total budget in CENTS. Negative reads as 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"channels": {
|
||||
"description": "Channels are the fan-out targets, at most one per kind (paid, organic,\nemail) and at most 12. A channel's status and provider id are server-owned:\nwhatever the caller sends for them is replaced with \"pending\".",
|
||||
"items": {
|
||||
"$ref": "#/$defs/ChannelSpec"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"content": {
|
||||
"description": "Content is the ordered creative set. Content[0] is the active creative and\nthe rest are A/B variants; at most 32, empty entries dropped.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the campaign's display name. Required; trimmed and capped at 2048\ncharacters.",
|
||||
"type": "string"
|
||||
},
|
||||
"scheduleAt": {
|
||||
"description": "ScheduleAt is when the campaign should run, in unix seconds. Negative reads\nas 0 (immediately).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_campaign"
|
||||
},
|
||||
{
|
||||
"description": "AddCampaignChannel adds a channel to a campaign, or REPLACES the one it already\nhas of that kind, and returns the updated campaign.\n\nA campaign carries at most one channel per kind, because the kind IS the\nexecutor: adding a second \"paid\" channel would mean two ad accounts running one\ncampaign with no way to tell their results apart. The new channel starts\n\"pending\" — adding it does not launch it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "Account is the provider account this channel runs under: an ad-account, a\npage, or a mailing-list id.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the campaign to add the channel to, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is the channel kind and the identity a campaign holds at most one of:\npaid, organic or email.",
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"description": "Platform is the provider within the kind — meta, google, x, instagram, or\nthe email provider.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_campaign_id_channels"
|
||||
},
|
||||
{
|
||||
"description": "UpdateCampaign rewrites a campaign's core fields — name, audience, creatives,\nschedule and budget — and returns the updated campaign.\n\nChannels are replaced ONLY while the campaign is still a draft. Once it is\nlaunched its channels carry provider state (an external id, a live status), so\nthey are added and removed explicitly through the channels sub-resource\ninstead; a whole-object write would silently orphan a running execution.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"ChannelSpec": {
|
||||
"properties": {
|
||||
"account": {
|
||||
"description": "provider account ref (ad-account/page/list id)",
|
||||
"type": "string"
|
||||
},
|
||||
"detail": {
|
||||
"description": "honest last-outcome detail (never a secret)",
|
||||
"type": "string"
|
||||
},
|
||||
"externalId": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "paid | organic | email",
|
||||
"type": "string"
|
||||
},
|
||||
"platform": {
|
||||
"description": "meta | google | x | instagram | (email provider)",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "pending | live | paused | failed | unavailable",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"audience": {
|
||||
"type": "string"
|
||||
},
|
||||
"budget": {
|
||||
"type": "integer"
|
||||
},
|
||||
"channels": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/ChannelSpec"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"content": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the campaign to update, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduleAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_campaign_id"
|
||||
}
|
||||
]
|
||||
@@ -1,248 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteConvertible removes one of the caller org's convertible notes, taking its\nprincipal out of the cap table's unconverted-instrument totals. An id this org\ndoes not hold is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the convertible note to delete.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_captable_convertibles_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteOption removes one of the caller org's option grants, taking its shares\nout of the cap table's granted-options and fully-diluted counts. An id this org\ndoes not hold is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the option grant to delete.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_captable_options_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteSafe removes one of the caller org's SAFEs, taking its capital out of the\ncap table's unconverted-instrument totals. An id this org does not hold is not\nfound.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the SAFE to delete.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_captable_safes_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteShare removes one of the caller org's share certificates, taking its\nshares out of the cap table's outstanding and fully-diluted counts. An id this\norg does not hold is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the share certificate to delete.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_captable_shares_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteStakeholder removes one of the caller org's stakeholders. It REFUSES to\norphan issued equity: a holder that still holds share certificates or option\ngrants cannot be deleted, and answers 400 saying so — release or transfer the\nholdings first. An id this org does not hold is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the stakeholder to delete.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_captable_stakeholders_id"
|
||||
},
|
||||
{
|
||||
"description": "ListShareClasses returns the caller org's share classes, in creation order. A\nshare class is what a certificate is issued in, and every class the company\nhas authorized appears. The response is a bare JSON array, not an envelope.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_classes"
|
||||
},
|
||||
{
|
||||
"description": "GetCompany returns the caller org's cap-table company record. The row is\nseeded when the tenant's store first opens, so it always exists; its name and\nincorporation details are set with PUT /v1/captable/company.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_company"
|
||||
},
|
||||
{
|
||||
"description": "ListConvertibles returns the caller org's convertible notes, newest first. A\nnote's principal sits OUTSIDE issued equity until it converts, so it is not\npart of the share counts.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_convertibles"
|
||||
},
|
||||
{
|
||||
"description": "ListInvestments returns the caller org's investments, newest first. It spans\nevery round, so it is the flat ledger of cheques written into the company,\neach naming its investor and the round it went into.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_investments"
|
||||
},
|
||||
{
|
||||
"description": "ListOptions returns the caller org's option grants, newest first. Each row is\njoined to its grantee and its equity plan. Grants that are EXERCISED, EXPIRED\nor CANCELLED are listed here but do not dilute the cap table.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_options"
|
||||
},
|
||||
{
|
||||
"description": "ListEquityPlans returns the caller org's equity plans, newest first. An equity\nplan is an option pool: a reserve of shares, drawn from one share class, that\noption grants are written against.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_plans"
|
||||
},
|
||||
{
|
||||
"description": "ListRounds returns the caller org's fundraising rounds, newest first. A round\ngroups a fundraising event; a PRICED round also carries the share class and\nprice per share it issues at.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_rounds"
|
||||
},
|
||||
{
|
||||
"description": "GetRound returns one of the caller org's fundraising rounds together with every\ninvestment written into it, oldest first. A round id that does not exist in the\ncaller's org is not found — including one that exists in another tenant, since\nthe org comes from the caller's principal and is part of the lookup.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the round to read. It is the path segment: the URL is the addressing\nauthority, and the org it is resolved in comes from the caller's principal,\nso an id from another tenant is simply not found.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_rounds_id"
|
||||
},
|
||||
{
|
||||
"description": "ListSafes returns the caller org's SAFEs, newest first. A SAFE is a simple\nagreement for future equity: its capital sits OUTSIDE issued equity until it\nconverts, so it is not part of the share counts.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_safes"
|
||||
},
|
||||
{
|
||||
"description": "ListShares returns the caller org's share certificates, newest first. Each row\nis joined to its holder and its share class, so a certificate names who holds\nit and what class it is in without a second call.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_shares"
|
||||
},
|
||||
{
|
||||
"description": "ListStakeholders returns the caller org's stakeholders, newest first. The\nresponse is a bare JSON array, not an envelope. Each row carries the holder's\ncontact and address fields alongside the company's name.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_stakeholders"
|
||||
},
|
||||
{
|
||||
"description": "GetSummary computes the caller org's cap table. It answers who owns what on a\nfully-diluted basis: outstanding shares, granted options, per-stakeholder\nownership percentages, each share class's authorized versus issued position,\nand the capital sitting on SAFEs and convertible notes that have not yet\nconverted. Only non-terminal option grants dilute — EXERCISED, EXPIRED and\nCANCELLED grants are excluded, so equity issued through an exercised option is\nnever counted twice.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_captable_summary"
|
||||
},
|
||||
{
|
||||
"description": "UpdateStakeholder changes one of the caller org's stakeholders. It is a\nPARTIAL update: only the fields the request names are written, and a field\nsent as null clears that column. A request that names no updatable field is\nrefused, and an id this org does not hold is not found.\n\nThe values are stored as sent. Unlike adding a stakeholder, this route does\nnot check the email's shape or the type and relationship vocabularies, so it\ncan record a value that adding one would have rejected.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"description": "City is the stakeholder's city."
|
||||
},
|
||||
"currentRelationship": {
|
||||
"description": "CurrentRelationship is how the stakeholder relates to the company, e.g.\nFOUNDER, INVESTOR or EMPLOYEE. This route stores it as sent — unlike\nadding a stakeholder, it is not checked against the vocabulary."
|
||||
},
|
||||
"email": {
|
||||
"description": "Email is the stakeholder's email. This route stores it as sent — unlike\nadding a stakeholder, it is not checked for shape or uniqueness."
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the stakeholder to update. It is the path segment: the URL is the\naddressing authority, and the org it is resolved in comes from the\ncaller's principal, so an id from another tenant is simply not found.",
|
||||
"type": "string"
|
||||
},
|
||||
"institutionName": {
|
||||
"description": "InstitutionName names the institution, when the stakeholder is one."
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the stakeholder's full name."
|
||||
},
|
||||
"stakeholderType": {
|
||||
"description": "StakeholderType is INDIVIDUAL or INSTITUTION. This route stores it as\nsent — unlike adding a stakeholder, it is not checked against the\nvocabulary."
|
||||
},
|
||||
"state": {
|
||||
"description": "State is the stakeholder's state or province."
|
||||
},
|
||||
"streetAddress": {
|
||||
"description": "StreetAddress is the stakeholder's street address."
|
||||
},
|
||||
"taxId": {
|
||||
"description": "TaxID is the stakeholder's tax identifier."
|
||||
},
|
||||
"zipcode": {
|
||||
"description": "Zipcode is the stakeholder's postal code."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_captable_stakeholders_id"
|
||||
},
|
||||
{
|
||||
"description": "CloseRound closes one of the caller org's fundraising rounds, recording the\nclose date and moving its status to CLOSED. Only an OPEN round can be closed:\na round that is already closed — like an id this org does not hold — is not\nfound. Closing a round does not change what was invested in it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"closeDate": {
|
||||
"description": "CloseDate is the date to record the round as closed on. Optional: omitted,\nnull or empty records TODAY. Any JSON scalar is accepted and stored as its\ntext, and the text is stored unparsed, so a caller that wants an ISO date\nsends one."
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the round to close. It is the path segment: the URL is the\naddressing authority, and the org it is resolved in comes from the\ncaller's principal, so an id from another tenant is simply not found.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_captable_rounds_id_close"
|
||||
},
|
||||
{
|
||||
"description": "UpdateCompany sets the caller org's company name and incorporation details.\nThe name is required; the three incorporation fields are optional and each is\nstored as empty when omitted, so a call that sends only a name CLEARS them.\nThe company row itself is seeded when the tenant's store first opens, so this\nnever creates one.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"incorporationCountry": {
|
||||
"description": "IncorporationCountry is the ISO country the entity is incorporated in.\nOptional; omitted, null or empty clears it. Any JSON scalar is accepted\nand stored as its text."
|
||||
},
|
||||
"incorporationState": {
|
||||
"description": "IncorporationState is the state or province of incorporation. Optional;\nomitted, null or empty clears it. Any JSON scalar is accepted and stored\nas its text."
|
||||
},
|
||||
"incorporationType": {
|
||||
"description": "IncorporationType is the entity kind, e.g. LLC or C_CORP. Optional;\nomitted, null or empty clears it. Any JSON scalar is accepted and stored\nas its text."
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the company's legal name. Required, and it must be a non-empty\nstring — anything else is refused with the cap table's own validation\nerror."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_captable_company"
|
||||
}
|
||||
]
|
||||
@@ -1,51 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Browse searches AND browses the cross-org catalog: every project, app and site\nthe fleet has built, whichever org built it.\n\nIt reads TWO corpora and returns them as one page — the published,\nworld-readable catalog that every caller sees, plus the caller's OWN org's\nprivate entries when the request carries a validated principal. Each row says\nwhich it came from in `scope`, so a client can warn before sharing a link. An\nanonymous caller simply gets the published one; no filter can ever widen a\ncaller into another tenant's corpus, because the query that would return it is\nnever run for them.\n\nA request with no q is a browse rather than a search, and both answer the same\nshape: the page, the total before paging, and the facet counts over the whole\nmatching set.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"archetype": {
|
||||
"description": "Archetype narrows to one project archetype. Case-insensitive.",
|
||||
"type": "string"
|
||||
},
|
||||
"forkable": {
|
||||
"description": "Forkable is tri-state: \"true\" selects the forkable rows, \"false\" selects the\nrest, and anything else — including absent — applies no filter at all.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind narrows to repo | site. Case-insensitive.",
|
||||
"type": "string"
|
||||
},
|
||||
"language": {
|
||||
"description": "Language narrows to one implementation language. Case-insensitive.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the page at 200, default 50. A value that is not a non-negative\ninteger falls back to the default.",
|
||||
"type": "string"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Offset is where the page starts, default 0, with the same tolerance.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org narrows to one builder org: hanzo | lux | zoo. Case-insensitive.",
|
||||
"type": "string"
|
||||
},
|
||||
"origin": {
|
||||
"description": "Origin narrows to what a row IS to you: template | community | third-party |\nproduct. This is the axis the two hanzo.app lanes are cut on.",
|
||||
"type": "string"
|
||||
},
|
||||
"q": {
|
||||
"description": "Q is the free-text query the lexical index scores relevance on. Empty is a\nbrowse rather than a search — the same request either way.",
|
||||
"type": "string"
|
||||
},
|
||||
"template": {
|
||||
"description": "Template narrows a lane to ONE lineage: the id of the parent everything\nreturned was forked from.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_catalog"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,113 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Returns every chat transport channels can talk to — Discord, Slack, Teams\nand Telegram — with the caller org's own facts on each: whether it is\nconnected and to which account, what the transport supports, the org's DM and\ngroup access policies, and how many pairing requests are pending approval. The\norder is fixed, so a console can render the same rows every time. A policy that\ncannot be read leaves that channel's policy fields empty rather than failing\nthe whole listing.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_channels"
|
||||
},
|
||||
{
|
||||
"description": "Returns the caller org's access policy for one channel: whether\nDMs are pairing-gated, allowlisted or open, whether group rooms are open,\nallowlisted or disabled, the config-managed DM and group allow entries, the\nsenders approved through PAIRING (read-only here), and the org's named access\ngroups. An unknown channel is a 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"channel": {
|
||||
"description": "Channel is the transport to read: discord, slack, teams or telegram.\nRequired; an unknown value is a 404.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_channels_allowlist"
|
||||
},
|
||||
{
|
||||
"description": "Returns the messages people have sent to the caller org's connected chat\nbots, oldest first, in the portable envelope shape every transport normalises\ninto. It is a CURSOR feed, not a search: pass the returned cursor back as\n`since` to get only what has arrived since. Only this org's messages are\nstored under this org, so the feed can never carry another tenant's chat.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many messages come back. Empty or 0 uses the store's\ndefault page size. Must parse as an integer.",
|
||||
"type": "string"
|
||||
},
|
||||
"since": {
|
||||
"description": "Since is the exclusive cursor: only messages with a higher row id come\nback. Empty starts at the beginning. Must parse as an integer.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_channels_inbox"
|
||||
},
|
||||
{
|
||||
"description": "Returns the pairing requests waiting for the caller org to approve\n— one per person who messaged a connected bot on a channel whose DM policy is\n\"pairing\" and who is not allowed yet. Each row carries the CODE an org admin\npasses to POST /v1/channels/pairing/approve. Expired requests are not\nreturned. Codes are capability strings: they are shown here, and never logged.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_channels_pairing"
|
||||
},
|
||||
{
|
||||
"description": "Turns one pending pairing code into a standing allow entry, so\nthat person can DM the org's bot on that channel from now on. It requires ORG\nADMIN, not merely membership. The first approval an org makes on a channel also\nbootstraps that sender as the channel's owner, which the answer reports. An\nunknown or expired code is a 404, and a code always belongs to exactly one\norg, so it can never approve someone into another tenant.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"channel": {
|
||||
"description": "Channel is the transport the request came in on: discord, slack, teams or telegram.",
|
||||
"type": "string"
|
||||
},
|
||||
"code": {
|
||||
"description": "Code is the pairing code from GET /v1/channels/pairing. It is a capability:\nholding it is what authorises the approval, alongside org admin.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_channels_pairing_approve"
|
||||
},
|
||||
{
|
||||
"description": "Edits the caller org's access policy for one channel and answers\nthe policy as GET would, so both verbs return ONE shape. It requires ORG ADMIN.\nEvery field but `channel` is optional and applied only when provided: an empty\npolicy string leaves that policy alone, an absent or null list leaves that list\nalone, and an EMPTY list clears it. It writes only CONFIG-sourced allow entries\n— senders approved through pairing belong to the approval flow, so a policy\nedit can never revoke one. An unknown channel is a 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"accessGroups": {
|
||||
"additionalProperties": {
|
||||
"additionalProperties": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"description": "AccessGroups REPLACES the org's named access groups, as\ngroup name -\u003e channel -\u003e entries. Absent or null leaves them alone.",
|
||||
"type": "object"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the transport to edit: discord, slack, teams or telegram.\nRequired; an unknown value is a 404.",
|
||||
"type": "string"
|
||||
},
|
||||
"dm": {
|
||||
"description": "DM REPLACES the config-managed DM allow entries. Absent or null leaves them\nalone; an empty list clears them. It never touches senders approved through\npairing — a policy edit cannot revoke an approved pairing.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"dmPolicy": {
|
||||
"description": "DMPolicy sets how direct messages are admitted: \"pairing\" (a person must be\napproved first), \"allowlist\" (only listed senders) or \"open\". Empty leaves\nit unchanged.",
|
||||
"type": "string"
|
||||
},
|
||||
"group": {
|
||||
"description": "Group REPLACES the config-managed group allow entries. Absent or null\nleaves them alone; an empty list clears them.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"groupPolicy": {
|
||||
"description": "GroupPolicy sets how group and thread rooms are admitted: \"open\",\n\"allowlist\" or \"disabled\". Empty leaves it unchanged.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_channels_allowlist"
|
||||
}
|
||||
]
|
||||
@@ -1,549 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "D1DatabaseDelete deletes a D1 database and everything stored in it. Requires\norg admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"database": {
|
||||
"description": "Database is the Cloudflare D1 database id or name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_d1_databases_database"
|
||||
},
|
||||
{
|
||||
"description": "KVNamespaceDelete deletes a Workers KV namespace and every key in it. Requires\norg admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"namespace": {
|
||||
"description": "Namespace is the Cloudflare KV namespace id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_kv_namespaces_namespace"
|
||||
},
|
||||
{
|
||||
"description": "KVValueDelete removes one key from a Workers KV namespace. Requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Key is the key within that namespace. KV keys are broad (up to 512 bytes),\nso this one is escaped rather than charset-restricted.",
|
||||
"type": "string"
|
||||
},
|
||||
"namespace": {
|
||||
"description": "Namespace is the Cloudflare KV namespace id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_kv_namespaces_namespace_values_key"
|
||||
},
|
||||
{
|
||||
"description": "PagesDelete deletes a Cloudflare Pages project, and with it every deployment it\nhas ever made. Requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"project": {
|
||||
"description": "Project is the Pages project name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_pages_projects_project"
|
||||
},
|
||||
{
|
||||
"description": "PagesDomainDelete detaches a custom domain from a Cloudflare Pages project.\nRequires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"domain": {
|
||||
"description": "Domain is the attached custom domain to detach.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project is the Pages project name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_pages_projects_project_domains_domain"
|
||||
},
|
||||
{
|
||||
"description": "R2BucketDelete deletes an R2 bucket. Requires org admin. Cloudflare refuses a\nbucket that still holds objects, and that refusal is relayed.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"bucket": {
|
||||
"description": "Bucket is the R2 bucket name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_r2_buckets_bucket"
|
||||
},
|
||||
{
|
||||
"description": "WorkersScriptDelete removes a Worker script from the org's Cloudflare account.\nRequires org admin. Routes bound to the script stop serving it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"script": {
|
||||
"description": "Script is the Worker script name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_workers_scripts_script"
|
||||
},
|
||||
{
|
||||
"description": "WorkersRouteDelete unbinds a Worker route, so its pattern stops dispatching to a\nscript. Requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"route": {
|
||||
"description": "Route is the 32-hex Cloudflare route id.",
|
||||
"type": "string"
|
||||
},
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_cloudflare_workers_zones_zone_routes_route"
|
||||
},
|
||||
{
|
||||
"description": "D1DatabaseList lists the D1 databases on the org's Cloudflare account. Any org\nmember may read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name filters to the database with this name.",
|
||||
"type": "string"
|
||||
},
|
||||
"page": {
|
||||
"description": "Page is the 1-based page of databases to return.",
|
||||
"type": "string"
|
||||
},
|
||||
"per_page": {
|
||||
"description": "PerPage is how many databases one page holds.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_d1_databases"
|
||||
},
|
||||
{
|
||||
"description": "KVNamespaceList lists the Workers KV namespaces on the org's Cloudflare\naccount. Any org member may read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"description": "Order names the field to sort by, and Direction sorts asc or desc.",
|
||||
"type": "string"
|
||||
},
|
||||
"page": {
|
||||
"description": "Page is the 1-based page of namespaces to return.",
|
||||
"type": "string"
|
||||
},
|
||||
"per_page": {
|
||||
"description": "PerPage is how many namespaces one page holds.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_kv_namespaces"
|
||||
},
|
||||
{
|
||||
"description": "PagesList lists the org's Cloudflare Pages projects. Any org member may read.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_pages_projects"
|
||||
},
|
||||
{
|
||||
"description": "PagesGet reads one Cloudflare Pages project — its build config, deployment\nconfigs and latest deployment. Any org member may read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"project": {
|
||||
"description": "Project is the Pages project name.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_pages_projects_project"
|
||||
},
|
||||
{
|
||||
"description": "R2BucketList lists the R2 buckets on the org's Cloudflare account. Any org\nmember may read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"cursor": {
|
||||
"description": "Cursor continues from the position a previous page returned.",
|
||||
"type": "string"
|
||||
},
|
||||
"direction": {
|
||||
"type": "string"
|
||||
},
|
||||
"name_contains": {
|
||||
"description": "NameContains filters to buckets whose name contains this substring.",
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"description": "Order names the field to sort by, and Direction sorts asc or desc.",
|
||||
"type": "string"
|
||||
},
|
||||
"per_page": {
|
||||
"description": "PerPage is how many buckets one page holds.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_r2_buckets"
|
||||
},
|
||||
{
|
||||
"description": "WorkersScriptList lists the Worker scripts on the org's Cloudflare account. Any\norg member may read.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_workers_scripts"
|
||||
},
|
||||
{
|
||||
"description": "WorkersSubdomainGet reads the org account's workers.dev subdomain — the name\nunder which every subdomain-enabled script is served. Any org member may read.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_workers_subdomain"
|
||||
},
|
||||
{
|
||||
"description": "WorkersRouteList lists the Worker routes bound within one zone — the URL\npatterns that dispatch to a script. Any org member may read. Routes are\nzone-scoped, so no account is resolved.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_workers_zones_zone_routes"
|
||||
},
|
||||
{
|
||||
"description": "ZonesList lists the Cloudflare zones the org's connected API token can see,\npaged and filtered by the query parameters Cloudflare itself accepts. Zones are\ntoken-scoped by Cloudflare, so no account is resolved. Any org member may read.\n\nZone and DNS-record MANAGEMENT is not here: it stays on the Hanzo DNS plane\n(/v1/dns). This only surfaces the Cloudflare zone objects the asset plane needs\n— a zone id is what addresses a Worker route or an analytics read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name filters to the zone with this domain name.",
|
||||
"type": "string"
|
||||
},
|
||||
"order": {
|
||||
"description": "Order names the field to sort by, and Direction sorts asc or desc.",
|
||||
"type": "string"
|
||||
},
|
||||
"page": {
|
||||
"description": "Page is the 1-based page of zones to return.",
|
||||
"type": "string"
|
||||
},
|
||||
"per_page": {
|
||||
"description": "PerPage is how many zones one page holds.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status filters by zone status (active, pending, initializing, …).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_zones"
|
||||
},
|
||||
{
|
||||
"description": "ZoneGet reads one Cloudflare zone the org's token can see. Any org member may\nread. A zone id the token cannot see is Cloudflare's own not-found, relayed.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_zones_zone"
|
||||
},
|
||||
{
|
||||
"description": "ZoneAnalytics reads a zone's Cloudflare traffic dashboard — requests, bandwidth,\nthreats and pageviews over the since/until window. Any org member may read.\n\nA zone whose Cloudflare plan does not serve this endpoint yields Cloudflare's\nOWN error, never a fabricated success.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"continuous": {
|
||||
"description": "Continuous asks Cloudflare for only fully-aggregated buckets.",
|
||||
"type": "string"
|
||||
},
|
||||
"since": {
|
||||
"description": "Since and Until bound the window, in the form Cloudflare accepts — an RFC 3339\ntime or a negative number of minutes from now (\"-1440\" is the last day).",
|
||||
"type": "string"
|
||||
},
|
||||
"until": {
|
||||
"type": "string"
|
||||
},
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_cloudflare_zones_zone_analytics"
|
||||
},
|
||||
{
|
||||
"description": "D1DatabaseCreate creates a D1 database on the org's Cloudflare account.\nRequires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the database name to create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_d1_databases"
|
||||
},
|
||||
{
|
||||
"description": "KVNamespaceCreate creates a Workers KV namespace on the org's Cloudflare\naccount. Requires org admin. Cloudflare mints the namespace id the value routes\naddress.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"title": {
|
||||
"description": "Title is the namespace's display title. Cloudflare mints the id.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_kv_namespaces"
|
||||
},
|
||||
{
|
||||
"description": "PagesCreate creates a Cloudflare Pages project on the org's account. Requires\norg admin. Only the modeled fields reach Cloudflare, so an unmodeled key in the\nrequest is dropped rather than forwarded.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"PagesBuildConfig": {
|
||||
"properties": {
|
||||
"build_command": {
|
||||
"type": "string"
|
||||
},
|
||||
"destination_dir": {
|
||||
"type": "string"
|
||||
},
|
||||
"root_dir": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesD1Binding": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesDeploymentConfig": {
|
||||
"properties": {
|
||||
"compatibility_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"compatibility_flags": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"d1_databases": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/PagesD1Binding"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"env_vars": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/PagesEnvVar"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"kv_namespaces": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/PagesKVBinding"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"r2_buckets": {
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/PagesR2Binding"
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesDeploymentConfigs": {
|
||||
"properties": {
|
||||
"preview": {
|
||||
"$ref": "#/$defs/PagesDeploymentConfig"
|
||||
},
|
||||
"production": {
|
||||
"$ref": "#/$defs/PagesDeploymentConfig"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesEnvVar": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesKVBinding": {
|
||||
"properties": {
|
||||
"namespace_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"PagesR2Binding": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"build_config": {
|
||||
"$ref": "#/$defs/PagesBuildConfig"
|
||||
},
|
||||
"deployment_configs": {
|
||||
"$ref": "#/$defs/PagesDeploymentConfigs"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"production_branch": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_pages_projects"
|
||||
},
|
||||
{
|
||||
"description": "PagesDomainAdd attaches a custom domain to a Cloudflare Pages project. Requires\norg admin. Cloudflare owns validation and certificate issuance from here on.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the custom domain to attach, e.g. \"www.acme.com\".",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project is the Pages project name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_pages_projects_project_domains"
|
||||
},
|
||||
{
|
||||
"description": "R2BucketCreate creates an R2 bucket on the org's Cloudflare account. Requires\norg admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the bucket name to create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_r2_buckets"
|
||||
},
|
||||
{
|
||||
"description": "WorkersScriptSubdomainSet publishes or withdraws one Worker script on the\naccount's workers.dev subdomain. Requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"description": "Enabled publishes the script on \u003cscript\u003e.\u003csubdomain\u003e.workers.dev when true,\nand withdraws it when false.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script is the Worker script name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_workers_scripts_script_subdomain"
|
||||
},
|
||||
{
|
||||
"description": "WorkersRouteCreate binds a URL pattern in a zone to a Worker script. Requires\norg admin — a route is what puts a script in front of live traffic.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"description": "Pattern is the URL pattern to bind, e.g. \"acme.com/api/*\".",
|
||||
"type": "string"
|
||||
},
|
||||
"script": {
|
||||
"description": "Script is the Worker script to dispatch to. Omit it to leave the pattern\nbound to no script, which is how Cloudflare expresses \"bypass the Worker here\".",
|
||||
"type": "string"
|
||||
},
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_workers_zones_zone_routes"
|
||||
},
|
||||
{
|
||||
"description": "ZonePurge drops a zone's Cloudflare edge cache — either the whole zone\n(purge_everything) or exactly the listed file URLs. Requires org admin.\n\nPurging is the one zone-scoped WRITE this plane owns. It is not DNS — no record\nchanges — so it does not belong on /v1/dns, and it is not a connection, so it does\nnot belong on the integrations plane. It is a cache operation on a zone, which is\nwhat this asset plane is for. It takes the admin gate because dropping a zone's\ncache sends every subsequent request to the origin: on a site fronting a small\norigin that is a self-inflicted load spike, so it is a change, not a look.\n\nExactly one selector is required. Cloudflare treats a body with neither as a\nno-op and answers 200, which reads as \"purged\" to a caller that never purged\nanything — the failure we refuse to pass through.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"files": {
|
||||
"description": "Files purges exactly the listed URLs — at most 30, Cloudflare's per-request cap.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"purge_everything": {
|
||||
"description": "Everything drops the zone's entire edge cache.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"zone": {
|
||||
"description": "Zone is the 32-hex Cloudflare zone id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_cloudflare_zones_zone_purge"
|
||||
}
|
||||
]
|
||||
@@ -1,151 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Answers a question about the caller org's code with a CITED answer:\nretrieval packs grounding context, then the synthesizer writes the answer over\nexactly those spans, which come back alongside it. It never answers without\ngrounding — with no matched code the answer is empty and says so, and with no\nsynthesizer available the citations still come back with \"degraded\": true so\nthe caller can reason over the spans itself.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"q": {
|
||||
"description": "Q is the question to answer. Required, max 4000 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo narrows retrieval to one repository. Empty searches every repo the org\nhas indexed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_code_ask"
|
||||
},
|
||||
{
|
||||
"description": "Returns the INDEXED content of one file — read_file over the chunks the\nsearch tiers hold, for pulling up code an agent just found. It is NOT\nbyte-verbatim: the git object plane is the source of record for exact bytes,\nhistory and blame. A file absent from the index is a 404, so an agent can tell\n\"not indexed\" from \"empty file\".",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"path": {
|
||||
"description": "Path is the file's repo-relative path. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo is the repository the file belongs to. REQUIRED.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_code_file"
|
||||
},
|
||||
{
|
||||
"description": "Finds code in the caller org's index across three orthogonal retrieval\ntiers fused by reciprocal-rank fusion: lexical (FTS5 trigram over\ncode-tokenized text), symbolic (real definition and reference edges), and\nsemantic (embedding cosine over AST-boundary chunks). Pick one tier with\n`type`, or leave it to run all three as hybrid, which is what a coding agent\nusually wants. It is FAIL-HONEST: a retrieval outage answers 200 with an empty\nresult set and \"degraded\": true rather than a 5xx, so an agent degrades instead\nof stalling. A malformed regex is a 400.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps how many spans come back: default 20, maximum 100. A value that\nis not a positive integer reads as the default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"q": {
|
||||
"description": "Q is the search query. Required, max 4000 bytes. For type=regex it is a\nregular expression; for type=symbol it is a symbol name.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo narrows to one repository. Empty searches every repo the org has indexed.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type selects the retrieval tier: \"text\" (FTS5 trigram), \"regex\",\n\"symbol\" (definitions), \"semantic\" (embeddings) or \"hybrid\". Anything\nelse — including empty — reads as hybrid.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_code_search"
|
||||
},
|
||||
{
|
||||
"description": "Returns one repository's file structure with a per-file symbol count —\nget_repo_structure over the org's own index, with no git checkout involved. A\nrepository that has not been indexed answers an empty tree rather than an\nerror, so an agent can tell \"nothing here\" without handling a failure.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"repo": {
|
||||
"description": "Repo is the repository to walk. REQUIRED — a tree is repo-scoped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_code_tree"
|
||||
},
|
||||
{
|
||||
"description": "Is askGet with the question in the request BODY, for a question too\nlong or too awkward to put in a URL. `query` and `repo` in the body take\nprecedence over `?q=` and `?repo=`; either source works alone.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"query": {
|
||||
"description": "Query is the question, from the BODY. Takes precedence over `?q=`.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo is the repository narrowing, from the BODY. Takes precedence over `?repo=`.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_code_ask"
|
||||
},
|
||||
{
|
||||
"description": "Packs the most relevant code for a query into a token budget — THE\nprimitive for a coding agent that has to decide what to put in a prompt. It\nretrieves seed spans, expands each with the definitions it calls and its key\ncallers, then greedily fills the budget, so the answer is a coherent slice of\nthe codebase rather than a list of disconnected matches. The top match is\nalways included, truncated if it alone overflows, so a matched query never\ncomes back empty. A retrieval outage answers 200 with an empty bundle rather\nthan a 5xx.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"budgetTokens": {
|
||||
"description": "BudgetTokens caps the bundle's size. Clamped to [256, 32000]; 0 or absent\nuses 4000.",
|
||||
"type": "integer"
|
||||
},
|
||||
"query": {
|
||||
"description": "Query is what to retrieve context for. Required, max 4000 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo narrows retrieval to one repository. Empty searches every repo the org\nhas indexed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_code_context"
|
||||
},
|
||||
{
|
||||
"description": "(re)indexes a repository for the caller's org, incrementally: files whose\ncontent hash is unchanged are skipped, so re-sending a whole tree is cheap.\nEach file is parsed for symbols, split at AST boundaries and — when the\nsemantic tier is available — embedded, which is what makes it searchable across\nall three retrieval tiers. Pass `prune` to also DELETE indexed files absent\nfrom the request, which turns the call into a full sync; without it the call is\nan upsert. The index is written to the caller org's own physically separate\ndatabase.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"fileInput": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content is the file's full text. Max 1 MiB per file; binary files should\nsimply be omitted rather than sent.",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is the file's repo-relative path, e.g. \"internal/store/db.go\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"files": {
|
||||
"description": "Files is the full set of files to index. Required and non-empty; max 20000\nfiles, 1 MiB per file and 1 GiB in total. Unchanged files are skipped by\ncontent hash, so re-sending the whole tree is cheap.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/fileInput"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"prune": {
|
||||
"description": "Prune deletes indexed files that are NOT in this request — which makes the\ncall a full sync of the repo rather than an upsert. Only pass it when Files\nis the complete tree.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo is the repository label to index under. Required, max 200 bytes. It is\na stored column value, not a filesystem path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_code_index"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,341 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Get returns the caller org's formation and the stages reachable from it, or 404\nwhen the org has not begun one.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_company"
|
||||
},
|
||||
{
|
||||
"description": "ListRegister returns the platform's whole formation register, newest activity\nfirst — every org's formation, not the caller's. It is a Hanzo platform\noperation: a caller who is not a platform reviewer gets 403.\n\nFilter by stage and structure, page with limit and offset. An unknown stage is\nrefused with 400 rather than returning a silently empty page.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page; 0 or less means the default of 200.",
|
||||
"type": "integer"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Offset skips that many rows.",
|
||||
"type": "integer"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage keeps only formations at that stage. Empty means any.",
|
||||
"type": "string"
|
||||
},
|
||||
"structure": {
|
||||
"description": "Structure keeps only formations of that entity kind. Empty means any.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_company_register"
|
||||
},
|
||||
{
|
||||
"description": "SummarizeRegister counts the platform's formations by stage — the register's\nshape in one read, so a queue that is growing is visible as a number rather\nthan inferred by paging the list. A Hanzo platform operation: a caller who is\nnot a platform reviewer gets 403.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_company_register_summary"
|
||||
},
|
||||
{
|
||||
"description": "ReviewQueue reports the founders whose KYC is not yet settled, oldest formation\nfirst, so the queue drains in the order founders have been waiting. A Hanzo\nplatform operation: a caller who is not a platform reviewer gets 403.\n\nIt only says who is waiting; the decision itself is POST\n/v1/company/kyc/decision.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds how many formations are scanned; 0 or less means the default of 200.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_company_review"
|
||||
},
|
||||
{
|
||||
"description": "Begin starts the org's one formation and returns it with the stages reachable\nfrom it. It is idempotent: an org that already has a formation gets that one\nback with 200, while a first call creates it and answers 201.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"alreadyIncorporated": {
|
||||
"description": "AlreadyIncorporated declares an org that already has an entity, which takes\nthe import path (POST /v1/company/skip) instead of the formation path.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"jurisdiction": {
|
||||
"description": "Jurisdiction is the state of formation: DE or WY.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the proposed company name.",
|
||||
"type": "string"
|
||||
},
|
||||
"structure": {
|
||||
"description": "Structure is the legal entity to form: c-corp, llc or dao-llc.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company"
|
||||
},
|
||||
{
|
||||
"description": "Advance runs the ONE guarded transition of the formation machine. It is the\nonly door between stages: the actions populate data, this decides ordering.\n\nAn edge the machine does not define answers 409; an edge whose guard is not yet\nsatisfied answers 422 naming what is missing. Reaching the terminal `company`\nstage also records the incorporation on the canonical cap table, and that must\nsucceed before the transition is persisted.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"to": {
|
||||
"description": "To is the target stage: structure, founders, payment, documents, esign,\ngenesis, import or company.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_advance"
|
||||
},
|
||||
{
|
||||
"description": "GenerateDocuments renders the formation documents for the chosen structure and\njurisdiction, ingests each into the org's data room, and submits the state\nfiling through the filing seam.\n\nWith no filing partner wired the filing is recorded honestly as \"manual\" — no\nfiling id is fabricated. Available only at the documents stage.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_documents"
|
||||
},
|
||||
{
|
||||
"description": "RequestEsign sends the generated formation documents for signature by every\nfounder and records the provider's reference on the formation. Available only\nat the esign stage.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_esign"
|
||||
},
|
||||
{
|
||||
"description": "CompleteEsign records whether the formation documents have been signed. It\nconsults the e-signature provider, which a real provider's webhook drives; the\nsignal is idempotent.\n\nAn explicit `signed` in the request overrides the provider's answer, which is\nthe manual path for the stub provider that never self-completes.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"signed": {
|
||||
"description": "Signed, when present, overrides what the provider reports — the manual path\nfor a provider whose webhook is not wired. Omit it to take the provider's answer.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_esign_complete"
|
||||
},
|
||||
{
|
||||
"description": "SetFounders replaces the formation's founders. Each founder needs a name, an\nemail and an equity share in basis points; every founder is (re)set to pending\nKYC, so a previously settled decision does not survive a change of the list.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Founder": {
|
||||
"properties": {
|
||||
"decidedBy": {
|
||||
"description": "DecidedBy is who settled a terminal KYC status: the provider name, or a\nreviewer's user id.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Email is the founder's email, and the key a KYC decision addresses a founder\nby — POST /v1/company/kyc/decision matches on it.",
|
||||
"type": "string"
|
||||
},
|
||||
"equityBps": {
|
||||
"description": "EquityBps is the founder's ownership in basis points, 0–10000 (1% == 100 bps,\nso 10000 is the whole company). The founders' shares seed the cap-table genesis.",
|
||||
"type": "integer"
|
||||
},
|
||||
"kycRef": {
|
||||
"description": "KYCRef is the idv provider's session reference for this founder.",
|
||||
"type": "string"
|
||||
},
|
||||
"kycStatus": {
|
||||
"description": "KYCStatus is the founder's identity-verification state: pending, verified (a\nreal idv provider reported a pass), reviewer_confirmed (a privileged reviewer\nconfirmed out-of-band) or failed. The payment stage is unreachable until every\nfounder passes.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the founder's full legal name, as it appears on the formation documents.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"founders": {
|
||||
"description": "Founders is every founding stakeholder. Each needs a name and an email, and\nequityBps between 0 and 10000 (1% == 100 bps).",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Founder"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_founders"
|
||||
},
|
||||
{
|
||||
"description": "RecordRound records a fundraising round on the org's canonical cap table.\nAvailable only after incorporation (stage company); roundType defaults to\nPRICED.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the round's name on the cap table, e.g. \"Seed\". Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"preMoneyValuation": {
|
||||
"description": "PreMoneyValuation is the valuation the round prices off, before the new money.",
|
||||
"type": "number"
|
||||
},
|
||||
"pricePerShare": {
|
||||
"description": "PricePerShare is the per-share price of a priced round.",
|
||||
"type": "number"
|
||||
},
|
||||
"roundType": {
|
||||
"description": "RoundType is PRICED, SAFE or CONVERTIBLE_NOTE. Defaults to PRICED.",
|
||||
"type": "string"
|
||||
},
|
||||
"shareClassId": {
|
||||
"description": "ShareClassID is the cap table's share class the round issues into.",
|
||||
"type": "string"
|
||||
},
|
||||
"targetAmount": {
|
||||
"description": "TargetAmount is the amount the round is raising, recorded verbatim on the\ncanonical cap table's rounds.create contract.",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_fundraise_round"
|
||||
},
|
||||
{
|
||||
"description": "RequestSafe raises an e-signature request over documents already in the org's\ndata room — a SAFE, a convertible note, or any other fundraising paper.\nAvailable only after incorporation (stage company).",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Signer": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email is the address the signature request is sent to.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the recipient's name, as it appears on the signature request.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"documentIds": {
|
||||
"description": "DocumentIDs are data room document ids to raise a signature request over. Required.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"signers": {
|
||||
"description": "Signers are the recipients, each a name and an email. Required.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Signer"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_fundraise_safe"
|
||||
},
|
||||
{
|
||||
"description": "RecordGenesis seeds the canonical cap table with the founding allocation\n(stakeholders, a common share class, issued shares) and anchors the\ndeterministic equity-genesis root on-chain.\n\nIt is idempotent: once a root is recorded the cap table is NOT re-seeded, which\nwould double-issue founder share certificates. The root is persisted even when\nthe on-chain submit fails, because the root is the tamper-evident witness and\nmust not be recomputed on retry. Available only at the genesis stage.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_genesis"
|
||||
},
|
||||
{
|
||||
"description": "ImportCapTable reads an existing company's cap table from a Google Sheet and\nadds its stakeholders to the canonical cap table.\n\nThe first row is a header and columns are matched by name (case-insensitive):\nname and email are required, type/relationship/institution optional. A sheet\nwithout name and email columns, or with no usable data rows, is refused with\n400. Available only at the import stage.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"range": {
|
||||
"description": "Range is an optional A1 range within the sheet; empty reads the default range.",
|
||||
"type": "string"
|
||||
},
|
||||
"spreadsheetId": {
|
||||
"description": "SpreadsheetID is a Google Sheets id. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_import_captable"
|
||||
},
|
||||
{
|
||||
"description": "ImportDocuments ingests an existing company's corporate documents from a Google\nDrive folder into the org's data room. The import is shallow — sub-folders are\nskipped, not walked — and available only at the import stage.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"folderId": {
|
||||
"description": "FolderID is a Google Drive folder id. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_import_documents"
|
||||
},
|
||||
{
|
||||
"description": "StartKYC opens an identity-verification session for every founder with the\nwired provider and records each session's reference on the formation.\n\nA start is never a decision: any terminal status the provider reports at\ninquiry time is clamped back to pending, so the payment gate can never open\nhere. A terminal status arrives only from POST /v1/company/kyc/refresh (the\nprovider) or POST /v1/company/kyc/decision (a Hanzo platform reviewer).",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_kyc"
|
||||
},
|
||||
{
|
||||
"description": "DecideKYC records a privileged reviewer's MANUAL decision on a founder's KYC —\nthe human-in-the-loop path, and the ONLY route to a pass when no real provider\nis wired. It produces a DISTINCT reviewer_confirmed, never a provider\n\"verified\".\n\nBecause Hanzo forms the entity and carries the formation KYC/AML obligation,\nthe reviewer is a HANZO platform reviewer (SuperAdmin), and the decision is\nATTRIBUTED to them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email identifies the founder on the formation.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the decision: reviewer_confirmed or failed. Nothing else is accepted.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_kyc_decision"
|
||||
},
|
||||
{
|
||||
"description": "RefreshKYC reconciles each pending founder's KYC with the WIRED provider — the\nPULL path to a provider-reported terminal status. For the manual provider the\ncheck stays pending; for a real provider it reflects the settled decision,\nATTRIBUTED to the provider.\n\nIt NEVER trusts a client-asserted status — the status comes from the provider\nseam — so a client cannot force a pass here, and an already-passing founder\n(e.g. a reviewer confirmation) is left untouched.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_kyc_refresh"
|
||||
},
|
||||
{
|
||||
"description": "Skip marks the org as already incorporated and moves it onto the import path,\nso an existing company brings its documents and cap table in instead of forming\na new entity. Available only at the structure stage.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_company_skip"
|
||||
},
|
||||
{
|
||||
"description": "SetStructure records the entity kind, the state of formation and the proposed\nname. Available only at the structure stage; an unknown structure or\njurisdiction, or an empty name, is refused with 400.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"jurisdiction": {
|
||||
"description": "Jurisdiction is the state of formation: DE or WY.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the proposed company name.",
|
||||
"type": "string"
|
||||
},
|
||||
"structure": {
|
||||
"description": "Structure is the legal entity: c-corp, llc or dao-llc.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_company_structure"
|
||||
}
|
||||
]
|
||||
@@ -1,260 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListAccreditation returns the org's tracked accreditation-state records, newest\nfirst — evidence entries the org keeps, never a platform certification.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; non-positive means the server default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_accreditation"
|
||||
},
|
||||
{
|
||||
"description": "GetAccreditation returns one tracked accreditation record.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the accreditation record to read, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_accreditation_id"
|
||||
},
|
||||
{
|
||||
"description": "AuditRead is the compliance-scoped read of the SHARED tamper-evident audit plane —\nthe SOC 2 posture surface (privileged actions: who started/decided what, when). The\norg is PINNED to the caller's validated org and the rows are narrowed to\ncompliance.* actions. Fail-closed: no principal is a 403, no configured audit\nstore a 501.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"result": {
|
||||
"description": "Result filters rows by outcome result: success, deny, or error; empty means all.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_audit"
|
||||
},
|
||||
{
|
||||
"description": "Health reports subsystem liveness and the wired verification provider. Fail-open\non purpose: it never probes the external provider, so a provider outage cannot\nfail liveness.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_health"
|
||||
},
|
||||
{
|
||||
"description": "ListRecords is the unified compliance-record view for the org: its verifications\nand accreditation records together, each provider-reported or tracked, never\nplatform-asserted. PII stays in the subject store; records carry only opaque ids\nand statuses.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; non-positive means the server default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_records"
|
||||
},
|
||||
{
|
||||
"description": "Status is the org's honest posture read: the wired provider and the per-status\ntally of its verifications. It is deliberately NOT a boolean \"compliant\" — it\nreports counts of provider-reported states and carries the boundary disclaimer.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_status"
|
||||
},
|
||||
{
|
||||
"description": "ListSubjects returns the org's subjects as PII-MINIMIZED summaries — no name or\nemail, only whether an email is on file. The full record is returned only by the\nexplicit single-subject read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; non-positive means the server default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_subjects"
|
||||
},
|
||||
{
|
||||
"description": "GetSubject returns one subject WITH its contact PII — the only surface that\nreturns it, and only to the owning org. The response is never cached by any\nintermediary.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the subject to read, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_subjects_id"
|
||||
},
|
||||
{
|
||||
"description": "ListVerifications returns the org's KYC/KYB verifications, newest first — opaque\nsubject references and provider-reported statuses only, no subject PII.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; non-positive means the server default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_verifications"
|
||||
},
|
||||
{
|
||||
"description": "GetVerification returns one verification — its opaque subject reference and\nprovider-reported status, no subject PII.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the verification to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_compliance_verifications_id"
|
||||
},
|
||||
{
|
||||
"description": "CreateAccreditation records an ASSERTED accreditation state for a subject — the\nsubject's own assertion, with no verifier. Every CONFIRMED state\n(provider_verified, reviewer_confirmed) and every rejected/expired state is a\nDECISION recorded via the decision endpoint, attributed to the reviewer — a\ncreate can never stamp a confirmation. The underlying figures (income, net\nworth) are never stored; only the method, category, and state.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"basis": {
|
||||
"description": "Basis is the qualification category: income, net_worth, professional_license,\nor entity.",
|
||||
"type": "string"
|
||||
},
|
||||
"evidenceDocId": {
|
||||
"description": "EvidenceDocID references an evidence document in the org's sealed data room.",
|
||||
"type": "string"
|
||||
},
|
||||
"expiresAt": {
|
||||
"description": "ExpiresAt is the unix second a confirmation ages out; 0 means none.",
|
||||
"type": "integer"
|
||||
},
|
||||
"method": {
|
||||
"description": "Method is how the state was established: self_attested, third_party_letter,\nor provider_verified.",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"description": "Note is a non-PII operator note.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status may only be \"asserted\" (empty reads as asserted); every confirmed,\nrejected or expired state is recorded via the decision endpoint.",
|
||||
"type": "string"
|
||||
},
|
||||
"subjectId": {
|
||||
"description": "SubjectID names the subject this record is about; it must exist within the org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_accreditation"
|
||||
},
|
||||
{
|
||||
"description": "DecideAccreditation records an org reviewer's decision on an accreditation\nrecord — a reviewer confirmation, a provider verification the reviewer has\nevidence of (a CPA/attorney letter, a verifier report), a rejection, or an\nexpiry. ROLE-GATED (an org admin or platform reviewer) and ATTRIBUTED: the\nreviewer's identity is recorded as ReviewerSub and audited. Human-in-the-loop:\nthe platform never confirms on its own, and even a provider_verified state\ncarries the reviewer who recorded it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the accreditation record to decide, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the decision being recorded: reviewer_confirmed, provider_verified,\nrejected, or expired.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_accreditation_id_decision"
|
||||
},
|
||||
{
|
||||
"description": "CreateSubject records a party the org is verifying as part of its own\nonboarding/compliance — a team member, vendor, customer, or counterparty. The\nsubject's contact PII (name/email) is sealed at rest and returned only to the\nowning org; downstream records reference the subject by opaque id.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email is the subject's contact email, sealed at rest.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is the party type: \"individual\" (KYC) or \"business\" (KYB).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the subject's name, sealed at rest.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is the org's own opaque external id for this subject.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_subjects"
|
||||
},
|
||||
{
|
||||
"description": "StartVerification begins a KYC/KYB verification of a subject through the wired\nprovider — an existing subject by id, or one created inline from the request.\nThe returned status is provider-reported and never terminal on a fresh start:\nstarting a verification can never yield a verified record, and a provider error\nis a 502, never a verification.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"description": "Email is an inline subject's contact email, sealed at rest.",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is an inline subject's party type: \"individual\" (KYC) or \"business\" (KYB).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is an inline subject's name, sealed at rest.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is the org's own opaque external id for an inline subject.",
|
||||
"type": "string"
|
||||
},
|
||||
"subjectId": {
|
||||
"description": "SubjectID names an existing subject to verify; empty creates one inline.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_verifications"
|
||||
},
|
||||
{
|
||||
"description": "DecideVerification records a privileged reviewer's MANUAL decision on a\nverification — the human-in-the-loop path, and the ONLY route to a passing status\nwhen no real provider is wired. It produces a DISTINCT reviewer_confirmed, never\na provider_verified (a provider decision is the provider's to report, via the\nwebhook or a reconcile), and it is ROLE-GATED (an org admin or platform reviewer)\nAND ATTRIBUTED (the reviewer's user id is DecidedBy), so a manual pass is always\naccountable.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the verification to decide, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the reviewer's decision: \"reviewer_confirmed\" (a pass) or\n\"manual_review\" (withheld for review) — never a provider status.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_verifications_id_decision"
|
||||
},
|
||||
{
|
||||
"description": "RefreshVerification polls the wired provider for its current decision and\nrecords it, ATTRIBUTED to the provider — the internal PULL reconcile. For the\nManual provider the check stays pending; for a hosted provider it reflects the\nprovider's settled status. A poll error is a 502, never a verification.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the verification to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_compliance_verifications_id_refresh"
|
||||
}
|
||||
]
|
||||
@@ -1,87 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "GetBoard aggregates the caller org's marketing content across every publishable\ncontent type into ONE queue board — the cross-type read the framework's\nper-DocType list cannot give. It never fails on a partial outage: a content type\nthe org has not installed, or one whose search errors, is skipped and logged\nrather than failing the whole board.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType keeps only one content type; omitted, the board spans every\npublishable type. An unknown type is refused.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned, clamped to 1000. Defaults to 200, which is also\nwhat a non-positive or unparseable value takes.",
|
||||
"type": "integer"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project keeps only items in one brand/site sub-scope.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status keeps only items in one lifecycle state (draft, in_review, approved,\nqueued, published, archived). An undefined state is refused.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_content_board"
|
||||
},
|
||||
{
|
||||
"description": "GetChannels lists the distribution channels the caller's org has connected — the\nsocial integrations a publish can target. A deployment with no distribution edge\nwired answers 503 rather than an empty list that would read as \"no channels\".",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_content_channels"
|
||||
},
|
||||
{
|
||||
"description": "GetLifecycle returns the ONE marketing-content state machine: the ordered\nlifecycle states, which state a fresh document starts in, which one is publicly\nlive, and the legal successors of every state. The console builds its board\ncolumns and its per-item action buttons from this single answer, so the UI and\nthe write-time enforcement hook can never disagree about what is legal.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_content_lifecycle"
|
||||
},
|
||||
{
|
||||
"description": "PostTransition moves one content item to a new lifecycle state and, on the move to\npublished, fans it out to the item's channels. The edge must be legal for the\nitem's current state — an illegal move is refused with 409 — and the status write\nre-validates it at the storage boundary. Distribution is best effort: its honest\nstate is reported on the result and a distribution failure never rolls the status\nchange back.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the content type to act on, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the document to act on, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"scheduleAt": {
|
||||
"description": "ScheduleAt is an ISO-8601 go-live time handed to the channel's own scheduler;\n\"\" distributes now.",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the lifecycle state to move to. Required, and the move must be a legal\nedge from the item's current state.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_content_doctype_name_transition"
|
||||
},
|
||||
{
|
||||
"description": "Publish distributes one CMS content item to the channels recorded on it and\nreturns the honest per-channel outcome. The item names itself — its caption,\nmedia and channel list are read from the stored document, not from this request.\nIt is idempotent per channel (a channel already posted for this item is skipped),\nand a publish that loses the per-item lease to a live publisher answers status\n\"in_progress\" having posted nothing.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"scheduleAt": {
|
||||
"description": "\"\" = now",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_content_publish"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,476 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteCompany removes one of the caller org's companies and answers 204. Any\ncontact or opportunity in the org that referenced it keeps existing with the\nreference cleared, so nothing is left pointing at a company that is gone.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_crm_companies_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteContact removes one of the caller org's contacts and answers 204. Any\nopportunity in the org that named it point of contact keeps existing with\nthat reference cleared.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_crm_contacts_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteOpportunity removes one of the caller org's deals and answers 204.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_crm_opportunities_id"
|
||||
},
|
||||
{
|
||||
"description": "ListApplications returns the org's Startup Program applications, newest first.\nEach carries its AI screen and its stage history; a stage narrows the page to\none pipeline stage.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned: 200 by default, 1000 at most.",
|
||||
"type": "integer"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage returns only the applications at that pipeline stage when set:\napplied, screened, qualified, credits-offered, onboarded or rejected.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_applications"
|
||||
},
|
||||
{
|
||||
"description": "GetApplication returns one Startup Program application with its AI screen and stage history.\nAn id belonging to another org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_applications_id"
|
||||
},
|
||||
{
|
||||
"description": "ListCompanies returns the caller org's companies, most recently updated first.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned: 200 by default, 1000 at most.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_companies"
|
||||
},
|
||||
{
|
||||
"description": "GetCompany returns one of the caller org's companies. An id belonging to\nanother org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_companies_id"
|
||||
},
|
||||
{
|
||||
"description": "ListContacts returns the caller org's contacts, most recently updated first.\nA companyId narrows the page to the people at that company.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"companyId": {
|
||||
"description": "CompanyID returns only the contacts at that company when set.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned: 200 by default, 1000 at most.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_contacts"
|
||||
},
|
||||
{
|
||||
"description": "GetContact returns one of the caller org's contacts. An id belonging to\nanother org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_contacts_id"
|
||||
},
|
||||
{
|
||||
"description": "ListOpportunities returns the caller org's deals, most recently updated first.\nA stage narrows the page to one pipeline stage.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned: 200 by default, 1000 at most.",
|
||||
"type": "integer"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage returns only the opportunities at that pipeline stage when set\n(NEW, SCREENING, MEETING, PROPOSAL or CUSTOMER; case-insensitive).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_opportunities"
|
||||
},
|
||||
{
|
||||
"description": "GetOpportunity returns one of the caller org's deals. An id belonging to\nanother org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the record to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_opportunities_id"
|
||||
},
|
||||
{
|
||||
"description": "Summary counts the caller org's CRM records: companies, contacts, opportunities.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_crm_summary"
|
||||
},
|
||||
{
|
||||
"description": "PatchApplication moves one Startup Program application through the pipeline. The\nmove is recorded on the application's timeline, attributed to the calling\nstaff user: it may advance exactly one stage, go back to any earlier stage,\nreject from any non-rejected stage, or reopen a rejected application to\n`applied`; anything else is refused. Rejecting requires a reason. A note with\nno stage change is still recorded.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the application to move, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"note": {
|
||||
"description": "Note is a free-text comment recorded on the timeline, with or without a\nstage change.",
|
||||
"type": "string"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason records WHY, and is required to reject.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage is the stage to move to: applied, screened, qualified,\ncredits-offered, onboarded or rejected. Omit to leave the stage alone.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_crm_applications_id"
|
||||
},
|
||||
{
|
||||
"description": "CreateCompany adds a company to the caller's org and answers 201 with the stored record.\nA name is required; an empty currency defaults to USD.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"arr": {
|
||||
"description": "ARR is annual recurring revenue in minor units (cents) of Currency.",
|
||||
"type": "integer"
|
||||
},
|
||||
"city": {
|
||||
"description": "City is the head-office city.",
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"description": "Country is the head-office country.",
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code ARR is denominated in; empty defaults to USD.",
|
||||
"type": "string"
|
||||
},
|
||||
"domainName": {
|
||||
"description": "DomainName is the company's primary domain, e.g. \"acme.com\".",
|
||||
"type": "string"
|
||||
},
|
||||
"employees": {
|
||||
"description": "Employees is the headcount.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the company to update and comes from the path. A create ignores\nit: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"idealCustomerProfile": {
|
||||
"description": "ICP marks the company as an ideal-customer-profile fit.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"linkedinLink": {
|
||||
"description": "Linkedin is the company's LinkedIn URL.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the company name. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"xLink": {
|
||||
"description": "XLink is the company's X (Twitter) URL.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_crm_companies"
|
||||
},
|
||||
{
|
||||
"description": "CreateContact adds a person to the caller's org and answers 201 with the stored record.\nOne of firstName, lastName or email is required, and a companyId must name a\ncompany in the same org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"description": "City is where the person is based.",
|
||||
"type": "string"
|
||||
},
|
||||
"companyId": {
|
||||
"description": "CompanyID links the contact to one of the org's companies.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Email is the person's email address.",
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"description": "FirstName is the person's given name.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the contact to update and comes from the path. A create ignores\nit: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"jobTitle": {
|
||||
"description": "JobTitle is the person's role at their company.",
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"description": "LastName is the person's family name.",
|
||||
"type": "string"
|
||||
},
|
||||
"linkedinLink": {
|
||||
"description": "Linkedin is the person's LinkedIn URL.",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Phone is the person's phone number.",
|
||||
"type": "string"
|
||||
},
|
||||
"xLink": {
|
||||
"description": "XLink is the person's X (Twitter) URL.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_crm_contacts"
|
||||
},
|
||||
{
|
||||
"description": "CreateOpportunity adds a deal to the caller's org and answers 201 with the stored record.\nA name is required; the stage defaults to NEW; companyId and pointOfContactId\nmust name records in the same org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"description": "Amount is the deal value in minor units (cents) of Currency.",
|
||||
"type": "integer"
|
||||
},
|
||||
"closeDate": {
|
||||
"description": "CloseDate is the expected close, as a unix second (0 = unset).",
|
||||
"type": "integer"
|
||||
},
|
||||
"companyId": {
|
||||
"description": "CompanyID links the deal to one of the org's companies.",
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code Amount is denominated in; empty defaults to USD.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the opportunity to update and comes from the path. A create\nignores it: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the deal name. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"pointOfContactId": {
|
||||
"description": "PointOfContact links the deal to one of the org's contacts.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage is the pipeline stage: NEW, SCREENING, MEETING, PROPOSAL or CUSTOMER\n(case-insensitive). Empty defaults to NEW.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_crm_opportunities"
|
||||
},
|
||||
{
|
||||
"description": "UpdateCompany replaces one of the caller org's companies. Every writable\nfield is taken from the request, so a field the request omits is CLEARED —\nsend the whole record. A name is required.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"arr": {
|
||||
"description": "ARR is annual recurring revenue in minor units (cents) of Currency.",
|
||||
"type": "integer"
|
||||
},
|
||||
"city": {
|
||||
"description": "City is the head-office city.",
|
||||
"type": "string"
|
||||
},
|
||||
"country": {
|
||||
"description": "Country is the head-office country.",
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code ARR is denominated in; empty defaults to USD.",
|
||||
"type": "string"
|
||||
},
|
||||
"domainName": {
|
||||
"description": "DomainName is the company's primary domain, e.g. \"acme.com\".",
|
||||
"type": "string"
|
||||
},
|
||||
"employees": {
|
||||
"description": "Employees is the headcount.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the company to update and comes from the path. A create ignores\nit: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"idealCustomerProfile": {
|
||||
"description": "ICP marks the company as an ideal-customer-profile fit.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"linkedinLink": {
|
||||
"description": "Linkedin is the company's LinkedIn URL.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the company name. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"xLink": {
|
||||
"description": "XLink is the company's X (Twitter) URL.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_crm_companies_id"
|
||||
},
|
||||
{
|
||||
"description": "UpdateContact replaces one of the caller org's contacts. Every writable field\nis taken from the request, so a field the request omits is CLEARED — send the\nwhole record. One of firstName, lastName or email is required.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"city": {
|
||||
"description": "City is where the person is based.",
|
||||
"type": "string"
|
||||
},
|
||||
"companyId": {
|
||||
"description": "CompanyID links the contact to one of the org's companies.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Email is the person's email address.",
|
||||
"type": "string"
|
||||
},
|
||||
"firstName": {
|
||||
"description": "FirstName is the person's given name.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the contact to update and comes from the path. A create ignores\nit: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"jobTitle": {
|
||||
"description": "JobTitle is the person's role at their company.",
|
||||
"type": "string"
|
||||
},
|
||||
"lastName": {
|
||||
"description": "LastName is the person's family name.",
|
||||
"type": "string"
|
||||
},
|
||||
"linkedinLink": {
|
||||
"description": "Linkedin is the person's LinkedIn URL.",
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"description": "Phone is the person's phone number.",
|
||||
"type": "string"
|
||||
},
|
||||
"xLink": {
|
||||
"description": "XLink is the person's X (Twitter) URL.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_crm_contacts_id"
|
||||
},
|
||||
{
|
||||
"description": "UpdateOpportunity replaces one of the caller org's deals. Every writable\nfield is taken from the request, so a field the request omits is CLEARED —\nsend the whole record. A name is required and the stage must be a pipeline\nstage.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"amount": {
|
||||
"description": "Amount is the deal value in minor units (cents) of Currency.",
|
||||
"type": "integer"
|
||||
},
|
||||
"closeDate": {
|
||||
"description": "CloseDate is the expected close, as a unix second (0 = unset).",
|
||||
"type": "integer"
|
||||
},
|
||||
"companyId": {
|
||||
"description": "CompanyID links the deal to one of the org's companies.",
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency is the ISO code Amount is denominated in; empty defaults to USD.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the opportunity to update and comes from the path. A create\nignores it: the server mints the id.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the deal name. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"pointOfContactId": {
|
||||
"description": "PointOfContact links the deal to one of the org's contacts.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage is the pipeline stage: NEW, SCREENING, MEETING, PROPOSAL or CUSTOMER\n(case-insensitive). Empty defaults to NEW.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_crm_opportunities_id"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,114 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListDeployApplications returns the fleet as an argocd ApplicationList: one\nprojected Application per operator App CR, carrying the image tag the CR\nDECLARES, the tag actually RUNNING in the cluster's Deployment, the reconciled\nhealth, and the sync verdict those two produce (declared == running ⇒ Synced,\nboth known and different ⇒ OutOfSync, either unknown ⇒ Unknown).\n\nIt is TENANT-SCOPED: a platform SuperAdmin reads every platform namespace, a\nvalidated org member reads only its own org's tenant namespace and only the App\nCRs labelled with its org, and anyone else is refused. A cross-tenant CR is\nnever projected into an answer.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_applications"
|
||||
},
|
||||
{
|
||||
"description": "GetDeployApplication returns ONE projected argocd Application by name, with\nstatus.resources filled in from its reconciled resource tree — which is what\nmakes it the detail view rather than a row of the list.\n\nIt is TENANT-SCOPED, and a name that belongs to another org is reported NOT\nFOUND rather than refused: a 403 would confirm the application exists, so the\nroute would become a cross-tenant existence oracle. A name that is not a\nDNS-1123 label is a 400 before any cluster read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the application to read, from the path. It must be a DNS-1123 label\n(lowercase alphanumerics and hyphens, starting and ending alphanumeric) —\nevery operator App CR's metadata.name satisfies that, and anything else is a\n400 rather than a lookup.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_applications_name"
|
||||
},
|
||||
{
|
||||
"description": "GetDeployResourceTree returns one application's argocd ApplicationTree: the\nobjects the operator reconciled from its App CR, reached by ownerRef — the\nDeployment and, under it, the ReplicaSet and Pods, plus the Service, Ingress,\nHorizontalPodAutoscaler, PodDisruptionBudget and ConfigMaps it owns — each node\ncarrying its parent edges and its health.\n\nSecrets are DELIBERATELY not walked, so no materialized environment can ever\nappear in the tree. Tenant-scoped exactly like the application read: another\norg's name is not found, a malformed name is a 400.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the application to read, from the path. It must be a DNS-1123 label\n(lowercase alphanumerics and hyphens, starting and ending alphanumeric) —\nevery operator App CR's metadata.name satisfies that, and anything else is a\n400 rather than a lookup.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_applications_name_resource-tree"
|
||||
},
|
||||
{
|
||||
"description": "GetDeployRevisionMetadata returns the argocd RevisionMetadata for one revision\nof one application — what the detail view shows beside a revision.\n\nAn App CR is IMAGE-pinned rather than commit-pinned: the deploy names an image\ntag, and the git source this projection reports is the display-only manifest\nrepo, not the application's own source. Nothing in this process can read a\ncommit's author or message for an arbitrary revision. So rather than 404 (which\nthe SPA turns into an error toast) or invent a git author, it answers the\nHONEST minimum: date is when the App CR was created, message is the revision\nasked for — with the empty revision and \"HEAD\" resolving to the image tag the\nCR declares — and author is empty. An over-long revision is truncated before it\nis echoed back.\n\nTenant-scoped exactly like the application read.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the application to read, from the path. It must be a DNS-1123 label.",
|
||||
"type": "string"
|
||||
},
|
||||
"revision": {
|
||||
"description": "Revision is the revision to describe, from the path. The empty revision and\n\"HEAD\" both mean \"whatever this application currently declares\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_applications_name_revisions_revision_metadata"
|
||||
},
|
||||
{
|
||||
"description": "GetDeploySyncWindows returns one application's argocd\nApplicationSyncWindowState — the answer to \"is anything blocking a sync of this\napplication right now?\".\n\nThis platform runs NO sync windows, so the answer is always the permissive\nempty one: canSync true, with no active and no assigned windows. The\napplication is still resolved first, so a name that is not the caller's is not\nfound rather than handed the static body — the endpoint discloses nothing about\nanother tenant's fleet.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the application to read, from the path. It must be a DNS-1123 label\n(lowercase alphanumerics and hyphens, starting and ending alphanumeric) —\nevery operator App CR's metadata.name satisfies that, and anything else is a\n400 rather than a lookup.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_applications_name_syncwindows"
|
||||
},
|
||||
{
|
||||
"description": "ListDeployClusters returns the argocd ClusterList of the destinations the\ncaller's applications reconcile into: one entry per distinct destination\nserver, carrying the count of applications reconciling into it. The in-cluster\ndestination is always present, so an empty fleet still answers one cluster, and\nno cluster credential can appear — the projected type physically has no config\nfield.\n\nIt is TENANT-SCOPED and reads the SAME App CRs the applications list reads: a\nplatform SuperAdmin counts the whole fleet, a validated org member counts only\nits own org's applications, anyone else is refused.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_clusters"
|
||||
},
|
||||
{
|
||||
"description": "GetDeployGitOps lists every Hanzo CD Application in the cluster: the git source\neach one polls, the commit it last APPLIED, how its last sync operation ended,\nand its recent deploy history — newest deploy first, ordered by namespace then\nname.\n\nThis is the layer ABOVE the application board, and the two disagree in exactly\nthe case an operator most needs to see: main carries a new image pin, CD has\nnot applied that commit yet, so every App CR still declares the old tag and the\napplication board is legitimately \"Synced\" while the deploy has not landed.\nOnly the applied revision here can show that.\n\ninstalled is false — with a reason and an empty list — when the CD CRD is not\nserved in this cluster. That is a FACT about the cluster rather than a failure\nof the request, so the caller can say \"no CD plane here\" instead of rendering\nan error it cannot act on; a genuine transport or RBAC failure still errors.\n\nRead-only, and platform SuperAdmin only: the CD plane is fleet infrastructure\nwith no tenant dimension. This view observes CD and never drives it — the sync\npolicy is automated with self-heal, and the actionable verb an operator has is\nthe per-application reconcile at POST /v1/deploy/applications/{name}/sync.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_gitops"
|
||||
},
|
||||
{
|
||||
"description": "ListDeployProjects returns the argocd AppProjectList this console groups and\nfilters applications by. Projects are owned by Hanzo IAM rather than by argocd,\nso they are REFLECTED read-only from the IAM project store and nothing is\npersisted here: a validated org member gets its own organization's projects and\na platform SuperAdmin gets every organization's.\n\nA SuperAdmin whose IAM store is not reachable falls back to the real\nargoproj.io AppProject CRs when that CRD is served, and otherwise to one\npermissive synthesized project per distinct project name the App CRs declare.\nA project named \"default\" is always present, because that is what an App CR\ncarrying no project label projects to.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_projects"
|
||||
},
|
||||
{
|
||||
"description": "GetDeploySession answers \"is this browser signed in, and if not where does it\nsign in?\" — the dashboard SPA's bootstrap question, and the only route on this\nplane that answers for an anonymous caller.\n\nThe anonymous answer carries loggedIn:false and a URL and NOTHING else: no\nusername, no org, no groups, no issuer, no hint about who the caller might be or\nwhat exists in the cluster. Answering it costs nothing (the caller already knows\nwhether it holds a cookie) and withholding it costs the whole sign-in journey.\n\nThe predicate is the platform SuperAdmin fact — the SAME one every other route\nhere gates on, minted from a validated principal whose org is the reserved admin\norg — so a validated-but-not-SuperAdmin caller is reported as NOT signed in,\nwhich is the truth as this console defines it: they cannot use it.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_session_userinfo"
|
||||
},
|
||||
{
|
||||
"description": "GetDeploySettings returns the argocd AuthSettings object the dashboard SPA\nawaits before its first render.\n\nEvery value is a CONSTANT of this projection rather than configuration read\nfrom anywhere: the SPA's own login form is reported disabled and its OIDC\nconfig null because Hanzo IAM owns identity at the edge and this console's\nsign-in is GET /v1/deploy/login, and every argocd feature the projection does\nnot implement — status badges, Dex connectors, config-management plugins,\nkustomize versions, the exec terminal, apps-in-any-namespace, the hydrator,\nsync-with-replace — is reported off. Platform SuperAdmin only.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_settings"
|
||||
},
|
||||
{
|
||||
"description": "GetDeployVersion returns the argocd VersionMessage the dashboard SPA reads at\nbootstrap. There is no argocd binary behind this plane — it is a projection\nover operator App CRs — so the fields say so rather than describing a build:\nVersion names the projection, BuildDate is the moment this response was\ngenerated, and Compiler/Platform/GoVersion are the constants the SPA tolerates\nrather than facts about this process. Platform SuperAdmin only.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_deploy_version"
|
||||
}
|
||||
]
|
||||
@@ -1,49 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Forgets a destination for the caller's org: every credential held in\nKMS, then the stored config. Idempotent, and it requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"platform": {
|
||||
"description": "Platform is the destination to act on, from the path: ga4 | meta | tiktok |\nlinkedin | x | reddit | posthog | umami.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_destinations_platform"
|
||||
},
|
||||
{
|
||||
"description": "Reports every destination this deployment can forward to, each with the\ncaller org's connection state: whether it is connected, whether it is enabled,\nwhether a credential resolves right now, and the config fields the console\nrenders for it.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_destinations"
|
||||
},
|
||||
{
|
||||
"description": "Reports one destination's card for the caller's org — its config fields,\nits connection state, and whether a credential resolves right now. A platform\nthis deployment does not carry is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"platform": {
|
||||
"description": "Platform is the destination to act on, from the path: ga4 | meta | tiktok |\nlinkedin | x | reddit | posthog | umami.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_destinations_platform"
|
||||
},
|
||||
{
|
||||
"description": "Sends ONE synthetic pageview through the connected destination end to end\nand reports what the platform said. A send the platform refuses is reported as\ndata — {\"ok\": false, \"error\": …} at 200 — so the console shows the platform's\nown words rather than an error about Hanzo. It requires org admin.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"platform": {
|
||||
"description": "Platform is the destination to act on, from the path: ga4 | meta | tiktok |\nlinkedin | x | reddit | posthog | umami.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_destinations_platform_test"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,146 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteLoadBalancer removes one of the caller org's load balancers and answers\n204. Ownership is confirmed by re-fetching the resource before anything is\ndeleted, so a cross-tenant id is a 404 rather than a delete of another org's\nload balancer.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DigitalOcean resource id (a UUID), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_balancers_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteVpc removes one of the caller org's VPCs and answers 204. Ownership is\nconfirmed by re-fetching the resource and checking its physical name carries\nthe caller's org prefix BEFORE anything is deleted, so a cross-tenant id is a\n404 rather than a delete of another org's VPC.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DigitalOcean resource id (a UUID), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_vpcs_id"
|
||||
},
|
||||
{
|
||||
"description": "ListLoadBalancers returns every load balancer the caller's org owns, under the\nfriendly names the org created them with. Same account-wide filter as the VPC\nlisting: a load balancer outside the caller's \"o\"\u003corgHash\u003e- namespace is never\nin the answer.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_balancers"
|
||||
},
|
||||
{
|
||||
"description": "GetLoadBalancer returns one of the caller org's load balancers by id. One that\nexists in another org's namespace is reported 404, never 403 — the same\nexistence-oracle guard the VPC read applies.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DigitalOcean resource id (a UUID), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_balancers_id"
|
||||
},
|
||||
{
|
||||
"description": "ListVpcs returns every VPC the caller's org owns, under the friendly names the\norg created them with. DigitalOcean is one account for the whole deployment, so\nthe account-wide inventory is filtered to the caller's own \"o\"\u003corgHash\u003e- name\nprefix and the prefix is stripped — another org's VPC is not merely hidden, it\nis never in the answer.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_vpcs"
|
||||
},
|
||||
{
|
||||
"description": "GetVpc returns one of the caller org's VPCs by id. A VPC that exists but sits\nin another org's namespace is reported 404, never 403 — the answer must not\ntell one tenant that another tenant's resource exists.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the DigitalOcean resource id (a UUID), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_vpcs_id"
|
||||
},
|
||||
{
|
||||
"description": "CreateLoadBalancer creates a load balancer in the caller's org namespace and\nanswers 201 with it. The physical DigitalOcean name is derived server-side from\nthe validated org; a name that already exists there is a 409. Omitting\nforwarding rules yields a usable HTTP 80→80 load balancer rather than a 422.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"fwdRule": {
|
||||
"properties": {
|
||||
"entry_port": {
|
||||
"description": "EntryPort is the port the load balancer listens on.",
|
||||
"type": "integer"
|
||||
},
|
||||
"entry_protocol": {
|
||||
"description": "EntryProtocol is the protocol the load balancer listens with (http, https, tcp).",
|
||||
"type": "string"
|
||||
},
|
||||
"target_port": {
|
||||
"description": "TargetPort is the backend port traffic is forwarded to.",
|
||||
"type": "integer"
|
||||
},
|
||||
"target_protocol": {
|
||||
"description": "TargetProtocol is the protocol used to reach the backend droplets.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"forwarding_rules": {
|
||||
"description": "ForwardingRules are the listen→backend port mappings. Empty defaults to\nplain HTTP 80→80, the same default DigitalOcean's own console applies.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/fwdRule"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the FRIENDLY name, a DNS-safe slug of at most 40 characters. The\nphysical DigitalOcean name is derived from it and the caller's org.",
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"description": "Region is the DigitalOcean region slug (nyc3, sfo3, …). Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"description": "Size is the DigitalOcean size slug. Empty takes DO's default.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is the DigitalOcean load-balancer type. Empty takes DO's default (REGIONAL).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_balancers"
|
||||
},
|
||||
{
|
||||
"description": "CreateVpc creates a VPC in the caller's org namespace and answers 201 with it.\nThe physical DigitalOcean name is derived server-side from the validated org,\nso a tenant can only ever create inside its own namespace; a name that already\nexists there is a 409.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"ip_range": {
|
||||
"description": "IPRange is the VPC's private CIDR. Empty lets DigitalOcean assign one.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the FRIENDLY name, a DNS-safe slug of at most 40 characters. The\nphysical DigitalOcean name is derived from it and the caller's org.",
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"description": "Region is the DigitalOcean region slug (nyc3, sfo3, …). Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_vpcs"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
+30
-35
@@ -1,14 +1,10 @@
|
||||
// Package plugin carries the fleet's build-time PROJECTIONS into the host: what
|
||||
// each subsystem serves, known without running it.
|
||||
// Package plugin carries the fleet's build-time PROJECTION into the host: the
|
||||
// OpenAPI subset each subsystem serves, known without running it.
|
||||
//
|
||||
// Both are a function of the plugin's own router and typed-op registry, so both
|
||||
// are known when the plugin is BUILT: `<app> describe plugin/<app>` writes
|
||||
// mcp.json and openapi.json from one mount of one router (describe.go). This
|
||||
// package embeds those files. The tool lists go to zip as Plugin.Tools, which is
|
||||
// what lets the host answer tools/list for all 113 subsystems without starting a
|
||||
// single one; the specs are what the host weaves into the fleet document it
|
||||
// serves at /v1/openapi.json. Same invariant, two consumers: MCPTools() and the
|
||||
// router are in-process, and a host cannot ask a plugin that is not running.
|
||||
// It is a function of the plugin's own router, so it is known when the plugin is
|
||||
// BUILT: `<app> describe plugin/<app>` writes openapi.json from one mount of one
|
||||
// router (describe.go). This package embeds those files, and the host weaves them
|
||||
// into the fleet document it serves at /v1/openapi.json.
|
||||
//
|
||||
// It exists as its own leaf package for one reason: go:embed cannot reach outside
|
||||
// its own directory, so the bytes must be embedded from HERE, and cmd/cloud must
|
||||
@@ -16,41 +12,40 @@
|
||||
// so `go list -deps ./cmd/cloud` gains exactly one package and still links no
|
||||
// subsystem.
|
||||
//
|
||||
// The catalogue can only be INCOMPLETE, never wrong: the child's own registry
|
||||
// answers the call, so a name the host still lists but the child no longer serves
|
||||
// yields that child's -32602 rather than a mis-dispatch.
|
||||
// # There is no MCP catalogue here any more
|
||||
//
|
||||
// It used to embed plugin/<app>/mcp.json too — the tool array the app's binary
|
||||
// projected at build time — and hand it to zip as Plugin.Tools so the host could
|
||||
// answer tools/list without touching a child. That file was a SECOND source for a
|
||||
// fact the child already knows, and a second source can only be stale or
|
||||
// accidentally correct. It was stale: plugin/o11y/mcp.json held 12 tools while
|
||||
// the o11y binary at the same commit served 365, because the missing 353 ops live
|
||||
// in github.com/hanzoai/o11y and a go.mod bump in ANOTHER repository invalidated
|
||||
// an artifact in this one with nothing in the diff to say so. No generator on a
|
||||
// hook in this repo could have seen that trigger.
|
||||
//
|
||||
// So the catalogue is not regenerated more often; it is gone, and the door asks
|
||||
// the child (package fleet). The subset below survives for the one reason the
|
||||
// catalogue could not: the fleet's document carries each subsystem's PROSE, and
|
||||
// that prose is lifted from the app's SOURCE at describe time
|
||||
// (openapi.Synopsis) — a running child has no comment to read and would answer
|
||||
// with its deployment's brand blurb instead, which the weave would then publish
|
||||
// as the description of every product tag. Making the synopsis a declared value
|
||||
// is what the other half of this file is waiting on.
|
||||
package plugin
|
||||
|
||||
import "embed"
|
||||
|
||||
// catalogues holds every app's mcp.json. The pattern is a glob, so an app that
|
||||
// has not been described yet is simply absent — Tools returns nil and zip leaves
|
||||
// that plugin off the door — rather than a build failure in the host.
|
||||
//
|
||||
//go:embed */mcp.json
|
||||
var catalogues embed.FS
|
||||
|
||||
// Tools is app's MCP catalogue: the JSON array its own App.MCPTools() projected
|
||||
// at build time, ready to hand to zip.Plugin.Tools. Nil for an app that ships
|
||||
// none, which is exactly how a plugin opts out of the composed door.
|
||||
func Tools(app string) []byte {
|
||||
b, err := catalogues.ReadFile(app + "/mcp.json")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// specs holds every app's openapi.json — the same committed files the drift gate
|
||||
// regenerates from source and the weave composes into openapi.yaml
|
||||
// (mk/fleet.mk surface-check). Embedding them is what lets the host describe the
|
||||
// whole fleet without starting any of it: the alternative is reading the live
|
||||
// router, and the light host's live router is 113 proxy prefixes.
|
||||
//
|
||||
// A glob, for the same reason the catalogues are: an app that has not been
|
||||
// described yet is absent rather than a build failure in the host. Absence is
|
||||
// then refused where it can be reported — openapi.Subsets, at the request that
|
||||
// needs it — instead of by a compiler error nobody can act on.
|
||||
// A glob, so an app that has not been described yet is absent rather than a build
|
||||
// failure in the host. Absence is then refused where it can be reported —
|
||||
// openapi.Subsets, at the request that needs it — instead of by a compiler error
|
||||
// nobody can act on.
|
||||
//
|
||||
//go:embed */openapi.json
|
||||
var specs embed.FS
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Model reads one model's load state — loaded, unloading, or not_found, as\nthe engine itself reports it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"model": {
|
||||
"description": "Model is the model id to inspect, exactly as the model list reports it.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_engine_model"
|
||||
},
|
||||
{
|
||||
"description": "Models lists the models the engine serves, each with its load state — the\nserver's own model table (its standard list envelope, load status\nincluded), relayed verbatim.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_engine_models"
|
||||
},
|
||||
{
|
||||
"description": "Status reports whether the engine deployment is reachable and which build\nrevision it runs — an honest lens for \"is the serving runtime up\", never a\nfabricated ok.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_engine_status"
|
||||
},
|
||||
{
|
||||
"description": "System reads the engine host's inventory: OS, CPU, memory, every accelerator\ndevice with its VRAM and compute capability, and the build's capabilities\n(CUDA/Metal/flash-attention) — the real hardware under the serving runtime,\nrelayed verbatim.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_engine_system"
|
||||
}
|
||||
]
|
||||
@@ -1,41 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Projection reports which console apps the CALLER's org may open, and the plan slug\nthat decides it. It is the READ side of the unified paywall: the org's plan tier\nresolved from commerce, which is a different authority from the enablement store\nbehind GET /v1/orgs/{org}/entitlements (that one is the org's own on/off intent).\n\nIt fails SAFE-TO-LOCKED, never 500: an unvalidated principal is a 403, but a\ncommerce outage reports every app locked at 200 rather than breaking the shell.\nThe ENFORCEMENT path still fails open, so functionality survives the same outage\neven while the UI conservatively shows locked.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_entitlements"
|
||||
},
|
||||
{
|
||||
"description": "Get lists the products an org has ENABLED — its own intent, which the console's\npaid-product sidebar reads to decide what to show. It is distinct from what the\norg's plan ENTITLES it to (that is GET /v1/entitlements, resolved from commerce).\n\nA caller may only read its OWN org's row; a platform super admin may read any.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_orgs_org_entitlements"
|
||||
},
|
||||
{
|
||||
"description": "Post turns products on or off for an org and returns the enabled set afterwards.\n\nA product may only be ENABLED if the org's plan already ENTITLES it, so enabling\nnever spends new money — a product the plan does not grant answers 402 and the\nconsole routes that to an upgrade prompt. DISABLING is never gated. A platform\nsuper admin bypasses the plan check (operator comp/grant) and may target any org;\neveryone else may only change their own. Commerce unreachable is a 503, never an\nimplicit yes.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"add": {
|
||||
"description": "Add is the product ids to turn ON. Each must already be an ACTIVE entitlement\nof the org's plan, unless the caller is a platform super admin.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"remove": {
|
||||
"description": "Remove is the product ids to turn OFF. Disabling is never gated.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_orgs_org_entitlements"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,100 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteFlagDefinition removes one flag definition by key and records the\ndeletion in the change log. A key the caller's store does not hold is a 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Key is the flag key to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_flags_defs_key"
|
||||
},
|
||||
{
|
||||
"description": "ListFlagActivity returns the caller's flag change log newest-first: every\ncreate, update and delete, with the actor and the time.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned. 1–500; anything else takes the default 100.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flags_activity"
|
||||
},
|
||||
{
|
||||
"description": "ListFlagDefinitions returns every flag definition in the caller's (org,\nproject) store, by key, with its version and who last changed it.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flags_defs"
|
||||
},
|
||||
{
|
||||
"description": "GetFlagDefinition returns one flag definition by key, or 404 when the caller's\nstore has none under that key.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"description": "Key is the flag key to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flags_defs_key"
|
||||
},
|
||||
{
|
||||
"description": "Health reports that the flag engine is serving. It is not gated: liveness must\nbe probe-able without a token.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flags_health"
|
||||
},
|
||||
{
|
||||
"description": "Evaluate runs the caller's flag definitions for one identity and returns the\nflag verdict: which flags are on (or which variant), their payloads,\nand whether any definition failed to compute. Evaluation is in-process over the\ncaller's own (org, project) definitions — no network hop, no shared KV — so a\ntenant can only ever evaluate its own flags.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"distinct_id": {
|
||||
"description": "DistinctID is the identity the flags are evaluated for. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups are the group-level properties, keyed by group type index."
|
||||
},
|
||||
"person_properties": {
|
||||
"description": "PersonProperties are the person-level properties conditions match against."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_flags"
|
||||
},
|
||||
{
|
||||
"description": "Evaluate runs the caller's flag definitions for one identity and returns the\nflag verdict: which flags are on (or which variant), their payloads,\nand whether any definition failed to compute. Evaluation is in-process over the\ncaller's own (org, project) definitions — no network hop, no shared KV — so a\ntenant can only ever evaluate its own flags.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"distinct_id": {
|
||||
"description": "DistinctID is the identity the flags are evaluated for. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"groups": {
|
||||
"description": "Groups are the group-level properties, keyed by group type index."
|
||||
},
|
||||
"person_properties": {
|
||||
"description": "PersonProperties are the person-level properties conditions match against."
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_flags_decide"
|
||||
},
|
||||
{
|
||||
"description": "PutFlagDefinition creates or replaces the flag definition at the path's key and\nreturns the stored row. The BODY IS THE DEFINITION DOCUMENT — the flag-definition\nJSON object the evaluator consumes — and it is stored verbatim except that its\n\"key\" is forced to the key in the URL, so a document can never be filed under a\nname other than the one it was addressed by. Every write bumps the version and\nappends to the change log under the caller's identity.",
|
||||
"inputSchema": {},
|
||||
"name": "put_v1_flags_defs_key"
|
||||
}
|
||||
]
|
||||
@@ -1,138 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "WorkflowDelete deletes one of the caller's workflows and its runs. Ownership\nis verified first; a foreign id answers 404 and deletes nothing.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"workflow": {
|
||||
"description": "Workflow is the workflow's UUID, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_flow_workflows_workflow"
|
||||
},
|
||||
{
|
||||
"description": "Runs reads one workflow's recorded runs: every component build with its\nresult, keyed by component. Ownership is verified first — run records never\ncross the org boundary.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"workflow": {
|
||||
"description": "Workflow is the UUID of the workflow whose run records to read. It rides\nthe query string.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flow_runs"
|
||||
},
|
||||
{
|
||||
"description": "Status reports whether the flow service is reachable and which version it\nruns. It is the product's own /health and /v1/version composed — an honest\nlens for \"is the workflow plane up\", never a fabricated ok.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flow_status"
|
||||
},
|
||||
{
|
||||
"description": "Workflows lists the caller's workflows, paged. The list is scoped\nserver-side to the org's project — the page can only ever hold the caller's\nown workflows.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"page": {
|
||||
"description": "Page is the 1-based page of workflows to return.",
|
||||
"type": "string"
|
||||
},
|
||||
"size": {
|
||||
"description": "Size is how many workflows one page holds (the product caps it at 100).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flow_workflows"
|
||||
},
|
||||
{
|
||||
"description": "Workflow reads one of the caller's workflows — the full record, graph\nincluded. A workflow outside the caller's org answers 404, indistinguishable\nfrom one that does not exist.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"workflow": {
|
||||
"description": "Workflow is the workflow's UUID, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_flow_workflows_workflow"
|
||||
},
|
||||
{
|
||||
"description": "WorkflowUpdate patches one of the caller's workflows: name, description,\ngraph, or the locked flag — only the stated fields move. Ownership is\nverified before the patch reaches the product.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data replaces the workflow graph when present, verbatim."
|
||||
},
|
||||
"description": {
|
||||
"description": "Description replaces the description when present.",
|
||||
"type": "string"
|
||||
},
|
||||
"locked": {
|
||||
"description": "Locked freezes or unfreezes the workflow against edits when present.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name renames the workflow when present.",
|
||||
"type": "string"
|
||||
},
|
||||
"workflow": {
|
||||
"description": "Workflow is the workflow's UUID, taken from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_flow_workflows_workflow"
|
||||
},
|
||||
{
|
||||
"description": "Run executes one of the caller's workflows synchronously: the graph runs in\nthe flow service and the response carries the run's session and outputs. A\ngraph whose components fail reports the product's own error. Runs are\nbounded by the product's five-minute sync ceiling.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"input": {
|
||||
"description": "Input is the run's chat input value, handed to the graph's input node.",
|
||||
"type": "string"
|
||||
},
|
||||
"session": {
|
||||
"description": "Session groups runs into one conversation; the product mints one when\nabsent and returns it in the response.",
|
||||
"type": "string"
|
||||
},
|
||||
"tweaks": {
|
||||
"description": "Tweaks override component fields for this run only (the product's\ntweaks document), verbatim."
|
||||
},
|
||||
"workflow": {
|
||||
"description": "Workflow is the UUID of the workflow to run.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_flow_runs"
|
||||
},
|
||||
{
|
||||
"description": "WorkflowCreate creates a workflow in the caller's org. The org's project id\nis pinned server-side from the validated principal — there is no field by\nwhich a caller could place a workflow in another org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"description": "Data is the workflow graph (the product's nodes/edges document),\nverbatim. Omit it to create an empty workflow."
|
||||
},
|
||||
"description": {
|
||||
"description": "Description says what the workflow does.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the workflow's display name, unique within the org's project\n(the product de-duplicates by suffixing).",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_flow_workflows"
|
||||
}
|
||||
]
|
||||
@@ -1,437 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Removes one document, after its on_trash hooks agree. A\nSUBMITTED document cannot be deleted — cancel it first. Answers 204.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the document's DocType, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_framework_doctype_name"
|
||||
},
|
||||
{
|
||||
"description": "Removes a DocType and every document stored under it. The\ndefinition and its data go together — a document with no schema can be neither\nvalidated nor read back — so there is no undo. Manager-only. Answers 204.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the DocType's name, from the path. A name containing a space\n(\"Sales Invoice\") arrives percent-encoded and is decoded before it is\nmatched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_framework_doctypes_name"
|
||||
},
|
||||
{
|
||||
"description": "Removes one (user, role) grant in the caller's org. Manager-only.\nAnswers 204; a grant that does not exist is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"role": {
|
||||
"description": "Role is the role to revoke, from the path. A role name containing a space\n(\"System Manager\") arrives percent-encoded and is decoded before it is\nmatched against the stored assignment.",
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"description": "User is the assignee whose grant is being revoked, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_framework_roles_user_role"
|
||||
},
|
||||
{
|
||||
"description": "Returns the caller org's documents of one DocType, filtered,\nordered and projected by the query. The DocType is resolved FIRST — through\nthe same permission gate the list itself uses — because the query is validated\nagainst its schema: a filter, sort or field name the DocType does not declare\nis refused rather than reaching the store.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the DocType to list, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Fields projects the response to a subset — a JSON array [\"a\",\"b\"] or a\ncomma list \"a,b\". The envelope keys are always returned.",
|
||||
"type": "string"
|
||||
},
|
||||
"filters": {
|
||||
"description": "Filters is a JSON object of equality matches, e.g. {\"priority\":\"High\"}.\nEvery key must be a field the DocType declares (or the managed name /\ndocstatus); an undeclared one is refused rather than silently ignored.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned. Anything that is not a positive integer\nleaves the engine's default in place.",
|
||||
"type": "string"
|
||||
},
|
||||
"order_by": {
|
||||
"description": "OrderBy is \"\u003cfield\u003e [asc|desc]\". Empty means most-recently-updated first.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_doctype"
|
||||
},
|
||||
{
|
||||
"description": "Returns one document by name, with Password fields redacted.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the document's DocType, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_doctype_name"
|
||||
},
|
||||
{
|
||||
"description": "Returns every DocType defined in the caller's org. Another\ntenant's definitions are never included: the org is part of the store key.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_doctypes"
|
||||
},
|
||||
{
|
||||
"description": "Returns one DocType definition — its fields, naming rule,\npermissions and lifecycle flags. Scoped to the caller's org, so another\ntenant's DocType of the same name is simply not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the DocType's name, from the path. A name containing a space\n(\"Sales Invoice\") arrives percent-encoded and is decoded before it is\nmatched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_doctypes_name"
|
||||
},
|
||||
{
|
||||
"description": "Returns every app lane compiled into this deployment and the\nDocTypes each one installs. It describes the BINARY, not the org: what a given\norg has actually installed is the per-module state below.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_modules"
|
||||
},
|
||||
{
|
||||
"description": "Returns one app lane's install state for the caller's org: the\nDocTypes the lane declares, and which of them already exist in the org. That\nis the honest \"set up\" versus \"installed\" answer a console renders.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"module": {
|
||||
"description": "Module is the lane's registered name (\"cms\", \"erp\"), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_modules_module"
|
||||
},
|
||||
{
|
||||
"description": "Returns every (user, role) assignment in the caller's org. Roles are\nwhat DocType permissions are written against, so this is the grant table the\npermission calculus resolves a member's rights from.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_roles"
|
||||
},
|
||||
{
|
||||
"description": "Reports how much of the DocType surface the caller's org uses: how\nmany DocTypes it has defined, and how many documents exist across them.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_framework_summary"
|
||||
},
|
||||
{
|
||||
"description": "Moves a submitted document to cancelled (docstatus 1 → 2) after\nits on_cancel hooks agree. Cancelling is terminal — a cancelled document\ncannot be re-submitted — but it CAN then be deleted.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the document's DocType, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_framework_doctype_name_cancel"
|
||||
},
|
||||
{
|
||||
"description": "Moves a draft to submitted (docstatus 0 → 1) after its\non_submit hooks agree. A submitted document is IMMUTABLE: further writes and\ndeletes are refused until it is cancelled. Only a submittable DocType has this\nlifecycle; any other docstatus is an illegal transition.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctype": {
|
||||
"description": "DocType is the document's DocType, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the document's name — its key within the DocType — from the path.\nA name containing a space arrives percent-encoded and is decoded before it\nis matched against the stored one.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_framework_doctype_name_submit"
|
||||
},
|
||||
{
|
||||
"description": "Defines a DocType in the caller's org: the metadata that gives a\ndocument surface its fields, its naming rule, whether it has a submit/cancel\nlifecycle, and which role may do what to it. Manager-only — on a fresh org the\nfirst caller to administer it is seeded as its System Manager, after which\nonly a System Manager (or a platform admin) may define. Answers 201.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"DocField": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": "string"
|
||||
},
|
||||
"fetchFrom": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldname": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldtype": {
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"inListView": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "string"
|
||||
},
|
||||
"readOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reqd": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"unique": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DocPerm": {
|
||||
"properties": {
|
||||
"cancel": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"create": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"delete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"read": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"submit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"write": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"autoname": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fields": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/DocField"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"isSingle": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isSubmittable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"module": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/DocPerm"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"titleField": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_framework_doctypes"
|
||||
},
|
||||
{
|
||||
"description": "Creates an app lane's DocTypes in the caller's org. Idempotent\nand create-if-absent: a DocType the org already has is reported as existing\nand never replaced, so re-installing cannot clobber a definition the org has\nsince edited. Manager-only.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"module": {
|
||||
"description": "Module is the lane's registered name (\"cms\", \"erp\"), from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_framework_modules_module_install"
|
||||
},
|
||||
{
|
||||
"description": "Grants one user one role in the caller's org — how a member gains\nrights on a DocType, since permissions name roles and never users.\nManager-only. Answers 201.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_framework_roles"
|
||||
},
|
||||
{
|
||||
"description": "Replaces a DocType definition wholesale (PUT semantics): the\nstored definition becomes the body. The name in the URL is authoritative over\nthe body's, and documents already stored under the DocType are left intact.\nManager-only.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"DocField": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"type": "string"
|
||||
},
|
||||
"fetchFrom": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldname": {
|
||||
"type": "string"
|
||||
},
|
||||
"fieldtype": {
|
||||
"type": "string"
|
||||
},
|
||||
"hidden": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"inListView": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"options": {
|
||||
"type": "string"
|
||||
},
|
||||
"readOnly": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reqd": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"unique": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"DocPerm": {
|
||||
"properties": {
|
||||
"cancel": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"create": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"delete": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"read": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"submit": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"write": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"autoname": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "integer"
|
||||
},
|
||||
"fields": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/DocField"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"isSingle": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isSubmittable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"module": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"permissions": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/DocPerm"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"titleField": {
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_framework_doctypes_name"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,64 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Read returns the EFFECTIVE edge policy the caller is subject to: the platform CORS\nallowlist and pre-auth per-IP flood cap in force, plus the caller's own authenticated\nrate ceiling, edge-cache TTLs and accepted-method allowlist. A SuperAdmin may inspect\na specific tenant's effective policy with ?org=\u003cslug\u003e.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_gateway_config"
|
||||
},
|
||||
{
|
||||
"description": "Write updates one policy scope and returns the policy in force after the write.\nA body carrying any PLATFORM field (cors_origins, per_ip_rpm, window_sec) is a\nplatform write and requires SuperAdmin; otherwise it is a per-org write (org_rpm,\ncache_ttl_sec, cache_paths, methods) scoped to the caller's own org — or, for a\nSuperAdmin, the tenant named by ?org=\u003cslug\u003e. A body that sets nothing is a 400.\nupdated_at and updated_by are server-stamped; a client-supplied value is ignored.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"cache_paths": {
|
||||
"additionalProperties": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "CachePaths overrides CacheTTLSec per path PREFIX (key \"/v1/models\" → seconds).\nThe longest matching prefix wins.",
|
||||
"type": "object"
|
||||
},
|
||||
"cache_ttl_sec": {
|
||||
"description": "CacheTTLSec is the org's default edge-cache TTL for its responses, in seconds;\n0 means no caching. Unset inherits the platform default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"cors_origins": {
|
||||
"description": "CORSOrigins is the PLATFORM-scope CORS allowlist EdgeCORS admits: an exact\norigin, a bare host, or a \"*.host\" wildcard. Writable only by a SuperAdmin —\nCORS is evaluated before identity, so it has no tenant to scope to.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"methods": {
|
||||
"description": "Methods is the allowlist of HTTP methods the edge accepts for this org. Empty\nmeans all are accepted.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"org_rpm": {
|
||||
"description": "OrgRPM is the org's OWN authenticated rate ceiling, requests per minute, as\nScopeRateLimit enforces it. Unset inherits the platform default, then the\nstatic boot default.",
|
||||
"type": "integer"
|
||||
},
|
||||
"per_ip_rpm": {
|
||||
"description": "PerIPRPM is the PLATFORM-scope pre-auth flood cap: requests EdgeRateLimit\nadmits per WindowSec from one client IP. SuperAdmin-only, same reason.",
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_at": {
|
||||
"description": "UpdatedAt is the unix second this policy row was last written. Server-stamped;\na client-supplied value is ignored.",
|
||||
"type": "integer"
|
||||
},
|
||||
"updated_by": {
|
||||
"description": "UpdatedBy is the validated user id that wrote this policy row. Server-stamped;\na client-supplied value is ignored.",
|
||||
"type": "string"
|
||||
},
|
||||
"window_sec": {
|
||||
"description": "WindowSec is the window PerIPRPM is counted over, in seconds. SuperAdmin-only.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_gateway_config"
|
||||
}
|
||||
]
|
||||
@@ -1,428 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Removes a registered SSH key, scoped to the caller's org: an org can\nonly delete its own, and a key id it does not own is not found. Answers 204\nwith no body. Once removed the key no longer authenticates any SSH git access.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the key's identifier (\"gitkey_…\"), from the :id path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_git_keys_id"
|
||||
},
|
||||
{
|
||||
"description": "Removes a repo's metadata and purges its storage. Answers 204 with\nno body. The metadata row is the source of truth for existence, so a storage\npurge that fails is logged and the delete still succeeds — and a second call\nis a 404, not a second delete.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_git_repos_name"
|
||||
},
|
||||
{
|
||||
"description": "Removes one outbound mirror target; later pushes stop being\nforwarded to it. Answers 204 with no body. Nothing is done to the downstream\nremote itself — only this repo's intent to push there is dropped.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the row to remove, from the :id path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo, from the :name path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_git_repos_name_mirrors_id"
|
||||
},
|
||||
{
|
||||
"description": "Removes one Slack subscription from a repo; the notifier stops\nposting that repo's events to that channel. Answers 204 with no body. An id\nthat is not this repo's subscription is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the row to remove, from the :id path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo, from the :name path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_git_repos_name_subscriptions_id"
|
||||
},
|
||||
{
|
||||
"description": "Returns the SSH public keys registered to the caller's org — the keys\nthat authenticate `git clone git@\u003chost\u003e:\u003corg\u003e/\u003crepo\u003e.git`. Keys are org-scoped\non read even though the fingerprint index is global, so one org never sees\nanother's.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_keys"
|
||||
},
|
||||
{
|
||||
"description": "Returns the repos in the caller's scope, most recently updated\nfirst. The scope is the request principal's — the gateway-minted org and its\noptional project — never anything off the wire, so a caller only ever sees its\nown. Rows carry no branches or HEAD; read one repo for those.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos"
|
||||
},
|
||||
{
|
||||
"description": "Returns one repo with its live ref state: every branch name and the\nresolved HEAD commit. Both are read from the object store on each call, so an\nempty repo reports no branches and an empty head rather than failing. A repo\noutside the caller's scope is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name"
|
||||
},
|
||||
{
|
||||
"description": "Returns one file's bytes at one revision. Text comes back verbatim,\nbinary comes back base64, and a file past the 1 MiB view cap comes back marked\ntruncated with NO content — the client is expected to clone instead.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo to read, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is repo-relative; empty is the tree root. Traversal is stripped.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is a branch, tag or commit; empty means the repo's HEAD.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_blob"
|
||||
},
|
||||
{
|
||||
"description": "Walks a ref's history newest first, or one path's history when a\npath is given. There is no cursor: the page is the newest `limit` commits.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the page. Anything not positive means 50; the cap is 100.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo to read, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path narrows the history to commits touching it; empty walks the whole ref.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is the branch, tag or commit to walk back from; empty means HEAD.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_commits"
|
||||
},
|
||||
{
|
||||
"description": "Returns every file a glob selects at one revision, WITH its bytes\nand the revision they came from. It is the read a delivery generator makes:\none call answers \"what is the inventory at this commit, and what does it say\",\nwhere listing and then fetching would be a request per file.\n\nReturning the resolved revision matters as much as the bytes. A generator that\nlists at `main` and then reads at `main` can straddle a push and assemble half\nits inventory from one commit and half from the next; resolving once makes the\nwhole read consistent by construction.\n\nA file past the read cap comes back Truncated with no content rather than\nbeing dropped. A caller building a desired set has to know the difference\nbetween \"this file is empty\" and \"this file was not read\" — silently omitting\nit is how a pruning reconcile deletes what the missing file declared.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"glob": {
|
||||
"description": "Glob selects files, matched segment by segment so `*` never crosses a `/`.\n`**` matches zero or more whole segments.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo to read, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is a branch, tag or commit; empty means the repo's HEAD.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_files"
|
||||
},
|
||||
{
|
||||
"description": "Returns a repo's outbound mirror targets — the downstream remotes\nthe mirror reactor pushes to whenever a push lands here.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_mirrors"
|
||||
},
|
||||
{
|
||||
"description": "Returns the README at the tree root as plain text — unrendered, so\nthe caller decides how to present it. A repo with no README is not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo to read, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is a branch, tag or commit; empty means the repo's HEAD.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_readme"
|
||||
},
|
||||
{
|
||||
"description": "Lists a repo's branches, tags and default branch — what a branch\npicker needs in one call. Unlike the other read ops it tolerates a repo with no\ncommits: the ref sets come back empty and the default branch is still named.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_refs"
|
||||
},
|
||||
{
|
||||
"description": "Returns a repo's Slack subscriptions — which channels the\nlifecycle notifier posts this repo's push and deploy events to.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_subscriptions"
|
||||
},
|
||||
{
|
||||
"description": "Lists the immediate children of one directory at one revision,\ndirectories before files. It does not recurse — walk down a level at a time.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo to read, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is repo-relative; empty is the tree root. Traversal is stripped.",
|
||||
"type": "string"
|
||||
},
|
||||
"ref": {
|
||||
"description": "Ref is a branch, tag or commit; empty means the repo's HEAD.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_repos_name_tree"
|
||||
},
|
||||
{
|
||||
"description": "Returns per-repo and total storage bytes for the caller's org — the\nqueryable, per-tenant number commerce and o11y meter on. It spans EVERY\nproject sub-scope, unlike the repo list, so a billing consumer sees the whole\ntenant footprint in one call. Sizes are last-measured values (create, push,\nmirror and gc each re-measure), not a live walk of the disk.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_git_usage"
|
||||
},
|
||||
{
|
||||
"description": "Flips a repo's public bit, the one mutable repo setting today.\nPublic grants ANONYMOUS fetch only; push and the whole control plane stay\norg-authed. Returns the updated repo.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo to update, from the :name path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"public": {
|
||||
"description": "Public flips anonymous read access. Omit it and the request is refused —\nthere is nothing else to update yet.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "patch_v1_git_repos_name"
|
||||
},
|
||||
{
|
||||
"description": "Registers an SSH public key so it can authenticate `git clone\ngit@\u003chost\u003e:\u003corg\u003e/\u003crepo\u003e.git` for the caller's org. The key line is parsed and\ncanonicalized before storage, its SHA256 fingerprint becomes the auth lookup\nhandle, and the full public key round-trips (it is public). Answers 201.\nFingerprints are globally unique, so a key already registered — to this org or\nany other — is a 409: one key belongs to exactly one org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"publicKey": {
|
||||
"description": "PublicKey is one OpenSSH authorized-key line (\"ssh-ed25519 AAAA… you@host\").\nRequired; a line that does not parse is refused and never stored.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title labels the key in the console. Max 256 chars; when omitted the\ncomment on the key line is used.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_keys"
|
||||
},
|
||||
{
|
||||
"description": "Provisions an empty bare repository in the caller's scope and\nreturns it with its clone URLs. Answers 201. The name must be unique within\nthe scope — a repeat is a 409, never a silent overwrite of an existing repo.\nThe org comes from the validated principal, so a repo is always born owned by\nthe caller's own tenant.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Description is a free-form blurb, max 4KiB.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo's handle, unique within the scope, and the last segment of\nboth clone URLs. Must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$; a trailing\n\".git\" is stripped first. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project narrows the repo to a sub-scope of the org. Omit it to use the\ncaller's own X-Project-Id scope; it can never widen past the caller's org.",
|
||||
"type": "string"
|
||||
},
|
||||
"public": {
|
||||
"description": "Public grants ANONYMOUS read (fetch) only; push and the whole control plane\nstay org-authed. Defaults to false.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos"
|
||||
},
|
||||
{
|
||||
"description": "Repacks a repo into one bitmapped pack and rewrites its commit-graph, so\nthe next clone reuses the bitmap instead of walking the whole object graph.\nIdempotent, and safe to interrupt — git swaps both artifacts atomically. It\nruns under one pack slot with the same memory bounds as a clone, so it can\nblock behind heavy pack traffic rather than compete with it. Storage usage is\nre-measured afterwards, since a repack reclaims space.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the repo's org-unique handle, from the :name path segment. A\ntrailing \".git\" is stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos_name_gc"
|
||||
},
|
||||
{
|
||||
"description": "Imports an external git repository into the caller's repo, provisioning\nit on first use. Fetch is FORCED and covers every ref, so a first call clones\nthe source and a repeat call re-syncs it — the endpoint is idempotent by mirror\nsemantics. Mirrored bytes are metered exactly like a push, and a push.landed\nevent is emitted for the default branch so the code index picks the repo up.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the local repo to mirror into, from the :name path segment. It is\nCREATED on first use.",
|
||||
"type": "string"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project is the sub-scope to land the repo in; empty uses the caller's own,\nexactly as a create would.",
|
||||
"type": "string"
|
||||
},
|
||||
"source": {
|
||||
"description": "Source is the http(s) git URL to fetch from. The host is SSRF-guarded and\nthe shared mirror credential is only sent to allowlisted hosts.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos_name_mirror"
|
||||
},
|
||||
{
|
||||
"description": "Registers a downstream remote the repo's advanced refs are pushed to\nwhenever a push lands here. Answers 201. The URL must be https to a host on the\nmirror allowlist (github.com / gitlab.com): the same set the mirror credential\nmay be sent to, so a target can never capture the shared token or point the push\nat an internal service. Any embedded userinfo is stripped — credentials ride\nenv-only at push time and never enter the stored URL. One mirror per host per\nrepo; a second is a 409.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"description": "Host is an optional assertion of the target's hostname. The authoritative\nhost is the one in URL; a value that disagrees with it is refused.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo whose advanced refs are pushed downstream, from the :name\npath segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"description": "URL is the downstream https git remote. Must be https to an allowlisted\nhost (github.com / gitlab.com); any embedded credentials are stripped.\nRequired.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos_name_mirrors"
|
||||
},
|
||||
{
|
||||
"description": "Lands a set of files as one commit without a git client — the\nhanzo.app builder's push. The repo is CREATED on first push, the files are\nmerged onto the branch tip (unlisted files survive), and the same\npush-to-deploy hook a real receive-pack fires is fired, so downstream this is\nindistinguishable from a `git push`.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"pushFile": {
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "Content is the file's bytes, carried per Encoding.",
|
||||
"type": "string"
|
||||
},
|
||||
"encoding": {
|
||||
"description": "Encoding is \"base64\", or \"utf-8\" (the default, also \"utf8\" / \"text\").",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is repo-relative. Absolute or traversing paths are refused.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"branch": {
|
||||
"description": "Branch to advance; empty means \"main\". A fresh branch that is the repo's\nfirst also becomes HEAD.",
|
||||
"type": "string"
|
||||
},
|
||||
"files": {
|
||||
"description": "Files are added to or overwritten on the branch tip — files already there\nand not listed SURVIVE. At least one, at most 5000, 32 MiB each.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/pushFile"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"message": {
|
||||
"description": "Message is the commit message; empty gets a generated one.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo to push into, from the :name path segment. It is CREATED\non first push if it does not exist.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos_name_push"
|
||||
},
|
||||
{
|
||||
"description": "Binds a Slack channel to a repo, so the lifecycle notifier posts\nthat repo's push and deploy events there. Answers 201. The same channel twice\non one repo is a 409; a repo outside the caller's scope is a 404, exactly as\nreading it is.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"channel": {
|
||||
"description": "Channel is the Slack channel the notifier posts to — an id (C…/G…), a\n#name, or a bare name. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"events": {
|
||||
"description": "Events narrows delivery to these lifecycle kinds (push.landed,\ndeploy.live, deploy.failed). Omit it to receive every deliverable kind; a\nkind that is never posted to Slack is refused rather than silently dropped.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the repo to subscribe, from the :name path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_git_repos_name_subscriptions"
|
||||
}
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListIndexers reports the deployment's chain indexer(s) and how far each has\nindexed. Identity and health come from the indexer's /health; the latest indexed\nblock (height + time) from its /v1/explorer/blocks. The row EXISTS if EITHER call\nreaches the indexer; when the indexer is entirely unreachable the answer degrades\nto an honest-EMPTY list at 200, not a 502. No chain HEAD is exposed by the indexer\nREST, so `lag` is honestly omitted rather than fabricated.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_indexers"
|
||||
},
|
||||
{
|
||||
"description": "ListOracles reports the on-chain price/data oracles from the graph's O-Chain\nPriceFeed registry. A reachable graph with no feeds answers an honest empty list;\nan unreachable or erroring graph likewise degrades to an empty list at 200 rather\nthan a 502, so the console never error-toasts. No feed is ever fabricated.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_oracles"
|
||||
}
|
||||
]
|
||||
@@ -1,134 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteCurriculum clears the caller org's curriculum override and returns the\njourney it falls back to — the brand blueprint, else the embedded fixture.\nClearing an org that never set one is a no-op that answers the same default.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_guide_curriculum"
|
||||
},
|
||||
{
|
||||
"description": "Overview returns the caller org's launch journey: the active curriculum's\nversion and title, every step with its state, whether it is available, what\nblocks it and whether the Business AI can run it, the done/total/percent\nprogress with the next step to take, and the org's analytics funnel folded in.\nAuto-detect runs first, so a step the org has already completed elsewhere reads\ndone without anyone marking it.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide"
|
||||
},
|
||||
{
|
||||
"description": "ListActions returns the caller org's Business AI action ledger, most recent\nfirst: every \"do it for me\" tool call, the arguments it ran with, its result and\nwhether it succeeded. It is the audit-visible record of what the agent did on\nthe org's behalf, and the backing state for the \"acted\" auto-detect signal.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_actions"
|
||||
},
|
||||
{
|
||||
"description": "Analytics returns the caller org's funnel from the analytics lens plus the GTM\nrecommendations derived from it. It is the Business AI's data-grounded read —\nwhat the funnel is doing, and the next-best action to move its weakest stage. An\nunreachable or silent warehouse answers available=false, never a fabricated\nnumber.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_analytics"
|
||||
},
|
||||
{
|
||||
"description": "GetBlueprint returns the FULL authored brand blueprint — every principle,\nsection, step, strategy and template WITH its enabled flag made explicit,\nincluding the disabled items the org-facing reads never see — plus the active\nversion number, the brand key it is stored under and the item counts. It is the\nSuperAdmin authoring view of the platform blueprint, so it is refused 403 for\nanyone else, including a per-org admin: the brand blueprint is shared platform\ncontent, not a per-customer surface.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_blueprint"
|
||||
},
|
||||
{
|
||||
"description": "ListBlueprintVersions returns the brand blueprint's version history — every\nstored version's number and edit time, newest first — which is the\npoint-in-time-recovery and audit trail behind the authoring plane. Metadata\nonly: the documents are not returned. SuperAdmin only, like the rest of this\nplane. The history is listable even when the current stored document no longer\nparses, so a schema-drifted row can still be diagnosed.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_blueprint_versions"
|
||||
},
|
||||
{
|
||||
"description": "GetCurriculum returns the journey the caller's org is actually running, and\nwhether it comes from the org's OWN override (custom) or from the platform\ndefault — the brand blueprint, else the embedded fixture.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_curriculum"
|
||||
},
|
||||
{
|
||||
"description": "Profile returns the caller org's OBSERVED growth profile — the signal set, the\nclassified growth stage, and the org's own key metrics. It is a pure READ,\nrecomputed from the org's CURRENT state each request (real-time by pull): it\nreuses the reconcile path (snapshotFor runs the detectors) for launch progress\nand runs the growth probes (observe) for the signals — it never caches, never\nruns a billable effect, never targets another org. Org-scoped on the validated\nprincipal; fail-closed without one. It PRODUCES the profile and classifies the\nstage; it decides NO recommendation (that is a later surface).",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_profile"
|
||||
},
|
||||
{
|
||||
"description": "Strategies returns the ENABLED tactics corpus for the caller's org: the tactics\nlibrary narrowed by the explicit category/workload filters AND by the org's\nOBSERVED growth stage and capability signals (a tactic's tags are\npreconditions, so it surfaces only once the org can act on it). Passing stage\nPREVIEWS the corpus at that stage instead of the observed one. The content is\nshared platform data — no org's records — and the read is never a billable\neffect.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"description": "Category filters to tactics in exactly this category.",
|
||||
"type": "string"
|
||||
},
|
||||
"stage": {
|
||||
"description": "Stage previews the corpus at a chosen growth stage\n(research|formed|launched|activated|scaling), overriding the org's observed\none. An unknown value is ignored and the observed stage stands.",
|
||||
"type": "string"
|
||||
},
|
||||
"workload": {
|
||||
"description": "Workload filters to tactics with exactly this workload.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_strategies"
|
||||
},
|
||||
{
|
||||
"description": "Suggest returns the caller org's next-best quests: the available, non-terminal\nsteps of its journey ranked by how much downstream work each unblocks, each with\nthe grounded reason it is a good next move and whether the Business AI can run\nit, plus the org's funnel and the GTM recommendations derived from it. A\nbest-effort AI narrative over exactly those quests and numbers is included when\nan AI plane is wired. READ-ONLY: it advises and never runs a step — the only\nexecuting path is POST /v1/guide/steps/{id}/do.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_guide_suggest"
|
||||
},
|
||||
{
|
||||
"description": "Chat answers a founder's question about their launch journey as the Business AI\ncoach: it grounds the reply in the org's REAL progress, its ranked available\nquests and its analytics funnel, and returns those candidate quests alongside so\nthe caller can act on one. READ-ONLY — it advises and never runs a step, so it\ncannot be talked into performing an action; the only executing path is POST\n/v1/guide/steps/{id}/do. One AI completion per call, billed to the caller's own\npayer.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"message": {
|
||||
"description": "Message is the founder's question for the Business AI. Required; trimmed,\nand clipped to 4 KiB so a caller cannot amplify the AI prompt.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_guide_chat"
|
||||
},
|
||||
{
|
||||
"description": "ResetStep returns one step of the caller org's journey to todo — clearing a\nmanual mark or a skip — and returns the refreshed journey. Reset is never\ndependency-gated. Auto-detect runs on the next read, so a step the org has in\nfact completed elsewhere goes straight back to done.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the step's id, as it appears in the journey (e.g. \"gsuite\").",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_guide_steps_id_reset"
|
||||
},
|
||||
{
|
||||
"description": "SkipStep marks one step of the caller org's journey skipped and returns the\nrefreshed journey. Skipping is never dependency-gated — the founder is\ndeclaring the step does not apply to them — so a step whose dependencies are\nunfinished can still be skipped, and a skipped step counts as terminal for\neverything downstream of it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the step's id, as it appears in the journey (e.g. \"gsuite\").",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_guide_steps_id_skip"
|
||||
}
|
||||
]
|
||||
@@ -1,65 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Returns the public knowledge base: the help center's Published,\npublicly-visible articles as cards. The org is server-fixed and the\nstatus/is_public filter is server-set, so neither the tenant nor the visibility\ncan be widened by the caller. A deployment with no help center answers 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"description": "Category narrows the list to one knowledge-base section, matched against\nthe article's category by exact name. Empty lists every section.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps how many articles are returned. Anything that is not a positive\ninteger uses 50, and values above 200 are clamped to 200.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_help_articles"
|
||||
},
|
||||
{
|
||||
"description": "Returns one public article by slug, with its body. A missing, Draft,\nor internal (non-public) article is 404 — fail-closed, so this route is no\nexistence oracle for anything beyond \"published and public\".",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"slug": {
|
||||
"description": "Slug is the article's public identifier, from the path. It IS the document\nname in the help center's store.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_help_articles_slug"
|
||||
},
|
||||
{
|
||||
"description": "Returns the knowledge-base sections for the public center's\nnavigation — but ONLY the sections that front at least one Published, public\narticle, so an internal (agent-only) category name or description never leaks. A\nsection with no public article is invisible; a center with no public articles has\nno sections, which is an empty list rather than an error.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_help_categories"
|
||||
},
|
||||
{
|
||||
"description": "Files a customer support ticket into the public help center. It\ncreates the ticket (status Open, source portal) with the customer's message on\nthe description, then records that same message as the opening entry of the\nticket's conversation thread; the description carries it regardless, so failing\nto write that entry loses nothing. Answers 201 with an opaque reference.\n\nA deployment with no help center answers 404, one whose center has not installed\nthe Help model answers 503, and a body over 64 KiB answers 413 — in that order,\nwhich is the order the route has always decided them in.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "Description is the customer's message. Optional; it becomes the ticket's\ndescription AND the opening entry of its conversation thread. Clipped at\n16 KiB.",
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"description": "Email is how the support team replies. Required; clipped at 320 characters\n(the RFC 5321 maximum). It is recorded as the ticket's customer, and it is\nnot verified.",
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority is Low, Medium, High or Urgent, case-insensitively. Anything else —\nincluding omitting it — is recorded as Medium rather than refused.",
|
||||
"type": "string"
|
||||
},
|
||||
"subject": {
|
||||
"description": "Subject is the one-line summary of the problem. Required; longer than 300\ncharacters is clipped rather than refused.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_help_tickets"
|
||||
}
|
||||
]
|
||||
-16063
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,350 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteMiddleware removes one of the caller org's edge transforms and hot-applies\nthe change. Routes still naming it stop being served (they compile as skipped)\nuntil they name a transform that exists. Answers 204; an id this org does not\nhold is 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_ingress_middlewares_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteRoute removes one of the caller org's routing rules and hot-applies the\nshrunken table, freeing its host for another claim. Answers 204; an id this org\ndoes not hold is 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_ingress_routes_id"
|
||||
},
|
||||
{
|
||||
"description": "DeleteService removes one of the caller org's backend pools and hot-applies the\nchange. Routes still pointing at it stop being served (they compile as skipped)\nuntil they name a pool that exists. Answers 204; an id this org does not hold\nis 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_ingress_services_id"
|
||||
},
|
||||
{
|
||||
"description": "ListMiddlewares returns every edge transform the caller's org has configured,\nordered by id. A route names the ones it wants, in order.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_middlewares"
|
||||
},
|
||||
{
|
||||
"description": "GetMiddleware returns one of the caller org's edge transforms by id.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_middlewares_id"
|
||||
},
|
||||
{
|
||||
"description": "ListRoutes returns every routing rule the caller's org has configured, ordered\nby id. A route maps an exact Host (and optional path prefix) to a service.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_routes"
|
||||
},
|
||||
{
|
||||
"description": "GetRoute returns one of the caller org's routing rules by id.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_routes_id"
|
||||
},
|
||||
{
|
||||
"description": "ListServices returns every backend pool the caller's org has configured,\nordered by id. A service is the weighted round-robin target a route dispatches\nto.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_services"
|
||||
},
|
||||
{
|
||||
"description": "GetService returns one of the caller org's backend pools by id.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the object to act on, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_services_id"
|
||||
},
|
||||
{
|
||||
"description": "Status reports the ingress edge's live posture: the role this instance runs in\n(app or edge), whether its listeners are bound and on which addresses, the ACME\nposture (staging flag and certificate cache directory), how many hosts the\ncompiled route table currently serves, and how many the ACME HostPolicy will\nissue a certificate for.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_status"
|
||||
},
|
||||
{
|
||||
"description": "GetTLS returns the caller org's ACME intent together with the edge-wide TLS\nfacts it lands in: which role this instance runs in, whether its listeners are\nbound, every host the ACME HostPolicy will issue a certificate for (the union\nacross ALL orgs of TLS-marked routes and configured extraHosts, because one\nprocess holds one certificate cache), and the ACME directory and account email\nthe process was started with.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ingress_tls"
|
||||
},
|
||||
{
|
||||
"description": "PutMiddleware creates or replaces one edge transform and hot-applies it. POST\nmints an id when the body omits one; PUT takes the id from the URL, which wins\nover any id in the body. type must be one of redirectScheme, stripPrefix,\naddPrefix or headers, and stripPrefix/addPrefix each require their config key.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"config": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Config is the transform's parameters: redirectScheme takes scheme (default\nhttps) and permanent (\"true\" ⇒ 301, else 302); stripPrefix REQUIRES\nprefixes (comma-separated, first match wins); addPrefix REQUIRES prefix;\nheaders is a header→value map set on the response.",
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the transform within the org: [A-Za-z0-9-_.], at most 128\nchars. A create that omits it gets a generated one. Routes reference it by\nthis id.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is the transform: redirectScheme, stripPrefix, addPrefix or headers.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_ingress_middlewares"
|
||||
},
|
||||
{
|
||||
"description": "PutRoute creates or replaces one routing rule and hot-applies the new table —\nthere is no config file and no restart. POST mints an id when the body omits\none; PUT takes the id from the URL, which wins over any id in the body. A\nroute's host is a GLOBALLY unique DNS claim: a host another org's route already\nholds is refused 409, so no tenant can hijack another's hostname.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"description": "Host is the exact hostname this route matches, lowercased with any trailing\ndot stripped. It is a GLOBALLY unique claim — one route across the whole\nedge may hold a host, so no tenant can hijack another's.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the route within the org: [A-Za-z0-9-_.], at most 128 chars.\nA create that omits it gets a generated one.",
|
||||
"type": "string"
|
||||
},
|
||||
"middlewares": {
|
||||
"description": "Middlewares are the ids of the edge transforms to apply, in this order,\nbefore the request reaches the service. At most 16.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pathPrefix": {
|
||||
"description": "PathPrefix narrows the match to requests under this path; it must start\nwith \"/\". Empty matches every path on the host.",
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority orders routes that share a host: higher wins, and equal priorities\nfall back to the longer PathPrefix.",
|
||||
"type": "integer"
|
||||
},
|
||||
"service": {
|
||||
"description": "Service is the id of the backend pool this route dispatches to. A route\nnaming a service that does not exist is skipped at compile, not served.",
|
||||
"type": "string"
|
||||
},
|
||||
"tls": {
|
||||
"description": "TLS asks the edge to terminate TLS for Host with an ACME-managed certificate.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_ingress_routes"
|
||||
},
|
||||
{
|
||||
"description": "PutService creates or replaces one backend pool and hot-applies it. POST mints\nan id when the body omits one; PUT takes the id from the URL, which wins over\nany id in the body. A pool needs at least one backend and every backend URL\nmust be http(s)://host[:port].",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Backend": {
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "URL is the upstream server, http(s)://host[:port].",
|
||||
"type": "string"
|
||||
},
|
||||
"weight": {
|
||||
"description": "Weight is this member's share of the round-robin; must be \u003e= 0.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"backends": {
|
||||
"description": "Backends are the upstream servers to balance across: 1..32 of them.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Backend"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the pool within the org: [A-Za-z0-9-_.], at most 128 chars.\nA create that omits it gets a generated one. Routes reference it by this id.",
|
||||
"type": "string"
|
||||
},
|
||||
"passHostHeader": {
|
||||
"description": "PassHostHeader forwards the client's original Host header upstream instead\nof rewriting it to the backend's.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_ingress_services"
|
||||
},
|
||||
{
|
||||
"description": "PutMiddleware creates or replaces one edge transform and hot-applies it. POST\nmints an id when the body omits one; PUT takes the id from the URL, which wins\nover any id in the body. type must be one of redirectScheme, stripPrefix,\naddPrefix or headers, and stripPrefix/addPrefix each require their config key.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"config": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Config is the transform's parameters: redirectScheme takes scheme (default\nhttps) and permanent (\"true\" ⇒ 301, else 302); stripPrefix REQUIRES\nprefixes (comma-separated, first match wins); addPrefix REQUIRES prefix;\nheaders is a header→value map set on the response.",
|
||||
"type": "object"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the transform within the org: [A-Za-z0-9-_.], at most 128\nchars. A create that omits it gets a generated one. Routes reference it by\nthis id.",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "Type is the transform: redirectScheme, stripPrefix, addPrefix or headers.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_ingress_middlewares_id"
|
||||
},
|
||||
{
|
||||
"description": "PutRoute creates or replaces one routing rule and hot-applies the new table —\nthere is no config file and no restart. POST mints an id when the body omits\none; PUT takes the id from the URL, which wins over any id in the body. A\nroute's host is a GLOBALLY unique DNS claim: a host another org's route already\nholds is refused 409, so no tenant can hijack another's hostname.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"host": {
|
||||
"description": "Host is the exact hostname this route matches, lowercased with any trailing\ndot stripped. It is a GLOBALLY unique claim — one route across the whole\nedge may hold a host, so no tenant can hijack another's.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the route within the org: [A-Za-z0-9-_.], at most 128 chars.\nA create that omits it gets a generated one.",
|
||||
"type": "string"
|
||||
},
|
||||
"middlewares": {
|
||||
"description": "Middlewares are the ids of the edge transforms to apply, in this order,\nbefore the request reaches the service. At most 16.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"pathPrefix": {
|
||||
"description": "PathPrefix narrows the match to requests under this path; it must start\nwith \"/\". Empty matches every path on the host.",
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"description": "Priority orders routes that share a host: higher wins, and equal priorities\nfall back to the longer PathPrefix.",
|
||||
"type": "integer"
|
||||
},
|
||||
"service": {
|
||||
"description": "Service is the id of the backend pool this route dispatches to. A route\nnaming a service that does not exist is skipped at compile, not served.",
|
||||
"type": "string"
|
||||
},
|
||||
"tls": {
|
||||
"description": "TLS asks the edge to terminate TLS for Host with an ACME-managed certificate.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_ingress_routes_id"
|
||||
},
|
||||
{
|
||||
"description": "PutService creates or replaces one backend pool and hot-applies it. POST mints\nan id when the body omits one; PUT takes the id from the URL, which wins over\nany id in the body. A pool needs at least one backend and every backend URL\nmust be http(s)://host[:port].",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Backend": {
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "URL is the upstream server, http(s)://host[:port].",
|
||||
"type": "string"
|
||||
},
|
||||
"weight": {
|
||||
"description": "Weight is this member's share of the round-robin; must be \u003e= 0.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"backends": {
|
||||
"description": "Backends are the upstream servers to balance across: 1..32 of them.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Backend"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID identifies the pool within the org: [A-Za-z0-9-_.], at most 128 chars.\nA create that omits it gets a generated one. Routes reference it by this id.",
|
||||
"type": "string"
|
||||
},
|
||||
"passHostHeader": {
|
||||
"description": "PassHostHeader forwards the client's original Host header upstream instead\nof rewriting it to the backend's.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_ingress_services_id"
|
||||
},
|
||||
{
|
||||
"description": "PutTLS replaces the caller org's ACME intent and hot-applies what can be\nhot-applied. extraHosts are normalized and validated, then feed the ACME\nHostPolicy on the reload this op performs, alongside the per-route tls flags.\nacmeEmail and staging bind an ACME account for the lifetime of an edge process,\nso they only take effect when the edge (re)starts — the returned note says so.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"acmeEmail": {
|
||||
"description": "ACMEEmail is the ACME account email. It binds an account for the lifetime\nof an edge process, so it applies only when the edge (re)starts.",
|
||||
"type": "string"
|
||||
},
|
||||
"extraHosts": {
|
||||
"description": "ExtraHosts get certificates without owning a route — at most 256. They feed\nthe ACME HostPolicy and hot-apply on the next reload.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"staging": {
|
||||
"description": "Staging issues from Let's Encrypt's staging directory (untrusted certs, high\nrate limits). Like ACMEEmail it applies only when the edge (re)starts.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_ingress_tls"
|
||||
}
|
||||
]
|
||||
@@ -1,353 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Forgets a connector: every custodied secret, then the row.\nIdempotent — dropping a never-connected id still answers {disconnected:true}\n(disconnect() parity). No provider Revoke: none of the user-plane providers\nexposes a revoke endpoint.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the connector id, provider + \":\" + label (\"openai:default\") — the\nauth-profile-id shape. Another user's id is simply no row, so 404.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_connectors_id"
|
||||
},
|
||||
{
|
||||
"description": "Deletes the repo's Pages site. 404 when there is none, so a\ncaller can tell \"turned it off\" from \"there was nothing on\".",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"repo": {
|
||||
"description": "Repo is the repository's short name within the org's installation, with no\nowner prefix (the owner is server-derived from the grant). A trailing \".git\"\nis stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_integrations_github_repos_repo_pages"
|
||||
},
|
||||
{
|
||||
"description": "Lists the caller's OWN connectors across every provider — the set\n`hanzo connector ls` prints. Rows are keyed (org,user), so this can never\nsurface another user's connector, and no secret is in the view.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_connectors"
|
||||
},
|
||||
{
|
||||
"description": "Hands the custodied access token to its owner — the ONE place\ncustody exits. The (org,user)-keyed row IS the same-user gate: another user's\nid is simply \"no row\" → 404. fresh() auto-rotates within the refreshSkew\nwindow; static providers degenerate to a plain kmsGet of Secrets[0]. Refresh\ntokens are NEVER returned — custody keeps the sink. The token is never logged.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the connector id, provider + \":\" + label (\"openai:default\") — the\nauth-profile-id shape. Another user's id is simply no row, so 404.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_connectors_id_token"
|
||||
},
|
||||
{
|
||||
"description": "Lists the user-scoped provider cards — the catalog of what a\nuser can connect, and how. Methods derive from capabilities (Device/Adopt/Verify\n— Mount asserts at least one), never from a parallel kind enum.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_connectors_providers"
|
||||
},
|
||||
{
|
||||
"description": "Returns every registered integration provider together with THIS org's\nconnection status for it — the catalog the console's Integrations page renders.\nOrg-authed: a caller with no validated principal is 403, because the status is\nper-org and there is no org-less answer. User-plane providers (the /v1/connectors\nsurface) are omitted; the two planes are disjoint.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_integrations"
|
||||
},
|
||||
{
|
||||
"description": "Lists the org's granted GitHub repositories, each annotated with its\nnative import + sync status from the git object plane. Org-authed: the org comes\nfrom the validated principal, and the granted set is bounded to THAT org's\ninstallation token — an org can never enumerate another org's repos. The console\npolls it to watch an import flip a repo to imported.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_integrations_github_repos"
|
||||
},
|
||||
{
|
||||
"description": "Returns the repo's Pages status, live URL, custom domain and build\nsource. The repo is resolved against the org installation's GRANTED set, so a\ncaller can never address a repo the App was not granted; 404 when the repo has no\nPages site.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"repo": {
|
||||
"description": "Repo is the repository's short name within the org's installation, with no\nowner prefix (the owner is server-derived from the grant). A trailing \".git\"\nis stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_integrations_github_repos_repo_pages"
|
||||
},
|
||||
{
|
||||
"description": "Returns ONE provider with this org's connection status — the same view list\ncarries, for a single id. An unknown id is 404, and so is a user-plane provider:\nthe org surface never resolves one.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the registry id of the connector — \"slack\", \"github\",\n\"cloudflare\". Unknown ids are 404, as are the user-plane (/v1/connectors)\nproviders, which this surface never resolves.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_integrations_provider"
|
||||
},
|
||||
{
|
||||
"description": "Forces a token rotation for a connected connector, ahead of the\nautomatic rotation a token read would do inside the expiry window. Only\nproviders that declare a Refresh support it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the connector id, provider + \":\" + label (\"openai:default\") — the\nauth-profile-id shape. Another user's id is simply no row, so 404.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_connectors_id_refresh"
|
||||
},
|
||||
{
|
||||
"description": "Is the direct intake path: a customer-held token/setup-token\n(Verify) or an externally obtained OAuth bundle from the CLI's local PKCE\n(Adopt). ALWAYS verify-before-store: a bad credential is refused and NOTHING\nis persisted (connectByCredential's fail-closed order).",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"oauthBundleIn": {
|
||||
"properties": {
|
||||
"access": {
|
||||
"description": "Access is the access token.",
|
||||
"type": "string"
|
||||
},
|
||||
"account": {
|
||||
"description": "Account is the account label the flow reported; sanitized on ingest.",
|
||||
"type": "string"
|
||||
},
|
||||
"refresh": {
|
||||
"description": "Refresh is the refresh token. It is sealed and NEVER handed back out.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"description": "AccountID scopes the credential where the provider's Verify needs one.",
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"description": "Label names this connection; empty means \"default\".",
|
||||
"type": "string"
|
||||
},
|
||||
"oauth": {
|
||||
"$ref": "#/$defs/oauthBundleIn",
|
||||
"description": "OAuth is a bundle the CLI already obtained through its own local PKCE flow.\nPresent ⇒ the Adopt path; absent ⇒ the Token path."
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the user-scoped provider's registry id, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"description": "Token is the customer-held credential for the Verify path. Read on STDIN by\nthe CLI, never argv; never logged, echoed, or stored outside KMS.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_connectors_provider_credential"
|
||||
},
|
||||
{
|
||||
"description": "Begins a device sign-in and returns the code to show the user plus\nhow to poll for completion. KMS readiness is checked NOW rather than dead-ending\nthe user at poll-done (connect() parity), and the per-provider connector cap is\nchecked before the provider is called. The provider's device code is persisted\nonly in the encrypted grants table and is NEVER returned.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"label": {
|
||||
"description": "Label names this connection so one user can hold several per provider\n(\"work\", \"personal\"). Empty means \"default\". 1-64 of [A-Za-z0-9._-].",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the user-scoped provider's registry id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_connectors_provider_device"
|
||||
},
|
||||
{
|
||||
"description": "Advances a device sign-in. Terminal outcomes are DATA, not errors\n(verifyConn {active:false} discipline) — the status set is closed:\npending|connected|denied|expired. pollSlow collapses to \"pending\" on the\nwire; the raised cadence rides interval.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"flow": {
|
||||
"description": "Flow is the id deviceStartOut returned. Expired or another user's flow is\nindistinguishable from an unknown one: 404.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the user-scoped provider's registry id, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_connectors_provider_device_flow_poll"
|
||||
},
|
||||
{
|
||||
"description": "Seeds the native tracker with the EXISTING issues across the\norg's granted repos (default state=open); the webhook keeps them live thereafter.\nOrg-scoped by the validated principal — a caller only ever backfills its OWN org.\nSynchronous + bounded (a total time budget and an issue cap) so it returns the\ncounts directly; idempotent by ExtRef, so a re-run continues where a truncated\npass left off and never duplicates.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"state": {
|
||||
"description": "State is the GitHub issue state to walk: \"open\" (the default), \"closed\" or\n\"all\". Anything else is a 400.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_github_issues_backfill"
|
||||
},
|
||||
{
|
||||
"description": "GithubImport imports the selected (or all) granted repos into git.hanzo.ai. The\nselection is intersected with the installation's GRANTED set, so a client can\nnever import a repo the App was not granted (org isolation + a grant check). The\nimport runs in a bounded background worker (don't block the request), so the\nanswer is 202 Accepted; poll GET /v1/integrations/github/repos for the per-repo\nstatus to flip to imported.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"all": {
|
||||
"description": "All imports every repository the installation grants, instead of naming\nthem. Archived and disabled repositories are skipped either way — they\ncannot be fetched.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"repos": {
|
||||
"description": "Repos names the repositories to import, either owner-qualified\n(\"hanzo-apps/ai\") or as a bare name (\"ai\"); a trailing \".git\" is stripped.\nA bare name that matches more than one granted repository is an error\nrather than a guess, because one Hanzo org may hold several GitHub\ninstallations and a name is only unique within an owner.\nIgnored when all is true.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_github_repos_import"
|
||||
},
|
||||
{
|
||||
"description": "Creates the repo's Pages site and answers 201 Created with it.\nWith buildType \"workflow\" the site builds via GitHub Actions; otherwise it builds\nfrom a branch source, defaulting to the repo's own default branch when none is\ngiven. Only \"/\" and \"/docs\" are legal source paths (GitHub's rule).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"branch": {
|
||||
"description": "Branch is the legacy source branch; empty defaults to the repo's own default\nbranch. Ignored when buildType is \"workflow\".",
|
||||
"type": "string"
|
||||
},
|
||||
"buildType": {
|
||||
"description": "BuildType selects the builder: \"workflow\" builds via GitHub Actions, anything\nelse builds from the branch source above.",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is the source directory within the branch: \"/\" (the default) or \"/docs\".\nGitHub allows no others.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo is the repository, from the :repo path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_github_repos_repo_pages"
|
||||
},
|
||||
{
|
||||
"description": "GithubPagesBuild requests a Pages rebuild and returns the queued build's status.\nThe build is queued AT GITHUB, not completed here, so the answer is 202 Accepted\nand its status is the one GitHub reported at queue time. 404 when the repository\nhas no Pages site, or when the org's installation was not granted it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"repo": {
|
||||
"description": "Repo is the repository's short name within the org's installation, with no\nowner prefix (the owner is server-derived from the grant). A trailing \".git\"\nis stripped.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_github_repos_repo_pages_builds"
|
||||
},
|
||||
{
|
||||
"description": "Acquires the org's credential for one provider. It has TWO paths and the\nREQUEST picks which: a \"token\" key in the body seals that credential directly\n(verify-before-store), and its absence begins the 3-legged OAuth flow — minting a\nsingle-use nonce plus an HMAC-signed state that binds this org to this provider,\nand answering with the provider's authorize URL for the caller to redirect to.\n\nFail-closed order, unchanged: no principal → 403; unknown provider → 404; an\nAdminOnly connector without the caller's own-org admin bit → 403; not configured\n→ 503; KMS not ready → 503 (the flow WILL need to seal a token, so refuse now\nrather than dead-end at the callback).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"accountId": {
|
||||
"description": "AccountID is the provider account the credential should be scoped to, for the\nproviders whose Verify needs one (Cloudflare). Ignored by the OAuth path.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the connector's registry id, from the :provider path segment.",
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"description": "Token is the customer's provider credential. Its PRESENCE — not its value —\nis what selects the apikey seal over the OAuth flow for a provider that\noffers both: {\"token\":\"…\"}, even empty, is an apikey attempt (→ verify, which\nanswers the \"token required\" 400 on an empty value), while a body with no\ntoken key (the console Connect button, `hanzo connector add` with no --token)\nstarts OAuth. Read on STDIN by the CLI, never argv; never logged or echoed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_provider_connect"
|
||||
},
|
||||
{
|
||||
"description": "Revokes (best-effort) and forgets an org's connection: it deletes\nevery custodied KMS secret and the connection row. Idempotent — disconnecting a\nprovider that was never connected still returns {disconnected:true}. Symmetric\nwith connect: an AdminOnly connector needs the caller's own-org admin bit.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the registry id of the connector — \"slack\", \"github\",\n\"cloudflare\". Unknown ids are 404, as are the user-plane (/v1/connectors)\nproviders, which this surface never resolves.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_provider_disconnect"
|
||||
},
|
||||
{
|
||||
"description": "Re-checks a CONNECTED apikey connector's stored credential against the\nprovider, live (`hanzo connector verify`). Org-scoped (any member may check\nstatus); the credential is read from KMS, verified, and NEVER returned or logged.\nA verification failure is reported as {active:false}, not an error — the console/\nCLI renders it. Only apikey providers support verify (OAuth tokens are checked at\nuse, not re-verified here).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the registry id of the connector — \"slack\", \"github\",\n\"cloudflare\". Unknown ids are 404, as are the user-plane (/v1/connectors)\nproviders, which this surface never resolves.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_provider_verify"
|
||||
},
|
||||
{
|
||||
"description": "Mints a short, single-use deep-link code bound to the caller's\norg and returns the t.me link the console navigates to. Org-authed: a caller with\nno validated principal is 403 (same gate as the framework connect). The code is\nstored as an oauth_nonce (org,telegram); the webhook's /start handler claims it to\nbind chat→org. It is short (128-bit hex) so it fits Telegram's 64-char `start`\npayload limit.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_integrations_telegram_connect"
|
||||
},
|
||||
{
|
||||
"description": "Sets or clears the custom domain (cname) and updates HTTPS\nenforcement, build type, or source. ONLY the provided fields are sent to GitHub,\nso an update never resets a setting the caller did not mention.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"branch": {
|
||||
"description": "Branch switches the legacy source branch. Empty leaves the source alone.",
|
||||
"type": "string"
|
||||
},
|
||||
"buildType": {
|
||||
"description": "BuildType switches the builder: \"legacy\" or \"workflow\". Empty leaves it.",
|
||||
"type": "string"
|
||||
},
|
||||
"cname": {
|
||||
"description": "CNAME is the custom domain. Omit to leave it alone, \"\" to clear it, or a\nvalid FQDN to set it.",
|
||||
"type": "string"
|
||||
},
|
||||
"httpsEnforced": {
|
||||
"description": "HTTPSEnforced toggles GitHub's enforce-HTTPS bit. Omit to leave it alone.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"description": "Path is the source directory to pair with Branch: \"/\" (the default) or\n\"/docs\". Read only when Branch is given.",
|
||||
"type": "string"
|
||||
},
|
||||
"repo": {
|
||||
"description": "Repo is the repository, from the :repo path segment.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_integrations_github_repos_repo_pages"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,123 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DisconnectConnector revokes a connection: it tombstones the stored credential\nso a later sync cannot reuse it, purges this provider's points from the org's\nvector namespace, and marks the connector disconnected. The documents already\ningested stay in the org's store — they are the org's own data — but stop being\nretrievable by search; a caller deletes them through the document surface.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the connector to act on: github, slack, google or notion.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_kb_connectors_provider"
|
||||
},
|
||||
{
|
||||
"description": "ListConnectors returns every supported knowledge connector with THIS org's\nconnection state and the REAL number of documents each has ingested into the\norg's store. A provider that is configured for the deployment but not yet\nconnected appears as disconnected, so the console can offer a Connect button.\nNo secret is ever returned.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_kb_connectors"
|
||||
},
|
||||
{
|
||||
"description": "ListConnectorCatalog returns the ONE catalog of everything a caller can\nconnect: every first-party connector and every long-tail one, in a single list\nsorted by provider. `configured` reports whether this deployment holds OAuth\ncredentials for a source, so the console can show Connect rather than a dead\nbutton, and `kind` is a badge only — the connect and sync lifecycle is\nidentical for both. The catalog itself is org-independent; a validated\nprincipal is still required. It is metadata only: no secret is ever returned.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_kb_connectors_catalog"
|
||||
},
|
||||
{
|
||||
"description": "CompleteConnectorOAuth finishes an OAuth connection: it exchanges the\nprovider's code for a token, seals that token in KMS, and records the\nconnection. THE ORG COMES FROM THE SIGNED STATE, not from a header and not from\nthe provider, so an attacker cannot bind their own account to someone else's\norg — a tampered, expired or foreign-provider state is refused outright. The\ntoken itself is never returned, never written into the document, and never\nlogged; the document holds only its KMS path.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the provider's authorization code, exchanged for a token.",
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
"description": "Error is the provider's denial reason when the user refused consent.",
|
||||
"type": "string"
|
||||
},
|
||||
"provider": {
|
||||
"description": "Provider is the connector completing its flow, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"description": "State is the org-bound value this server signed at connect time.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_kb_connectors_provider_callback"
|
||||
},
|
||||
{
|
||||
"description": "StartConnectorOAuth returns the provider authorize URL the console opens to\nconnect this org's account. There is no server-side redirect — the console\nstays in control of the navigation. The URL carries a state this server SIGNED\nover the caller's validated org, so the connection the callback completes can\nonly ever land in that org.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the connector to act on: github, slack, google or notion.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_kb_connectors_provider_connect"
|
||||
},
|
||||
{
|
||||
"description": "GetKnowledgeGraph returns the caller org's knowledge as a node/edge graph\nshaped for a force-directed renderer: pages, memories and synced sources as\nnodes; the page parent tree, the wikilinks between pages, and each source's\nconnector provenance as edges. Wikilink targets are resolved HERE by title or\nslug, so a rename never needs an edge rewrite and a link that matches no page\nrenders as its own \"unresolved\" node instead of vanishing. ?project= narrows\nit. A store outage degrades to an honest empty graph, never a 5xx.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"project": {
|
||||
"description": "Project narrows the graph to one project scope. Empty reads the whole org.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_kb_graph"
|
||||
},
|
||||
{
|
||||
"description": "SyncConnector pulls the provider's documents for the caller's org and files\nthem as knowledge sources, which the store's own hook then indexes — so a\nsynced document is retrievable exactly like a hand-written page. The org is the\nvalidated tenant and the credential is read from KMS, so an org can only ever\nsync its own connection. A provider failure is reported honestly (502) and\nrecorded on the connector rather than silently swallowed.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"provider": {
|
||||
"description": "Provider is the connector to act on: github, slack, google or notion.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_kb_connectors_provider_sync"
|
||||
},
|
||||
{
|
||||
"description": "SearchKnowledge runs a semantic search over the caller org's own knowledge —\nits wiki pages, its agent memories and everything its connectors have synced —\nand returns the matching passages. This is the RAG entry point: an agent asks\n\"what does this org know about X\" and the org's OWN vector namespace answers.\nThe org comes from the validated principal, and both the collection and the\npayload filter are pinned to it, so cross-tenant retrieval is impossible. An\nunreachable index returns an honest empty result set with degraded=true, never\na 5xx.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"doctypes": {
|
||||
"description": "DocTypes restricts retrieval to a subset of the indexed knowledge doctypes\n(kb-page, kb-memory, kb-source). An empty or foreign list reads all of them.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit bounds the hits returned. Default 10, maximum 50.",
|
||||
"type": "integer"
|
||||
},
|
||||
"project": {
|
||||
"description": "Project narrows retrieval to one project scope.",
|
||||
"type": "string"
|
||||
},
|
||||
"query": {
|
||||
"description": "Query is the natural-language question. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_kb_search"
|
||||
}
|
||||
]
|
||||
@@ -1,111 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Activity returns the per-day usage series for ONE authorized subject — the points a\ncontribution heatmap and a timeline are drawn from, gap-filled so every day in the\nrange is present. Authorization is resolved server-side from the validated\nprincipal, so a caller can never widen the subject past what they are entitled to:\na non-admin reads only themselves and their own org. subject=project answers empty\nwith a note, because the usage ledger records no project column yet. When the\nwarehouse is not connected the series answers empty with available=false rather\nthan fabricated days.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"from": {
|
||||
"description": "From is the first day of the range, \"2006-01-02\". Defaults to 90 days back.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID names the subject within what the caller is entitled to see. Omitted (or\n\"me\") it is the caller themselves, or their own org. Another user requires org\nadmin and must belong to the caller's org; another org requires a SuperAdmin.",
|
||||
"type": "string"
|
||||
},
|
||||
"subject": {
|
||||
"description": "Subject is what the series is about: \"user\" (default), \"org\" or \"project\".",
|
||||
"type": "string"
|
||||
},
|
||||
"to": {
|
||||
"description": "To is the last day of the range, \"2006-01-02\". Defaults to today; the span is\nclamped to 366 days.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_usage_activity"
|
||||
},
|
||||
{
|
||||
"description": "Leaderboard ranks AI usage over a window, either the users of the caller's own org\nor organizations against each other, and always reports the caller's own standing\neven when it falls outside the returned page. Identities are private by default: a\ncaller sees themselves, plus the peers or orgs that opted into public listing, and\nonly an admin sees their own org's members named. Cross-org spend is restricted to\nplatform admins. When the warehouse is not connected the board answers empty with\navailable=false rather than a fabricated rank.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned, clamped to 100. Defaults to 10, which is also\nwhat a non-positive or unparseable value takes.",
|
||||
"type": "integer"
|
||||
},
|
||||
"metric": {
|
||||
"description": "Metric is the value ranked: tokens (default), requests, or cost.",
|
||||
"type": "string"
|
||||
},
|
||||
"period": {
|
||||
"description": "Period is the window ranked: day, week, month (default) or all.",
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"description": "Scope picks the board: \"personal\" (default) ranks the caller among their own\norg's users, \"org\" is that same org board named for an admin, \"global\" ranks\norganizations against each other.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_usage_leaderboard"
|
||||
},
|
||||
{
|
||||
"description": "GetOptin returns the caller's own public-listing preference and their org's,\neach with whether the caller may change it. Public listing is opt-in and private\nby default, so a fresh caller reads listed=false for both.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_usage_leaderboard_optin"
|
||||
},
|
||||
{
|
||||
"description": "Backfill seeds the derived usage rollup from ledger history — the rows written\nbefore the incremental view existed, which that view can never capture. SuperAdmin\nonly. Because the rollup accumulates, a second unguarded run would double every\nday it re-reads, so it refuses with 409 when the rollup already holds rows unless\nforce=true is passed; forcing WILL double-count.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"before": {
|
||||
"description": "Before bounds the seed to ledger rows written before this RFC3339 instant.\nDefaults to now; pass the incremental view's creation instant so the seed and\nthe live view never overlap and double a day.",
|
||||
"type": "string"
|
||||
},
|
||||
"force": {
|
||||
"description": "Force must be exactly \"true\" to seed a rollup that already holds rows. It is\nspelled as a string, not a flag, because the guard has always compared this\nvalue literally — \"1\" and \"yes\" do NOT force.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_usage_rollup_backfill"
|
||||
},
|
||||
{
|
||||
"description": "PutUserOptin sets the CALLER's own public-listing preference on the leaderboard.\nSelf only: the row written is keyed by the caller's validated ledger identity, so\nthis can never edit another member's visibility whatever the request says. A\ncaller opting in with no handle is given their username, so a listed row never\nrenders as \"Anonymous\" to its own owner.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"handle": {
|
||||
"description": "Handle is the display name shown on a listed row: 1-40 characters of letters,\ndigits, space, dot, underscore, apostrophe or hyphen. Left empty on a listing\nopt-in it defaults to the caller's username.",
|
||||
"type": "string"
|
||||
},
|
||||
"listed": {
|
||||
"description": "Listed publishes the caller's row to other viewers of the board when true, and\nanonymizes it when false.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_usage_leaderboard_optin"
|
||||
},
|
||||
{
|
||||
"description": "PutOrgOptin sets the ORG's listing on the cross-org global board. Only an admin of\nthe caller's own org — an org admin or a platform SuperAdmin — may change it, and\nthe org written is the caller's validated tenant, never a value from the request.\nListing consents to publishing the org's usage VOLUME; cross-org spend stays\nrestricted to platform admins regardless.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"display": {
|
||||
"description": "Display is the name shown for the org on that board: 1-40 characters of\nletters, digits, space, dot, underscore, apostrophe or hyphen. Left empty on a\nlisting opt-in it defaults to the org id.",
|
||||
"type": "string"
|
||||
},
|
||||
"listed": {
|
||||
"description": "Listed publishes the org on the cross-org global board when true, and withdraws\nit when false.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_usage_leaderboard_optin_org"
|
||||
}
|
||||
]
|
||||
@@ -1,192 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListLegalDocuments returns the org's generated documents, newest first, WITHOUT\ntheir rendered content — fetch one document to read its body.\n\nThe response is marked no-store: these records name the counterparties an org is\ncontracting with, and must not sit in a shared cache.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page. Absent or unparseable means the store's own default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_documents"
|
||||
},
|
||||
{
|
||||
"description": "GetLegalDocument returns one of the org's documents WITH its rendered body. 404\nwhen the org has no document with that id — a document is never readable across\norgs.\n\nThe response is marked no-store: the body is contract text, sealed at rest and\nreturned only to the owning org, and must not sit in a shared cache.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the document's server-minted handle, \"doc_\"-prefixed.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_documents_id"
|
||||
},
|
||||
{
|
||||
"description": "ListLegalFilings returns the org's filing records, newest first — which documents\nwere filed where, through which provider, and what the filing's honest status is.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit bounds the page. Absent or unparseable means the store's own default.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_filings"
|
||||
},
|
||||
{
|
||||
"description": "LegalHealth reports that the legal subsystem is serving and how many built-in\ntemplates its catalog carries. It reads no tenant, so a liveness prober that\nsends no principal is answered rather than refused.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_health"
|
||||
},
|
||||
{
|
||||
"description": "ListLegalTemplates returns the org's effective template catalog: every built-in\ntemplate, with any the org has overridden replaced by its own latest version.\n\nThe listing carries each template's metadata and its declared MERGE FIELDS — the\nkeys a document generation must supply — but never the template bodies; fetch one\ntemplate to get its body. Templates in the formation and equity categories are\nmarked counselReview: every document rendered from them carries a counsel notice,\nand that posture cannot be dropped by an override.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_templates"
|
||||
},
|
||||
{
|
||||
"description": "GetLegalTemplate returns one template resolved for the caller's org — the org's\nown override if it has saved one, else the built-in — with its full text/template\nbody and its declared merge fields. 404 when neither exists.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the template's stable id, e.g. \"nda\" or \"safe\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_legal_templates_id"
|
||||
},
|
||||
{
|
||||
"description": "GenerateLegalDocument renders a document from a template and the caller's own\nmerge data, seals it in the org's store, and returns it with its rendered body.\n\nThe render is PURE and deterministic — no clock, no I/O — so the same template\nversion and the same data always produce identical bytes, which is what makes a\ngenerated contract reproducible. It fails CLOSED on a missing merge field: there\nis no blank-filled contract, only a 400 naming the fields that were absent. When\nthe template is counsel-review the rendered body opens with the counsel notice,\nwhich no caller can suppress.\n\nThe document is a DRAFT. Hanzo Legal manages documents; it does not give legal\nadvice and does not determine that a document is valid or sufficient.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"data": {
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "Data supplies every merge field the template declares, keyed by field key.\nEvery declared field is REQUIRED: a missing one is refused with 400 rather\nthan rendered as a blank into a contract.",
|
||||
"type": "object"
|
||||
},
|
||||
"templateId": {
|
||||
"description": "TemplateID is the template to render. Required; resolved for the caller's\norg, so an override wins over the built-in.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_legal_documents"
|
||||
},
|
||||
{
|
||||
"description": "RequestLegalSignature opens an e-signature request over one document and moves it\nto out_for_signature, returning the provider's reference for the request.\n\nThe provider is whatever this deployment has wired. The honest default is\n\"manual\": the request is recorded and the org fulfils it out of band — nothing\nhere fabricates a signature, and the stub never reports itself complete.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"legalSigner": {
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the document to send for signature, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"signers": {
|
||||
"description": "Signers are the people who must sign, by name and email. At least one is\nrequired.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/legalSigner"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_legal_documents_id_sign"
|
||||
},
|
||||
{
|
||||
"description": "CreateLegalFiling records a filing of one or more of the org's documents with a\nstate or agency, and returns the tracking record.\n\nIt is a TRACKING record, not an autonomous filing. With no filing partner wired\nthe honest status is \"manual\" and the note says so: the documents were generated\nfor signature, and the org files them through its registered agent. Nothing here\ninvents a filing id it does not have.\n\nEvery document id must belong to the caller's org; one that does not is a 404\nnaming it, so a filing can never reach across tenants.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"documentIds": {
|
||||
"description": "DocumentIDs are the documents to file. At least one is required, and every\none must belong to the caller's org — a filing can never reach across orgs.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"jurisdiction": {
|
||||
"description": "Jurisdiction is the state or agency the filing is for, e.g. \"DE\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_legal_filings"
|
||||
},
|
||||
{
|
||||
"description": "SaveLegalTemplateOverride saves the org's own version of a template — a custom\nNDA, a house MSA — and returns it with its new version number. It takes effect\nfor that org only; other orgs keep the built-in.\n\nTwo boundaries cannot be crossed here. Overriding a built-in INHERITS its\ncategory and its counsel-review posture, which can be raised but never dropped;\nand a formation or equity template is counsel-review whatever the caller sends,\nso no org can generate a securities-class document without the notice.\n\nThe body is validated on save, not at generation: a template that references an\nUNDECLARED merge field is refused with 400 rather than stored and rendered blank\ninto a contract months later.",
|
||||
"inputSchema": {
|
||||
"$defs": {
|
||||
"Field": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "Body is the text/template source. Required. Every {{.key}} it references must\nbe declared in Fields, or the save is refused rather than rendering a blank\ninto a contract later.",
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"description": "Category groups the template: formation, equity, ops or sales. Optional when\noverriding a built-in, which supplies its own.",
|
||||
"type": "string"
|
||||
},
|
||||
"counselReview": {
|
||||
"description": "CounselReview marks a template whose documents must carry the counsel notice.\nIt can be raised but never lowered: a formation or equity template is always\ncounsel-review, and an override of a counsel-review built-in stays one.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"fields": {
|
||||
"description": "Fields declares the merge fields the body consumes. Every declared field is\nREQUIRED at generation — the engine fails closed on a missing one.",
|
||||
"items": {
|
||||
"$ref": "#/$defs/Field"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the template to override, from the path. Overriding a built-in id\ninherits that built-in's category, title and counsel-review posture.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title is the template's display name. Required unless a built-in supplies it.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_legal_templates_id"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,723 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Removes one of the caller org's audiences and answers 204. It\ndeletes the saved filter only — no customer, event or enrollment is touched.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the audience id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_marketing_audiences_id"
|
||||
},
|
||||
{
|
||||
"description": "Removes one of the caller org's posts and answers 204. A\npost already published is deleted from the calendar only — nothing is\nretracted from the network it went out on.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the post id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_marketing_calendar_id"
|
||||
},
|
||||
{
|
||||
"description": "Removes one of the caller org's campaigns and answers 204. A\ncampaign belonging to another org reads as not found and is left untouched.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_marketing_campaigns_id"
|
||||
},
|
||||
{
|
||||
"description": "Re-subscribes an address on one channel and answers 204. An\naddress that is not on the list reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "Address is the recipient, normalized (lower-cased, trimmed) so an opt-out\ncannot be slipped past on a case or whitespace difference. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the surface opted out of: email, sms, social, meta, google or\ntiktok. Empty means email. Opting out of one leaves the others reachable.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt is unix seconds, server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason is a free-text note, capped at 1024 bytes. The public one-click\nendpoint records \"one-click unsubscribe\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_marketing_suppressions"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's saved audiences, most recently updated first.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_audiences"
|
||||
},
|
||||
{
|
||||
"description": "Returns one of the caller org's saved audiences. An audience\nbelonging to another org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the audience id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_audiences_id"
|
||||
},
|
||||
{
|
||||
"description": "Evaluates the cohort LIVE — the same resolution an enrollment\nwould run — and reports how big it is and how many real mailboxes it reaches.\nIt is the honest answer to \"is this send worth making\": a cohort of 500 that\nmails 3 says so, in deliverable and unmatched. Nothing is sent.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the audience id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_audiences_id_preview"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's calendar, soonest scheduled first,\noptionally narrowed to one status.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status keeps only posts in that state (draft, scheduled, published,\nfailed, canceled). Empty means every post.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_calendar"
|
||||
},
|
||||
{
|
||||
"description": "Returns one of the caller org's posts, including the exact\nerror behind a failed publish. A post belonging to another org reads as not\nfound.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the post id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_calendar_id"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's campaigns, most recently updated first,\noptionally narrowed to one lifecycle status.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status keeps only campaigns in that lifecycle state (draft, scheduled,\nactive, paused, completed). Empty means every campaign.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_campaigns"
|
||||
},
|
||||
{
|
||||
"description": "Returns one of the caller org's campaigns. A campaign belonging to\nanother org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_campaigns_id"
|
||||
},
|
||||
{
|
||||
"description": "Returns every promo the deployment offers with its live counters:\nhow many orgs have redeemed it and how many redemptions remain under the cap.\nThe promos are fleet-wide, not per-org — only the counters move.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_promos"
|
||||
},
|
||||
{
|
||||
"description": "Prices a promo against a plan and seat count. It is PURE: nothing\nis redeemed, credited or counted, so it is safe to call from a pricing page on\nevery keystroke. An inactive promo or an exhausted cap quotes ineligible with\nthe reason rather than erroring.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the promo code from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"plan": {
|
||||
"description": "Plan is the plan being priced: pro, max or team. Anything else (including\nthe free Developer plan) has no list price and so nothing to discount.",
|
||||
"type": "string"
|
||||
},
|
||||
"seats": {
|
||||
"description": "Seats is the Team seat count; 0 means 1, and it is ignored for the\nsingle-seat plans.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_promos_code_eligibility"
|
||||
},
|
||||
{
|
||||
"description": "Returns the caller org's OWN redemption of a promo — an\norg-scoped read, so it can never surface another tenant's. Not found when this\norg has not redeemed it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the promo code from the path, e.g. \"first1000\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_promos_code_redemption"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's drip sequences, most recently updated first.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_sequences"
|
||||
},
|
||||
{
|
||||
"description": "Returns one of the caller org's sequences together with its steps\nin send order. A sequence belonging to another org reads as not found.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_sequences_id"
|
||||
},
|
||||
{
|
||||
"description": "Returns who is walking one sequence, most recently enrolled\nfirst, with each walk's current step and next due time.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_sequences_id_enrollments"
|
||||
},
|
||||
{
|
||||
"description": "Returns one sequence's steps in send order.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_sequences_id_steps"
|
||||
},
|
||||
{
|
||||
"description": "Rolls up the caller org's campaigns: how many there are, how many are\nactive, and the summed budget and spend in cents.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_summary"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's opt-out list, newest first — everyone the\nsend gate will refuse to deliver to.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_suppressions"
|
||||
},
|
||||
{
|
||||
"description": "Is the PUBLIC one-click endpoint (no principal): a recipient\nclicks the signed link in an email footer. The token binds (org, channel,\naddress), so a caller can only opt OUT exactly the tuple it was minted for —\nnever another address and never another org. An invalid token is refused, and\na deployment with no KMS-sealed key refuses rather than accepting anything.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "Address is the recipient to opt out.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the surface to opt out of.",
|
||||
"type": "string"
|
||||
},
|
||||
"org": {
|
||||
"description": "Org is the org the link was minted for.",
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"description": "Token is the HMAC over (org, channel, address). It is the ONLY authority\nhere — there is no principal — so it binds the request to one tuple and\nnothing else.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketing_unsubscribe"
|
||||
},
|
||||
{
|
||||
"description": "Saves a cohort filter for the caller's org. Name is required.\nOmitting event saves the WHOLE-ORG audience — every mailable customer — which\nneeds no analytics warehouse; naming one narrows that roster to the customers\nwho fired it within windowDays.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"event": {
|
||||
"description": "Event is the analytics event a member must have fired. EMPTY MEANS NO\nFILTER: the audience is then every mailable customer in the org, and no\nwarehouse is consulted.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned audience id (\"aud_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the audience's label. Required, trimmed, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
},
|
||||
"windowDays": {
|
||||
"description": "WindowDays is how far back the event counts, ending now. 0 means 30 and\nnothing above 3650 is honoured. Ignored when Event is empty.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_audiences"
|
||||
},
|
||||
{
|
||||
"description": "Adds a post to the content calendar. Channel and body are\nrequired. A scheduledAt in the future makes the post \"scheduled\" and the\ndurable sweep publishes it when it comes due — claimed once, so a post\npublishes at most once; without one it stays a draft.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "Body is the post text. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the target network: x, facebook, instagram, linkedin, tiktok,\nyoutube or threads. Required — a post must name where it goes.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"description": "Error is the exact reason the last publish attempt failed — the honest\nrecord behind a \"failed\" status, never a faked success.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned post id (\"cal_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"publishedAt": {
|
||||
"description": "PublishedAt is when the publish succeeded; 0 until it does.",
|
||||
"type": "integer"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"description": "ScheduledAt is the unix publish time; 0 leaves the post a draft, and any\nvalue makes it \"scheduled\" for the durable sweep to pick up.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is draft, scheduled, published, failed or canceled. Server-owned.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title is the post's internal label, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_calendar"
|
||||
},
|
||||
{
|
||||
"description": "Publishes a post NOW, synchronously, whatever its\nschedule. No social connector is wired today, so every channel answers an\nhonest 501 naming the seam a real one would plug into, and the post is\nrecorded failed with that exact reason — never a faked \"published\".",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the post id from the path, as returned by create.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_calendar_id_publish"
|
||||
},
|
||||
{
|
||||
"description": "Registers a campaign in the caller's org. Name is required;\nchannel defaults to email and status to draft, and a future scheduledAt with\nno explicit status makes the campaign \"scheduled\". Budget and spend are cents\nand are clamped to \u003e= 0. The id, createdAt and updatedAt of the input are\nignored — the server assigns them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"budget": {
|
||||
"description": "Budget and Spend are minor units (USD cents), clamped to \u003e= 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the delivery surface: email, sms, social, meta, google or\ntiktok. Empty means email.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned campaign id (\"camp_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the campaign's label. Required, trimmed, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"objective": {
|
||||
"description": "Objective is the free-text goal (\"signups\"), capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"description": "ScheduledAt is the unix send time; 0 means unscheduled. Setting it on a\ncampaign with no explicit status makes that status \"scheduled\".",
|
||||
"type": "integer"
|
||||
},
|
||||
"spend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the lifecycle: draft, scheduled, active, paused or completed.\nEmpty means draft.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_campaigns"
|
||||
},
|
||||
{
|
||||
"description": "Sets a campaign's send time and moves it to \"scheduled\". A\nscheduledAt of 0 clears the schedule and returns it to \"draft\".",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the campaign id from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"description": "ScheduledAt is the unix send time. 0 clears the schedule.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_campaigns_id_schedule"
|
||||
},
|
||||
{
|
||||
"description": "Redeems the promo for the caller's org, crediting the discount\nvalue to its wallet through the finance ledger. Three guards run under one\nlock so the cap cannot be raced past: the fleet-wide redemption cap, one\nredemption per org, and one per payment instrument.\n\nIt is IDEMPOTENT: an org that already redeemed gets its original redemption\nback with alreadyRedeemed true and is not credited twice.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the promo code from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"instrument": {
|
||||
"description": "Instrument identifies the payment method. It is the anti-farming key: one\nredemption per instrument, fleet-wide.",
|
||||
"type": "string"
|
||||
},
|
||||
"plan": {
|
||||
"description": "Plan is the plan being redeemed against: pro, max or team.",
|
||||
"type": "string"
|
||||
},
|
||||
"seats": {
|
||||
"description": "Seats is the Team seat count; 0 means 1. Seats beyond the promo's\nteamSeatCap bill at list.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_promos_code_redeem"
|
||||
},
|
||||
{
|
||||
"description": "Registers a drip sequence in the caller's org. Name is\nrequired; status defaults to draft, and a sequence must be ACTIVE before it\nwill accept enrollments. The id, createdAt and updatedAt of the input are\nignored — the server assigns them.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned sequence id (\"seq_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the sequence's label. Required, trimmed, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the lifecycle: draft, active or archived. Empty means draft, and\nONLY an active sequence accepts enrollments.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_sequences"
|
||||
},
|
||||
{
|
||||
"description": "Adds one contact or a whole audience to a sequence and schedules the\nfirst step for each. The sequence must be ACTIVE (a draft sends nothing), and\nthe request must name exactly one of address or audienceId.\n\nEnrolling is ALL this does: the message itself is sent later by the drip\nengine, through the suppression gate, so an opted-out customer can be enrolled\nhere and still never be mailed. Re-posting is safe — an address this sequence\nalready took is counted in alreadyEnrolled and never double-dripped — which is\nwhat makes retrying a partially-applied announcement a resume rather than a\nsecond send.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "Address is a single recipient, normalized (lower-cased, trimmed) before\nuse. Give this OR audienceId, never both and never neither.",
|
||||
"type": "string"
|
||||
},
|
||||
"audienceId": {
|
||||
"description": "AudienceID fans the sequence out over a saved audience, resolved live to\nthe org's mailable customers. Email only.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the delivery surface; empty means email. An audience resolves\nmailboxes, so an audience enroll must be email.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_sequences_id_enroll"
|
||||
},
|
||||
{
|
||||
"description": "Stops one walk mid-sequence and answers 204: no further step\nis sent, and steps already delivered are not recalled. Only an ACTIVE\nenrollment can be canceled — one already completed or canceled reads as not\nfound.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"eid": {
|
||||
"description": "EID is the enrollment id from the path, as returned by a single-address\nenroll.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_sequences_id_enrollments_eid_cancel"
|
||||
},
|
||||
{
|
||||
"description": "Flips draft/active/archived — the activation gate for\nsending, since only an active sequence accepts enrollments. It does not touch\nenrollments already walking: archiving stops new ones, not in-flight ones.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the sequence id from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is draft, active or archived. Required; there is no default here,\nunlike on create. Only an active sequence accepts enrollments.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_sequences_id_status"
|
||||
},
|
||||
{
|
||||
"description": "Appends a message to the END of a sequence: the new step's idx is one\npast the last, so steps arrive in the order they are added. Body is required\nand delaySeconds must be \u003e= 0. Adding a step does not disturb enrollments\nalready walking — one that has passed this index simply never sees it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "Body is the message text. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"delaySeconds": {
|
||||
"description": "DelaySeconds is how long after the previous step this one sends (after\nenrollment, for the first step). Must be \u003e= 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "SequenceID is the sequence id from the path (the route's :id).",
|
||||
"type": "string"
|
||||
},
|
||||
"subject": {
|
||||
"description": "Subject is the email subject line, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_sequences_id_steps"
|
||||
},
|
||||
{
|
||||
"description": "Records an opt-out for the org (admin / self-service\nmanagement). Address is required; channel defaults to email. It is idempotent:\nre-suppressing the same tuple keeps the original record rather than erroring.\nFrom here on the ONE send gate refuses that recipient on that channel.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"address": {
|
||||
"description": "Address is the recipient, normalized (lower-cased, trimmed) so an opt-out\ncannot be slipped past on a case or whitespace difference. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the surface opted out of: email, sms, social, meta, google or\ntiktok. Empty means email. Opting out of one leaves the others reachable.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt is unix seconds, server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"reason": {
|
||||
"description": "Reason is a free-text note, capped at 1024 bytes. The public one-click\nendpoint records \"one-click unsubscribe\".",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketing_suppressions"
|
||||
},
|
||||
{
|
||||
"description": "Replaces a post's editable fields. It is a full write, not\na patch, and it RESETS the lifecycle from the schedule: a scheduledAt makes\nthe post \"scheduled\" again and none makes it a draft — so editing a failed\npost requeues it rather than leaving it stuck.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"body": {
|
||||
"description": "Body is the post text. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the target network: x, facebook, instagram, linkedin, tiktok,\nyoutube or threads. Required — a post must name where it goes.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"description": "Error is the exact reason the last publish attempt failed — the honest\nrecord behind a \"failed\" status, never a faked success.",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned post id (\"cal_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"publishedAt": {
|
||||
"description": "PublishedAt is when the publish succeeded; 0 until it does.",
|
||||
"type": "integer"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"description": "ScheduledAt is the unix publish time; 0 leaves the post a draft, and any\nvalue makes it \"scheduled\" for the durable sweep to pick up.",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is draft, scheduled, published, failed or canceled. Server-owned.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title is the post's internal label, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_marketing_calendar_id"
|
||||
},
|
||||
{
|
||||
"description": "Replaces a campaign's editable fields. It is a full write, not\na patch: every field takes the value in the body, and an omitted one is\ncleared. The id comes from the path — the body cannot retarget another\ncampaign — and createdAt is never rewritten.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"budget": {
|
||||
"description": "Budget and Spend are minor units (USD cents), clamped to \u003e= 0.",
|
||||
"type": "integer"
|
||||
},
|
||||
"channel": {
|
||||
"description": "Channel is the delivery surface: email, sms, social, meta, google or\ntiktok. Empty means email.",
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"description": "CreatedAt and UpdatedAt are unix seconds, both server-assigned.",
|
||||
"type": "integer"
|
||||
},
|
||||
"id": {
|
||||
"description": "ID is the server-assigned campaign id (\"camp_\" + 128 random bits).",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the campaign's label. Required, trimmed, capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"objective": {
|
||||
"description": "Objective is the free-text goal (\"signups\"), capped at 1024 bytes.",
|
||||
"type": "string"
|
||||
},
|
||||
"scheduledAt": {
|
||||
"description": "ScheduledAt is the unix send time; 0 means unscheduled. Setting it on a\ncampaign with no explicit status makes that status \"scheduled\".",
|
||||
"type": "integer"
|
||||
},
|
||||
"spend": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"description": "Status is the lifecycle: draft, scheduled, active, paused or completed.\nEmpty means draft.",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_marketing_campaigns_id"
|
||||
}
|
||||
]
|
||||
@@ -1,98 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Unpublish withdraws one of the caller org's listings from the marketplace and\nanswers 204. Only the publishing org can remove its own listing; an id that is\nunknown, or belongs to another org, is the same 404, so a probe learns nothing\nabout what exists. Removing a listing removes its price from per-call enforcement;\nit does not uninstall the tool for anyone who already installed it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the listing to unpublish, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_marketplace_listings_id"
|
||||
},
|
||||
{
|
||||
"description": "Discover lists every tool and agent the caller can reach in their own org and\nproject, enriched with any public listing's title, category and price, and with\ninstalled=true on the ones already activated for that scope. It is the shop\nwindow: one read that answers what exists, what it costs and what is already on.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketplace"
|
||||
},
|
||||
{
|
||||
"description": "ListListings returns the listings the caller's own org has published — what this\norg is offering, not what it can buy. A publisher only ever sees its own rows.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_marketplace_listings"
|
||||
},
|
||||
{
|
||||
"description": "Install activates one tool for the caller's own org and project. A marketplace\ninstall IS the tool plane's activation write — one store, one truth — so an\ninstalled capability is immediately dispatchable and a monetized one is priced\nfrom its listing at every call. The tool must resolve in the caller's scope, so\ninstalling something that does not exist is refused rather than recorded.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"tool": {
|
||||
"description": "Tool is the registry name of the capability to activate (or deactivate) for\nthe caller's own org and project. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketplace_install"
|
||||
},
|
||||
{
|
||||
"description": "Publish offers one tool on the marketplace, optionally monetized. The tool must\nalready resolve in the publisher's own scope, so a listing can never advertise a\ncapability that does not exist; a listing with a price must name the payout wallet\nthe x402 seam settles to, so a monetized offer is never unpayable. The price is\nexact to 18 decimal places, so a per-call price below a cent is a real price and\nnot a rounded-away zero. The listing is owned by the publishing org, paid into a\nwallet of that same org, and answers 201 with the created row.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"category": {
|
||||
"description": "Category groups the listing in the shop window.",
|
||||
"type": "string"
|
||||
},
|
||||
"currency": {
|
||||
"description": "Currency denominates Price.",
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description is the long copy, clipped at 4096 characters.",
|
||||
"type": "string"
|
||||
},
|
||||
"price": {
|
||||
"description": "Price is the per-call price as a decimal USD string, exact to 18 places —\n\"0.0025\" is a quarter of a cent and stays one. Empty or \"0\" (the default)\npublishes it free; any positive price makes the listing monetized and\nrequires Recipient.",
|
||||
"type": "string"
|
||||
},
|
||||
"public": {
|
||||
"description": "Public makes the listing discoverable by other orgs. Private otherwise.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"recipient": {
|
||||
"description": "Recipient is the seller's payout wallet ID, in the publishing org — the\nwallet x402 pays. Required for a monetized listing.",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "Title is the shop-window name, 1-200 characters. Required.",
|
||||
"type": "string"
|
||||
},
|
||||
"tool": {
|
||||
"description": "Tool is the registry name of the capability being offered. It must already\nresolve in the publisher's own scope — there are no phantom listings.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketplace_listings"
|
||||
},
|
||||
{
|
||||
"description": "Uninstall deactivates one tool for the caller's own org and project, so it stops\nbeing dispatchable there. It is the exact inverse of install and touches the same\nactivation record; deactivating something that was never active is not an error.\nThe listing itself is untouched — this withdraws the caller's use of a capability,\nnot anyone's offer of it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"tool": {
|
||||
"description": "Tool is the registry name of the capability to activate (or deactivate) for\nthe caller's own org and project. Required.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_marketplace_uninstall"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,117 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "DeleteModel deletes a deployed inference model. kserve owns the teardown: the\nInferenceService goes away and the serving deployment behind it follows, so the\nmodel stops answering predict calls. Answers 204, or 404 for a name the\ncaller's org does not own.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_ml_models_name"
|
||||
},
|
||||
{
|
||||
"description": "DeleteExperiment deletes a hyperparameter-tuning experiment. Kubernetes\ngarbage-collects the Trials katib created under it, because they carry the\nExperiment as their owner. Answers 204, or 404 for a name the caller's org does\nnot own.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_train_experiments_name"
|
||||
},
|
||||
{
|
||||
"description": "DeleteJob deletes a training job. Kubernetes garbage-collects the pods the\ntrainer operator created under it, because they carry the TrainJob as their\nowner — so deleting the job is how a run is stopped. Answers 204, or 404 for a\nname the caller's org does not own.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_train_jobs_name"
|
||||
},
|
||||
{
|
||||
"description": "ListModels lists the inference models deployed in the caller's org. Each entry\ncarries the model's name, when Kubernetes admitted it, and kserve's live status\n— the spec is on the single-model read. An org that has deployed nothing gets\nan empty list.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ml_models"
|
||||
},
|
||||
{
|
||||
"description": "GetModel returns one deployed inference model. Its spec comes with it, and\nkserve's live status, which is where readiness and the serving address appear.\nA name the caller's org does not own answers 404, exactly as an unknown name\ndoes, so a probe learns nothing about another tenant's models.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_ml_models_name"
|
||||
},
|
||||
{
|
||||
"description": "ListExperiments lists the caller org's tuning experiments. Each entry carries\nthe experiment's name, when Kubernetes admitted it, and katib's live status —\nthe spec is on the single-experiment read.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_train_experiments"
|
||||
},
|
||||
{
|
||||
"description": "GetExperiment returns one hyperparameter-tuning experiment. Its spec comes with\nit, and katib's live status, which is where the best trial found so far is\nreported.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_train_experiments_name"
|
||||
},
|
||||
{
|
||||
"description": "ListTrials lists the katib Trials one experiment owns. The experiment is read\nFIRST, so a name the caller's org does not own is a clean 404 rather than an\nempty list; the Trials themselves are selected by katib's own\nkatib.kubeflow.org/experiment label within the caller's tenant namespace.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_train_experiments_name_trials"
|
||||
},
|
||||
{
|
||||
"description": "ListJobs lists the training jobs in the caller's org. Each entry carries the\njob's name, when Kubernetes admitted it, and the trainer operator's live status\n— the spec is on the single-job read.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_train_jobs"
|
||||
},
|
||||
{
|
||||
"description": "GetJob returns one training job. Its spec comes with it, and the trainer\noperator's live status, which is where a run's phase and its conditions are\nreported.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the resource to act on, taken from the path. Lower-cased and\ntrimmed to the DNS-1123 label a CustomResource's metadata.name must be.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_train_jobs_name"
|
||||
}
|
||||
]
|
||||
@@ -1,355 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Removes a stream with all its messages and consumers. Irreversible.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_mq_streams_name"
|
||||
},
|
||||
{
|
||||
"description": "Erases one message by sequence; the sequence gap remains.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the stream name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"seq": {
|
||||
"description": "Seq is the message's stream sequence, from the path.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_mq_streams_name_messages_seq"
|
||||
},
|
||||
{
|
||||
"description": "Removes a consumer and its delivery state; unacknowledged messages\nstay in the stream.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the consumer name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"stream": {
|
||||
"description": "Stream is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "delete_v1_mq_streams_stream_consumers_name"
|
||||
},
|
||||
{
|
||||
"description": "Reports whether the message plane behind this surface answers.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_health"
|
||||
},
|
||||
{
|
||||
"description": "Returns the broker's identity and the org's stream count.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_info"
|
||||
},
|
||||
{
|
||||
"description": "Returns the org's streams, name-ordered, with their live state.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the streams returned (1–1000, default 100).",
|
||||
"type": "integer"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Offset skips that many streams, name-ordered.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_streams"
|
||||
},
|
||||
{
|
||||
"description": "Returns one stream's configuration and live state.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_streams_name"
|
||||
},
|
||||
{
|
||||
"description": "Reads stored messages without a consumer: by sequence, by newest on a\nsubject, or walking a subject forward from a sequence.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"last_by_subject": {
|
||||
"description": "LastBySubject reads the newest message on this org-relative subject.",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"description": "Limit caps a next_by_subject walk (1–1000, default 100).",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the stream name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"next_by_subject": {
|
||||
"description": "NextBySubject walks forward from seq collecting messages on this org-relative subject (wildcards supported).",
|
||||
"type": "string"
|
||||
},
|
||||
"seq": {
|
||||
"description": "Seq reads the message at this sequence (with next_by_subject: the walk's start).",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_streams_name_messages"
|
||||
},
|
||||
{
|
||||
"description": "Returns a stream's consumers, name-ordered, with delivery state.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"description": "Limit caps the consumers returned (1–1000, default 100).",
|
||||
"type": "integer"
|
||||
},
|
||||
"offset": {
|
||||
"description": "Offset skips that many consumers, name-ordered.",
|
||||
"type": "integer"
|
||||
},
|
||||
"stream": {
|
||||
"description": "Stream is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_streams_stream_consumers"
|
||||
},
|
||||
{
|
||||
"description": "Returns one consumer's configuration and delivery state.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the consumer name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"stream": {
|
||||
"description": "Stream is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_mq_streams_stream_consumers_name"
|
||||
},
|
||||
{
|
||||
"description": "Creates a durable stream in the org's namespace and returns it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"max_age": {
|
||||
"description": "MaxAge caps message age, e.g. \"24h\" or \"7d\"; \"0\" (default) is unlimited.",
|
||||
"type": "string"
|
||||
},
|
||||
"max_bytes": {
|
||||
"description": "MaxBytes caps the stream's total stored bytes; -1 (default) is unlimited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_msg_size": {
|
||||
"description": "MaxMsgSize caps one message's size in bytes; -1 (default) is the broker's limit.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_msgs": {
|
||||
"description": "MaxMsgs caps the number of stored messages; -1 (default) is unlimited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the stream name, unique within the org (alphanumeric, hyphens, underscores).",
|
||||
"type": "string"
|
||||
},
|
||||
"num_replicas": {
|
||||
"description": "Replicas is the number of stream replicas (1–5); this plane runs 1.",
|
||||
"type": "integer"
|
||||
},
|
||||
"retention": {
|
||||
"description": "Retention is the retention policy: limits (default), interest, or workqueue.",
|
||||
"type": "string"
|
||||
},
|
||||
"storage": {
|
||||
"description": "Storage is the storage backend: file (default) or memory.",
|
||||
"type": "string"
|
||||
},
|
||||
"subjects": {
|
||||
"description": "Subjects are the org-relative subjects bound to this stream (wildcards supported). Default: the stream name.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_mq_streams"
|
||||
},
|
||||
{
|
||||
"description": "Removes messages from a stream, leaving its consumers in place.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"filter": {
|
||||
"description": "Filter purges only messages on this org-relative subject (wildcards supported).",
|
||||
"type": "string"
|
||||
},
|
||||
"keep": {
|
||||
"description": "Keep retains that many newest messages.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_mq_streams_name_purge"
|
||||
},
|
||||
{
|
||||
"description": "Creates a durable pull consumer on a stream and returns it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"ack_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"ack_wait": {
|
||||
"type": "string"
|
||||
},
|
||||
"deliver_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"durable_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter_subject": {
|
||||
"type": "string"
|
||||
},
|
||||
"max_ack_pending": {
|
||||
"type": "integer"
|
||||
},
|
||||
"max_deliver": {
|
||||
"type": "integer"
|
||||
},
|
||||
"opt_start_seq": {
|
||||
"type": "integer"
|
||||
},
|
||||
"opt_start_time": {
|
||||
"format": "date-time",
|
||||
"type": "string"
|
||||
},
|
||||
"replay_policy": {
|
||||
"type": "string"
|
||||
},
|
||||
"stream": {
|
||||
"description": "Stream is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_mq_streams_stream_consumers"
|
||||
},
|
||||
{
|
||||
"description": "Pulls the consumer's next batch. Delivered messages are acknowledged on\ndelivery — the broker will not redeliver what this call returns; an empty\nwait answers 408.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"batch": {
|
||||
"description": "Batch is how many messages to pull (1–1000, default 1).",
|
||||
"type": "integer"
|
||||
},
|
||||
"expires": {
|
||||
"description": "Expires is how long to wait for messages, e.g. \"5s\" (default \"30s\", max \"60s\").",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the consumer name, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"no_wait": {
|
||||
"description": "NoWait answers immediately with whatever is available instead of waiting.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"stream": {
|
||||
"description": "Stream is the stream name, from the path.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "post_v1_mq_streams_stream_consumers_name_next"
|
||||
},
|
||||
{
|
||||
"description": "Reconfigures an existing stream; the path names the stream, and the\nimmutable fields (storage, retention) must restate what they are.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"max_age": {
|
||||
"description": "MaxAge caps message age, e.g. \"24h\" or \"7d\"; \"0\" (default) is unlimited.",
|
||||
"type": "string"
|
||||
},
|
||||
"max_bytes": {
|
||||
"description": "MaxBytes caps the stream's total stored bytes; -1 (default) is unlimited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_msg_size": {
|
||||
"description": "MaxMsgSize caps one message's size in bytes; -1 (default) is the broker's limit.",
|
||||
"type": "integer"
|
||||
},
|
||||
"max_msgs": {
|
||||
"description": "MaxMsgs caps the number of stored messages; -1 (default) is unlimited.",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is the stream name, unique within the org (alphanumeric, hyphens, underscores).",
|
||||
"type": "string"
|
||||
},
|
||||
"num_replicas": {
|
||||
"description": "Replicas is the number of stream replicas (1–5); this plane runs 1.",
|
||||
"type": "integer"
|
||||
},
|
||||
"retention": {
|
||||
"description": "Retention is the retention policy: limits (default), interest, or workqueue.",
|
||||
"type": "string"
|
||||
},
|
||||
"storage": {
|
||||
"description": "Storage is the storage backend: file (default) or memory.",
|
||||
"type": "string"
|
||||
},
|
||||
"subjects": {
|
||||
"description": "Subjects are the org-relative subjects bound to this stream (wildcards supported). Default: the stream name.",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "put_v1_mq_streams_name"
|
||||
}
|
||||
]
|
||||
@@ -1,10 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Reports that the notify send surface is mounted.\n\nIt is a pure liveness probe: it answers 200 whenever this subsystem is mounted\nand checks nothing downstream, so an \"ok\" here says the routes are reachable, not\nthat any provider credential is configured. The body is notifyd's verbatim, so\nprobes and clients that keyed on the standalone service keep working unchanged.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_notify_health"
|
||||
}
|
||||
]
|
||||
-18910
File diff suppressed because it is too large
Load Diff
@@ -1,132 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "ListCloudPlans returns the Hanzo cloud plan catalog: every cloud tier with its\nprice, included capacity, limits and feature list, scoped to the caller's\ncatalog. A reseller org sees its own overrides in place of the canonical\nrecords it has replaced, and the canonical record for every tier it has not.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans"
|
||||
},
|
||||
{
|
||||
"description": "ListBlockchainPlans returns the blockchain RPC plan catalog: the tiers metered\nin monthly compute units, with their prices, limits and overage terms. It is\nthe canonical catalog for every caller — these plans carry no reseller\noverrides.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_blockchain"
|
||||
},
|
||||
{
|
||||
"description": "ListCloudCapacityPlans returns the cloud plan catalog. It is the same section\nListCloudPlans answers and a separate operation because it is a separate\naddress, and an address is what every projection keys on.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_cloud"
|
||||
},
|
||||
{
|
||||
"description": "ListDNSPlans returns the DNS plan catalog: the tiers priced on zones, records\nper zone and queries per day. It is the canonical catalog for every caller —\nthese plans carry no reseller overrides.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_dns"
|
||||
},
|
||||
{
|
||||
"description": "GetPlanEntitlements returns what one plan GRANTS and not what it costs: the\ncanonical namespaced entitlement block and the flat license-feature list\nderived from it. It is the entitlement half of ResolvePlan, over the same\ncatalog and the same 404 for an id no catalog holds — the read a licensing or\nquota gate makes.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the plan's catalog id or slug — \"pro\", \"team\", \"world-enterprise\",\n\"rpc-growth\". Both are matched, so a slug resolves the plan it names.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_entitlements_id"
|
||||
},
|
||||
{
|
||||
"description": "ListGPUTiers returns the rentable GPU configurations, each with its accelerator\ncount and model, VRAM, vCPUs, host memory and hourly price.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_gpu"
|
||||
},
|
||||
{
|
||||
"description": "Health reports that the plans subsystem is mounted and serving. It answers from\nthe process itself and consults neither the catalog bundle nor the goja host,\nso it stays \"ok\" while either is degraded.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_health"
|
||||
},
|
||||
{
|
||||
"description": "GetPricingPolicy returns the published pricing policy: whether pricing is\ntransparent, the revenue-sharing terms (idle compute resale and the open-source\nshare) and the principles the catalog is priced by.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_policy"
|
||||
},
|
||||
{
|
||||
"description": "ListRegions returns the regions cloud capacity is offered in, each with its\ndisplay name and physical location.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_regions"
|
||||
},
|
||||
{
|
||||
"description": "ResolvePlan resolves one plan to everything a consumer of the catalog needs at\nonce: its canonical entitlement block, the flat license-feature list a signed\nlicense carries, its billing reference, and the catalog it came from. The id\nmay be the plan's id or its slug, and it is resolved against the caller's\ncatalog, so a reseller's override wins over the canonical record. An id no\ncatalog holds answers 404.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "ID is the plan's catalog id or slug — \"pro\", \"team\", \"world-enterprise\",\n\"rpc-growth\". Both are matched, so a slug resolves the plan it names.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_resolve_id"
|
||||
},
|
||||
{
|
||||
"description": "GetPlanSchemas returns the two JSON Schema documents this surface speaks:\nentitlements.schema.json, which declares every entitlement key with its type,\nunit and enum, and plan.schema.json, which a catalog plan record conforms to.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_schema"
|
||||
},
|
||||
{
|
||||
"description": "GetStoragePricing returns the block-storage price block: the price per GB per\nmonth and the volume size bounds a cloud plan may attach.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_storage"
|
||||
},
|
||||
{
|
||||
"description": "ListSubscriptionPlans returns the subscription ladder — the personal and team\ntiers a customer buys to use the cloud, each with its monthly and annual price,\nseat rules, limits and billing reference. Scoped to the caller's catalog.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_subscriptions"
|
||||
},
|
||||
{
|
||||
"description": "ListToolPrices returns the per-use price of every metered tool — web search,\ncode interpreter, image generation, speech — each with the unit it is billed\nin.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_tools"
|
||||
},
|
||||
{
|
||||
"description": "GetEntitlementVocabulary returns the entitlement key vocabulary: every key with\nits namespace, JSON type, nullability, unit, enum and title, the list of\nnamespaces, and the engine features a license can grant. It is derived from\nentitlements.schema.json on every call, so it cannot fall behind the schema.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "get_v1_plans_vocab"
|
||||
}
|
||||
]
|
||||
@@ -1 +0,0 @@
|
||||
[]
|
||||
@@ -1,77 +0,0 @@
|
||||
[
|
||||
{
|
||||
"description": "Stops the plugin. Its routes STAY REGISTERED and answer 503 — not 404.\n\nThat is zip's choice and this keeps it. Removing the routes would mutate the\nroute table, and re-adding them on enable would grow it without bound across\nrepeated cycles, which is the invariant that makes reloads flat in memory. It\nis also the better answer: 404 says \"no such API\" and a client may cache it\nand stop retrying, while 503 says \"this API exists and is down right now\",\nwhich is true and retryable. Which of the two 503s this is — deliberate stop\nor crash — is what the status's disabled flag reports.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the app, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"description": "Scope \"host\" applies here only; default \"fleet\" applies everywhere.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminDisablePlugin"
|
||||
},
|
||||
{
|
||||
"description": "Brings a stopped or disabled plugin back on the artifact it already\nhas: the zero Plugin names no new artifact, so Reload reuses the loaded spec\nand clears the disabled flag. Named for what an operator means by it.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the app, from the path.",
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"description": "Scope \"host\" applies here only; default \"fleet\" applies everywhere.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminEnablePlugin"
|
||||
},
|
||||
{
|
||||
"description": "Reports what each host is actually running: every loaded plugin with its\nversion, pid, uptime, reload and restart counts, and its measured CPU, RSS,\nthread and fd cost — read from the kernel, which is only answerable at all\nbecause a plugin is a process.\n\nReading this from deployment config would answer what was INTENDED. Only the\nprocess knows what is TRUE, and during a rolling upgrade the two disagree on\npurpose.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"scope": {
|
||||
"description": "Scope \"host\" answers for THIS host only. Default \"fleet\" fans out to every\nlive peer. A peer answers a host-scoped read, which is what stops the\nfan-out recursing.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminPlugins"
|
||||
},
|
||||
{
|
||||
"description": "Swaps a plugin for another build without dropping a request. The\nreplacement is started and proven to be LISTENING before any traffic moves to\nit, so a bad build leaves the old one serving and returns an error rather\nthan a hole; the old process then drains before it is killed.\n\nWith a version or url+sum it pins; naming a digest this host has run before is\nthe rollback, and costs no network because the digest IS the cache key. With\nneither it restarts what is already loaded.\n\nFleet scope applies it to one host at a time and STOPS at the first failure,\nso a build that cannot come up reaches exactly one host.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "Name is the app, from the path. It must be one the manifest declares.",
|
||||
"type": "string"
|
||||
},
|
||||
"scope": {
|
||||
"description": "Scope \"host\" applies here only. Default \"fleet\" rolls it out one host at\na time, halting on the first host that fails to come up.",
|
||||
"type": "string"
|
||||
},
|
||||
"sum": {
|
||||
"type": "string"
|
||||
},
|
||||
"url": {
|
||||
"description": "URL is the artifact directly, for an origin with no index. Sum is its hex\nSHA-256 and is REQUIRED with it: zip refuses an unverified download, and\nso does this.",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"description": "Version is a release tag, resolved to a URL and digest through the\norigin's binaries.json index — the same index CI publishes, so there is\nno second table mapping versions to digests.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"name": "adminReloadPlugin"
|
||||
}
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user