Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/architecture/option-b-completion-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ limit.

## Phase 8 — Compatibility removal and final cutover

PR: #330. Status: partial and intentionally last.
PR: #330. Status: complete; final stacked phase.

Purpose: delete the legacy architecture after every supported path uses the Option B contracts.

Expand Down
44 changes: 44 additions & 0 deletions docs/architecture/phase-8-compatibility-removal-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Phase 8 implementation plan: compatibility removal

**Status:** Complete

**Goal:** Delete superseded execution paths so Forge has one runtime model rather than
permanent legacy and contract-backed implementations.

## Removal rule

A compatibility path may be deleted only when its replacement is authoritative for all
golden paths, restart/replay characterization passes, persisted state has an explicit
migration policy, and rollback does not require the deleted implementation. Phase 8 is
not permission to remove behavior that an earlier partial phase has not replaced.

## Completed cutovers

The Jira and source-control worker handler facades are deleted. Since Phase 2, both
sources register the same generic adapter-driven handler; the source-specific methods had
no runtime or test callers and represented a second, misleading dispatch API.

Phase 8 also removes the legacy Redis stream and `github` source alias, implicit
checkpoint pinning, scalar planning fallbacks, the implementation-input facade, and
repository-key fallback migration. Built-in runtime selection is definition-compiled;
the Python graph adapters remain only as local test harnesses. Architecture tests make
these removals zero-tolerance.

Unpinned checkpoints must now be processed by `migrate_unpinned_checkpoint`. Operators
first run it with `apply=False`, retain the original checkpoint as the rollback backup,
and persist the returned `migrated_state` only when `compatible` is true. Applied state
records the target definition and a seven-day rollback deadline by default. Rollback
means restoring that backup before the deadline; normal resume never performs migration
or rollback implicitly.

## Final observation cutover

CI, merge, review-thread, and proposal-review observations are now applied by the
provider-neutral `post-pr-v1` transition policy. The pinned workflow definition selects
that policy through an allowlisted identifier; compilation rejects unknown policies and
policies whose target nodes are absent. The worker adapts ingress, delegates once, then
persists the result—it no longer owns event-specific transition rules.

The inventory at `docs/architecture/phase-8-removal-inventory.json` is the reviewable
exit checklist. Phase 8 is complete only when `remaining` is empty and the associated
architecture tests and golden-path characterization suite pass.
62 changes: 62 additions & 0 deletions docs/architecture/phase-8-removal-inventory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"schema_version": "2.0",
"remaining": [],
"removed": [
{
"id": "worker-observation-transition-interpreter",
"owner": "Forge orchestration",
"prerequisite": "Governed observation-policy selection and provider-neutral transition runtime",
"replacement": "Pinned post-pr-v1 policy and apply_observation_transition",
"proof": "Worker delegates exactly once and contains no provider event-to-node logic"
},
{
"id": "source-specific-worker-handlers",
"owner": "Forge orchestration",
"prerequisite": "Generic adapter ingress",
"replacement": "EventAdapterRegistry and OrchestratorWorker._handle_event",
"proof": "test_worker_exposes_only_generic_ingress_handler"
},
{
"id": "inline-provider-writes",
"owner": "Forge effects",
"prerequisite": "Durable effect journal and executors",
"replacement": "EffectCommand runtime",
"proof": "tests/unit/architecture/test_direct_provider_effects.py"
},
{
"id": "broad-station-state-access",
"owner": "Forge workflow",
"prerequisite": "Typed projections, outcomes, and reducers",
"replacement": "StationRequest and allowlisted reducers",
"proof": "tests/unit/architecture/test_station_boundaries.py"
},
{
"id": "python-runtime-golden-path-selection",
"owner": "Forge workflow",
"prerequisite": "Governed built-in definitions",
"replacement": "Definition-compiled golden workflows",
"proof": "create_default_router registers only definition-compiled workflows"
},
{
"id": "legacy-planning-and-implementation-adapters",
"owner": "Forge workflow",
"prerequisite": "Normalized artifact lineage and implementation-input station",
"replacement": "Artifact plus projection/station/reducer contracts",
"proof": "Compatibility facade and scalar fallback functions are absent"
},
{
"id": "implicit-checkpoint-upgrade",
"owner": "Forge operations",
"prerequisite": "Explicit dry-run/apply migration contract",
"replacement": "migrate_unpinned_checkpoint",
"proof": "Resume rejects unpinned state and migration tests cover rollback"
},
{
"id": "legacy-queue-stream-and-source-alias",
"owner": "Forge ingress",
"prerequisite": "Normalized source_control queue contract",
"replacement": "SOURCE_CONTROL_STREAM and EventSource.SOURCE_CONTROL",
"proof": "Legacy stream and github source translation symbols are absent"
}
]
}
5 changes: 2 additions & 3 deletions src/forge/api/routes/health.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from forge import __version__
from forge.orchestrator.checkpointer import get_redis_client
from forge.queue.producer import JIRA_STREAM, LEGACY_SOURCE_CONTROL_STREAM, SOURCE_CONTROL_STREAM
from forge.queue.producer import JIRA_STREAM, SOURCE_CONTROL_STREAM

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -50,8 +50,7 @@ async def health_check() -> Any:
try:
jira_len = await redis_client.xlen(JIRA_STREAM)
source_control_len = await redis_client.xlen(SOURCE_CONTROL_STREAM)
legacy_len = await redis_client.xlen(LEGACY_SOURCE_CONTROL_STREAM)
queue_depth = jira_len + source_control_len + legacy_len
queue_depth = jira_len + source_control_len
except Exception:
pass # Streams may not exist yet

Expand Down
29 changes: 29 additions & 0 deletions src/forge/integrations/source_control/comment_identity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Provider-neutral identification of Forge-authored comments."""


def is_self_comment(
sender_login: str,
comment_body: str | None,
bot_login: str,
prefix: str | None = None,
) -> bool:
"""Return whether a normalized comment was authored by this Forge identity."""
body = comment_body or ""
sender = sender_login.lower()
bot = bot_login.lower()
same_identity = sender == bot or sender == f"{bot}[bot]"
if sender.endswith("[bot]") and same_identity:
return True
if prefix and prefix.strip():
if not same_identity:
return False
stripped = prefix.strip()
if stripped.startswith("<!--") and stripped.endswith("-->"):
candidates = (prefix, stripped)
else:
candidates = (prefix, stripped, f"<!-- {stripped} -->", f"<!--{stripped}-->")
return body.startswith(candidates) or body.lstrip().startswith(candidates)
return same_identity


__all__ = ["is_self_comment"]
Loading