Add marketplace CLI publishing helpers
This commit is contained in:
28
client.go
28
client.go
@@ -396,6 +396,34 @@ func (c *Client) SubmitLocalVaultForkEvidence(ctx context.Context, installationI
|
|||||||
return c.postObject(ctx, "/v2/local-vault/installations/"+url.PathEscape(installationID)+"/witness/checkpoints", M{"forkEvidence": forkEvidence}, idempotency(options))
|
return c.postObject(ctx, "/v2/local-vault/installations/"+url.PathEscape(installationID)+"/witness/checkpoints", M{"forkEvidence": forkEvidence}, idempotency(options))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetMarketplaceItem(ctx context.Context, slug string) (M, error) {
|
||||||
|
return c.getObject(ctx, "/v1/marketplace/items/"+url.PathEscape(slug), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SubmitMarketplaceAsset(ctx context.Context, input MarketplaceAssetSubmitInput, options ...RequestOptions) (M, error) {
|
||||||
|
return c.postObject(ctx, "/v1/marketplace/publisher/assets", M{
|
||||||
|
"manifest": input.Manifest,
|
||||||
|
"sourceRef": input.SourceRef,
|
||||||
|
"visibility": input.Visibility,
|
||||||
|
"pricingModel": input.PricingModel,
|
||||||
|
"priceCents": input.PriceCents,
|
||||||
|
}, idempotency(options))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListMarketplaceReviewAssets(ctx context.Context, state string) ([]M, error) {
|
||||||
|
values := url.Values{}
|
||||||
|
if state != "" {
|
||||||
|
values.Set("state", state)
|
||||||
|
}
|
||||||
|
var out []M
|
||||||
|
err := c.requestJSON(ctx, http.MethodGet, "/v1/platform/marketplace/assets", values, nil, "", &out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ApproveMarketplaceAsset(ctx context.Context, slug string, reason string, options ...RequestOptions) (M, error) {
|
||||||
|
return c.postObject(ctx, "/v1/platform/marketplace/assets/"+url.PathEscape(slug)+"/approve", M{"reason": reason}, idempotency(options))
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) getObject(ctx context.Context, path string, values url.Values) (M, error) {
|
func (c *Client) getObject(ctx context.Context, path string, values url.Values) (M, error) {
|
||||||
var out M
|
var out M
|
||||||
err := c.requestJSON(ctx, http.MethodGet, path, values, nil, "", &out)
|
err := c.requestJSON(ctx, http.MethodGet, path, values, nil, "", &out)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
attesto "git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go"
|
attesto "git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go"
|
||||||
|
"git.rotz.ai/rotzmediagroup/attesto-v1/sdk/go/connectorkit"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cliVersion = "0.2.0"
|
const cliVersion = "0.2.0"
|
||||||
@@ -133,6 +134,8 @@ func (a *app) dispatch(ctx context.Context, args []string) error {
|
|||||||
return a.connectors(ctx, args[1:])
|
return a.connectors(ctx, args[1:])
|
||||||
case "local-vault":
|
case "local-vault":
|
||||||
return a.localVault(ctx, args[1:])
|
return a.localVault(ctx, args[1:])
|
||||||
|
case "marketplace":
|
||||||
|
return a.marketplace(ctx, args[1:])
|
||||||
case "readiness":
|
case "readiness":
|
||||||
return a.readiness(args[1:])
|
return a.readiness(args[1:])
|
||||||
default:
|
default:
|
||||||
@@ -937,6 +940,189 @@ func (a *app) localVaultWitness(ctx context.Context, args []string, fork bool) e
|
|||||||
return a.write(out)
|
return a.write(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplace(ctx context.Context, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return errors.New("marketplace subcommand required")
|
||||||
|
}
|
||||||
|
switch args[0] {
|
||||||
|
case "init":
|
||||||
|
return a.marketplaceInit(args[1:])
|
||||||
|
case "validate":
|
||||||
|
return a.marketplaceValidate(args[1:])
|
||||||
|
case "submit":
|
||||||
|
return a.marketplaceSubmit(ctx, args[1:])
|
||||||
|
case "review-list":
|
||||||
|
return a.marketplaceReviewList(ctx, args[1:])
|
||||||
|
case "publish":
|
||||||
|
return a.marketplacePublish(ctx, args[1:])
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown marketplace subcommand: %s", args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplaceInit(args []string) error {
|
||||||
|
fs := flag.NewFlagSet("marketplace init", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(a.err)
|
||||||
|
output := fs.String("output", "", "manifest output path")
|
||||||
|
slug := fs.String("slug", "", "lower-kebab-case connector slug")
|
||||||
|
name := fs.String("name", "", "connector display name")
|
||||||
|
version := fs.String("version", "", "semantic version")
|
||||||
|
category := fs.String("category", "", "marketplace category")
|
||||||
|
summary := fs.String("summary", "", "short public summary")
|
||||||
|
description := fs.String("description", "", "public description")
|
||||||
|
publisherSlug := fs.String("publisher-slug", "", "publisher slug")
|
||||||
|
publisherName := fs.String("publisher-name", "", "publisher display name")
|
||||||
|
repositoryURL := fs.String("repository-url", "", "public release source URL")
|
||||||
|
docsURL := fs.String("docs-url", "", "public documentation URL")
|
||||||
|
capabilitiesRaw := fs.String("capabilities", "", "comma-separated capabilities")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if *output == "" {
|
||||||
|
return errors.New("output is required")
|
||||||
|
}
|
||||||
|
manifest := connectorkit.Manifest{
|
||||||
|
SchemaVersion: "1.0",
|
||||||
|
Slug: *slug,
|
||||||
|
Name: *name,
|
||||||
|
Version: *version,
|
||||||
|
AssetType: "connector",
|
||||||
|
Category: *category,
|
||||||
|
Summary: *summary,
|
||||||
|
Description: *description,
|
||||||
|
Publisher: map[string]string{
|
||||||
|
"slug": *publisherSlug,
|
||||||
|
"name": *publisherName,
|
||||||
|
},
|
||||||
|
Repository: map[string]string{"url": *repositoryURL},
|
||||||
|
Documentation: map[string]string{
|
||||||
|
"url": *docsURL,
|
||||||
|
},
|
||||||
|
Capabilities: splitCSV(*capabilitiesRaw),
|
||||||
|
Evidence: map[string]bool{
|
||||||
|
"offlineVerification": true,
|
||||||
|
"receipts": true,
|
||||||
|
"witnessCompatible": true,
|
||||||
|
},
|
||||||
|
Security: map[string]bool{
|
||||||
|
"dependencyScan": true,
|
||||||
|
"secretScan": true,
|
||||||
|
},
|
||||||
|
SupportedLanguages: []string{"en", "nl", "de", "fr", "es", "pl", "it"},
|
||||||
|
}
|
||||||
|
if len(manifest.Capabilities) == 0 {
|
||||||
|
return errors.New("at least one capability is required")
|
||||||
|
}
|
||||||
|
result := connectorkit.ValidateManifest(manifest)
|
||||||
|
if !result.OK {
|
||||||
|
return a.write(map[string]any{"ok": false, "findings": result.Findings})
|
||||||
|
}
|
||||||
|
raw, err := json.MarshalIndent(manifest, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(*output, append(raw, '\n'), 0o600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.write(map[string]any{"ok": true, "manifest": *output, "evidenceScore": result.EvidenceScore, "tier": result.Tier})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplaceValidate(args []string) error {
|
||||||
|
fs := flag.NewFlagSet("marketplace validate", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(a.err)
|
||||||
|
file := fs.String("manifest-file", "", "connector manifest JSON file")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifest, err := readConnectorManifest(*file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result := connectorkit.ValidateManifest(manifest)
|
||||||
|
return a.write(map[string]any{"ok": result.OK, "evidenceScore": result.EvidenceScore, "tier": result.Tier, "findings": result.Findings})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplaceSubmit(ctx context.Context, args []string) error {
|
||||||
|
fs := flag.NewFlagSet("marketplace submit", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(a.err)
|
||||||
|
manifestFile := fs.String("manifest-file", "", "connector manifest JSON file")
|
||||||
|
sourceRef := fs.String("source-ref", "", "real connector release source URL")
|
||||||
|
visibility := fs.String("visibility", "private", "private or public")
|
||||||
|
pricingModel := fs.String("pricing-model", "free", "free or paid")
|
||||||
|
priceCents := fs.Int("price-cents", 0, "price in cents for paid assets")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
manifest, err := readConnectorManifest(*manifestFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
result := connectorkit.ValidateManifest(manifest)
|
||||||
|
if !result.OK {
|
||||||
|
return fmt.Errorf("manifest validation failed: %d finding(s)", len(result.Findings))
|
||||||
|
}
|
||||||
|
rawManifest, err := manifestToMap(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var price *int
|
||||||
|
if *pricingModel == "paid" {
|
||||||
|
price = priceCents
|
||||||
|
}
|
||||||
|
client, err := a.bearerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := client.SubmitMarketplaceAsset(ctx, attesto.MarketplaceAssetSubmitInput{
|
||||||
|
Manifest: rawManifest,
|
||||||
|
SourceRef: *sourceRef,
|
||||||
|
Visibility: *visibility,
|
||||||
|
PricingModel: *pricingModel,
|
||||||
|
PriceCents: price,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplaceReviewList(ctx context.Context, args []string) error {
|
||||||
|
fs := flag.NewFlagSet("marketplace review-list", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(a.err)
|
||||||
|
state := fs.String("state", "pending", "pending, published, rejected, revoked, or all")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client, err := a.bearerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := client.ListMarketplaceReviewAssets(ctx, *state)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.write(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) marketplacePublish(ctx context.Context, args []string) error {
|
||||||
|
fs := flag.NewFlagSet("marketplace publish", flag.ContinueOnError)
|
||||||
|
fs.SetOutput(a.err)
|
||||||
|
slug := fs.String("slug", "", "marketplace asset slug")
|
||||||
|
reason := fs.String("reason", "", "review reason")
|
||||||
|
if err := fs.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
client, err := a.bearerClient()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
out, err := client.ApproveMarketplaceAsset(ctx, *slug, *reason)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return a.write(out)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *app) readiness(args []string) error {
|
func (a *app) readiness(args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return errors.New("readiness subcommand required")
|
return errors.New("readiness subcommand required")
|
||||||
@@ -1081,6 +1267,35 @@ func readStringMap(path string) (map[string]string, error) {
|
|||||||
return out, json.Unmarshal(raw, &out)
|
return out, json.Unmarshal(raw, &out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func readConnectorManifest(path string) (connectorkit.Manifest, error) {
|
||||||
|
raw, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return connectorkit.Manifest{}, err
|
||||||
|
}
|
||||||
|
var manifest connectorkit.Manifest
|
||||||
|
return manifest, json.Unmarshal(raw, &manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func manifestToMap(manifest connectorkit.Manifest) (attesto.M, error) {
|
||||||
|
raw, err := json.Marshal(manifest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var out attesto.M
|
||||||
|
return out, json.Unmarshal(raw, &out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func splitCSV(raw string) []string {
|
||||||
|
values := []string{}
|
||||||
|
for _, part := range strings.Split(raw, ",") {
|
||||||
|
value := strings.TrimSpace(part)
|
||||||
|
if value != "" {
|
||||||
|
values = append(values, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values
|
||||||
|
}
|
||||||
|
|
||||||
func getProofObject(ctx context.Context, client *attesto.Client, kind attesto.VerifyKind, id string, _ string) (attesto.M, error) {
|
func getProofObject(ctx context.Context, client *attesto.Client, kind attesto.VerifyKind, id string, _ string) (attesto.M, error) {
|
||||||
switch kind {
|
switch kind {
|
||||||
case attesto.VerifyWindow:
|
case attesto.VerifyWindow:
|
||||||
|
|||||||
@@ -98,6 +98,121 @@ func TestStreamsCreateCallsAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarketplaceInitAndValidate(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
manifestFile := filepath.Join(dir, "attesto.connector.json")
|
||||||
|
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
code := run([]string{
|
||||||
|
"--json",
|
||||||
|
"marketplace",
|
||||||
|
"init",
|
||||||
|
"--output", manifestFile,
|
||||||
|
"--slug", "acme-risk-connector",
|
||||||
|
"--name", "ACME Risk Connector",
|
||||||
|
"--version", "1.0.0",
|
||||||
|
"--category", "ai-governance",
|
||||||
|
"--summary", "Produces Attesto evidence for ACME risk decisions.",
|
||||||
|
"--description", "Produces verifiable Proofstream events for ACME risk decisions.",
|
||||||
|
"--publisher-slug", "acme",
|
||||||
|
"--publisher-name", "ACME",
|
||||||
|
"--repository-url", "https://git.example.com/acme/risk-connector",
|
||||||
|
"--docs-url", "https://docs.example.com/acme/risk-connector",
|
||||||
|
"--capabilities", "proofstream,offline-verification",
|
||||||
|
}, &stdout, &stderr, testEnv(t, nil))
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit=%d stderr=%s", code, stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), `"evidenceScore": 95`) {
|
||||||
|
t.Fatalf("unexpected init output: %s", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.Reset()
|
||||||
|
stderr.Reset()
|
||||||
|
code = run([]string{"--json", "marketplace", "validate", "--manifest-file", manifestFile}, &stdout, &stderr, testEnv(t, nil))
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("exit=%d stderr=%s", code, stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), `"ok": true`) {
|
||||||
|
t.Fatalf("unexpected validate output: %s", stdout.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarketplaceSubmitAndPublishCallRealAPIs(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
manifestFile := filepath.Join(dir, "attesto.connector.json")
|
||||||
|
writeMarketplaceManifest(t, manifestFile)
|
||||||
|
|
||||||
|
var submitCalled bool
|
||||||
|
var publishCalled bool
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Authorization") != "Bearer tenant-or-platform-token" {
|
||||||
|
t.Fatalf("missing auth header")
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/v1/marketplace/publisher/assets":
|
||||||
|
submitCalled = true
|
||||||
|
var body map[string]any
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
t.Fatalf("submit json: %v", err)
|
||||||
|
}
|
||||||
|
if body["sourceRef"] != "https://git.example.com/acme/risk-connector/releases/v1.0.0" {
|
||||||
|
t.Fatalf("unexpected sourceRef: %#v", body["sourceRef"])
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"asset":{"slug":"acme-risk-connector","name":"ACME Risk Connector"},"validation":{"ok":true},"evidence":{"action":"asset_validation_finished","receiptHash":"rh","payloadHash":"ph"}}`))
|
||||||
|
case r.Method == http.MethodPost && r.URL.Path == "/v1/platform/marketplace/assets/acme-risk-connector/approve":
|
||||||
|
publishCalled = true
|
||||||
|
var body map[string]any
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||||
|
t.Fatalf("publish json: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(body["reason"].(string), "validation passed") {
|
||||||
|
t.Fatalf("unexpected reason: %#v", body["reason"])
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"asset":{"slug":"acme-risk-connector","listingState":"published"},"evidence":{"action":"asset_published","receiptHash":"rh","payloadHash":"ph"}}`))
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
env := testEnv(t, map[string]string{"ATT_TOKEN": "tenant-or-platform-token"})
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
code := run([]string{
|
||||||
|
"--json", "--base-url", server.URL, "--token-env", "ATT_TOKEN",
|
||||||
|
"marketplace", "submit",
|
||||||
|
"--manifest-file", manifestFile,
|
||||||
|
"--source-ref", "https://git.example.com/acme/risk-connector/releases/v1.0.0",
|
||||||
|
"--visibility", "public",
|
||||||
|
"--pricing-model", "free",
|
||||||
|
}, &stdout, &stderr, env)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("submit exit=%d stderr=%s", code, stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "asset_validation_finished") {
|
||||||
|
t.Fatalf("unexpected submit output: %s", stdout.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout.Reset()
|
||||||
|
stderr.Reset()
|
||||||
|
code = run([]string{
|
||||||
|
"--json", "--base-url", server.URL, "--token-env", "ATT_TOKEN",
|
||||||
|
"marketplace", "publish",
|
||||||
|
"--slug", "acme-risk-connector",
|
||||||
|
"--reason", "validation passed and release source reviewed",
|
||||||
|
}, &stdout, &stderr, env)
|
||||||
|
if code != 0 {
|
||||||
|
t.Fatalf("publish exit=%d stderr=%s", code, stderr.String())
|
||||||
|
}
|
||||||
|
if !strings.Contains(stdout.String(), "asset_published") {
|
||||||
|
t.Fatalf("unexpected publish output: %s", stdout.String())
|
||||||
|
}
|
||||||
|
if !submitCalled || !publishCalled {
|
||||||
|
t.Fatalf("expected submit and publish calls, submit=%v publish=%v", submitCalled, publishCalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadVector(t *testing.T) map[string]any {
|
func loadVector(t *testing.T) map[string]any {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
raw, err := os.ReadFile(filepath.Join("..", "..", "..", "..", "golden-vectors", "proofstream-v0.1-alpha", "one-stream-two-events.json"))
|
raw, err := os.ReadFile(filepath.Join("..", "..", "..", "..", "golden-vectors", "proofstream-v0.1-alpha", "one-stream-two-events.json"))
|
||||||
@@ -111,6 +226,48 @@ func loadVector(t *testing.T) map[string]any {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeMarketplaceManifest(t *testing.T, path string) {
|
||||||
|
t.Helper()
|
||||||
|
manifest := map[string]any{
|
||||||
|
"schemaVersion": "1.0",
|
||||||
|
"slug": "acme-risk-connector",
|
||||||
|
"name": "ACME Risk Connector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"assetType": "connector",
|
||||||
|
"category": "ai-governance",
|
||||||
|
"summary": "Produces Attesto evidence for ACME risk decisions.",
|
||||||
|
"description": "Produces verifiable Proofstream events for ACME risk decisions.",
|
||||||
|
"publisher": map[string]any{
|
||||||
|
"slug": "acme",
|
||||||
|
"name": "ACME",
|
||||||
|
},
|
||||||
|
"repository": map[string]any{
|
||||||
|
"url": "https://git.example.com/acme/risk-connector",
|
||||||
|
},
|
||||||
|
"documentation": map[string]any{
|
||||||
|
"url": "https://docs.example.com/acme/risk-connector",
|
||||||
|
},
|
||||||
|
"capabilities": []string{"proofstream", "offline-verification"},
|
||||||
|
"evidence": map[string]bool{
|
||||||
|
"offlineVerification": true,
|
||||||
|
"receipts": true,
|
||||||
|
"witnessCompatible": true,
|
||||||
|
},
|
||||||
|
"security": map[string]bool{
|
||||||
|
"dependencyScan": true,
|
||||||
|
"secretScan": true,
|
||||||
|
},
|
||||||
|
"supportedLanguages": []string{"en", "nl", "de", "fr", "es", "pl", "it"},
|
||||||
|
}
|
||||||
|
raw, err := json.MarshalIndent(manifest, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(path, append(raw, '\n'), 0o600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testEnv(t *testing.T, values map[string]string) func(string) string {
|
func testEnv(t *testing.T, values map[string]string) func(string) string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
return func(key string) string {
|
return func(key string) string {
|
||||||
|
|||||||
12
types.go
12
types.go
@@ -269,3 +269,15 @@ type LocalVaultInstallation struct {
|
|||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
UpdatedAt string `json:"updatedAt"`
|
UpdatedAt string `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MarketplaceAssetSubmitInput struct {
|
||||||
|
Manifest M `json:"manifest"`
|
||||||
|
SourceRef string `json:"sourceRef"`
|
||||||
|
Visibility string `json:"visibility"`
|
||||||
|
PricingModel string `json:"pricingModel"`
|
||||||
|
PriceCents *int `json:"priceCents,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MarketplaceReviewInput struct {
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user