Track HiveGrid Cloud planning reference
This commit is contained in:
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"
|
||||
Reference in new issue
Block a user