169 lines
6.8 KiB
Plaintext
169 lines
6.8 KiB
Plaintext
// Phase 06 #21 — Plugin runtime metadata.
|
|
//
|
|
// One row per installed plugin per tenant. The .wasm binary lives on
|
|
// disk (managed by the operator and verified via wasmSha256); this
|
|
// table only stores the metadata the worker needs to look up + verify
|
|
// + invoke a plugin: which version is installed, who signed it, what
|
|
// capabilities the manifest declared, and whether it is currently
|
|
// enabled.
|
|
//
|
|
// Hot-reload and side-by-side version updates are intentionally
|
|
// deferred. Per-tenant capability grants are modeled by
|
|
// TenantPluginCapabilityGrant below, and hook subscriptions are declared in
|
|
// the signed plugin manifest.
|
|
|
|
model Plugin {
|
|
id String @id @default(cuid())
|
|
tenantId String
|
|
pluginId String
|
|
version String
|
|
manifest Json
|
|
/// Hex SHA-256 of the .wasm binary at install time. Worker re-hashes
|
|
/// on load and refuses to invoke when this does not match.
|
|
wasmSha256 String
|
|
/// Ed25519 public key (hex) recorded from the manifest at install.
|
|
signaturePublicKey String
|
|
enabled Boolean @default(false)
|
|
installedAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
userConfigs PluginUserConfig[]
|
|
capabilityGrants TenantPluginCapabilityGrant[]
|
|
|
|
@@unique([tenantId, pluginId])
|
|
@@index([tenantId, enabled])
|
|
@@index([pluginId, version])
|
|
}
|
|
|
|
// Phase 06 / F-001 — explicit per-tenant capability consent.
|
|
//
|
|
// Phase 22 originally deferred the consent dialog and let every
|
|
// manifest-declared capability run implicitly. F-001 closes that gap:
|
|
// installs no longer auto-enable; the lifecycle service requires a
|
|
// matching `TenantPluginCapabilityGrant` row for every capability the
|
|
// manifest declares before the Plugin row can transition to
|
|
// `enabled = true`. The UI surface that drives the operator-facing
|
|
// dialog is intentionally NOT shipped in this commit (separate task);
|
|
// the backend stays usable from CLI / API today.
|
|
//
|
|
// One row per (tenantId, pluginId, capabilityName). `scopeJson` carries
|
|
// the per-capability scope the operator approved (e.g. allowed-hosts
|
|
// list for http.outbound) — when null, the manifest's declared scope
|
|
// applies as-is.
|
|
model TenantPluginCapabilityGrant {
|
|
id String @id @default(cuid())
|
|
tenantId String
|
|
pluginId String
|
|
capabilityName String
|
|
scopeJson Json?
|
|
grantedAt DateTime @default(now())
|
|
/// User id of the operator who approved the grant; null for installs
|
|
/// approved via the CLI / automation path.
|
|
grantedByUserId String?
|
|
|
|
plugin Plugin @relation(fields: [pluginId, tenantId], references: [pluginId, tenantId], onDelete: Cascade)
|
|
|
|
@@unique([tenantId, pluginId, capabilityName])
|
|
@@index([tenantId, pluginId])
|
|
}
|
|
|
|
// Phase 06 #22 — per-user plugin enablement + config blob.
|
|
//
|
|
// Default: an installed + enabled Plugin row applies to every user in
|
|
// the tenant. When a plugin needs per-user opt-in (e.g. a Slack
|
|
// notifier that asks the user for their own channel) the host writes
|
|
// a PluginUserConfig row and the hook producer respects `enabled`
|
|
// before fanning out the invocation.
|
|
//
|
|
// `configJson` is a free-form per-user settings blob (Slack channel,
|
|
// preferred locale, etc.). Plugins read it through the plugin sandbox
|
|
// host-binding once that capability lands; for the foundation only the
|
|
// host UI writes it.
|
|
model PluginUserConfig {
|
|
id String @id @default(cuid())
|
|
pluginId String
|
|
userId String
|
|
enabled Boolean @default(true)
|
|
configJson Json?
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
plugin Plugin @relation(fields: [pluginId], references: [id], onDelete: Cascade)
|
|
|
|
@@unique([pluginId, userId])
|
|
@@index([userId])
|
|
}
|
|
|
|
// Phase 06 #23 — durable per-invocation audit row.
|
|
//
|
|
// One row per `toolctrl.plugin-invoke` job outcome. Capability-use rows
|
|
// reference the same `pluginInvocationId` so operators can correlate the
|
|
// high-level invocation outcome with the individual host capability
|
|
// calls emitted during sandbox execution. The row intentionally stores
|
|
// outcome metadata only, not raw plugin output or input payloads.
|
|
// No FK is declared yet so pre-existing capability-use rows and retry
|
|
// batches remain valid; correlate records by this stable invocation id.
|
|
model PluginInvocation {
|
|
id String @id
|
|
tenantId String
|
|
pluginId String
|
|
version String
|
|
hookName String
|
|
outcome String
|
|
failureReason String?
|
|
message String?
|
|
durationMs Int
|
|
fuelUsed Int?
|
|
createdAt DateTime @default(now())
|
|
|
|
@@index([tenantId, createdAt(sort: Desc)])
|
|
@@index([tenantId, pluginId, createdAt(sort: Desc)])
|
|
@@index([outcome, createdAt(sort: Desc)])
|
|
}
|
|
|
|
// Phase 06 #23 — per-capability-use audit row.
|
|
//
|
|
// Every successful (or denied / errored) host-fn call inside a plugin
|
|
// invocation appends a row here so a tenant admin can answer "what did
|
|
// this plugin actually DO?" — independent of what the manifest said it
|
|
// might do. The data is intentionally minimal: capability name, a
|
|
// hashed pointer to the payload (no raw body — DSGVO + storage bloat),
|
|
// outcome, and a timestamp. Retention is governed by
|
|
// `PLUGIN_AUDIT_RETENTION_DAYS` (cron-cleaned by the worker).
|
|
//
|
|
// Rows are batch-written from the capability gateway via the outbox
|
|
// pattern — the gateway buffers per-invocation events and emits one
|
|
// `plugin.capability-use.batch` outbox event at sandbox close so we
|
|
// never block the plugin-hot-path on the DB.
|
|
model PluginCapabilityUse {
|
|
id String @id @default(cuid())
|
|
/// PluginInvocation row id. No FK yet; rows are correlated by value so
|
|
/// existing capability-use batches remain migration-safe.
|
|
pluginInvocationId String
|
|
tenantId String
|
|
pluginId String
|
|
capability String
|
|
/// SHA-256 of the host-fn arg-shape (URL+method for http.outbound,
|
|
/// key for storage.*, etc.) — lets forensics cross-check without
|
|
/// retaining tenant payload bodies.
|
|
payloadHash String?
|
|
outcome String
|
|
errorCode String?
|
|
durationMs Int?
|
|
timestamp DateTime @default(now())
|
|
|
|
/// F-003 — per-tenant hash chain. `prevHash` is the `rowHash` of the
|
|
/// preceding row in this tenant's chain (or NULL for the genesis
|
|
/// row). `rowHash` is SHA-256(prevHash || canonical(row)). A verifier
|
|
/// can walk the chain and detect tampering: any modified or deleted
|
|
/// row breaks the chain at that point. Nullable for back-compat with
|
|
/// pre-F-003 rows; new inserts always populate both fields.
|
|
prevHash String?
|
|
rowHash String?
|
|
|
|
@@index([tenantId, timestamp(sort: Desc)])
|
|
@@index([tenantId, pluginId, timestamp(sort: Desc)])
|
|
@@index([pluginInvocationId])
|
|
@@index([timestamp], name: "plugin_capability_use_timestamp_idx")
|
|
}
|