Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,20 @@ jobs:
go-version-file: go.mod
- run: go install github.com/magefile/mage@v1.17.2 && echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
- run: mage build:toolchains

# This example owns its program and dependencies; it is not part of OAP's
# module, CLI, image catalog, or default installation.
codecovbot-example:
runs-on: ubuntu-latest
timeout-minutes: 15
defaults:
run:
working-directory: examples/codecovbot/coverage-tool
steps:
- uses: actions/checkout@v6
- uses: actions/setup-go@v7
with:
go-version-file: examples/codecovbot/coverage-tool/go.mod
cache-dependency-path: examples/codecovbot/coverage-tool/go.sum
- run: go test -race ./...
- run: docker build -t codecovbot-tool:2.0.0 .
5 changes: 5 additions & 0 deletions PRIMITIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,11 @@ in-channel.
`historyresp/`, `e2e/`), `pkg/channels/channelevents/` (the envelope + event
model), `pkg/channels/channelinteractions/` (semantic interaction categories),
`pkg/channels/channelkey/`, `pkg/channels/interact/`, `pkg/channels/notice/`
- **Deferred webhook admission:** `pkg/channels/webhookqueue/` persists
`WebhookAdmission` deliveries and cohort claims before session creation;
optional receiver admission capabilities keep provider readiness in each kind.
GitHub workflow inputs select prerequisite workflows during installation and
bind Check Run publication to immutable PR revisions and run attempts.
- **Asset renderers:** `pkg/channels/channelassets/` — `Renderer` interface +
`registry/`, with renderers `html/`, `css/`, `svg/`, `image/`, and the
operator-only `mcpui/`
Expand Down
6 changes: 5 additions & 1 deletion cmd/oap/internal/agentcmd/install_channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ func (w channelWiring) prepareDeclaredChannels(
credentialPreflights = append(credentialPreflights, preflight)
}
run, _, err := channelwizard.PrepareRun(ctx, kind.Wizard(), channelkinds.WizardInput{
Namespace: w.kube.Namespace, Seeded: p.Seeded, WorkingDir: ".", OperatorShell: true,
Namespace: w.kube.Namespace, Config: p.Required.Config, Seeded: p.Seeded, WorkingDir: ".", OperatorShell: true,
}, p.Required.Kind, tui.NewState(), p.Required.Role, channelWizardSeeds(p), map[string]bool{
wizardkeys.KeyExternalBaseURL: endpoint.NeedsReadyURL(),
}, w.runOptions(p))
Expand Down Expand Up @@ -1067,6 +1067,7 @@ func (w channelWiring) drive(ctx context.Context, kind channelkinds.Kind, o *cha
} else {
w.announce(p)
wizIn := channelkinds.WizardInput{
Config: p.Required.Config,
K8s: w.kube.Controller,
Namespace: w.kube.Namespace,
// The SAME map the driver consults to decide which questions to drop,
Expand Down Expand Up @@ -1220,6 +1221,9 @@ func (w channelWiring) finishCommand(p channelplan.ChannelPlan) string {
if role := strings.TrimSpace(p.Required.Role); role != "" {
cmd += " --role " + role
}
if len(p.Required.Config) > 0 {
cmd += " --handler-config '" + strings.ReplaceAll(string(p.Required.Config), "'", "'\"'\"'") + "'"
}
return cmd
}

Expand Down
11 changes: 11 additions & 0 deletions cmd/oap/internal/channelcmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ package channelcmd

import (
"context"
"encoding/json"
"fmt"
"io"
"slices"
Expand Down Expand Up @@ -60,6 +61,7 @@ import (
// as one struct so runChannelCreate has a single parameter to grow, and so the
// `oap init` monitoring offer can call the same function with the same shape.
type channelCreateOptions struct {
handlerConfig string
// kind is the channel kind to build. Empty prompts for one, which a
// non-interactive run refuses.
kind string
Expand Down Expand Up @@ -125,6 +127,7 @@ func newChannelCreateCmd(g *apcmd.Globals) *cobra.Command {
}
f := cmd.Flags()
f.StringVar(&opts.kind, "kind", "", "Kind to create (e.g., slack, fake)")
f.StringVar(&opts.handlerConfig, "handler-config", "", "Kind-owned handler arguments as a JSON object")
// The caveat is spelled out because --apply=false reads like a dry run and
// is not one on every route: a kind whose flow creates something OUTSIDE
// the cluster — the Slack wizard's "create the app for me" route mints a
Expand Down Expand Up @@ -215,6 +218,13 @@ func runChannelCreate(ctx context.Context, in io.Reader, out io.Writer, g *apcmd
return fmt.Errorf("unknown kind %q (registered: %v)", kindName, KindNames())
}

var handlerConfig json.RawMessage
if opts.handlerConfig != "" {
handlerConfig = json.RawMessage(opts.handlerConfig)
if err := registry.ValidateHandlerConfig(kindName, handlerConfig); err != nil {
return err
}
}
// Flags are read into State before anything runs: a screen whose answer is
// already there asks nothing, which is what makes both pre-filling and
// --non-interactive work off one mechanism.
Expand Down Expand Up @@ -273,6 +283,7 @@ func runChannelCreate(ctx context.Context, in io.Reader, out io.Writer, g *apcmd
// nobody is watching, and this contract gives it no per-question hook to
// find out later.
wizIn := channelkinds.WizardInput{
Config: handlerConfig,
K8s: cli,
Namespace: ns,
Seeded: seeded.Values,
Expand Down
4 changes: 3 additions & 1 deletion cmd/oap/internal/channelwizard/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package channelwizard
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"net/url"
Expand Down Expand Up @@ -136,14 +137,15 @@ func TestRunChannelWizard_AnswerForAKindsOwnInputIsAccepted(t *testing.T) {
require.NoError(t, err, "Seed")

_, answered, err := Run(context.Background(), w,
channelkinds.WizardInput{Namespace: "default"}, "demo-kind", st, "", seeded,
channelkinds.WizardInput{Namespace: "default", Config: json.RawMessage(`{"synthetic":"handler"}`)}, "demo-kind", st, "", seeded,
scriptedOptions("demo-team\n"))
require.NoError(t, err,
"--answer naming one of this kind's own declared inputs must be accepted, not refused as a key it does not ask")

require.True(t, w.resulted, "the run must have reached Result")
assert.Equal(t, map[string]string{"org": "demo-org", "team": "demo-team"}, w.gotAnswers,
"Result must receive the seeded answer and the asked one alike")
assert.JSONEq(t, `{"synthetic":"handler"}`, string(w.gotIn.Config))
assert.Equal(t, "default", w.gotIn.Namespace,
"Result must receive the same WizardInput Inputs was given")
assert.Empty(t, answered.Notes(),
Expand Down
1 change: 1 addition & 0 deletions cmd/oap/internal/installcmd/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var crGroupVersionResources = []schema.GroupVersionResource{
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "spiceboxtoolspecs"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "spiceboxclasses"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "mcpservers"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "webhookadmissions"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "channels"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "artifactrenders"},
{Group: "agentprimitives.authzed.com", Version: "v1alpha1", Resource: "agentsessiongrants"},
Expand Down
3 changes: 2 additions & 1 deletion config/crds/agentprimitives.authzed.com_agentclasses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1302,7 +1302,8 @@ spec:
additionalProperties:
x-kubernetes-preserve-unknown-fields: true
description: |-
Config is a map of installer-bound values exposed to this class's
Config is a map of non-secret installer-bound values exposed to the
agent model as JSON data in its system prompt and to this class's
toolspec constraint CEL as the `config` root (see the toolspec
validator). It is generic and toolkit-agnostic: it never mentions repos
or any tool-specific shape — a constraint decides what a value means.
Expand Down
28 changes: 23 additions & 5 deletions config/crds/agentprimitives.authzed.com_channels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -254,11 +254,6 @@ spec:
and keys live in the credentialsRef Secret, never in spec.
type: string
events:
default:
- opened
- synchronize
- reopened
- ready_for_review
description: Events are the pull_request actions that become a
review.
items:
Expand All @@ -283,6 +278,29 @@ spec:
reviewed is the one value the field cannot hold. Same shape as
ChannelMetaagentSpec.Enabled in this file.
type: boolean
trigger:
description: Trigger is opt-in; absent preserves legacy pull_request
behavior.
properties:
actions:
items:
type: string
type: array
event:
type: string
readiness:
type: string
workflows:
additionalProperties:
items:
type: string
type: array
description: Workflows maps owner/repository to required workflow
paths.
type: object
required:
- event
type: object
required:
- appSlug
type: object
Expand Down
147 changes: 147 additions & 0 deletions config/crds/agentprimitives.authzed.com_webhookadmissions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.20.1
name: webhookadmissions.agentprimitives.authzed.com
spec:
group: agentprimitives.authzed.com
names:
categories:
- authzed
- spicebox
kind: WebhookAdmission
listKind: WebhookAdmissionList
plural: webhookadmissions
shortNames:
- wha
singular: webhookadmission
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.channelName
name: Channel
type: string
- jsonPath: .spec.recordType
name: Type
type: string
- jsonPath: .status.phase
name: Phase
type: string
- jsonPath: .status.reason
name: Reason
priority: 1
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: |-
WebhookAdmission durably records a verified webhook delivery or an atomic
provider-cohort claim before creating an AgentSession. It contains no secret
credential. Provider admission workers update only status; the delivery body
and Channel incarnation are immutable evidence.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: WebhookAdmissionSpec is immutable after creation.
properties:
body:
description: |-
Body is the authenticated webhook payload, never an installation token.
Keep the wire encoding as a plain base64 string: Kubernetes 1.30 CEL
byte conversion rejects the standard +/ alphabet in immutable-spec checks.
The Go JSON decoder still enforces base64 and preserves exact bytes.
MaxLength bounds the base64 representation of a 512 KiB delivery.
maxLength: 699052
type: string
channelName:
description: ChannelName names the input Channel in this namespace.
maxLength: 253
minLength: 1
type: string
channelUID:
description: ChannelUID pins the Channel incarnation across deletion
and recreation.
maxLength: 128
minLength: 1
type: string
enqueuedAt:
description: EnqueuedAt starts the bounded retention period independently
of retries.
format: date-time
type: string
event:
description: Event is the verified provider event name.
maxLength: 128
type: string
recordType:
description: RecordType separates raw deliveries from deduplicated
CI-cohort claims.
enum:
- delivery
- cohort
type: string
required:
- channelName
- channelUID
- enqueuedAt
- recordType
type: object
x-kubernetes-validations:
- message: webhook admission evidence is immutable
rule: self == oldSelf
status:
description: WebhookAdmissionStatus holds the durable worker lease and
admission result.
properties:
leaseUntil:
description: |-
LeaseUntil serializes workers using status resource-version comparisons
and acts as retry-not-before after a transient provider or delivery error.
format: date-time
type: string
phase:
description: Phase is pending until the delivery or cohort was processed
conclusively.
enum:
- pending
- done
type: string
reason:
description: Reason records deferral, expiry, provider failure, or
supersession evidence.
type: string
retryCount:
description: RetryCount counts consecutive transient failures for
bounded retry backoff.
format: int32
minimum: 0
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
1 change: 1 addition & 0 deletions config/crds/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ resources:
- agentprimitives.authzed.com_subagentrequests.yaml
- agentprimitives.authzed.com_toolcalls.yaml
- agentprimitives.authzed.com_useridentities.yaml
- agentprimitives.authzed.com_webhookadmissions.yaml
- agentprimitives.authzed.com_workshops.yaml
- agentprimitives.authzed.com_workshopprobes.yaml
- agentprimitives.authzed.com_workspacesources.yaml
6 changes: 6 additions & 0 deletions config/webd/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,9 @@ rules:
- apiGroups: ["agentprimitives.authzed.com"]
resources: ["agentidentities"]
verbs: ["get"]

# Persist only authenticated deferred webhook evidence. A dedicated resource
# avoids granting the browser-facing pod arbitrary ConfigMap creation.
- apiGroups: ["agentprimitives.authzed.com"]
resources: ["webhookadmissions"]
verbs: ["create", "get"]
Loading
Loading