feat(addons): declare the connector file operations in Slack and Telegram

The two connectors move files through connector.file.get and
connector.file.put but their manifests asked for nothing, so the install
dialog and blanket approval could not cover them. Declare both as
optional: a connector works without files. Delivery is pull, so neither
calls connector.inbound and it is not declared.

The audit of official packages now reads the operation constants of the
shared connector code. A deployment whose policy file already grants the
connector prefix sees no change.

Assisted-by: Claude:claude-sonnet-5-5 go
This commit is contained in:
Ettore Di Giacinto committed 2026-11-03 21:50:00 +00:00
1 parent 8a78f99525
commit 43f4a2845b
3 files changed
+56 -24

No files matched your search

+3
View File
@@ -10,6 +10,9 @@
"protocol": { "min": 1, "max": 1 },
"dataFormat": { "min": 1, "max": 1 },
"capabilities": {
"hostOperations": {
"optional": ["connector.file.get", "connector.file.put"]
},
"connector": {
"kinds": [
{
+3
View File
@@ -10,6 +10,9 @@
"protocol": { "min": 1, "max": 1 },
"dataFormat": { "min": 1, "max": 1 },
"capabilities": {
"hostOperations": {
"optional": ["connector.file.get", "connector.file.put"]
},
"connector": {
"kinds": [
{
+50 -24
View File
@@ -27,6 +27,19 @@ var officialRequests = map[string]contract.HostOperations{
"office": {Required: strings.Fields("workspace.export workspace.import")},
"sandbox": {Required: strings.Fields("workspace.changes workspace.commit workspace.materialize"), Optional: strings.Fields("egress.audit egress.check workspace.admin")},
"web": {Optional: []string{"workspace.admin"}},
// A connector moves files only when the person turns files on, so both
// file operations are optional. Delivery is pull, so neither add-on calls
// connector.inbound.
"slack": {Optional: strings.Fields("connector.file.get connector.file.put")},
"telegram": {Optional: strings.Fields("connector.file.get connector.file.put")},
}
// connectorRoot holds the file transfer code that slack and telegram share. It
// names its operations by constant, so the audit reads those too.
const connectorRoot = "addons/internal/connector"
var connectorConstants = map[string]string{
"ConnectorInboundOp": contract.ConnectorInboundOp, "ConnectorFilePutOp": contract.ConnectorFilePutOp, "ConnectorFileGetOp": contract.ConnectorFileGetOp,
}
var _ = Describe("official typed package contracts", func() {
@@ -47,31 +60,44 @@ var _ = Describe("official typed package contracts", func() {
// passed to jobundo. An unknown operation-shaped literal is not evidence
// of support; compare independently with the real registered vocabulary.
var used []string
Expect(filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
f, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
if err != nil {
return err
}
ast.Inspect(f, func(n ast.Node) bool {
lit, ok := n.(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
roots := []string{root}
if id == "slack" || id == "telegram" {
roots = append(roots, filepath.Join("../../..", connectorRoot))
}
for _, root := range roots {
Expect(filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
f, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
if err != nil {
return err
}
ast.Inspect(f, func(n ast.Node) bool {
if sel, ok := n.(*ast.SelectorExpr); ok {
if pkg, ok := sel.X.(*ast.Ident); ok && pkg.Name == "contract" {
if value, ok := connectorConstants[sel.Sel.Name]; ok {
used = append(used, value)
}
}
}
lit, ok := n.(*ast.BasicLit)
if !ok || lit.Kind != token.STRING {
return true
}
value, err := strconv.Unquote(lit.Value)
Expect(err).NotTo(HaveOccurred())
if slices.Contains(hostops.SupportedOperations(), value) {
used = append(used, value)
}
return true
}
value, err := strconv.Unquote(lit.Value)
Expect(err).NotTo(HaveOccurred())
if slices.Contains(hostops.SupportedOperations(), value) {
used = append(used, value)
}
return true
})
return nil
})).To(Succeed())
})
return nil
})).To(Succeed())
}
declared := append(slices.Clone(want.Required), want.Optional...)
slices.Sort(used)
used = slices.Compact(used)