Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
1 parent b3d5ea6764
commit e341c5938a
5784 files changed
+1104929 -3

No files matched your search

@@ -0,0 +1,35 @@
# capability-use
Three host capabilities, one handler. Designed as the reference for
the audit-trail page: every invocation produces a non-trivial set of
`PluginCapabilityUse` rows you can browse from
`/app/:tenant/settings/plugins/:id/audit`.
## Capabilities used
| Capability | Used for |
| --------------- | ----------------------------------------------- |
| `log` | Structured logging through `ctx.log` |
| `storage.read` | Look up the tenant's Slack webhook URL |
| `http.outbound` | POST a notification to `hooks.slack.com` |
The manifest restricts `http.outbound` via `allowedHosts =
["hooks.slack.com"]` — any other host triggers a denied row instead of
egress.
## Why each call shows up in the audit trail
The capability gateway emits an `onCall` event per host-fn call. The
runtime wires that to `PluginCapabilityUseRecorder`, which buffers
entries per invocation then flushes one outbox batch at sandbox close.
The worker `plugin-capability-use` job persists each row. The audit
page filters by capability + UTC date and pages by keyset cursor.
## Tenant-admin pre-requisites
Before this plugin can post:
1. Install + enable the plugin via the plugins settings page.
2. Write the Slack webhook URL under storage key
`slack:<tenantId>:webhook-url` (operator action — see
`docs/plugins/security-best-practices.md`).
@@ -0,0 +1,64 @@
/**
* Capability-use example — Phase 06 #23.
*
* One handler that touches every "regular" capability so the audit-
* trail page shows a non-trivial set of rows per invocation:
* 1. storage.read to fetch the tenant's Slack webhook URL
* 2. http.outbound to POST a notification to Slack
* 3. log everywhere for structured tracing
*
* When run with the host audit recorder wired in, each capability call
* emits one PluginCapabilityUse row with outcome=success / error and a
* sha256(payloadDescriptor). Tenant admins see exactly which channel
* received which event without the message body ever being persisted.
*/
import { defineHook } from "../../src/index.js";
export interface EntityCreatedInput {
readonly tenantId: string;
readonly entityName: string;
readonly recordId: string;
}
export const handleEntityCreated = defineHook<EntityCreatedInput, { posted: boolean }>(
async (input, ctx) => {
ctx.log.info(
{ tenantId: input.tenantId, entity: input.entityName },
"capability-use.handleEntityCreated invoked"
);
if (!ctx.storage) {
ctx.log.warn({}, "storage capability not bound — manifest must declare storage.read");
return { posted: false };
}
const webhookUrl = await ctx.storage.get(`slack:${input.tenantId}:webhook-url`);
if (!webhookUrl) {
ctx.log.info({ tenantId: input.tenantId }, "no slack webhook configured — skipping");
return { posted: false };
}
if (!ctx.http) {
ctx.log.warn({}, "http capability not bound — manifest must declare http.outbound");
return { posted: false };
}
const body = JSON.stringify({
text: `New ${input.entityName} created (id ${input.recordId})`,
});
const response = await ctx.http.fetch({
url: webhookUrl,
method: "POST",
headers: { "Content-Type": "application/json" },
body,
});
if (response.status >= 200 && response.status < 300) {
ctx.log.info({ status: response.status }, "slack webhook posted");
return { posted: true };
}
ctx.log.error(
{ status: response.status, body: response.body.slice(0, 256) },
"slack webhook returned non-2xx"
);
return { posted: false };
}
);
@@ -0,0 +1,35 @@
# Phase 06 #23 example — capability-use plugin.
#
# Demonstrates multiple capabilities working together inside a single
# hook handler:
# - http.outbound for a webhook call (restricted to hooks.slack.com)
# - storage.read to look up the tenant's per-user channel mapping
# - log for structured tracing throughout
# Each host-fn call gets one row in PluginCapabilityUse, so the tenant
# admin's audit-trail page surfaces exactly what this plugin did per
# invocation.
schemaVersion = 1
id = "de.toolctrl.examples.capability-use"
version = "0.1.0"
name = "Capability-Use Demo"
description = "Calls http.outbound + storage.read + log on every invocation. Exercises the audit trail."
entry = "plugin.wasm"
wasmSha256 = "0000000000000000000000000000000000000000000000000000000000000000"
signature = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
signedBy = "0000000000000000000000000000000000000000000000000000000000000000"
allowedHosts = ["hooks.slack.com"]
[[capabilities]]
name = "log"
[[capabilities]]
name = "storage.read"
[[capabilities]]
name = "http.outbound"
scope = { allowedHosts = ["hooks.slack.com"] }
[[hooks]]
name = "entity.created"
export = "handleEntityCreated"