ReachWeaver 0.2.0 release notes
Released on 2026-10-11.
- Clone-to-safe-preview onboarding and a versioned release gate (issue #27 slice). docs/QUICKSTART.md takes a new user from clone to a one-post dry-run preview and two manual export packages with no credentials. docs/TROUBLESHOOTING.md maps the exact error messages to fixes. docs/SECURITY-CHECKLIST.md covers the first live send, each release and incidents.
reachweaver/onboarding.pyaudits both research registries: every platform without a live adapter has a manual alternative (manual_export,draft_export,native_manual) or an explicithuman_only/unsupportedmarker, and Reddit and Hacker News stay human-only.scripts/release_gate.py(make release-gate) checks version consistency, this changelog, required documents, README coverage of every CLI subcommand, relative links, the skill bundle, the blocker table and a passing offline test suite.tests/test_onboarding.pyruns the quickstart commands from the document against a fresh copy of the repository with an empty environment and sockets blocked.examples/manual-variant.threads.jsonis the quickstart’smanual-exportinput. - Unsupported feature-claim review flags (issue #10 slice).
claims-checkandcampaign-previewflag a draft that claims a feature (“adds”, “supports”, “integrates with”, “now has” and similar) when the claimed feature’s key words are absent from the evidence the variant cites. The result is aneeds_human_reviewwarning withcategory: featureandmissing_terms; staging and export are not blocked. See docs/CLAIM-GROUNDING.md. baseline-eval(issue #16 slice). A read-only comparison of personalized slots, variant campaigns or saved readouts against calendar-plan’s generic, non-personalized baseline over an explicit--since/--untilperiod, using seeded percentile-bootstrap intervals, permutation p-values and Holm–Bonferroni across every comparison in a run. Results are labelled explore/exploit and split into correlational and randomized lists, and small samples reportinsufficient_datainstead of 0. The strict sign-test gate is unchanged. See docs/TIMING-EVALUATION.md.media-storyboard(issue #18) turns one campaign brief into an offline scaffold: a vertical-video storyboard (storyboard.jsonandstoryboard.md), an SRT subtitle skeleton with valid timing (plus WebVTT for WebVTT-only targets), amedia-checkmanifest with a LinkedIn image draft and the vertical video, and an image spec per platform built from the sourced constraint rows. Scene copy, captions, subtitle text and alt text are{{write: ...}}placeholders andrights_confirmedstartsfalse, somedia-checkblocks until a person writes them. No media or copy is generated and no network is used. See docs/MEDIA-ASSETS.md.- Crop and UI-overlay safe-area checks (
media-storyboard --check). Text boxes outside a target’s safe area are errors and focal boxes are warnings, for a centered-crop (cover) or letterbox (contain) fit. The built-in TikTok, Instagram Reels and YouTube Shorts zones are labelled unverified;--safe-zones FILEaccepts owner-sourced numbers. Generation stays opt-in: local tools cost nothing through ReachWeaver, and a paid provider bills the user directly. ReachWeaver has no generation integration. - Offline security regression suite (issue #26 slice).
tests/test_security_regressions.pycovers poisoned feeds, injected drafts and comments, fake-token and personal-data leaks across reports and exports, and dependency-compromise guards. Open findings are tracked as expected failures and listed in docs/THREAT-MODEL.md. - Work merged into
mainunder version 0.2.0 before this changelog existed, as recorded in the README and architecture: source-bound campaigns (campaign-check,campaign-stage,campaign-export); exact-payload approval binding (#7); rolling per-account live budgets andqueue-health(#11); the fail-closed capability registry (#6) and X/Reddit/Pinterest go/no-go decisions (#24); canonical schemas andschema-export(#4); credential hygiene audit and threat model (#5); Bluesky and Mastodon adapter hardening (#12, #13); LinkedIn member text adapter (#14); UTM links, attribution and public-countermetrics-fetch(#15, #25); timing evaluation and preregistered growth experiments (#16); engagement triage drafts (#17); offlinemedia-check(#18);manual-exportfor Threads, Instagram, Facebook Pages, YouTube and TikTok (#19-#23);sources-scanandsignal-brief(#9);claims-checkandcampaign-preview(#10);landing-check(#25);health,dead-letter, opt-out list, caps file and privacy lifecycle (#26); optional Postiz backend (#30); field pilot protocol andpilot-report(#27); the worldwide distribution planner; full mypy coverage (#8).
Security
Section titled “Security”Account and credential hardening (issue #5, owner-approved 2026-10-09). Offline and mock-tested only (tests/test_hardening.py); not exercised on a real host or account.
load_accountsalso refuses an account whoseenv_prefixderives or overlaps a credential no account owns (REACHWEAVER_GITHUB_TOKEN, read bysources-scan --online);credentials-auditreports it asreserved_credential_variable. Example: a Mastodon prefixREACHWEAVER_GITHUBwould otherwise read the GitHub token.- Breaking:
publish --livealso requiresREACHWEAVER_ENV=prod, in addition to--liveandREACHWEAVER_ALLOW_LIVE=1, for native adapters and the Postiz backend.policy.require_live_gateruns before any account is loaded, any credential is read or any request is made. Set the marker only on the production host.doctor,health,privacy-status,credentials-auditand the dry run showREACHWEAVER_ENVasdev,staging,prod,unsetorunrecognized; other values are never echoed. - Breaking: overlapping
env_prefixvalues are refused.load_accountsfails closed, naming the two account ids, when two accounts share anenv_prefixor when one account’s credential variable name equals or begins another’s, over every adapter suffix and the Postiz_POSTIZ_API_KEYsuffix (accounts.env_prefix_conflicts). Configs that shared a prefix, including several Postiz accounts sharing one key variable, must give each account its own prefix. - systemd
LoadCredential=support. One accessor,accounts.credential/require_credential, now serves every credential read (publishers, connections, LinkedIn, Postiz, metrics fetch, GitHub sources, health, audit). With$CREDENTIALS_DIRECTORYset it reads only$CREDENTIALS_DIRECTORY/<NAME>and never the environment: absolute path, directory not group/world-writable,O_NOFOLLOWregular file, at most 64 KiB, UTF-8, one trailing newline stripped. Errors name the variable only. File values are redacted byprivacy.redactlike secret environment variables. Without the directory, environment variables work exactly as before. credentials-auditreads through the same accessor and addscredential_source,runtime_environmentand the findingsoverlapping_env_prefix,unsafe_credential_file,unsafe_credentials_directoryandenv_credential_shadowed.healthreportscredential_sourceper account and globally, and acredential_unreadableblocked reason..claude/settings.jsondenies Claude Code Bash commands that runapproveorpublish --liveor containREACHWEAVER_ALLOW_LIVE=1orREACHWEAVER_ENV=prod(a harness rule, not a ReachWeaver control).- The inert systemd example adds
LimitCORE=0,UMask=0077and commentedLoadCredential=/ production-onlyREACHWEAVER_ENVlines. Account onboarding, the threat model and the security policy are updated; the security policy adds a private e-mail fallback for vulnerability reports.
media-checksubtitle URL warnings show only scheme, host and path (?…marks a hidden query or fragment); a translated cue’s?token=query string oruser:password@no longer reaches the report.- Release gate: human-only rows can no longer drop their
distribution_id. The human-only ⇒blockedrule ran only when a scorecard row haddistribution_id, so deleting it from thehacker_newsrow let that row claimoffline_mock_tested. A row whoseidorcapability_idnames a human-only distribution-registry entry must now carry that entry asdistribution_idand beblocked(scripts/release_gate.py;ReleaseReadinessScorecardintests/test_onboarding.py). media-checktranslated subtitles. A URL that a translated cue adds and its source cue lacks is now a warning (a link injected during translation is a phishing risk). Digit runs are normalised as text instead ofstr(int(run)), which raisedValueErrorpast Python’s 4300-digit limit; leading zeros are ignored and long runs are shortened in findings. An SRT index such as²or①(accepted bystr.isdigit(), rejected byint()) crashed the check and is now a “numeric index” error, and an over-long timestamp hour field is a malformed timestamp instead of anint()/float()overflow. Locale tags compare case-insensitively (RFC 5646), so anen-ustrack is the source of anen-USasset andtarget_localesmatching ignores case. See docs/MEDIA-ASSETS.md.calendar-plan --audience-activityDST fall-back collision. When a fall-back in the planner zone mapped two activity hours to the same local slot (UTC CSV,as_of2026-10-21, Europe/Rome: Sunday 00:00 and 01:00 UTC are both 02:00), the lower-ranked hour’s provenance overwrote the top hour’s and one top hour was lost.audience.derive_slotsnow keeps the higher-ranked hour, warns with the dropped hour, and fills from the next-ranked hour;calendar.propose_calendaralso keeps the first provenance per window. See docs/CALENDAR-PLANNER.md.- Similarity tokenizer and zero-width spaces in Thai, Lao, Khmer and Myanmar. Dropping every format character also removed U+200B where these scripts use it as the word break, so a one-word edit in ZWSP-segmented Thai scored 0.00 instead of 0.40. U+200B next to a letter of those scripts is now a space; elsewhere it, the joiners, word joiner, BOM, soft hyphen and bidi controls are still dropped, so the zero-width evasion tests stay green. See docs/CLAIM-GROUNDING.md.
- Markdown table escaping.
media-storyboardput the owner’s--safe-zonessource URL (and campaign ids) into its Markdown tables raw, so a|, backtick or newline could add cells or rows; these cells are now escaped.report --format markdowncells now also escape backslash, backtick,[ ] ( ) * _(CommonMark backslash escapes) besides pipes and HTML, as defence in depth (report_format.markdown_cell). media-checknow rejects SRT or WebVTT subtitle files that still contain an unresolved placeholder or TODO.reachweaver.__version__said0.1.0whilepyproject.tomlsaid0.2.0. Both now say0.2.0, and the release gate fails on any mismatch.- The README five-minute setup scheduled a post for a future time and then ran
publish --post-id, which refuses posts that are not yet due. The preview now runs beforeschedule.
Not done
Section titled “Not done”- No owner account has been connected and no live end-to-end send has been run. The live adapters (Bluesky, Mastodon, Telegram, LinkedIn member) are mock-tested only.
- No field pilot has been run, so there are no pilot results (issue #27 stays open for the consented pilot and its report).
