title: "Research note — #6164 Remove the frozen hosted / sync / drain surfaces from open-core" mission: issue-6164-remove-hosted-from-core issue: 6164 updated: 2026-10-13 status: grounding-complete / ESCALATION-PENDING
Research note — issue #6164
Read-only code-grounding + two discovery squads (code-grounding, related-issues), run before any edit. The branch issue-6164-remove-hosted-from-core is cut from origin/main @ fe26f8f8. Governing authorities read in full: #6164, charter ADR 2026-10-06-1 (+ amendment 2026-10-12), ADR 2026-09-26-3 (hosted-interaction opt-in), ADR 2026-10-01-2 (Mission Status Read API), CLAUDE.md, .kittify/charter/charter.md, procedure semi-auto-delivery.
0. Headline
#6164 cannot be executed as a literal "delete the listed files + sever imports". Two independent squads converged on the same conclusion: the issue's REMOVE-list is factually wrong in 3 places, incomplete in ~6 load-bearing places (the removal will not compile without them), and names 2 shared modules whose deletion forces a behavior change in a KEEP path. These are escalation-class decisions (brief §2; procedure semi-auto-delivery pause-and-escalate). They are listed in §5. The run is PAUSED for an operator ruling before any deletion.
Baseline evidence: the four named gate files are GREEN before any change — tests/architectural/test_remote_contact_owner.py tests/architectural/test_pyproject_shape.py tests/architectural/test_shared_package_boundary.py tests/cli/test_hosted_surface_visibility.py → 61 passed in 266.58s (exit 0).
1. Invariants that must survive (with authority)
amendment 2026-10-12: "Producing an event never depends on whether a drain is enabled"). The registry machinery (register__fanout_handler, fire__fanout, reset_handlers) survives as a publish/subscribe seam; the Zeitgeist handlers that currently occupy its three slots are removed, leaving the seam handler-less (every fire_* no-ops over an empty list). Drain is always off in open-core (ADR 2026-09-26-3 R-1: two gates, no env var, neither ships configured), so the fan-out is already observably inert — the seam surviving handler-less is behavior-neutral.
API** stay green. The Read API is read-only and drain-independent (ADR 2026-10-01-2 D-6: "returns the same answer whether hosted drain is on or off"); it has no edge into the REMOVE-set beyond the shared status/ reducer read models.
research command) and issue-matrix stay.
upgrade/migrations/m_4_0_0_retired_hosted_target.py, m_4_0_0rc5_hosted_endpoint_session_backfill.py.
(entry points: cli/commands/charter/{generate.py,__init__.py,_status_collectors.py}). The issue's guessed path cli/commands/charter/sync.py does not exist. Name collision only.
- Produce-drain adapter seam
status/adapters.pystays live (ADR2026-10-06-1 - **Local event / decisioning ledger (
status/,decisions/) + the Mission Status Read trackerstays visible for local providers (beads,fp);research(mission- The two retirement/backfill migrations are KEPT (consumers still need the cleanup):
charter sync/ charter-pack freshness is untouched — it ischarter.activation.sync- No CHANGELOG entry (#6164 constraint). This removes a never-shipped-configured surface.
2. Confirmed REMOVE-set (unambiguous)
Whole packages: src/specify_cli/zeitgeist_client/ (20 files), src/specify_cli/saas_client/ (6 files), src/specify_cli/auth/ (~35 files, whole tree). Hidden hosted CLI command modules: cli/commands/auth.py + 7 _auth_*.py, cli/commands/{live_work.py,moments.py,routes.py,zeitgeist.py}. Hosted posture/core: core/saas_sync_config.py (defines sync_active, is_saas_sync_enabled, SAAS_SYNC_ENV_VAR); status/zeitgeist_bridge.py. Tracker SaaS provider: tracker/saas_client.py, tracker/saas_readiness.py, tracker/saas_service.py (unnamed by the issue but backs the SaaS branches + issue_search), tracker/feature_flags.py (back-compat shim re-exporting saas_sync_config).
Residue identifiers to delete-or-excise (acceptance checkbox): SPEC_KITTY_ENABLE_SAAS_SYNC, SPEC_KITTY_SYNC_*, SPEC_KITTY_SAAS_URL / [sync].server_url, sync_active(), OWNED_SYNC_UNSUPPORTED (the last survives only as dead comments in two KEEP files).
#4204 delivery seam — already retired
GitHub #4204 was CLOSED not_planned on 2026-10-13 (today), in service of this removal. src/specify_cli/{delivery,event_journal,sync} are already gone (Aug-2026 sync retirement); no SQLite delivery-ledger file remains in src/. The live residue is in-memory only: live_work/coalesce.py (burst coalescer) and zeitgeist_client/receipts.py (zeitgeist-receipts.sqlite3, identity-only receipts) — both inside REMOVE packages.
3. REMOVE-set the issue OMITS but which is load-bearing (removal will not compile without)
+ zeitgeist_bridge; reached lazily + drain-gated from KEEP retrospective/lifecycle_events.py.
hosted_posture; reached from KEEP charter/plan interviews + cli/commands/charter/_widen.py.
status/adapters.py producer wiring.
binding wired into mission-creation via tracker/__init__.py.
saas_client+widen); the surrounding charter interview is KEEP.
src/specify_cli/live_work/(14 files) — depends onzeitgeist_client+hosted_posturesrc/specify_cli/widen/(8 files) — SaaS audience-widening; depends onsaas_client+src/specify_cli/events/runtime_moments.py— runtime moment producer (drain); reached fromsrc/specify_cli/readiness/auth.py— auth leg of the otherwise-KEEPreadiness/package.src/specify_cli/tracker/origin.py+tracker/origin_consumer.py— SaaS-first pending-originsrc/specify_cli/cli/commands/charter/_widen.py— charter-interview widen leg (imports
4. KEEP-side surgery (sever cleanly, enabler-first per brownfield discipline)
default wiring + the import-tail registration + the three hosted_posture.drain_posture gates + the runtime-moment-producer wiring; KEEP the registry/fire/reset machinery. Seam survives handler-less. (This is more than import-severing — it is the ADR KEEP guarantee made concrete.)
to gate the LOCAL .kittify/derived/<mission>/ execution-state projection (ADR 2026-09-26-3 D-2/FR-010 — this is a LOCAL feature, NOT hosted). See §5-E: ledger_posture/LedgerPosture must be RELOCATED out of the deleted hosted_posture.py, not deleted.
kill-switch, MOMENT_HANDLER_DISABLE_ENV_VARS, sync_kill_switch_active, moment_handlers_disabled_reason) once adapters no longer registers handlers; KEEP is_truthy/is_interactive/pre_review_gate_skip_reason.
_set_hosted_surfaces_hidden, reveal_hosted_surfaces, the five hosted registrars + their _ALL_COMMAND_REGISTRARS/_COMMAND_REGISTRARS entries, the issue-search registration line + map entry, the live-work hook fast-path; and the reveal_hosted_surfaces(app, sys.argv) call in src/specify_cli/__init__.py. KEEP tracker + research.
is_saas_sync_enabled/saas_sync_disabled_message + the SaaS tracker branches.
SAAS_PROVIDERS, the feature_flags re-exports, the origin-consumer registration; keep the local (beads/fp) dispatch via tracker/factory.py.
(stays, no-ops). No surgery beyond docstrings.
hosted branch of decision widen; keep the local decision-ledger core.
status/adapters.py— drop thezeitgeist_bridgeimport +ensure_zeitgeist_moment_handlersstatus/emit.py,coordination/status_transition.py— consumehosted_posture.ledger_posturecore/env.py— KEEP module; excise the sync/moment-handler residue (SPEC_KITTY_SYNC_DISABLEcli/commands/__init__.py— remove_HOSTED_SURFACE_NAMES,_hosted_surface_entries,readiness/coordinator.py,cli/commands/mission_type.py,cli/commands/tracker.py— severtracker/service.py,tracker/config.py,tracker/__init__.py— remove SaaS branches,tracker/store.py— nothing to remove (no saas/egress imports). Issue over-specified.decisions/emit.py— only docstring refs + the KEEP seam callfire_lifecycle_saas_fanoutcli/commands/decision.py— sever the lazyfrom specify_cli.saas_client import SaasClient
5. ESCALATIONS — operator decisions required BEFORE deletion
Both squads independently flagged these; all five verified firsthand by this session.
KEEP path (behavior change). Its EgressDestination.LOCAL_SUBPROCESS branch is the sole consent gate protecting the KEPT beads/fp local tracker sync (spawns the global bd/fp executable with issue data). Used by KEEP tracker/local_service.py:37,223,246,264. Polarity: at LOCAL_SUBPROCESS, absent key ⇒ REFUSE (LocalTrackerEgressRefusedError). Only the HOSTED_SERVICE branch is hosted. Decision:** (a, recommended) KEEP the file, drop only the HOSTED_SERVICE branch, keep the three local guards — non-breaking, honors "keep tracker local providers"; (b) delete it + the guards and accept losing the local consent gate (a security behavior change). The issue text implies (b) but conflates the local gate with hosted egress.
It is a general fail-closed env-var value-redaction allowlist (redact()), imported by the env-file doctor cli/commands/_env_file_doctor.py:38 (KEEP operator surface). Deleting it breaks the doctor or strips a fail-closed property (would leak secret env values into doctor output). *Recommendation: KEEP the file; only excise the hosted var names from its _PRINTABLE_VARS allowlist.*
m_4_0_0rc5 reads a stored encrypted auth session via auth.secure_storage.file_fallback.peek_stored_session (its whole job, ADR 2026-09-26-3 D-6/FR-016) + auth.config.is_retired_first_party_url + auth.server_target.resolve_server_target_or_none; m_4_0_0_retired_hosted_target imports auth.config (DEFAULT_HOSTED_SAAS_URL, is_retired_first_party_url). Making the import lazy does NOT help — the package is gone. Decision: how much of the session-peek reader + host-match helpers to vendor/localize into the migrations (or a tiny kept helper) to keep FR-016 honest for already-upgraded machines, vs. accepting that with auth/ gone there are no new sessions so the backfill becomes a best-effort no-op (inline only is_retired_first_party_url, which is a pure host match).
removing live_work/, widen/, events/runtime_moments.py, readiness/auth.py, tracker/{saas_service,origin,origin_consumer,feature_flags}.py, cli/commands/charter/_widen.py. They are purely hosted. Recommendation: include them (they are consequences of the named removals, not new scope), with operator acknowledgment per brief "do not expand scope".
judgment call).** Deleting core/hosted_posture.py wholesale breaks the LOCAL derived-projection feature. Plan: relocate ledger_posture + LedgerPosture + their repo-bool helpers to a kept module (e.g. core/ledger_posture.py), re-point status/emit.py + coordination/status_transition.py. Behavior-neutral. Flagged for awareness; proceeding as planned unless told otherwise.
- **§5-A
tracker/egress_verdict.py— SHARED; deleting removes a fail-closed consent gate on a - §5-B
core/secret_redaction.py— issue premise WRONG; it is a KEEP surface, not hosted-egress. - §5-C KEPT migrations functionally depend on the
auth/package (not just an import). - §5-D scope expansion (the §3 omitted packages). The removal will not compile without also
- **§5-E
hosted_posture.ledger_posture/LedgerPosturerelocation (required severing, not a
6. Gates — what changes (named-file discipline, NO_FULL_HEAVY_SUITES_IN_MISSION)
src/kernel/git/; empty allowlist; HTTP relay contact is not in this census. Removing hosted code only reduces git-contact sites → stays green, no edit. (Baseline green.)
auth/saas/zeitgeist refs → unaffected, no edit. (Baseline green.)
wheel trees → no edit. (Baseline green.)
hidden/reveal behavior → obsolete; delete or invert to assert those names no longer register.
with hosted edges gone it would fail to import the removed modules → delete / empty its edge list.
from specify_cli import saas_client is STILL allowed → flip it; add the removed packages to _RETIRED_PATHS + the pyproject TID251 ban list.
hosted surfaces are hidden (not visible) so removing them does not move the visible count → expected to stay green; regenerate docs/api/cli-commands.md and confirm.
test_remote_contact_owner.py— scopes only remote-contacting git argv outsidetest_shared_package_boundary.py— enforces events/tracker external-import boundary only; notest_pyproject_shape.py— removed packages are subpackages ofspecify_cli, not top-leveltests/cli/test_hosted_surface_visibility.py— directly asserts_HOSTED_SURFACE_NAMEStests/architectural/test_hosted_drain_gate.py— proves hosted edges sit behind the drain gate;tests/architectural/test_no_retired_subsystems.py— has a POSITIVE test assertingtests/docs/test_check_cli_reference_freshness.py— baseline is 252 visible commands; the
7. Packaging / deps
exclude lists that name removed files, and the pyproject TID251 ban list (add new retired modules).
(zeitgeist stdio adapter). KEEP httpx/requests (charter pack sources).
- Wheel
packageslist unchanged (subpackages ofspecify_cli). Clean the coverage/ruff/mypy - Possibly-orphaned deps to verify before dropping:
cryptography(auth secure-storage),mcp
8. Relocation (PR B) — BLOCKED
PR B relocates the REMOVE-set + its tests into spec-kitty/event-router. Attaching that repo via the add_repo tool is denied by this session's auto-mode classifier (reason "Permission Grant"); retried once. This is a background run with no live approval path. event-router's layout could not be inspected, so the landing shape for the moved code is undetermined. Operator action: grant a permission rule for mcp__claude-code-remote__add_repo (or attach event-router to the session). The core REMOVE-set (PR A's file set) is defined by #6164 and is independent of event-router's layout, so PR A's engineering can proceed once the §5 escalations are ruled on.
9. Related issues / discovery verdict
those mapped in §2–§4; stated explicitly (not an omission).
Closes: #6164.Refs: #4204 (closednot_plannedtoday, effectively closed-by this removal).- #645 (charter extraction, GA-gating) — does NOT touch any REMOVE-set file; no conflict.
- #4971, #2875 (hosted/sync) — already closed; no open sibling blocks or conflicts with this work.
domain:hostedlabel = exactly the three client packages this removes. No related surfaces beyond