Skip to content
Merged
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
49 changes: 49 additions & 0 deletions crates/ironclaw_first_party_extensions/assets/slack/manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,55 @@ flags = [
[[product_adapter.inbound.required_credentials]]
handle = "slack_bot_token"

[[product_adapter.inbound.required_credentials]]
handle = "slack_signing_secret"

[[product_adapter.inbound.egress]]
host = "slack.com"
credential_handle = "slack_bot_token"

# Host-ingress routes projected by the serve layer (ironclaw_reborn_composition
# ::slack_serve). The route path/method/policy live here as data — the axum
# handler + HMAC verifier stay in Rust. Credential coherence: each route is
# verified by `slack_signing_secret` (the secret behind the runtime's HMAC
# webhook verifier), declared in required_credentials above. `slack_bot_token`
# is the outbound egress credential and does not verify inbound routes.
[[product_adapter.inbound.host_ingress]]
credential_handles = ["slack_signing_secret"]

Comment on lines +48 to +50
[product_adapter.inbound.host_ingress.descriptor]
route_id = "slack.events"
method = "post"
route_pattern = "/webhooks/slack/events"

[product_adapter.inbound.host_ingress.descriptor.policy]
listener_class = "public_webhook"
auth = { type = "required", schemes = ["webhook_signature"] }
scope_source = "host_resolved"
body_limit = { type = "limited", max_bytes = 1048576 }
rate_limit = { type = "limited", scope = "global", max_requests = 12000, window_seconds = 60 }
cors = "not_applicable"
websocket_origin = "not_applicable"
streaming = "none"
audit = "public_callback"
effect_path = { type = "product_workflow" }

[[product_adapter.inbound.host_ingress]]
credential_handles = ["slack_signing_secret"]

[product_adapter.inbound.host_ingress.descriptor]
route_id = "slack.commands"
method = "post"
route_pattern = "/webhooks/slack/commands"

[product_adapter.inbound.host_ingress.descriptor.policy]
listener_class = "public_webhook"
auth = { type = "required", schemes = ["webhook_signature"] }
scope_source = "host_resolved"
body_limit = { type = "limited", max_bytes = 16384 }
rate_limit = { type = "limited", scope = "global", max_requests = 6000, window_seconds = 60 }
cors = "not_applicable"
websocket_origin = "not_applicable"
streaming = "none"
audit = "public_callback"
effect_path = { type = "product_workflow" }
24 changes: 23 additions & 1 deletion crates/ironclaw_product_adapter_registry/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,23 @@ Owns ProductAdapter host-api projection contracts for IronClaw Reborn.
- validate every egress credential handle is declared in
`required_credentials`,
- keep `(host, credential_handle)` pairs distinct.
- A section MAY declare `host_ingress` routes (`[[product_adapter.<sub>.host_ingress]]`),
each carrying a full host-owned `ironclaw_host_api::IngressRouteDescriptor`
(validated by host_api's own `Deserialize` — dotted route id, absolute path,
and every policy invariant incl. the fail-closed floor that a
`public_webhook` listener MUST require `webhook_signature`) plus the
`credential_handles` that verify it. This crate does NOT re-own ingress
route/policy vocabulary — it only projects the descriptor and enforces
**ingress credential coherence**, which is fail-closed:
- every `credential_handles` entry must be declared in `required_credentials`
(mirrors the egress rule, so ingress handles flow into the same declared
set installation bindings validate against),
- an auth-required route (`IngressAuthPolicy::Required`) must name at least
one verifying credential handle,
- route ids stay distinct within a section.
Mounting these routes (descriptor → axum route + verifier) is the serve
layer's job, NOT this crate's — this crate still must not route webhooks,
resolve secret material, or bind HTTP ingress.
- ProductAdapter runtime projection must keep the cross-write invariant at
read time: every surfaced installation must remain valid against its
registered manifest and current ProductAdapter sections.
Expand All @@ -38,6 +55,11 @@ Owns ProductAdapter host-api projection contracts for IronClaw Reborn.
mismatch, redacted health, and cross-write invariant maintenance.
- Integration tests in `tests/manifest_ingestion.rs` cover manifest
parsing, unknown-field rejection, inline-secret rejection, and egress
credential validation.
credential validation — plus host-ingress route projection over the wire,
the inherited host_api fail-closed floor (`public_webhook` without
`webhook_signature` rejected), and ingress credential coherence. The
ingress credential-coherence matrix (undeclared handle, auth-required route
missing a credential, duplicate route id, happy projection) has focused
unit coverage in `src/lib.rs` `mod tests`.
- `cargo test -p ironclaw_architecture reborn_crate_dependency_boundaries_hold`
pins crate dependency boundary.
Loading
Loading