thth 3.1.0__tar.gz → 3.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {thth-3.1.0 → thth-3.1.2}/PKG-INFO +2 -2
- {thth-3.1.0 → thth-3.1.2}/README.md +1 -1
- {thth-3.1.0 → thth-3.1.2}/mcp/server.py +87 -8
- {thth-3.1.0 → thth-3.1.2}/skills/thth/SKILL.md +3 -0
- {thth-3.1.0 → thth-3.1.2}/skills/thth-admin/SKILL.md +1 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_admin_http_mcp.py +3 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_ask.py +5 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_replies.py +13 -2
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_json_contract.py +4 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_leave.py +2 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_leave_providers.py +13 -5
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_read_coordination.py +3 -3
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_carryover_timeout.py +1 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_review_followups.py +1 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_stop_observation.py +1 -1
- {thth-3.1.0 → thth-3.1.2}/tests/test_v21b_approved_writes.py +3 -0
- thth-3.1.2/tests/test_v311_leave_same_user.py +68 -0
- thth-3.1.2/tests/test_v311_mastodon_retract.py +218 -0
- thth-3.1.2/tests/test_v311_morning_overdue.py +58 -0
- thth-3.1.2/tests/test_v311_morning_own_exclusion.py +93 -0
- thth-3.1.2/tests/test_v311_morning_unanswered_window.py +62 -0
- thth-3.1.2/tests/test_v311_notifications_guidance.py +51 -0
- thth-3.1.2/tests/test_v311_replies_owned.py +103 -0
- thth-3.1.2/tests/test_v312_leave_shared_token.py +126 -0
- thth-3.1.2/tests/test_v312_own_handles_stopped.py +112 -0
- thth-3.1.2/tests/test_v312_report_admin.py +269 -0
- thth-3.1.2/tests/test_v312_report_channel.py +275 -0
- thth-3.1.2/tests/test_v312_report_inbox.py +312 -0
- thth-3.1.2/tests/test_v312_report_mcp_user.py +154 -0
- thth-3.1.2/thth/VERSION +1 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/mastodon.py +51 -0
- {thth-3.1.0 → thth-3.1.2}/thth/admin_log.py +4 -2
- {thth-3.1.0 → thth-3.1.2}/thth/admin_report.py +3 -0
- {thth-3.1.0 → thth-3.1.2}/thth/cli.py +25 -3
- {thth-3.1.0 → thth-3.1.2}/thth/collect.py +30 -0
- {thth-3.1.0 → thth-3.1.2}/thth/incident.py +9 -0
- {thth-3.1.0 → thth-3.1.2}/thth/leave.py +51 -16
- {thth-3.1.0 → thth-3.1.2}/thth/morning.py +179 -21
- {thth-3.1.0 → thth-3.1.2}/thth/operations_handoff.py +28 -1
- {thth-3.1.0 → thth-3.1.2}/thth/redact.py +64 -0
- {thth-3.1.0 → thth-3.1.2}/thth/replies.py +62 -5
- thth-3.1.2/thth/report_inbox.py +898 -0
- {thth-3.1.0 → thth-3.1.2}/thth/report_isolation.py +2 -0
- {thth-3.1.0 → thth-3.1.2}/thth/report_service.py +105 -0
- {thth-3.1.0 → thth-3.1.2}/thth/retract_cli.py +7 -5
- {thth-3.1.0 → thth-3.1.2}/thth/unanswered.py +2 -1
- thth-3.1.0/thth/VERSION +0 -1
- {thth-3.1.0 → thth-3.1.2}/.gitignore +0 -0
- {thth-3.1.0 → thth-3.1.2}/LICENSE +0 -0
- {thth-3.1.0 → thth-3.1.2}/accounts.example/bluesky.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/accounts.example/mastodon.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/accounts.example/threads.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/accounts.example/x.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/bin/thth +0 -0
- {thth-3.1.0 → thth-3.1.2}/bin/thth-remote +0 -0
- {thth-3.1.0 → thth-3.1.2}/bin/thth-run +0 -0
- {thth-3.1.0 → thth-3.1.2}/pyproject.toml +0 -0
- {thth-3.1.0 → thth-3.1.2}/systemd/thth@.service +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/__init__.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/conftest.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/coordination_snapshot.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/fixtures/GraphemeBreakTest-17.0.0.txt +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/fixtures/build_injection_eval.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/fixtures/callback/worker_responses.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/fixtures/injection_eval.json +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/fixtures/umami-bile.md +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/__init__.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_oauth_server.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_push_adapter.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_redirect_server.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_search_adapter.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_threads_reader.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_threads_writes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/fake_x.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/hold_lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/hold_repo_lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/helpers/worker_probe.mjs +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_27_acceptance_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_account_name_check.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_account_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_accounts_dir.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_adapter_boundary.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_admin_log.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_admin_notifications.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_admin_repairs.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_admin_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_after_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_analytics_comparison.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_analytics_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_api_diagnostic.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_api_error_body.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_app_env_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_approval.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_approve_cmd.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_atlas_fifth_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_atlas_fourth_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_atlas_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_atlas_second_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_atlas_third_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit1_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit2_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_b_counterexamples.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_b_m1_semantics.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_b_n1_depth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_b_r1_closure.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_v201.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_audit_v23_details.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_auth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_auth_identity_required.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_behind_remote.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_bluesky_adapter.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_bluesky_secret_scrub.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_board_needs_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_board_running.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_bundle.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_charcount.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_cli_entrypoint.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_cli_help.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_cli_vm_path.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_collect.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_collect_engagements.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_collect_runs.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_collect_sent.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_comparability.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_control_chars_refused.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_conversation_paging.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_count_for.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_docs_links.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_doctor.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_doctor_all_permissions.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_doctor_next_step.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_duplicate_front_matter_keys.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_editorial_audit.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_editorial_n3_n4.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_engagements.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_external_acceptance.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_fake_api.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_form_spec.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_form_spec_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_forms.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_fresh_install.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_front_matter_injection.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_healthcheck_notifications.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_hypotheses.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_hypothesis_boundaries.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_hypothesis_units.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_impact_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_inbox_granted_scopes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_incident_notifications.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_independent_thread_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_independent_topic_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_independent_topic_review_r2.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_inflight.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_inflight_tag_body_hash.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_jst.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_lint.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_list_projections.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_llm_trial.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_llm_trial_engage.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_m2_independent.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_maintain.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_mastodon_adapter.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_mastodon_visibility.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_max_per_run.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_mcp.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_mcp_robustness.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_measured.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_media_accounts.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_media_next_steps.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_media_roundtrip.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_mismatch_diagnostics.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_n5_reply_null_element.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_n6_replies_incomplete_ledger.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_n7_doctor_error_body.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_no_limits.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_observation_provenance.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_operations_handoff.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_packaging.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_preview.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_production_mode.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_pull_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_queue.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_quiet_hours.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_r2_r4_r5.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_read_cli_bare_runtimeerror.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_reason_adoption.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_redact.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_redirect_token_leak.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_refresh.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_refresh_destination.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_rejudge_new.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_rejudge_r2_r3.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_replies_refresh.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_repo_lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_repo_sync.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_report_checkout_import.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_report_http.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_report_isolation.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_report_service.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_reverse_audit.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_case_id_cluster_m2.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_case_id_cluster_r2.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_evidence_account.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_form_spec_and_case_id.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_provenance.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_review_records.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_revoke.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_roundtrip.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_run_missing_env.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_run_record_failclosed.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_runs_collected.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_runs_read_rows.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_schedule_multiple.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_schedule_view.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_scopes_source_requested.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_search_engage_targets.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_search_media_routes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_secret_value_redact.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_secrets_grep.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_security_small_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_select.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_selfupdate.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_send.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_send_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_send_control_chars.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_send_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_send_visibility.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_sent_integrity.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_server_json.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_site.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_skill_doc.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_study_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_systemd.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_thread_publish.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_thread_read.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_threadrun.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_threads_read_permissions.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_threadshape.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_thth_root.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_token_exists.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_token_scopes_record.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_token_set.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_advice.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_models.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_review_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_shelf_observers.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topic_store.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topics_record_concurrent.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_topics_tool.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_trial_frictions.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_admin_transaction.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_approve_account.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_length.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_lock_wait.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_mastodon_scopes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_mentions.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_post_urls.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_repo_paths.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_reposts.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_study_add.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_tool_version.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_unanswered.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_unanswered_scope.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_where_arguments.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_where_filters.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v210_who_arguments.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_auth_integration.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_authflow.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_bluesky_stdin.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_doctor_binding.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_mastodon_auth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v211_x_auth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_approval_relay.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_ask_coordination.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_budget.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_budget_auth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_budget_mcp.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_deletion.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_job_completion_fault.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_leave_gate.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_leave_token_safety.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_mastodon_guide.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_mcp_compat.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_missing_token.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_server_writes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_token_parent.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v212_write_storage.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2131_send_dry_run.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2131_threads_publish_wait.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2132_send_inflight_hint.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2132_threads_create_timeout.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2132_threads_create_unknown.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_acceptance_fixes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_adts_asf.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_approval_attachments.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_attachment_kinds.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_external.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_metadata.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_metadata_uri.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_metadata_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_record.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_record_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bluesky_webm_named.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_bmff_typed_metadata.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_budget_lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_capabilities.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_graphemes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_auth_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_bmff_audio.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_flac.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_gif.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_link.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_mp3.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_ogg_opus.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_ogg_vorbis.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_poll.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_primary_size_boundary.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_quote.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_thumbnail.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_wav.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_webm_audio.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_mastodon_webm_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_cleanup.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_formats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_foundation.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_prepared.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_relay.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_relay_refusals.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_media_upload.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_receipt_deadline.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_stage810_audit.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_static_diagnostics.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_systemd_worker.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_ghost.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_gif.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_link.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_media_spoiler.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_poll.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_quote.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_quote_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_replies.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_text.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_text_spoiler.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_threads_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_transport_review.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_video_typed_metadata.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_vorbis_comment_boundary.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v213_webm_schema_boundary.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2141_draft_lint_reason.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v2141_managed_repo_umask.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v214_release_signing.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v214_x_adapter.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v214_x_auth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v214_x_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v214_x_posts_budget.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v28_tags.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v310_morning.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v310_search_timeout.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v310_watch_words.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v31_analysis.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_v31_collection.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_value_domains_shown.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_version.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_where_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_who_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_writeback_rebase.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_writeback_validate.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/tests/test_x_metrics.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/__init__.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/__main__.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/_grapheme_data.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/account_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/account_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/accounts.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/__init__.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/auth_mastodon.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/auth_threads.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/auth_x.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/base.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/bluesky.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/bluesky_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/bluesky_video.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/mastodon_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/threads.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/threads_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/x.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/adapters/x_media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/admin_notifications.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/after_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/analytics_comparison.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/analytics_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/analytics_shapes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/analytics_threads.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/api_diagnostic.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/appconfig.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/appenv.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/approval.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/approval_jobs.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/approval_relay.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/ask.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/ask_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/audioformats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/authclients.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/authflow.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/authpassword.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/bluesky_metadata.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/budget_x.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/budget_x_posts.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/bundle.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/collection_context.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/collection_status.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/core.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/deletion.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/doctor.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/ebmlaudio.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/ebmlvideo.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/engagements.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/flacformats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/forms.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/graphemes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/handoff_cursor.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/healthcheck.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/httpsafe.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/inflight.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/jst.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/leave_gate.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/lint.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/lock.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/maintain.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/managed_repo.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/measured.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media_capabilities.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media_cleanup.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media_delivery.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media_relay.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/media_uploads.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/mediaformats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/mpeg_audio.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/oauth.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/oggformats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/postid.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/queuefile.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/read_coordination.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/read_window.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/report_details.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/report_http.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/runs.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/scopes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/secrets_fs.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/select.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/selfupdate.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/sent.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/server_files.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/server_writes.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/stop_observation.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/study_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/study_report.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/systemd_gen.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/tags.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/thread_read.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/threadrun.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/threads_read_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/threadshape.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/threadthrow.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/tool_version.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/topic_advice.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/topic_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/topic_models.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/topic_store.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/topics.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/vorbisformats.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/watch_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/where_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/who_cli.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/writeback.py +0 -0
- {thth-3.1.0 → thth-3.1.2}/thth/x_metrics.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: thth
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: 承認済みの投稿だけを投げる門(Threads・Bluesky・Mastodon)。人が承認し、記録は利用者の git に残る。
|
|
5
5
|
Project-URL: Homepage, https://thth.me
|
|
6
6
|
Project-URL: Source, https://github.com/aokings/thth
|
|
@@ -125,7 +125,7 @@ timer(systemd・`thth systemd` で生成)は10分ごとに実行し、投稿
|
|
|
125
125
|
- **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
|
|
126
126
|
- **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
|
|
127
127
|
|
|
128
|
-
- **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`admin`。
|
|
128
|
+
- **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
|
|
129
129
|
- **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
|
|
130
130
|
- **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
|
|
131
131
|
- **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
|
|
@@ -101,7 +101,7 @@ timer(systemd・`thth systemd` で生成)は10分ごとに実行し、投稿
|
|
|
101
101
|
- **英語の文書**: [README.en.md](README.en.md)・[docs/usage.en.md](docs/usage.en.md)・[llms.txt](llms.txt)。
|
|
102
102
|
- **skill**: `skills/thth/SKILL.md`(wheel にも入る)。
|
|
103
103
|
|
|
104
|
-
- **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`admin`。
|
|
104
|
+
- **動くもの**(`thth --help` の全サブコマンド): `lint`・`preview`・`approve`・`account`・`revoke`・`posts`・`replies`・`measured`・`threads`・`after`・`analytics-report`・`study-report`・`study`・`unanswered`・`handoff-report`・`morning`・`serve-reports`・`approval-worker`・`topics`・`forms`・`queue`・`schedule`・`throw`・`run`・`systemd`・`board`・`collect`・`pull`・`auth`・`refresh`・`maintain`・`send`・`doctor`・`app`・`token`・`ask`・`mentions`・`profile`・`thread`・`where`・`who`・`retract`・`location`・`notifications`・`report`・`admin`。
|
|
105
105
|
- **最初の本番投稿の記録**: 2026-09-09、@aoking に疎通確認を 1 本(`17916074118445631`)。
|
|
106
106
|
- **未着手**: X・Facebook ページ・Instagram の各アダプタ。トピック検索の権限(tester には降りない)。泉のサーバ(v2-5)。
|
|
107
107
|
- **権限の制約**: tester に降りる scope は 5 つ。削除はできない。
|
|
@@ -534,6 +534,24 @@ ADMIN_TOOLS.append({'name':'thth_admin_watch_set','description':'Replace the wat
|
|
|
534
534
|
'words':{'type':'array','items':{'type':'string'}}},
|
|
535
535
|
'required':['account','words','by'],'additionalProperties':False}})
|
|
536
536
|
|
|
537
|
+
# 報告の口の実装側(設計 3.1.2 §1)。返事と閉じるは `by` 必須・変更ログに
|
|
538
|
+
# presence-only で残る。書き出し(export)は repo に書くので CLI だけ。
|
|
539
|
+
ADMIN_TOOLS += [
|
|
540
|
+
{'name':'thth_admin_reports_list','description':'List bug reports and requests from every project (status open by default, or closed/all); administrator only; read-only',
|
|
541
|
+
'inputSchema':{'type':'object','properties':{'status':{'type':'string','enum':['open','closed','all']}},
|
|
542
|
+
'required':[],'additionalProperties':False}},
|
|
543
|
+
{'name':'thth_admin_reports_show','description':'Read one report with its body, reproduction steps and replies; administrator only; read-only',
|
|
544
|
+
'inputSchema':{'type':'object','properties':{'report_id':{'type':'string'}},
|
|
545
|
+
'required':['report_id'],'additionalProperties':False}},
|
|
546
|
+
{'name':'thth_admin_reports_reply','description':'Add a reply to one report (shown to the reporter in operations_handoff tool.reports); administrator only, by required; refused if the text looks like a secret',
|
|
547
|
+
'inputSchema':{'type':'object','properties':{'report_id':{'type':'string'},'text':{'type':'string'},'by':{'type':'string'}},
|
|
548
|
+
'required':['report_id','text','by'],'additionalProperties':False}},
|
|
549
|
+
{'name':'thth_admin_reports_close','description':'Close one report with a reason (fixed, wontfix, duplicate, invalid) and the version that settled it; administrator only, by required',
|
|
550
|
+
'inputSchema':{'type':'object','properties':{'report_id':{'type':'string'},'by':{'type':'string'},
|
|
551
|
+
'reason':{'type':'string','enum':['fixed','wontfix','duplicate','invalid']},'version':{'type':'string'}},
|
|
552
|
+
'required':['report_id','reason','version','by'],'additionalProperties':False}},
|
|
553
|
+
]
|
|
554
|
+
|
|
537
555
|
SERVER_TOOLS = [
|
|
538
556
|
{"name":"thth_"+name,"description":"Scoped server "+name,
|
|
539
557
|
"inputSchema":{"type":"object","properties":{key:{"type":"string"} for key in ("account",*keys)},
|
|
@@ -566,6 +584,52 @@ del _tool
|
|
|
566
584
|
|
|
567
585
|
|
|
568
586
|
|
|
587
|
+
# 報告の口の利用者側(設計 3.1.2 §1)。**サーバ型だけに出す**——手元の stdio
|
|
588
|
+
# (pip 版)で置いても実装側には届かないので、届かない口は並べない。誰が置いたかは
|
|
589
|
+
# credential の `actor` が決める(要求の欄からは作らない)。
|
|
590
|
+
REPORT_TOOLS = [
|
|
591
|
+
{"name": "thth_report_file",
|
|
592
|
+
"description": "道具が断った・結果が期待と違った・欲しい形がある、のどれかならこれで置く"
|
|
593
|
+
"(kind は bug か request。実装側が読み、返事は thth_report_show と "
|
|
594
|
+
"operations_handoff の tool.reports に出る。秘密らしき値が含まれていたら置かない)",
|
|
595
|
+
"inputSchema": {"type": "object", "properties": {
|
|
596
|
+
"account": {"type": "string"},
|
|
597
|
+
"kind": {"type": "string", "enum": ["bug", "request"]},
|
|
598
|
+
"title": {"type": "string", "description": "1 行・120 字まで"},
|
|
599
|
+
"body": {"type": "string", "description": "8,000 字まで"},
|
|
600
|
+
"repro": {"type": "string", "description": "再現手順(任意・4,000 字まで)"}},
|
|
601
|
+
"required": ["account", "kind", "title", "body"], "additionalProperties": False}},
|
|
602
|
+
{"name": "thth_report_list",
|
|
603
|
+
"description": "自分の project の報告と、実装側の返事の数を読む(読むだけ)",
|
|
604
|
+
"inputSchema": {"type": "object", "properties": {
|
|
605
|
+
"status": {"type": "string", "enum": ["open", "closed", "all"]}},
|
|
606
|
+
"additionalProperties": False}},
|
|
607
|
+
{"name": "thth_report_show",
|
|
608
|
+
"description": "報告 1 件の本文と実装側の返事を読む(読むだけ)",
|
|
609
|
+
"inputSchema": {"type": "object", "properties": {"report_id": {"type": "string"}},
|
|
610
|
+
"required": ["report_id"], "additionalProperties": False}},
|
|
611
|
+
]
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
# **うまくいかなければ報告の口へ**(設計 3.1.2 §3.5)。利用者の道具の説明文の
|
|
615
|
+
# 末尾に 1 句。定数(TOOLS 等)は書き換えず、並べるときに足す——「売り文句」の
|
|
616
|
+
# 正本(設計 v2 §1・「自分の泉」§2)はそのまま残る。
|
|
617
|
+
REPORT_HINT = "(うまくいかなければ thth_report_file)"
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def _with_report_hint(tools):
|
|
621
|
+
return [tool if tool["name"].startswith("thth_report_")
|
|
622
|
+
else dict(tool, description=tool["description"] + REPORT_HINT) for tool in tools]
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _channel_note():
|
|
626
|
+
"""断りの応答に添える 2 つ目の text(静的な 1 行・本文も account 名も入れない)。"""
|
|
627
|
+
if APP_DIR not in sys.path:
|
|
628
|
+
sys.path.insert(0, APP_DIR)
|
|
629
|
+
from thth.report_inbox import CHANNEL_LINE
|
|
630
|
+
return {"type": "text", "text": CHANNEL_LINE}
|
|
631
|
+
|
|
632
|
+
|
|
569
633
|
def server_mode():
|
|
570
634
|
return 'THTH_REPORT_CREDENTIALS' in os.environ or 'THTH_REPORT_TOKEN' in os.environ
|
|
571
635
|
|
|
@@ -579,15 +643,16 @@ def server_tools(context):
|
|
|
579
643
|
# account/project しか対象にできない(`report_service.execute_morning()`)。
|
|
580
644
|
reports = reports + [tool for tool in TOOLS if tool['name']=='thth_morning']
|
|
581
645
|
from thth.server_writes import WRITE_OPERATIONS
|
|
582
|
-
return reports + [tool for tool in SERVER_TOOLS
|
|
583
|
-
if context.writes or tool['name'][5:] not in WRITE_OPERATIONS]
|
|
646
|
+
return _with_report_hint(reports + [tool for tool in SERVER_TOOLS
|
|
647
|
+
if context.writes or tool['name'][5:] not in WRITE_OPERATIONS]) + REPORT_TOOLS
|
|
584
648
|
|
|
585
649
|
|
|
586
650
|
def server_call(name, arguments):
|
|
587
651
|
from thth.report_service import execute_report, execute_mcp_report, ReportServiceError
|
|
588
652
|
from thth.server_writes import execute, WRITE_OPERATIONS, SAFE_ERRORS, DRAFT_REASONS
|
|
589
653
|
context=authenticated_context()
|
|
590
|
-
|
|
654
|
+
# 断りは 1 つ目の text が静的な理由、2 つ目が受け口の案内(設計 3.1.2 §3.5)。
|
|
655
|
+
failure=lambda value:{'content':[{'type':'text','text':value},_channel_note()],'isError':True}
|
|
591
656
|
if context is None: return failure('unauthorized')
|
|
592
657
|
tool=next((tool for tool in server_tools(context) if tool['name']==name),None)
|
|
593
658
|
if tool is None: return failure('unsupported_operation')
|
|
@@ -613,6 +678,12 @@ def server_call(name, arguments):
|
|
|
613
678
|
result=execute_admin_write(context,request)
|
|
614
679
|
elif operation in WRITE_OPERATIONS:
|
|
615
680
|
result=execute(context,request,via='mcp')
|
|
681
|
+
elif operation.startswith('admin_reports_'):
|
|
682
|
+
from thth.report_service import execute_admin_reports
|
|
683
|
+
result=execute_admin_reports(context,request)
|
|
684
|
+
elif operation in ('report_file','report_list','report_show'):
|
|
685
|
+
from thth.report_service import execute_user_reports
|
|
686
|
+
result=execute_user_reports(context,request)
|
|
616
687
|
elif operation=='morning':
|
|
617
688
|
from thth.report_service import execute_morning
|
|
618
689
|
result=execute_morning(context,request)
|
|
@@ -626,7 +697,11 @@ def server_call(name, arguments):
|
|
|
626
697
|
# 理由は静的な符丁の表にあるものだけ。lint の自由文は通さない。
|
|
627
698
|
detail=getattr(exc,'reason',None)
|
|
628
699
|
return failure('invalid_draft: '+(detail if detail in DRAFT_REASONS else 'validation_failed'))
|
|
629
|
-
|
|
700
|
+
from thth.report_inbox import REASONS as REPORT_REASONS
|
|
701
|
+
if str(exc)=='duplicate_report' and getattr(exc,'report_id',None):
|
|
702
|
+
# 既存の id は同じ account の報告だけ(重複の検査がそう絞っている)。
|
|
703
|
+
return failure('duplicate_report: '+exc.report_id)
|
|
704
|
+
return failure(str(exc) if str(exc) in SAFE_ERRORS or str(exc) in REPORT_REASONS or str(exc) in ('budget_change_durability_unconfirmed','budget_change_partially_recorded','budget_change_refused','watch_change_refused') else 'request_unavailable')
|
|
630
705
|
except Exception:
|
|
631
706
|
return failure('request_unavailable')
|
|
632
707
|
|
|
@@ -670,7 +745,7 @@ def call_tool(name: str, arguments: dict | None) -> dict:
|
|
|
670
745
|
if isinstance(name, str) and name.startswith("thth_admin_"):
|
|
671
746
|
context = admin_context()
|
|
672
747
|
if context is None:
|
|
673
|
-
return {"content": [{"type": "text", "text": "unauthorized"}], "isError": True}
|
|
748
|
+
return {"content": [{"type": "text", "text": "unauthorized"}, _channel_note()], "isError": True}
|
|
674
749
|
from thth.report_service import execute_report, ReportServiceError
|
|
675
750
|
try:
|
|
676
751
|
if "operation" in arguments: raise ReportServiceError("invalid_request")
|
|
@@ -679,7 +754,7 @@ def call_tool(name: str, arguments: dict | None) -> dict:
|
|
|
679
754
|
return {"content": [{"type": "text", "text": json.dumps(result, ensure_ascii=False)}]}
|
|
680
755
|
except (ValueError, TypeError, ReportServiceError) as error:
|
|
681
756
|
reason = "invalid_options" if str(error) == "invalid_options" else "invalid_request"
|
|
682
|
-
return {"content": [{"type": "text", "text": reason}], "isError": True}
|
|
757
|
+
return {"content": [{"type": "text", "text": reason}, _channel_note()], "isError": True}
|
|
683
758
|
if name == "thth_lint":
|
|
684
759
|
proc = run_cli(["lint", arguments["file"], "--json"])
|
|
685
760
|
text = proc.stdout
|
|
@@ -838,7 +913,7 @@ def call_tool(name: str, arguments: dict | None) -> dict:
|
|
|
838
913
|
proc = run_cli(args)
|
|
839
914
|
text = proc.stdout
|
|
840
915
|
else:
|
|
841
|
-
return {"content": [{"type": "text", "text": f"unknown tool: {name}"}], "isError": True}
|
|
916
|
+
return {"content": [{"type": "text", "text": f"unknown tool: {name}"}, _channel_note()], "isError": True}
|
|
842
917
|
|
|
843
918
|
if name.startswith("thth_topic_") or name in (
|
|
844
919
|
"before_you_post", "after_you_posted", "analytics_report", "operations_handoff", "study_report", "thread_read", "where_to_appear",
|
|
@@ -854,7 +929,11 @@ def call_tool(name: str, arguments: dict | None) -> dict:
|
|
|
854
929
|
is_error = proc.returncode not in (0, 1) # lint は 1 も正常な「検査結果」
|
|
855
930
|
if not text:
|
|
856
931
|
text = proc.stderr
|
|
857
|
-
|
|
932
|
+
content = [{"type": "text", "text": text}]
|
|
933
|
+
# 断りなら受け口の案内を 2 つ目に(CLI の理由行に既に載っていれば重ねない)。
|
|
934
|
+
if is_error and _channel_note()["text"] not in text:
|
|
935
|
+
content.append(_channel_note())
|
|
936
|
+
return {"content": content, "isError": is_error}
|
|
858
937
|
|
|
859
938
|
|
|
860
939
|
def _send(obj: dict) -> None:
|
|
@@ -5,6 +5,8 @@ description: 投稿する前・返信を書く前・出したあと・絡みに
|
|
|
5
5
|
|
|
6
6
|
**開始時は、まず `thth_morning`(CLI は `thth morning <project|account> --json`)を 1 回。** 昨日から何があって今日なにをすればよいかが 6 段(道具・返していないもの・昨日の自分・世間・予定・次の一手)で返る。`handoff-report --since-last-read` を包含するので、版の確認もこれ 1 回で済む。呼ぶと栞(前回読んだ時点)が進む——進めたくないときだけ `mark: false`(CLI は `--no-mark`)。取れなかった段は `{"value": null, "cannot_say": "<理由>"}`(`provider_timeout`・`budget_exhausted`・`scope_missing`・`no_watch_words`・`not_supported`・`unavailable`)で、**0 件とは別**。第 3 段の監視語は**管理者が入れる**(`thth admin watch set <account> 語 … --by <名前>`)——語が無い account は `no_watch_words` で飛ぶので、**代わりに語を選ばない**。第 5 段は候補の列挙だけで、**本文は返らない**(書くのはあなたと人の会話の側)。詳しくは `docs/使い方_毎朝の一枚_3.1.md`。
|
|
7
7
|
|
|
8
|
+
**断られたら・違和感があれば報告の口へ**: `thth_report_file`(CLI は `thth report file <account> --kind bug|request --title … --body-file … --by <名前>`)。道具が断った・結果が期待と違った・欲しい形がある、のどれかなら 1 回で置ける。返事は `thth_report_show` と `handoff-report --since-last-read` の `tool.reports` に出る。秘密らしき値が含まれていたら置かれない(伏せてから置き直す)。
|
|
9
|
+
|
|
8
10
|
段だけを読み直したいときは `thth handoff-report <account> --since-last-read --json` を読む。`tool.changed_since_last_read` が真なら、手元の `tool.notes_root_local_hint` を手掛かりに `tool.release_notes` の相対名を読んでから作業する。既読の記録は `--mark-read --by <名前>` の明示時だけ。`tool.notes_reason` が `notes_directory_unavailable` なら、このインストールには読める docs がない。空の `release_notes` を「変更なし」と解釈しない。
|
|
9
11
|
|
|
10
12
|
添付を付ける前に `tool.capabilities` を読む(媒体ごとの「出せるもの」表。`thth doctor` にも同じ表が出る。`unverified` は「非対応」ではない)。
|
|
@@ -141,6 +143,7 @@ MCP `after_you_posted`。**24h の刻みが無ければ `null`**(0 と混ぜ
|
|
|
141
143
|
|
|
142
144
|
- `thth --help` / `thth <subcommand> --help` が正本。
|
|
143
145
|
- `thth doctor <account>` が「いま投稿できる状態か」を最初から最後まで言う。
|
|
146
|
+
- 道具の不具合・欲しい形は `thth report file`(MCP `thth_report_file`)で実装側に届ける。
|
|
144
147
|
- 使い方の全文: `docs/使い方_プロジェクトのセッション向け_2026-09-09.md`。
|
|
145
148
|
|
|
146
149
|
## 運用と分析のレポート(v3系・2.5.0 から)
|
|
@@ -3,7 +3,7 @@ name: thth-admin
|
|
|
3
3
|
description: Read authenticated THTH administrator inventory, changes and token status; hand findings to a person.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
開始時は `thth admin diff --since-last-read --json` を読み、変化があった account を `thth admin account <name> --json`
|
|
6
|
+
開始時は `thth admin diff --since-last-read --json` を読み、変化があった account を `thth admin account <name> --json` で確認する。利用者からの不具合と要望は `thth admin reports list --json`(MCP `thth_admin_reports_list`・既定は開いているものだけ)で読み、`show <id>` で全文を見る。返事は `thth admin reports reply <id> --by <名前> --text-file <file>`、閉じるときは `close <id> --by <名前> --reason fixed|wontfix|duplicate|invalid --version <版>`。返事と閉じるは人(masaru)の判断を経てから打つ。写しを残すときは `thth admin reports export --to <私有の dir> --by <名前>`。**私有の置き場へ。公開 repo には置かない**(本文は利用者が書いた文。既定の行き先は無い)。週1回 `thth admin tokens --json` を確認する。
|
|
7
7
|
|
|
8
8
|
読んだ結果から承認・投稿・設定変更をしない。必要な対処は根拠・欠測理由・観測時刻とともに人に渡す。記録が無い null は正常・停止・期限なしの証拠ではない。API probe は人が明示的に必要とした場合の CLI `--probe` のみ。
|
|
9
9
|
|
|
@@ -39,7 +39,9 @@ def test_admin_all_registry_and_mcp_auth_rechecked(credentials,monkeypatch):
|
|
|
39
39
|
assert server.admin_context().scope=='admin'
|
|
40
40
|
assert {tool['name'] for tool in server.ADMIN_TOOLS}=={
|
|
41
41
|
'thth_admin_inventory','thth_admin_account','thth_admin_diff','thth_admin_log',
|
|
42
|
-
'thth_admin_tokens','thth_admin_release','thth_admin_budget_set','thth_admin_watch_set'
|
|
42
|
+
'thth_admin_tokens','thth_admin_release','thth_admin_budget_set','thth_admin_watch_set',
|
|
43
|
+
# 報告の口の実装側(設計 3.1.2 §1)。
|
|
44
|
+
'thth_admin_reports_list','thth_admin_reports_show','thth_admin_reports_reply','thth_admin_reports_close'}
|
|
43
45
|
result=server.call_tool('thth_admin_inventory',{})
|
|
44
46
|
assert set(json.loads(result['content'][0]['text'])['by_account'])=={'first','second'}
|
|
45
47
|
assert server.call_tool('thth_admin_diff',{'mark_read':True})['isError']
|
|
@@ -780,7 +780,11 @@ def test_本物の台帳でrc0でcannot_sayが出る(tmp_path):
|
|
|
780
780
|
answer = json.loads(proc.stdout)
|
|
781
781
|
assert answer["provenance"]["source"] == "local"
|
|
782
782
|
assert answer["cannot_say"], "手元の水で言い切ってしまっている"
|
|
783
|
-
|
|
783
|
+
# 本物の水は日々増えるので、中央値の有無そのものは固定できない(2026-09-23:
|
|
784
|
+
# kopicha の 24h の観測が n=24 になり、中央値 0 が正直な答えになった)。見るのは
|
|
785
|
+
# 規約 2「n が min_n に満たなければ中央値を言わない」が本物の水でも守られること。
|
|
786
|
+
branches = answer["expected"]["branches_24h"]
|
|
787
|
+
assert (branches["median"] is None) == (branches["n"] < ask_mod.DEFAULT_MIN_N)
|
|
784
788
|
|
|
785
789
|
|
|
786
790
|
def test_P3_6_THTH_NOWで時刻を固定できる(account, thth_root):
|
|
@@ -24,6 +24,14 @@ def _replies_path(account, post_id: str = "POST1") -> str:
|
|
|
24
24
|
return os.path.join(account["repo_dir"], "data", "sns", "replies", f"{post_id}.ndjson")
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _own_post(account, post_id: str = "POST1") -> None:
|
|
28
|
+
"""その account が出した記録(`sent/`)を置く。`thth replies` は置き場を共有する
|
|
29
|
+
他 account の返信を返さないので(3.1.1)、CLI の試験は所有の記録を先に置く。"""
|
|
30
|
+
from thth import accounts, approval, sent
|
|
31
|
+
sent.write(accounts.state_dir_for(account["name"]), post_id=post_id, text="本文",
|
|
32
|
+
body_hash=approval.compute_body_hash("本文"), sent_at="2026-09-10T09:00:00+09:00")
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
def test_返信を読めること(isolated_account):
|
|
28
36
|
"""`kind: reply` の行だけが `replies` に入り、`kind: fetch` の行は `fetches` に入る。"""
|
|
29
37
|
_write_ndjson(_replies_path(isolated_account), [
|
|
@@ -129,14 +137,16 @@ def test_CLIがjsonでloadと同じものを返す(isolated_account, capsys):
|
|
|
129
137
|
"post_id": "POST1", "collected_at": "2026-09-10T19:00:00+09:00"},
|
|
130
138
|
])
|
|
131
139
|
|
|
140
|
+
_own_post(isolated_account)
|
|
141
|
+
|
|
132
142
|
args = argparse.Namespace(account=isolated_account["name"], post=None, json=True)
|
|
133
143
|
rc = cli_mod.cmd_replies(args)
|
|
134
144
|
captured = capsys.readouterr()
|
|
135
145
|
|
|
136
146
|
assert rc == 0
|
|
137
147
|
printed = json.loads(captured.out)
|
|
138
|
-
expected = replies_mod.load(isolated_account["name"])
|
|
139
|
-
assert printed == expected
|
|
148
|
+
expected = replies_mod.load(isolated_account["name"], owned_only=True)
|
|
149
|
+
assert printed == expected and [r["id"] for r in printed["replies"]] == ["R1"]
|
|
140
150
|
|
|
141
151
|
|
|
142
152
|
def test_CLIの人向け出力は身内に印をつける(isolated_account_factory, capsys):
|
|
@@ -149,6 +159,7 @@ def test_CLIの人向け出力は身内に印をつける(isolated_account_facto
|
|
|
149
159
|
{"kind": "reply", "id": "R2", "text": "よその返信", "username": "だれか",
|
|
150
160
|
"post_id": "POST1", "collected_at": "2026-09-10T19:00:00+09:00"},
|
|
151
161
|
])
|
|
162
|
+
_own_post(account)
|
|
152
163
|
|
|
153
164
|
args = argparse.Namespace(account=account["name"], post=None, json=False)
|
|
154
165
|
rc = cli_mod.cmd_replies(args)
|
|
@@ -25,7 +25,10 @@ def json_parsers():
|
|
|
25
25
|
# must acquire a table row, rather than quietly escaping the contract.
|
|
26
26
|
COMMANDS = '''lint|preview|approve|account|revoke|posts|replies|measured|threads|study-report|analytics-report|after|topics|forms|queue|schedule|throw|handoff-report|board|pull|maintain|doctor|app show|admin inventory|admin account|admin log|admin tokens|admin timers|admin release|admin diff|ask before-you-post|mentions|profile|thread|where|who|retract|location|topics suggest|topics observe|topics record-decision|topics decision|topics observation|topics retract|topics unretract|topics profile|topics adopt-reason|topics adoptions|topics record-vocabulary|topics vocabulary|topics record-review|topics review|topics record-form-spec|topics form-spec|topics form-check|topics improvements|topics impact|topics record-hypothesis|topics hypotheses'''.split('|') + ['account add', 'account migrate', 'unanswered', 'study add', 'admin deletion sync', 'admin budget', 'admin media cleanup-retry', 'admin media gc',
|
|
27
27
|
# 3.1.0: 毎朝の一枚と監視語(設計 3.1.0 §1・§3)。
|
|
28
|
-
'morning', 'admin watch set', 'admin watch show'
|
|
28
|
+
'morning', 'admin watch set', 'admin watch show',
|
|
29
|
+
# 3.1.2: 報告の口(設計 3.1.2 §1)。
|
|
30
|
+
'report file', 'report list', 'report show', 'admin reports list', 'admin reports show',
|
|
31
|
+
'admin reports reply', 'admin reports close', 'admin reports export']
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def test_json_table_covers_every_parser():
|
|
@@ -149,7 +149,8 @@ def test_shared_manual_grant_does_not_tell_person_to_revoke_other_account(env,wo
|
|
|
149
149
|
a=env['root']/'secrets/alpha.json';b=env['root']/'secrets/beta.json';b.write_bytes(a.read_bytes())
|
|
150
150
|
assert leave.command(SimpleNamespace(name='alpha',by='operator',json=False))==0
|
|
151
151
|
result=leave.read('alpha')
|
|
152
|
-
|
|
152
|
+
# 3.1.2 件 6・裁定 09-23: 値だけの共有(別ファイル)なら自分の file は消し、相手の file は残す。
|
|
153
|
+
assert result['remote']=='unconfirmed_shared' and not a.exists() and b.exists()
|
|
153
154
|
out=capsys.readouterr().out
|
|
154
155
|
assert '切り分け' in out and '本人が解除' not in out
|
|
155
156
|
|
|
@@ -84,8 +84,11 @@ def test_shared_mastodon_grant_refuses_remote_revoke_but_shared_client_alone_doe
|
|
|
84
84
|
cfg,tokenpath=configure(env,provider,monkeypatch,'mastodon');old=tokenpath.read_bytes()
|
|
85
85
|
other=env['root']/'accounts/beta.json';b=json.loads(other.read_text());b.update(media='mastodon',instance=provider['base']);other.write_text(json.dumps(b))
|
|
86
86
|
(env['root']/'secrets/beta.json').write_bytes(old)
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
# 3.1.2 件 6: 共有の接続は遠隔で失効させず(calls==[])、止まらずに退出を終える。
|
|
88
|
+
# 裁定 09-23: 値だけの共有(別ファイル)なら自分の token file は消す。相手の file は残る。
|
|
89
|
+
result=leave.run('alpha',by='operator')
|
|
90
|
+
assert result['phase']=='completed' and result['remote']=='unconfirmed_shared' and 'token_shared' not in result['preserved']
|
|
91
|
+
assert provider['calls']==[] and not tokenpath.exists() and (env['root']/'secrets/beta.json').read_bytes()==old
|
|
89
92
|
|
|
90
93
|
|
|
91
94
|
def test_b_credential_commit_before_revoke_is_rechecked_after_inventory(env,provider,monkeypatch):
|
|
@@ -99,8 +102,11 @@ def test_b_credential_commit_before_revoke_is_rechecked_after_inventory(env,prov
|
|
|
99
102
|
authflow.commit_manual('beta',cfg_b,token_a,snapshot=snapshot,session=None,by='operator')
|
|
100
103
|
return {'status':'revoked'}
|
|
101
104
|
monkeypatch.setattr(approval_relay,'signed_request',worker)
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
# 3.1.2 件 6: 共有の接続は遠隔で失効させず(calls==[])、止まらずに退出を終える。
|
|
106
|
+
# 裁定 09-23: 値だけの共有(別ファイル)なら自分の token file は消す。相手の file は残る。
|
|
107
|
+
result=leave.run('alpha',by='operator')
|
|
108
|
+
assert result['phase']=='completed' and result['remote']=='unconfirmed_shared' and 'token_shared' not in result['preserved']
|
|
109
|
+
assert provider['calls']==[] and not tokenpath.exists()
|
|
104
110
|
assert json.loads((env['root']/'secrets/beta.json').read_text())==token_a
|
|
105
111
|
|
|
106
112
|
|
|
@@ -166,5 +172,7 @@ def test_b_exchange_to_commit_holds_registry_lease_and_late_shared_grant_vetoes_
|
|
|
166
172
|
assert a.is_alive() and provider['calls']==[]
|
|
167
173
|
release.set();a.join(4);b.join(4)
|
|
168
174
|
assert not a.is_alive() and not b.is_alive() and results==[0]
|
|
169
|
-
|
|
175
|
+
# 3.1.2 件 6: 共有の接続は遠隔で失効させず(calls==[])、止まらずに退出を終える。
|
|
176
|
+
assert leave_errors==[] and provider['calls']==[]
|
|
177
|
+
assert leave.read('alpha')['phase']=='completed' and leave.read('alpha')['remote']=='unconfirmed_shared'
|
|
170
178
|
assert accounts.load_token(accounts.load_account('beta'))['access_token']==value['access_token']
|
|
@@ -61,7 +61,7 @@ def test_cli_reads_do_not_wait_or_call_core_when_leave_holds_exclusive(env,conte
|
|
|
61
61
|
start=time.monotonic();rc=cli.main(command);elapsed=time.monotonic()-start
|
|
62
62
|
captured=capsys.readouterr();assert rc==2 and elapsed<1
|
|
63
63
|
assert json.loads(captured.out)=={'error':'account_leaving','cannot_say':['account_leaving']}
|
|
64
|
-
assert captured.err.strip()=='account_leaving'
|
|
64
|
+
assert captured.err.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
65
65
|
|
|
66
66
|
|
|
67
67
|
def test_dry_send_input_then_nonwaiting_lease(env,tmp_path,capsys):
|
|
@@ -69,7 +69,7 @@ def test_dry_send_input_then_nonwaiting_lease(env,tmp_path,capsys):
|
|
|
69
69
|
with exclusive():
|
|
70
70
|
start=time.monotonic();rc=cli.main(['send','alpha','--text-file',str(text)])
|
|
71
71
|
assert time.monotonic()-start<1 and rc==2
|
|
72
|
-
assert capsys.readouterr().err.strip()=='account_leaving'
|
|
72
|
+
assert capsys.readouterr().err.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
73
73
|
assert not (env['root']/'state/alpha/sent').exists()
|
|
74
74
|
|
|
75
75
|
|
|
@@ -145,7 +145,7 @@ def test_real_cli_child_rejects_ex_under_deadline(env,content):
|
|
|
145
145
|
result=subprocess.run([sys.executable,'-c',"from thth.cli import main;raise SystemExit(main(['queue','alpha','--json']))"],capture_output=True,text=True,timeout=2)
|
|
146
146
|
except subprocess.TimeoutExpired:pytest.fail('reader waited for the exit drain')
|
|
147
147
|
assert result.returncode==2 and json.loads(result.stdout)['cannot_say']==['account_leaving']
|
|
148
|
-
assert result.stderr.strip()=='account_leaving'
|
|
148
|
+
assert result.stderr.strip()=='account_leaving\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
149
149
|
|
|
150
150
|
|
|
151
151
|
def test_report_holds_lease_through_calculation_and_release(env,monkeypatch):
|
|
@@ -51,7 +51,7 @@ raise SystemExit(cli.main(json.loads(sys.argv[1])))
|
|
|
51
51
|
# cannot trap this pytest process before an elapsed-time assertion.
|
|
52
52
|
result = subprocess.run([sys.executable, '-c', code, json.dumps(command), str(marker)],
|
|
53
53
|
capture_output=True, text=True, timeout=3)
|
|
54
|
-
assert result.returncode == 2 and result.stderr.strip() == 'account_leaving'
|
|
54
|
+
assert result.returncode == 2 and result.stderr.strip() == 'account_leaving\n' + __import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
55
55
|
if operation == 'replies':
|
|
56
56
|
assert json.loads(result.stdout) == {'error': 'account_leaving', 'cannot_say': ['account_leaving']}
|
|
57
57
|
assert not marker.exists()
|
|
@@ -14,7 +14,7 @@ def test_run_invalid_name_before_any_storage_or_effect(monkeypatch,capsys,name):
|
|
|
14
14
|
monkeypatch.setattr('thth.incident.notify',forbidden)
|
|
15
15
|
assert cli.main(['run',name])==2
|
|
16
16
|
captured=capsys.readouterr();value=json.loads(captured.out)
|
|
17
|
-
assert captured.err.strip()=='invalid_account_name'
|
|
17
|
+
assert captured.err.strip()=='invalid_account_name\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
18
18
|
assert value['error']==value['record_unavailable']=='invalid_account_name'
|
|
19
19
|
assert value['account'] is None and value['runs_recorded'] is False
|
|
20
20
|
assert '使えない字' in value['message'] and 'account_stopped' not in captured.out
|
|
@@ -30,7 +30,7 @@ def test_run_reason_and_no_side_effect(env,monkeypatch,capsys,state,expected):
|
|
|
30
30
|
monkeypatch.setattr('thth.healthcheck.notify',lambda *a,**kw:pytest.fail('healthcheck notification'))
|
|
31
31
|
monkeypatch.setattr('thth.incident.notify',lambda *a,**kw:pytest.fail('incident notification'))
|
|
32
32
|
assert cli.main(['run','alpha'])==2
|
|
33
|
-
shown=capsys.readouterr();assert shown.err.strip()==expected
|
|
33
|
+
shown=capsys.readouterr();assert shown.err.strip()==expected+'\n'+__import__('thth.report_inbox').report_inbox.CHANNEL_LINE # 3.1.2 §3.5: 断りの理由行の後ろに受け口の案内 1 行
|
|
34
34
|
value=json.loads(shown.out)
|
|
35
35
|
assert value=={'account':'alpha','mode':'production','action':'skip','status':'error','error':expected,
|
|
36
36
|
'runs_recorded':False,'record_unavailable':expected}
|
|
@@ -645,6 +645,9 @@ def test_DELETEが飛ぶ経路はretract_cliのdelete_postだけ():
|
|
|
645
645
|
src = inspect.getsource(retract_cli)
|
|
646
646
|
assert src.count(".delete_post(") == 1 # 呼び出しは 1 か所(docstring の言及は除く)
|
|
647
647
|
assert inspect.getsource(threads_mod).count('method="DELETE"') == 1
|
|
648
|
+
# Mastodon の DELETE(3.1.1)も `delete_post` の中の 1 か所だけ。
|
|
649
|
+
assert inspect.getsource(mastodon).count('"DELETE"') == 1
|
|
650
|
+
assert '"DELETE"' in inspect.getsource(mastodon.MastodonAdapter.delete_post)
|
|
648
651
|
|
|
649
652
|
|
|
650
653
|
def test_Threadsのdelete_postは数字以外のidを断る():
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""3.1.1 件 1: 退出の「共有」判定は token の中身(値・ファイル)に限る。
|
|
2
|
+
|
|
3
|
+
同じ instance の同じ利用者(user_id 同一)の別台帳は、Mastodon/X では共有と見ない。
|
|
4
|
+
失効は渡した token の値だけに効くので、相手の token file・台帳・停止状態に触らずに
|
|
5
|
+
退出が completed まで通ることを、loopback の fake 媒体で固定する。
|
|
6
|
+
"""
|
|
7
|
+
import json
|
|
8
|
+
import pytest
|
|
9
|
+
from thth import accounts,leave,leave_gate,approval_relay
|
|
10
|
+
from tests.test_v212_server_writes import env
|
|
11
|
+
from tests.test_v212_leave_providers import provider,configure
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def same_user_beta(env,provider,tokenpath,media,*,same_bytes=False):
|
|
15
|
+
"""beta を alpha と同じ媒体・同じ instance・同じ user_id にする。token の値は既定で別。"""
|
|
16
|
+
user='synthetic-user-'+media
|
|
17
|
+
a=json.loads(tokenpath.read_text());a['user_id']=user;tokenpath.write_text(json.dumps(a))
|
|
18
|
+
path=env['root']/'accounts/beta.json';b=json.loads(path.read_text());b['media']=media
|
|
19
|
+
if media=='mastodon':b['instance']=provider['base']
|
|
20
|
+
path.write_text(json.dumps(b))
|
|
21
|
+
beta_token=env['root']/'secrets/beta.json'
|
|
22
|
+
if same_bytes:beta_token.write_bytes(tokenpath.read_bytes())
|
|
23
|
+
else:
|
|
24
|
+
t=json.loads(beta_token.read_text());t['user_id']=user;beta_token.write_text(json.dumps(t))
|
|
25
|
+
assert t['access_token']!=a['access_token']
|
|
26
|
+
return beta_token,path
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize('media',['mastodon','x'])
|
|
30
|
+
def test_same_user_different_token_leave_completes_and_leaves_other_account_untouched(env,provider,monkeypatch,media):
|
|
31
|
+
_,tokenpath=configure(env,provider,monkeypatch,media)
|
|
32
|
+
beta_token,beta_ledger=same_user_beta(env,provider,tokenpath,media)
|
|
33
|
+
other_token=beta_token.read_bytes();other_ledger=beta_ledger.read_bytes()
|
|
34
|
+
other_access=json.loads(other_token)['access_token']
|
|
35
|
+
result=leave.run('alpha',by='operator')
|
|
36
|
+
assert result['phase']=='completed' and result['remote']=='confirmed'
|
|
37
|
+
assert 'token_shared' not in result['preserved'] and not tokenpath.exists()
|
|
38
|
+
# 媒体の失効に来たのは自分の token の値だけ。相手の値は 1 度も送られていない。
|
|
39
|
+
sent=[form['token'][0] for _,form,_ in provider['calls']]
|
|
40
|
+
assert provider['access'] in sent and other_access not in sent
|
|
41
|
+
assert set(sent)<={provider['access'],provider['refresh']}
|
|
42
|
+
# 相手の token file と台帳は byte 一致で残り、停止もしていない。
|
|
43
|
+
assert beta_token.read_bytes()==other_token and beta_ledger.read_bytes()==other_ledger
|
|
44
|
+
assert not leave_gate.stopped('beta') and leave.read('beta') is None
|
|
45
|
+
assert accounts.load_token(accounts.load_account('beta'))['access_token']==other_access
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_same_token_bytes_do_not_revoke_remotely(env,provider,monkeypatch):
|
|
49
|
+
_,tokenpath=configure(env,provider,monkeypatch,'mastodon')
|
|
50
|
+
beta_token,_=same_user_beta(env,provider,tokenpath,'mastodon',same_bytes=True)
|
|
51
|
+
old=tokenpath.read_bytes()
|
|
52
|
+
# 3.1.2 件 6: 失効は投げない(他方も死ぬ)。止まらずに completed まで進む
|
|
53
|
+
# (完了までの形は tests/test_v312_leave_shared_token.py)。
|
|
54
|
+
result=leave.run('alpha',by='operator')
|
|
55
|
+
assert result['phase']=='completed' and result['remote']=='unconfirmed_shared'
|
|
56
|
+
# 裁定 09-23: 値だけの共有(別ファイル)なら自分の token file は消す。相手の file は残る。
|
|
57
|
+
assert provider['calls']==[] and not tokenpath.exists() and beta_token.read_bytes()==old
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_threads_same_user_is_still_token_shared_and_preserved(env,monkeypatch):
|
|
61
|
+
monkeypatch.setattr(approval_relay,'signed_request',lambda *a:{'status':'revoked'})
|
|
62
|
+
paths=[env['root']/'secrets'/(n+'.json') for n in ('alpha','beta')]
|
|
63
|
+
for p in paths:
|
|
64
|
+
t=json.loads(p.read_text());t['user_id']='synthetic-threads-user';p.write_text(json.dumps(t))
|
|
65
|
+
old=[p.read_bytes() for p in paths]
|
|
66
|
+
result=leave.run('alpha',by='operator')
|
|
67
|
+
assert result['phase']=='completed' and result['preserved']==['token_shared'] and result['remote']=='unconfirmed_shared'
|
|
68
|
+
assert [p.read_bytes() for p in paths]==old and not leave_gate.stopped('beta')
|