First supervised live pilot: local account authorization and one-post delivery
Status (2026-10-15): The account-identity checks are implemented and verified with offline mocks. Bluesky and Mastodon each have one first-party post receipt from 2026-10-11 (docs/receipts/); there is no other verified live account and no real growth measurement yet. Do not report a completed pilot until the owner runs the commands on their own device and records an actual remote post URL. The steps below are not a substitute for official API scopes, provider approval or terms of service.
Safety model
Section titled “Safety model”- The owner signs in to the provider’s official interface and handles MFA/permission prompts. Never paste app passwords, OAuth tokens, OTPs or browser cookies into Claude Code, ChatGPT, GitHub, screenshots or a PR.
- Local
config/accounts.jsonstores public identifiers only; it is ignored by Git. Credentials must be injected via a trusted local environment or secrets manager, not committed.envfiles. Avoid shell commands with literal secrets because shell history and process listings can disclose them. doctor,showand dry-runpublishare offline.connect-check --onlineperforms explicit remote identity/authentication queries but does not post content. Bluesky uses its create-session endpoint; this is an authentication operation, not a social post.connect-checkreturningidentity: verifiedproves that the configured account identity was observed, not that all future posting scopes, rate limits or media requirements are approved. For Bluesky and Mastodon,write_permission: unverifiedis intentional. Telegram channel administrators are checked usinggetChatMember.- A first live post is always one exact post ID with an observed owner approval, global pause checked,
--live, the explicitREACHWEAVER_ALLOW_LIVE=1toggle,REACHWEAVER_ENV=prod(set only on the machine that publishes) and the identity preflight; other queued posts must remain untouched. Do not run an unattended scheduler for the first pilot.
Pilot 0: offline readiness (no secrets)
Section titled “Pilot 0: offline readiness (no secrets)”From a trusted Linux/macOS shell in the checked-out private repository:
python3 -m unittest discover -s tests -qcp config/accounts.example.json config/accounts.json# Edit only IDs/handles/server/chat identifiers in the ignored file.python3 -m reachweaver doctorpython3 -m reachweaver initIf an earlier state directory was created with broad permissions, correct it locally with chmod 700 state and chmod 600 state/reachweaver.sqlite (only after validating ownership/contents); the CLI now refuses a permissive POSIX directory or SQLite file. The default state/ is ignored by Git.
Pilot 1: Bluesky (recommended first)
Section titled “Pilot 1: Bluesky (recommended first)”A condensed, step-by-step runbook with OWNER-ONLY and SAFE-OFFLINE tags, exit codes, emergency stop, teardown and receipt recording is in BLUESKY-PILOT.md.
-
On the official Bluesky site/app, create a dedicated app password for an account you control (not your main login password). Verify the app-password feature and policy through the provider. In
config/accounts.json, sethandleto the exact account handle andenv_prefixtoREACHWEAVER_BSKY. -
Add
REACHWEAVER_BSKY_IDENTIFIERandREACHWEAVER_BSKY_APP_PASSWORDto your own trusted local process environment. To avoid shell history, use silent terminal input, e.g.:Terminal window export REACHWEAVER_BSKY_IDENTIFIER='your-handle.bsky.social'read -r -s -p 'Bluesky app password (input hidden): ' REACHWEAVER_BSKY_APP_PASSWORDprintf '\n'export REACHWEAVER_BSKY_APP_PASSWORDDo not run the password entry through an AI-operated terminal session or terminal logs. Shell environment storage remains process-accessible; prefer a keyring/secret manager for persistent deployment.
-
With the owner present, run
python3 -m reachweaver connect-check --account my-bluesky --online. It should report the expected account ID, platform,identity: verified, andwrite_permission: unverified(because an identity check is not a write test). Any mismatch blocks publishing; fix the local account mapping before proceeding. -
Prepare one real, harmless and verified text post (never the bundled placeholder/sample) and record its ID:
Terminal window python3 -m reachweaver draft --account my-bluesky --text 'A genuine project update backed by my public release notes.'python3 -m reachweaver show POST_ID # displays exact full content and destinationpython3 -m reachweaver check POST_IDpython3 -m reachweaver approve POST_ID # only after the owner reviews the exact textpython3 -m reachweaver publish --post-id POST_ID # DRY RUN; no networkRecommended for the pilot, both optional (#7, owner-approved 2026-10-09, proposal). Turn them on in your own terminal before
approve:- D6 human confirmation:
python3 -m reachweaver approval-mode --confirmation tty_hash_prefix.approveof a draft andpublish --livethen print the exact payload and itspayload sha256, need a real terminal on stdin and stdout, and accept only the first 8 characters of that hash typed on/dev/tty. A script or agent subprocess is refused. This is friction, not a security boundary. - D7 owner approval key: provide
REACHWEAVER_APPROVAL_SECRET(at least 32 characters) in your own terminal, only to the commands that need it, and runpython3 -m reachweaver approval-mode --require-keyonce. From then onapprove,scheduleandpublish --liverefuse without it (approval_key_unavailable). Every approval is then HMAC-signed, and an edited or forged database row is refused before any claim. It is a boundary only if the agent cannot read the key; no command removes the latch. Steps: owner approval key.
python3 -m reachweaver approval-modewith no options shows both settings. - D6 human confirmation:
-
Only if the owner explicitly authorizes sending this exact post to the verified account, perform the single supervised send:
Terminal window REACHWEAVER_ENV=prod REACHWEAVER_ALLOW_LIVE=1 python3 -m reachweaver publish --live --post-id POST_ID -
Confirm the actual post in the intended native account and archive the returned URL/ID in your private pilot notes. For an explicitly selected post the command returns exit code
0on a confirmed send,2when blocked before sending, or3when the remote outcome requires review. If the CLI marksneeds_review, do not blindly retry: inspect the native account first, then usereconcilewith evidence of the real outcome. Rotate/revoke the app password after experiments as appropriate.
Mastodon, Telegram and LinkedIn follow-up
Section titled “Mastodon, Telegram and LinkedIn follow-up”- Mastodon: In ignored account config specify
server: https://<trusted-instance>andhandle: your-local-usernameoryour-local-username@instance. Provision a user-authorized token through the instance’s supported process, usingread:accounts(or supportedprofile) for identity checks andwrite:statusesfor publishing; check current scope support.connect-check --account my-mastodon --onlineverifies the local account username viaGET /api/v1/accounts/verify_credentials; this cannot demonstrate that the token also haswrite:statuses. - Telegram channels: In ignored metadata specify the exact
chat_id,channel_usernamewhere public, and expectedbot_username. Create/authorize the bot and add it to the exact channel with permission to post.connect-check --account my-telegram-channel --onlinereads the bot identity, target channel and posting permissions. Private chats/groups are intentionally unsupported by this first automated connector. - LinkedIn personal profile (member): a text-only Posts API adapter exists and is mock-tested only (issue #14). In ignored metadata set
person_idand the pinnedlinkedin_version(optionallyauthorization_expires_at);connect-check --account my-linkedin --onlinecomparesGET /v2/userinfosubwithperson_idand reportswrite_permission: unverified. Optionally, add"scope_check": "introspect"and export the app’sREACHWEAVER_LINKEDIN_CLIENT_IDandREACHWEAVER_LINKEDIN_CLIENT_SECRET: read-only token introspection must then show an active member token withw_member_socialbeforeconnect-checkreportsverifiedand before every send is claimed. It stores the app client secret on the publishing host (LINKEDIN.md, section 3a). Organization pages are refused. Follow LINKEDIN.md for the developer app, scopes and token steps; the same one-post supervised pilot applies. - Optional Postiz backend: mock-tested only, no instance connected (issue #30). A Postiz acceptance is recorded as
needs_reviewuntil you check the native post and runreconcile; see POSTIZ.md. - Instagram, Threads, TikTok, YouTube and Facebook Pages: no live posting from ReachWeaver yet. The Instagram, Threads, TikTok and YouTube adapters are mock-tested only and stay disabled in the capability registry until the owner records a live receipt. Use
manual-export --platform PLATFORM --variant VARIANT.json --out NEW_DIR(creator package and checklist) and post in the native app (manual export). - LinkedIn organization pages, X and others: No direct automated posting authorization yet. X is also the only listed platform with a per-request API price; any future X adapter first needs the per-provider spend ceiling described in worker operations. Use
campaign-exportand post manually until official app consent and media/API workflows are independently implemented and tested. Never use stored login cookies or browser automation to evade restrictions.
Preconditions before enabling TikTok, YouTube or a Meta adapter
Section titled “Preconditions before enabling TikTok, YouTube or a Meta adapter”Owner decision of 2026-10-21: none of this is built now, because none of these send paths can run today (only Bluesky, Mastodon, Telegram and LinkedIn are live-capable, and every video adapter is off). Each item is a precondition of that platform’s enable change and is done, reviewed and tested in the same change, before its first supervised send:
- TikTok, pause during status polling. After the upload, ReachWeaver checks the status every 10 seconds, up to 30 times, and does not re-read the emergency pause between checks. Before enabling, the polling loop must notice
reachweaver pauseand stop (once the upload has started, a stopped send can only end inneeds_review, never in a resend). - TikTok, creator-inbox deliveries. A video delivered to the creator’s inbox is stored as
needs_reviewwith reasontiktok_creator_inbox_delivered_not_public. That makesqueue-healthcount it as an ambiguous outcome and makes the account hold block further sends to that account until you reconcile it. Decide both together; the recommendation is to keep the hold and only relabel the item inqueue-health. - YouTube, cooldown on
quotaExceeded. The adapter recognisesquotaExceeded, but no provider cooldown is recorded for it today (only a 429 records one). Before enabling, aquotaExceededanswer must also record the provider cooldown, so the next run does not try again at once. - Meta (Threads, Instagram, Facebook Pages), after the first Meta receipt. Decide how Meta error codes are handled (a documented throttle that arrives as HTTP 400 records no cooldown today; decisions D19-5 and D21-4) and whether to send
appsecret_proof(decision D21-6, which needs the app secret on the sending host). These depend on what the first supervised Meta send shows, so they are decided after it and before any wider use.
The platform pages repeat the item that concerns them: TikTok, YouTube, Facebook.
Evidence checklist before reporting success
Section titled “Evidence checklist before reporting success”Keep a private pilot record (not a public Issue): date/time with timezone; target account ID and observed native profile; original evidence/source link; exact reviewed text hash; approver; dry-run result; remote post URL/ID and native timestamp; delivery status; errors/unknown outcomes; permissions and service policy date; native analytics snapshot at predeclared age; whether other queued posts remained untouched. Avoid screenshots containing tokens. No reach improvement is established from a single test post.
Official references (recheck before activation)
Section titled “Official references (recheck before activation)”- Bluesky: https://docs.bsky.app/docs/tutorials/creating-a-post and session lexicon https://github.com/bluesky-social/atproto/blob/main/lexicons/com/atproto/server/createSession.json
- Mastodon: https://docs.joinmastodon.org/methods/accounts/ and https://docs.joinmastodon.org/api/oauth-scopes/
- Telegram Bot API: https://core.telegram.org/bots/api#getme and https://core.telegram.org/bots/api#getchatmember
