diff --git a/HANDOFF.md b/HANDOFF.md index cc2d206..3d390e4 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -4,11 +4,10 @@ - GitHub: `https://github.com/Jdelg718/BotFucker` - Default branch: `main` -- Latest merged milestone: Phase 11 guarded YOLO policy (`feat: add guarded yolo policy`, PR #12) -- Current working branch: `phase-12-n8n-import-dry-run-validation` -- Current PR target: Phase 12 real n8n import/dry-run validation — PR #13 open and CI green: `https://github.com/Jdelg718/BotFucker/pull/13` -- Tomorrow start: merge PR #13 if still green, then branch from updated `main` for Phase 13 Reviewed Action Bridge Promotion Plan -- Current local demo target: demonstrate deterministic local review, optional mocked LLM classifier fallback/validation, approved-action export, dry-run n8n bridge contract, fail-closed YOLO policy checks, and real n8n import validation results +- Latest merged milestone: Phase 12 real n8n import/dry-run validation (`docs: validate n8n import dry run`, PR #13) +- Current working branch: `phase-13-reviewed-action-bridge-promotion-plan` +- Current PR target: Phase 13 Reviewed Action Bridge Promotion Plan — docs/tests only, no OAuth/live provider mutation +- Current local demo target: demonstrate deterministic local review, optional mocked LLM classifier fallback/validation, approved-action export, dry-run n8n bridge contract, fail-closed YOLO policy checks, real n8n import validation results, and reviewed bridge-promotion gate - Current promo artifact: `promo/botfucker-animated-explainer/renders/botfucker-animated-explainer_narrated-final.mp4` ## What BotFucker Is @@ -35,6 +34,7 @@ docs/n8n-workflow.md # n8n import operator guide and safety checklist docs/n8n-approved-action-bridge.json # importable n8n approved-action dry-run bridge docs/n8n-approved-action-bridge.md # approved-action bridge operator guide docs/provider-auth-plan.md # provider auth/action boundary plan +docs/reviewed-action-bridge-promotion-plan.md # Phase 13 reviewed live-bridge gate; no OAuth/live mutation README.md # user-facing setup and project overview outreach_filter.py # compatibility CLI wrapper botfucker/models.py # normalized email/classification/review models @@ -267,16 +267,17 @@ python3 -m unittest discover -s tests -v ## Next PR Recommendation -Build **Phase 13: Reviewed Action Bridge Promotion Plan**, not OAuth. +After Phase 13, keep OAuth on hold. The next safe step is either documentation review cleanup for the promotion gate or a mocked/sandbox-only processed-audit state prototype that still performs no live provider mutation. -Suggested scope: +Do **not** add real OAuth, provider credentials, or live n8n provider mutation nodes until the Phase 13 gate has Rex/Gus review and provider-specific sandbox evidence. -1. Define how one provider action type graduates from dry-run to live review. -2. Keep credentials in n8n only. -3. Design persistent processed-`audit_id` state. -4. Document rollback and emergency stop. -5. Require provider-specific sandbox/manual tests. -6. Require Rex/Gus security/ops review before any live mutation node is connected. +Suggested follow-up scope: + +1. Review Phase 13 plan with Rex/Gus. +2. Decide the first provider/action pair for sandbox review, likely `approve_warning` only. +3. Prototype processed-`audit_id` state with fake/sample data only. +4. Keep credentials in n8n only. +5. Require rollback and emergency-stop proof before any live provider action node is connected. ## Suggested Prompt for Kodex/Codex diff --git a/README.md b/README.md index a23ce96..f29618d 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,16 @@ Compatibility fixes shipped: - file paths use `/home/node/.n8n-files`, because n8n 2.18.5 blocks arbitrary local file paths - approved-action bridge parses `readWriteFile` binary JSON via `getBinaryDataBuffer` +## Phase 13 Reviewed Action Bridge Promotion Plan + +Phase 13 adds the reviewed path for promoting one approved provider action type from dry-run evidence toward a separately reviewed live n8n bridge. It is documentation and tests only: no OAuth, no provider credentials in BotFucker core, no live Gmail/Microsoft/IMAP/SMTP mutation nodes, and no change to local UI/provider behavior. + +Artifact: + +- [`docs/reviewed-action-bridge-promotion-plan.md`](docs/reviewed-action-bridge-promotion-plan.md) — operator/security/ops gate for one-action-at-a-time live bridge review. + +The plan requires persistent processed-`audit_id` state, rollback and emergency-stop procedures, provider-specific sandbox/manual tests, and Rex/Gus review before any live mutation node is connected. + ## Test Before Going Live Compile-check the script and package: diff --git a/ROADMAP.md b/ROADMAP.md index c1a3326..34632c6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -294,11 +294,13 @@ Acceptance criteria: - No Gmail/Microsoft/IMAP/SMTP mutation credentials are attached. - Activation remains manual and reviewed. -### Phase 13 — Review Action Bridge Promotion Plan +### Phase 13 — Reviewed Action Bridge Promotion Plan ✅ + +Status: implemented on `phase-13-reviewed-action-bridge-promotion-plan`. Goal: define the reviewed path from dry-run logs to a live provider bridge without adding OAuth/provider mutation directly to BotFucker core. -Deliverables: +Delivered: - operator checklist for promoting one provider action type at a time - explicit credential ownership in n8n only @@ -313,6 +315,7 @@ Acceptance criteria: - Only reviewed approved-action records are eligible. - Every provider mutation is idempotent, audited, and reversible where possible. - No live provider credentials are committed or exported. +- No OAuth, live provider mutation nodes, or provider behavior changes are added by this phase. ## Local Kodex/Codex Demo Plan @@ -360,26 +363,27 @@ Use fake or sanitized JSON only. Real mailbox payloads stay out of the repo. ## Near-Term Recommendation -Next PR should be **Phase 13: Reviewed Action Bridge Promotion Plan**, not OAuth implementation. +After Phase 13 is reviewed and merged, the next PR should be **Phase 14: durable bridge ledger design or sandbox-only live bridge scaffold**, not broad OAuth implementation. Recommended scope: -- define how one provider action type graduates from dry-run to live review +- choose one provider/action pair only +- implement or document the durable processed-`audit_id` ledger before any provider mutation - keep credentials in n8n only -- design persistent processed-`audit_id` state -- document rollback and emergency stop +- keep dry-run as the default path +- prove emergency stop exits before provider mutation - require provider-specific sandbox/manual tests - require Rex/Gus security/ops review before any live mutation node is connected -OAuth can still wait. We proved the n8n package imports and dry-runs in the real beast. Next is a promotion plan for one action type at a time, because wiring live mail mutation without a checklist is how you manufacture regret at scale. +OAuth can still wait. We proved the n8n package imports and dry-runs in the real beast, and Phase 13 defines the promotion gate. Next is one tiny, reviewed step toward a provider bridge — not a live-mail fireworks show. -### Tomorrow restart checklist +### Restart checklist after Phase 13 -1. Re-check PR #13 CI and mergeability. -2. Squash-merge PR #13 into `main` if still green. -3. Pull updated `main` and create Phase 13 branch. -4. Build **Reviewed Action Bridge Promotion Plan** only — still no OAuth, no provider credentials, no live action node activation. -5. Use tests/docs to prove the promotion plan keeps credentials in n8n, persists processed `audit_id` state, and requires rollback/security/operator review. +1. Re-check Phase 13 PR CI and mergeability. +2. Squash-merge Phase 13 into `main` if still green. +3. Pull updated `main` and create a Phase 14 branch. +4. Pick exactly one sandbox provider/action pair, likely `approve_warning` only if Kent explicitly wants reply-send tested. +5. Build durable dedupe/ledger scaffolding and emergency-stop proof before wiring any provider mutation. ## Team Utilization diff --git a/docs/n8n-approved-action-bridge.md b/docs/n8n-approved-action-bridge.md index 18298dc..0e11b14 100644 --- a/docs/n8n-approved-action-bridge.md +++ b/docs/n8n-approved-action-bridge.md @@ -89,13 +89,19 @@ There are intentionally no Gmail, Microsoft, IMAP, SMTP, send-mail, move-mail, d ## Future live bridge rules -When this graduates from dry-run to live provider execution: +When this graduates from dry-run to live provider execution, follow the Phase 13 promotion plan in [`docs/reviewed-action-bridge-promotion-plan.md`](reviewed-action-bridge-promotion-plan.md). Phase 13 is a plan and review gate only: no OAuth, no provider credentials in BotFucker core, and no live provider mutation nodes in the checked-in starter workflows. -- keep the live workflow separate from the import workflow +Minimum rules: + +- keep the live workflow separate from the import workflow and this dry-run starter +- promote only one reviewed provider/action pair at a time - require explicit enablement - retain dry-run mode -- persist processed `audit_id` values durably +- persist processed `audit_id` values durably before provider mutation - log each attempted provider action and result -- prefer archive/quarantine over delete +- document rollback and emergency-stop behavior +- require provider-specific sandbox/manual tests +- require security and ops review before any live provider action node is connected +- prefer archive/quarantine over delete; permanent deletion is out of scope for the first promotion review - never execute unapproved, dismissed, whitelist, or blacklist events unless those action types get their own reviewed export contract - never expose provider tokens or private headers to BotFucker core or the browser UI diff --git a/docs/reviewed-action-bridge-promotion-plan.md b/docs/reviewed-action-bridge-promotion-plan.md new file mode 100644 index 0000000..864c053 --- /dev/null +++ b/docs/reviewed-action-bridge-promotion-plan.md @@ -0,0 +1,178 @@ +# Phase 13 Reviewed Action Bridge Promotion Plan + +Phase 13 is a promotion plan, not a provider implementation. It defines how one approved action type can move from n8n dry-run logs toward live provider execution after review, without adding OAuth, provider credentials, or live mailbox mutation nodes to BotFucker core. + +The default remains boring and safe: BotFucker imports bounded provider-shaped JSON, records human review state locally, exports approved intent, and the n8n approved-action bridge stays dry-run until a separate reviewed live bridge is created and approved. + +## Non-goals + +Do not do these in Phase 13: + +- Do not implement Gmail OAuth. +- Do not implement Microsoft OAuth. +- Do not add IMAP passwords, SMTP passwords, OAuth tokens, API keys, cookies, or provider credentials to BotFucker core. +- Do not add Gmail, Microsoft, IMAP, SMTP, send-mail, move-mail, delete-mail, archive, or label mutation nodes to the checked-in starter workflows. +- Do not activate the n8n workflows by default. +- Do not execute provider mutations from the local UI, review CLI, tests, samples, or docs examples. +- Do not promote more than one provider action type in a single reviewed change. + +## Promotion target for the first live review + +The first eligible live bridge review should be limited to one action type: + +- Approved action: `approve_warning` +- Source schema: `botfucker.approved_actions.v1` +- Source requirement: human-approved SQLite audit event exported by `export-approved-actions` +- Initial live behavior candidate: send or draft a provider reply only after sandbox/manual validation +- Safer default: keep dry-run enabled and log `would_execute` until the security and ops checklists are signed off + +Permanent deletion is out of scope. Whitelist, blacklist, archive, move, and label actions require their own export contracts and separate reviewed promotion plans. + +## Credential ownership + +Provider credentials belong in n8n only. + +BotFucker core must never store or receive: + +- OAuth access tokens +- OAuth refresh tokens +- provider API keys +- IMAP passwords +- SMTP passwords +- browser-visible provider cookies +- private provider headers + +The local UI, local SQLite review DB, JSON samples, tests, and checked-in n8n workflow exports must remain credential-free. n8n can own provider connections later, but a live bridge must be created as a separate operator-owned workflow after review. + +## Required live bridge shape + +A future live bridge must be separate from both checked-in starters: + +1. Import workflow: provider fetch to normalized local review input. +2. Dry-run approved-action bridge: validates approved exports and logs what would execute. +3. Reviewed live bridge: operator-created, provider-specific, explicit opt-in, and security/ops approved. + +The live bridge must retain a dry-run switch. The reviewed version should fail closed unless all of these are true: + +- source schema is exactly `botfucker.approved_actions.v1` +- action has `safety_scope: provider_action_export_only` +- action has `provider_execution: not_performed` +- `approved_action` is exactly the one action type under review +- `audit_id` has not already been processed +- provider, message, and thread identifiers are present for that provider +- emergency stop is off +- daily action limit has not been exceeded +- operator explicitly enabled this provider/action pair + +## Persistent processed-audit design + +Dry-run currently supports a starter `BOTFUCKER_PROCESSED_AUDIT_IDS` list. Live bridge work must replace that with durable state before any provider mutation. + +Minimum durable fields: + +```json +{ + "audit_id": "audit-0001", + "action_id": "bf-action-audit-0001", + "provider": "gmail", + "approved_action": "approve_warning", + "message_id": "gmail-msg-123", + "thread_id": "gmail-thread-7", + "status": "processed", + "dry_run": false, + "provider_result_id": "provider-response-id-or-null", + "processed_at": "2026-05-18T00:00:00Z", + "processed_by_workflow": "botfucker-reviewed-live-bridge-v1" +} +``` + +Rules: + +- Use `audit_id` as the primary idempotency key. +- Check durable state before the provider node executes. +- Insert a pending/attempted record before live mutation if the state store supports transactions or equivalent lock behavior. +- Record provider result, failure, and rollback status after execution. +- Never retry a processed `audit_id` blindly; manual review is required for ambiguous failures. +- Keep the durable processed-audit store in n8n/operator infrastructure, not BotFucker core. + +Acceptable early storage options are n8n Data Store, a provider-bridge SQLite/Postgres table owned by n8n, or a locked append-only file on the n8n host. Environment variables are not acceptable for live processed-audit state. + +## Rollback and emergency stop + +A reviewed live bridge must document rollback before it is enabled. + +Emergency stop requirements: + +- one operator-visible switch that stops provider mutations immediately +- default value is stopped/off for live mutation +- documented place where the switch is configured in n8n +- test showing the bridge exits before provider mutation when the stop is active + +Rollback requirements for `approve_warning`: + +- log the provider message/reply identifier returned by the provider +- document whether the provider supports deleting, retracting, or annotating the sent/drafted reply +- if reversal is unsafe or unsupported, document that rollback is notification/manual remediation only +- keep a manual incident note template for bad sends +- preserve processed-audit state even when rollback is manual so duplicate sends do not compound the mistake + +## Provider-specific sandbox/manual test plan + +Before any provider mutation node is connected to a production account, run provider-specific tests in a sandbox or controlled test mailbox. + +Minimum manual test matrix: + +- Import one fake/sandbox message into local review state. +- Approve exactly one `approve_warning` item locally. +- Export approved actions from the local CLI. +- Run the bridge in dry-run mode and verify `provider_execution: not_performed`. +- Enable the reviewed live bridge only for the sandbox provider/action pair. +- Execute one action against a sandbox/test message. +- Verify exactly one provider-side result was created. +- Re-run the same export and confirm dedupe prevents duplicate mutation. +- Turn on emergency stop and confirm no provider mutation occurs. +- Trigger a controlled provider failure and confirm the audit state is not marked successfully processed. +- Remove sandbox/test artifacts and record cleanup. + +Provider-specific notes must document provider rate limits, reply/draft semantics, delete/retract availability, and how to identify a safe test mailbox/thread. + +## Security review checklist + +Rex/security review must pass before any live provider action node is connected: + +- no provider credentials in the repo, samples, docs examples, local SQLite review DB, browser JSON, or test fixtures +- live bridge is separate from BotFucker core and checked-in dry-run starter +- provider node cannot execute unless schema, safety scope, action type, emergency stop, and dedupe checks pass +- durable processed-`audit_id` state exists and is checked before mutation +- logs redact tokens, cookies, private headers, and message bodies where possible +- untrusted message content cannot alter workflow control flow +- rollback/emergency-stop documentation exists +- action limits are configured for the first live review + +## Ops review checklist + +Gus/ops review must pass before any live provider action node is connected: + +- workflow starts inactive or disabled for live mutation by default +- operator can run dry-run and inspect `would_execute` output +- processed-audit storage is backed up or otherwise durable enough for the target +- failures are visible in n8n execution history or external logs +- cleanup steps exist for sandbox data +- production activation requires explicit human action +- final reviewed workflow name/version is recorded + +## Promotion gate + +A live provider bridge is not ready until all gate items are true: + +- one provider/action pair only +- dry-run evidence exists +- sandbox/manual evidence exists +- persistent processed-`audit_id` state exists +- emergency stop tested +- rollback/remediation documented +- Rex/security approval recorded +- Gus/ops approval recorded +- no changes added OAuth or provider mutation behavior to BotFucker core + +Until then, the correct state is dry-run only. The robot can wait; the inbox cannot un-send regret. diff --git a/tests/test_reviewed_action_bridge_promotion_plan.py b/tests/test_reviewed_action_bridge_promotion_plan.py new file mode 100644 index 0000000..a05c132 --- /dev/null +++ b/tests/test_reviewed_action_bridge_promotion_plan.py @@ -0,0 +1,88 @@ +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +PLAN_PATH = REPO_ROOT / "docs" / "reviewed-action-bridge-promotion-plan.md" +README_PATH = REPO_ROOT / "README.md" +ROADMAP_PATH = REPO_ROOT / "ROADMAP.md" +HANDOFF_PATH = REPO_ROOT / "HANDOFF.md" +ACTION_BRIDGE_GUIDE_PATH = REPO_ROOT / "docs" / "n8n-approved-action-bridge.md" + + +class ReviewedActionBridgePromotionPlanTests(unittest.TestCase): + def test_plan_exists_and_is_phase_13_only(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + self.assertIn("Phase 13 Reviewed Action Bridge Promotion Plan", plan) + self.assertIn("promotion plan, not a provider implementation", plan) + self.assertIn("Reviewed Action Bridge Promotion Plan", ROADMAP_PATH.read_text(encoding="utf-8")) + self.assertIn("Reviewed Action Bridge Promotion Plan", HANDOFF_PATH.read_text(encoding="utf-8")) + + def test_plan_forbids_oauth_credentials_and_live_mutation_creep(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + for phrase in ( + "Do not implement Gmail OAuth", + "Do not implement Microsoft OAuth", + "Do not add IMAP passwords", + "Do not add Gmail, Microsoft, IMAP, SMTP, send-mail, move-mail, delete-mail, archive, or label mutation nodes", + "Do not activate the n8n workflows by default", + "Do not execute provider mutations from the local UI", + ): + self.assertIn(phrase, plan) + + def test_plan_limits_first_promotion_to_one_reviewed_action_type(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + self.assertIn("Approved action: `approve_warning`", plan) + self.assertIn("human-approved SQLite audit event", plan) + self.assertIn("Do not promote more than one provider action type", plan) + self.assertIn("Whitelist, blacklist, archive, move, and label actions require their own export contracts", plan) + + def test_plan_requires_n8n_credential_ownership_and_separate_live_bridge(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + self.assertIn("Provider credentials belong in n8n only", plan) + self.assertIn("BotFucker core must never store or receive", plan) + self.assertIn("Reviewed live bridge", plan) + self.assertIn("separate from both checked-in starters", plan) + self.assertIn("dry-run switch", plan) + + def test_plan_defines_persistent_audit_id_dedupe_before_live_mutation(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + self.assertIn("Persistent processed-audit design", plan) + self.assertIn('"audit_id": "audit-0001"', plan) + self.assertIn("Use `audit_id` as the primary idempotency key", plan) + self.assertIn("Check durable state before the provider node executes", plan) + self.assertIn("Environment variables are not acceptable for live processed-audit state", plan) + + def test_plan_requires_rollback_emergency_stop_sandbox_security_and_ops_reviews(self): + plan = PLAN_PATH.read_text(encoding="utf-8") + + for phrase in ( + "Rollback and emergency stop", + "one operator-visible switch that stops provider mutations immediately", + "Provider-specific sandbox/manual test plan", + "Security review checklist", + "Ops review checklist", + "Rex/security approval recorded", + "Gus/ops approval recorded", + ): + self.assertIn(phrase, plan) + + def test_existing_docs_link_phase_13_plan_without_claiming_live_actions_exist(self): + readme = README_PATH.read_text(encoding="utf-8") + bridge_guide = ACTION_BRIDGE_GUIDE_PATH.read_text(encoding="utf-8") + + self.assertIn("reviewed-action-bridge-promotion-plan.md", readme) + self.assertIn("reviewed-action-bridge-promotion-plan.md", bridge_guide) + for doc in (readme, bridge_guide): + self.assertIn("dry-run", doc.lower()) + self.assertIn("no OAuth", doc) + self.assertIn("no provider credentials", doc) + + +if __name__ == "__main__": + unittest.main()