Status: phase-four executable contracts. The HTTP application and its routes are locally tested; no public deployment or credentialed external-service result is claimed.
POST /integrations/slack/events
Required request headers:
Content-Type: application/json
X-Slack-Request-Timestamp: 1770000000
X-Slack-Signature: v0=<hex-hmac-sha256>Representative Slack event body:
{
"token": "not persisted",
"team_id": "T123",
"api_app_id": "A123",
"type": "event_callback",
"event_id": "Ev123",
"event_time": 1770000000,
"event": {
"type": "message",
"channel": "C123",
"user": "U123",
"text": "I can bring the keys on Friday",
"ts": "1770000000.000100"
}
}Rules:
- Verify Slack's
v0:{timestamp}:{rawBody}HMAC-SHA256 signature before JSON parsing. - Reject timestamps outside the configured replay window.
- Deduplicate by the canonical opaque message identity and source reference in the business store.
- Never persist the token, signature, or raw message in application logs.
- Convert accepted events into the canonical event below before entering the Graph.
Success response:
{
"accepted": true,
"event_id": "message_opaque"
}The HTTP adapter is suitable only behind a public HTTPS deployment that accepts Slack's network connection and validates the Slack signature itself. The verified AgentCore Runtime invocation endpoint requires IAM or OAuth authentication and is not claimed as a direct Slack Request URL.
quorum-slack-ingress-smoke sends three real HTTP requests to this endpoint: a correctly signed URL
verification body, the same body with a forged signature, and a correctly signed message event while
the model gate is closed. The required statuses are 200, 401, and 503, respectively. The last
request must stop before model, Memory, database, Gateway, Slack-outbound, or Google calls.
quorum-slack-socket uses slack_sdk.socket_mode.SocketModeClient with the versioned
src/quorum/slack-app-manifest.json contract. The app-level token has connections:write; it is not
a bot OAuth scope. The bot scopes are exactly channels:history, chat:write, and im:write, and the
only subscribed event is message.channels.
For every Socket Mode events_api envelope, Quorum first sends
SocketModeResponse(envelope_id=<received envelope ID>). It then passes the inner Slack
event_callback payload through the same SlackEventConverter used by the HTTP route. Only a typed,
pseudonymized CanonicalMessageEvent can reach the processor. Ignored and malformed envelopes are
acknowledged but never processed. Processor failures emit only processor_error; raw text and tokens
are not logged.
The commands have deliberately different authority:
quorum-slack-socket validatereads and validates the manifest without credentials or network calls.quorum-slack-socket probereceives one event and reports opaque transport/redaction evidence; it constructs no model, Memory client, database engine, or Gateway client and makes no outbound Slack Web API call.quorum-slack-socket serveruns the production ledger processor and refuses to start unless the Bedrock cost gate is explicitly open.
quorum-slack-live-evidence is the credentialed test-workspace acceptance command. Preview is the
default and performs no network call. Live execution requires --confirm-live-posts, requires
QUORUM_BEDROCK_ENABLED and QUORUM_EXECUTION_ENABLED to remain false, and waits for this exact
synthetic marker in QUORUM_SLACK_DEMO_CHANNEL_ID:
Synthetic transport check: Quorum live evidence.
The marker envelope is acknowledged before conversion. Its canonical event is explicitly
synthetic, while transport=slack_socket_mode independently records that it traversed the real
provider transport. The three posted product surfaces use the same versioned synthetic replay
fixture. A matching marker from another channel cannot trigger posts.
After one matching event, the command sends exactly three visible messages and validates four Web API
responses: group chat.postMessage, conversations.open, private chat.postMessage, and weekly
chat.postMessage. No model, Memory, business database, Gateway, Google Workspace, or execution tool
is constructed or called.
Representative successful report:
{
"schema_version": "1.0",
"status": "provider_responses_validated",
"evidence_scope": "dedicated_test_workspace_synthetic",
"transport": "slack_socket_mode",
"envelope_ack_sent": true,
"canonical_events_observed": 1,
"canonical_event_classification": "synthetic",
"fixture_data_classification": "synthetic",
"messages_sent": 3,
"surface_types": ["group_receipt", "private_question", "weekly_summary"],
"web_api_responses_validated": 4,
"visual_inspection_required": true,
"model_calls": 0,
"memory_events": 0,
"database_writes": 0,
"gateway_tool_calls": 0,
"google_workspace_calls": 0,
"execution_tool_calls": 0,
"external_side_effect_calls": 4
}The actual report also contains completed_at and the public synthetic dataset_id. It never
contains tokens, raw or pseudonymized workspace/channel/user/message IDs, message bodies, envelope
IDs, or Slack timestamps. envelope_ack_sent=true means the SDK's Socket Mode response method
returned; it does not claim a second acknowledgment from Slack.
status=provider_responses_validated proves successful Web API response contracts only; the operator
must visually inspect the three messages before claiming the product surfaces were rendered.
The four provider calls are not atomic. A sanitized delivery error can occur after an earlier message
was accepted, so the operator must inspect Slack before retrying; Quorum does not automatically
repeat the evidence run.
This is an internal typed boundary, not a public HTTP endpoint.
{
"schema_version": "1.0",
"organization_id": "org_opaque",
"channel_id": "channel_opaque",
"message_id": "message_opaque",
"actor_id": "person_opaque",
"occurred_at": "2026-08-26T10:00:00Z",
"text": "I can bring the keys on Friday",
"data_classification": "redacted-real",
"source": {
"provider": "slack",
"workspace_id": "workspace_opaque",
"source_message_ref": "slack:C123:1770000000.000100"
}
}Allowed data_classification values are synthetic and redacted-real. Raw or unredacted data is
not a valid Graph input.
Quorum has exactly three outbound Slack interactions:
- one
chat.postMessagegroup receipt with optional Open and Undo URL buttons; - one
conversations.open(users=<one participant>)call followed by one privatechat.postMessagedecision question; - one weekly
chat.postMessagewith six compact fields: closed decisions, decision-latency P50, total interruptions, people interrupted, maximum interrupts per person, and undo rate.
WeeklySummary rejects a weekly interruption total that cannot fit within the declared number of
people and the default two-interrupt-per-person budget. Synthetic smoke messages begin with
Synthetic demo and the weekly context states that they are not a measured real-world outcome.
quorum-slack-smoke previews by default; only --confirm-live-posts performs the three Web API
writes. The command makes zero model calls and zero execution-tool calls.
The Ledger Curator returns a typed ExtractionEnvelope. A candidate is rejected before storage if
its source reference differs from the current event or its evidence quote is not present verbatim
in the event text.
{
"commitments": [
{
"operation": "create",
"task_class": "item_handoff",
"summary": "Bring the storage keys",
"owner_id": "person_opaque",
"due_at": "2026-08-28T17:00:00Z",
"target_commitment_id": null,
"confidence": 0.96,
"evidence": {
"source_message_ref": "slack:C123:1770000000.000100",
"evidence_quote": "I will bring the storage keys"
}
}
],
"ambiguities": []
}This is an internal typed boundary returned by DatabaseLedger.apply(), not a public HTTP endpoint.
The call executes one transaction containing the processed-message idempotency claim, all accepted
commitment changes, and their audit events.
{
"upserted": [
{
"commitment_id": "cmt_opaque",
"organization_id": "org_opaque",
"task_class": "item_handoff",
"summary": "Bring the storage keys",
"owner_id": "person_opaque",
"due_at": "2026-08-28T17:00:00Z",
"status": "open",
"source_message_refs": ["slack:C123:1770000000.000100"],
"created_at": "2026-08-26T10:00:01Z",
"updated_at": "2026-08-26T10:00:01Z",
"confidence": 0.96
}
],
"rejected": [],
"duplicate_event": false
}Retry contract:
- an exact retry of the same organization and source-message reference returns
duplicate_event: truewithout writing another commitment or audit event; - reusing that identity with different canonical content raises an idempotency conflict;
- rejected candidates return a stable rejection code and are never persisted;
- an exception rolls back the message claim, commitment rows, and audit events together.
The production database URL is supplied only through QUORUM_DATABASE_URL. Supported schemes are
postgresql+psycopg:// for production and sqlite+pysqlite:// for local development. All reads and
mutations require organization_id; this storage layer never accepts raw Slack payloads.
This is the typed input to the Risk Appraiser and Quorum Router. The three risk dimensions are declared values, not free-form model output. Candidate deciders are ordered and unique.
{
"schema_version": "1.0",
"action_id": "action_opaque",
"organization_id": "org_opaque",
"requested_by_id": "person_requester",
"action_class": "event_decision",
"tool_name": "calendar_create_tentative_event",
"summary": "Create a tentative planning event",
"reversibility": "reversible",
"impact_radius": "individual",
"money_impact": "none",
"candidate_decider_ids": ["person_a", "person_b"],
"action_arguments": {
"title": "Tentative planning event",
"starts_at": "2026-08-28T09:00:00+08:00",
"ends_at": "2026-08-28T10:00:00+08:00",
"time_zone": "Asia/Shanghai",
"receipt_channel_id": "C123"
},
"requested_at": "2026-08-26T10:00:00Z"
}Allowed risk values are:
reversibility:reversible,compensatable, orirreversible;impact_radius:individual,group, orexternal;money_impact:none,budgeted, orunbudgeted.
DecisionPolicyStore.decide() is idempotent on (organization_id, action_id). Reusing the same
identity with different request content is a conflict. The method locks the autonomy profile and
candidate budget accounts, computes spend in the same transaction, and appends one requested
interrupt event for each selected decider.
{
"action_id": "action_opaque",
"organization_id": "org_opaque",
"requested_by_id": "person_requester",
"action_class": "event_decision",
"tool_name": "calendar_create_tentative_event",
"arguments_fingerprint": "64-lowercase-hex-characters",
"risk": {
"score": 0,
"tier": "low",
"reversibility_points": 0,
"impact_radius_points": 0,
"money_impact_points": 0,
"reasons": [
"reversibility:reversible=0",
"impact_radius:individual=0",
"money_impact:none=0"
]
},
"autonomy": {
"level": 0,
"consecutive_approvals": 0,
"rejection_count": 0,
"undo_count": 0
},
"required_quorum": 1,
"selected_decider_ids": ["person_a"],
"budgets": [{"participant_id": "person_a", "spent": 0, "limit": 2}],
"status": "awaiting_approval",
"timeout_at": "2026-08-27T10:00:00Z",
"timeout_default": "execute_and_notify"
}Decision statuses are authorized, awaiting_approval, deferred_budget, approved, rejected,
expired, executed, and undone. Interrupt responses may only come from selected deciders and may
not be changed. Repeated identical responses are idempotent. Three completed approvals promote one
autonomy level; rejection or a valid undo lowers one level. A response received at or after the
persisted timeout is not accepted: the timeout default is applied first. Undo accepts only authorized,
approved, or executed reversible actions and is itself idempotent.
The Executor passes organization_id and action_id in the tool input.
QuorumAutonomyGate re-reads the persisted decision, verifies the exact tool name and canonical
SHA-256 fingerprint of every other argument, and creates one Strands interrupt per selected quorum
member. Optional top-level null values are omitted during canonicalization.
{
"name": "quorum-approval-0",
"reason": {
"action_id": "action_opaque",
"participant_id": "person_a",
"risk_tier": "low",
"required_quorum": 1,
"timeout_at": "2026-08-27T10:00:00Z",
"timeout_default": "execute_and_notify"
}
}Accepted response payloads are the strings approve, approved, yes, or y, or an object whose
decision contains one of those values. Any other response is a rejection. Missing policy, a tool
name or argument mismatch, or a non-executable status cancels the tool call.
The implemented Strands tool names are:
calendar_create_tentative_event: Calendar v3events.insert, withstatus=tentativeandsendUpdates=none; undo callsevents.delete.gmail_create_draft: Gmail v1users.drafts.create; it never calls a send endpoint; undo callsusers.drafts.delete.forms_create_response_request: Forms v1forms.createplusforms.batchUpdate; undo deletes the created file through Drive v3files.delete.
Every typed tool input requires organization_id and action_id. Provider-specific fields must
exactly match the arguments fingerprint approved by the policy layer. All three tools reject unknown
fields and irreversible policy decisions.
ActionExecutionService returns this internal typed boundary after a successful provider call:
{
"organization_id": "org_opaque",
"action_id": "action_opaque",
"tool_name": "calendar_create_tentative_event",
"provider": "google_calendar",
"external_resource_id": "opaque-provider-id",
"external_url": "https://provider.example/opaque-resource",
"status": "executed",
"reversible": true,
"executed_at": "2026-08-27T09:00:00Z",
"undo_expires_at": "2026-08-28T09:00:00Z",
"undo_url": "https://demo.example/actions/undo?token=redacted"
}An exact retry returns the persisted receipt without repeating the provider call. A transport error,
5xx response, invalid response, or missing provider resource ID is recorded as uncertain and is not
automatically retried. The database stores no action arguments or undo token plaintext.
POST /invocations is hosted by AgentCore Runtime through BedrockAgentCoreApp.
Required session header:
X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: <opaque-session-id>Request body:
{
"organization_id": "org_opaque",
"prompt": "Create the already-approved tentative event.",
"data_classification": "synthetic",
"action_request": {
"schema_version": "1.0",
"action_id": "action_opaque",
"organization_id": "org_opaque",
"requested_by_id": "person_requester",
"action_class": "event_decision",
"tool_name": "calendar_create_tentative_event",
"summary": "Create a tentative planning event",
"reversibility": "reversible",
"impact_radius": "individual",
"money_impact": "none",
"candidate_decider_ids": ["person_a"],
"action_arguments": {},
"requested_at": "2026-08-26T10:00:00Z"
},
"interrupt_responses": []
}Response body:
{
"session_id": "opaque-session-id-at-least-33-characters",
"status": "completed|interrupted|failed",
"execution_order": ["listener", "ledger_curator"],
"interrupts": [],
"usage": {}
}The session ID is required, 33–256 characters, starts with an alphanumeric character, and contains only alphanumerics, hyphens, or underscores. The invocation and action-request organization IDs must match.
Model access is an operator-controlled deployment setting, never a request field. Production starts
with QUORUM_BEDROCK_ENABLED=false. While disabled, a valid request returns HTTP 503 before
AgentCore Memory, Gateway, or Bedrock is initialized:
{
"error": "Bedrock model calls are disabled; set QUORUM_BEDROCK_ENABLED=true only for a controlled run"
}When an operator deliberately enables a bounded run, QUORUM_BEDROCK_MAX_TOKENS limits each model
response to 64–1024 output tokens and defaults to 384. This application guard is not an AWS billing
hard limit.
The Runtime entrypoint also accepts one narrow, synthetic-only probe contract. It is evaluated before the PostgreSQL/SQLite engine, AgentCore Memory, AgentCore Gateway, or Bedrock model path is initialized. Unknown fields are rejected, and the fixed privacy sentinel is never copied to a span.
{
"kind": "observability_probe",
"probe_id": "probe_opaque",
"organization_id": "org_synthetic",
"data_classification": "synthetic",
"privacy_sentinel": "synthetic-payload-must-not-appear-in-telemetry"
}{
"kind": "observability_probe",
"status": "completed",
"probe_id": "probe_opaque",
"session_id": "opaque-session-id-at-least-33-characters",
"trace_id": "32-lowercase-hex-characters",
"span_id": "16-lowercase-hex-characters"
}The custom span is named quorum.observability.probe and may contain only the opaque probe,
organization, and session IDs plus quorum.data_classification=synthetic. The managed verification
must find that span in the Runtime's spans stream, match its trace and span IDs to the response,
prove the sentinel is absent, and report zero model, Memory event, Gateway tool, Slack, and Google
Workspace calls.
The deployment workflow allows up to 15 minutes for each account-level X-Ray destination transition;
AWS reports PENDING and rejects a second destination update until the first transition is ACTIVE.
Before enabling CloudWatchLogs, the workflow ensures that
AWSServiceRoleForCloudWatchApplicationSignals exists and records whether it created the role. On
rollback, it restores the prior X-Ray destination and indexing percentage, removes the named resource
policy and newly created managed log groups, then deletes that role only if the workflow created it.
The Application Signals CloudTrail service-linked channel is an AWS-owned account prerequisite:
customer DeleteChannel calls are not permitted, so the workflow reports but does not claim to
delete it. No Quorum business data or raw message content is sent to that channel.
The manual Verify AgentCore Memory and Gateway workflow creates a uniquely named, short-lived
Memory resource with the following control-plane request. It waits for ACTIVE, verifies the two
strategy names, creates zero events, and deletes the resource before the job ends.
{
"name": "QuorumMemory<workflow-run-id>",
"description": "Short-lived Quorum strategy verification with zero events",
"eventExpiryDuration": 7,
"memoryStrategies": [
{"semanticMemoryStrategy": {"name": "QuorumFacts", "namespaceTemplates": ["/facts/{actorId}/"]}},
{"summaryMemoryStrategy": {"name": "QuorumSummaries", "namespaceTemplates": ["/summaries/{actorId}/{sessionId}/"]}}
],
"tags": {
"Project": "Quorum",
"DataClassification": "SyntheticOnly",
"CostMode": "ZeroModel"
}
}This proves the managed Memory resource and namespace contract only. It does not claim a model-backed memory extraction result, because the cost-safe verification writes zero events.
The contract was exercised in ap-northeast-1 on September 1, 2026. The public run reported
memory_status=ACTIVE, both configured strategy names, events_created=0, and complete cleanup. See
the evidence record and
GitHub Actions run.
The same workflow creates an IAM-authenticated MCP Gateway and a Lambda target. The target contains
the exact schemas returned by gateway_tool_definitions():
{
"gatewayIdentifier": "<gateway-id>",
"name": "quorum-execution",
"targetConfiguration": {
"mcp": {
"lambda": {
"lambdaArn": "<short-lived-lambda-arn>",
"toolSchema": {
"inlinePayload": [
{"name": "calendar_create_tentative_event", "inputSchema": {}},
{"name": "gmail_create_draft", "inputSchema": {}},
{"name": "forms_create_response_request", "inputSchema": {}}
]
}
}
}
},
"credentialProviderConfigurations": [{"credentialProviderType": "GATEWAY_IAM_ROLE"}]
}The verifier waits for the Gateway and target to reach READY, uses a SigV4-authenticated MCP client
to run initialization and tools/list, and requires exactly the three names above. It deliberately
runs zero tools/call requests. The Lambda also defaults QUORUM_EXECUTION_ENABLED=false; an
attempted invocation fails before database, Slack, or Google clients are initialized.
The September 1 public run reported lambda_gate=execution-disabled,
gateway_status=READY, authentication=AWS_IAM, the exact three tool names, tool_calls=0, and
complete cleanup. This is control-plane and tool-discovery evidence, not execution evidence.
The Strands interrupt response is sent back to the same logical session.
{
"organization_id": "org_opaque",
"prompt": "Resume the approved action.",
"data_classification": "synthetic",
"action_request": {"...": "the identical original action request"},
"interrupt_responses": [
{
"interrupt_id": "interrupt_opaque",
"response": "approve"
}
]
}GET is deliberately non-mutating so Slack link previews and security scanners cannot consume a
single-use token:
GET /actions/undo?token=<single-use-signed-token>It returns an HTML confirmation form. The response sets Cache-Control: no-store and
Referrer-Policy: no-referrer so the single-use token is not cached or forwarded as a referrer. The
user-confirmed mutation is:
POST /actions/undo
Content-Type: application/x-www-form-urlencoded
token=<single-use-signed-token>Success response:
{
"action_id": "action_opaque",
"status": "undone",
"undone_at": "2026-08-26T10:05:00Z"
}The token is HMAC-SHA256 signed, scoped to one organization and action, expires after 24 hours, and
is excluded from logs. Only its SHA-256 digest is persisted. It is atomically consumed before the
provider delete call; tampered, expired, or reused tokens fail closed. Provider undo failure remains
auditable as undo_failed and does not silently restore the token.
POST /demo/replays/synthetic-week
The request body is empty.
Every replay response must state its provenance:
{
"replay_id": "replay_opaque",
"dataset_id": "synthetic_week_v1",
"data_classification": "synthetic",
"baseline": {"message_count": 214, "closed_decisions": 3},
"quorum": {"interruption_count": 6, "closed_decisions": 6},
"disclaimer": "Synthetic demonstration data; not a measured real-world outcome."
}The full snapshot is retrieved with GET /demo/metrics/{replay_id}. Unknown replay IDs return 404.
The public GitHub Pages build cannot expose a Python API. It reads the versioned
synthetic-week.json artifact with the same response shape and an explicit static replay ID. An
executable test compares every non-ID field with ReplayStore; the page explicitly states that this
is static synthetic evidence and not a deployed AgentCore result.
{
"error": {
"code": "INVALID_SIGNATURE",
"message": "The request could not be authenticated.",
"retryable": false,
"trace_id": "opaque-trace-id"
}
}Error messages must never echo raw message content or credentials.