Skip to content

M10+M11 (engine): organization compatibility campaigns and continuous upstream watchers - #68

Merged
kadireren7 merged 17 commits into
mainfrom
feat/m10-m11-org-campaigns-watchers
Oct 2, 2026
Merged

kadireren7 merged 17 commits into
mainfrom
feat/m10-m11-org-campaigns-watchers

Conversation

@kadireren7

Copy link
Copy Markdown
Owner

M10 + M11 (engine side): organization compatibility campaigns and continuous upstream watchers

Public-engine half of the final milestone block. The hosted half (scheduling, credentials, dashboard) is a separate PR in the private patchfrog-cloud repository, which pins this branch's head.

Not merged automatically. Please review before merge.

What this adds

  • M10 campaigns (patchfrog/campaigns/): one upstream change across a workspace's repositories as one idempotent CompatibilityCampaign; derived (never stored) organization graph; org blast radius that never rounds uncertainty to "safe"; deterministic repository/campaign states; freshness (FRESH/STALE/UNKNOWN/ACCESS_LOST); per-repository isolation; bounded explicit-identity internal contracts; dossier (JSON + markdown); Alembic 0038; patchfrog campaigns analyze|demo.
  • M11 watchers (patchfrog/watchers/): source adapters (PyPI, npm, GitHub Releases, OpenAPI URL, structured changelog feeds, manual), snapshot comparison with an opaque cursor, baseline-on-first-observation, duplicate suppression, watch-registry aggregation, the campaign launch gate (structural proof by default), an SSRF-safe fetcher with typed errors and pure backoff.
  • Pipeline glue: process_change_for_workspace (gate -> campaign -> M6->M9 per repo -> audit trail -> persist, one transaction), human-approval support, label-validated metrics, lossless event serialization, tenant-scoped registry read, draft-PR support, GitHubClient.get_default_branch.
  • CI log hygiene: the disposable GitHub App key/webhook secret are generated at run time and masked instead of living in the job env: block that GitHub prints; a regression test fails on the old workflow.

Boundary

Everything that determines how compatibility is understood, analyzed, planned, verified or decided is here. Nothing hosted (schedules, cursors storage, credentials, accounts, dashboard) is. docs/product-boundary.md has the table.

Honest findings made while building (all fixed, all tested)

  • A failed checkout used to fall back to registry evidence, which for an SDK without a built-in discovery adapter reads as "not affected" -> now FAILED (evidence_error).
  • An SDK surface restored from a stored cursor had no SdkSurface and crashed the diff.
  • Two pollers racing to record one change surface IntegrityError at flush, not only commit (Cloud side).
  • PARTIALLY_VERIFIED/UNVERIFIED precedence and M9 no_op_unchanged (human-closed PR) are now treated as a human decision, not "in flight".

Verification

  • ruff, mypy . --strict clean; Alembic single head 0038; fresh upgrade + downgrade/upgrade round trip on real PostgreSQL 16.
  • Full pytest with PostgreSQL + Redis and PATCHFROG_REQUIRE_POSTGRES=1: see the validation summary (validation/m10_m11/latest-summary.md) for the final numbers.
  • M4 cost benchmark: output identical to the committed baseline. Both Docker images build; 9 engine Celery tasks registered.
  • Real-PostgreSQL concurrency test for the campaign store (8 concurrent upserts of one identity -> one row).
  • No M4-M9 behavior changed: VerificationOutcome precedence, M9 PR identity, stale-base protection unchanged.

Limits (unchanged by this PR)

Verification needs a working bwrap sandbox (the stock Docker profile reports it unavailable -> nothing can be VERIFIED there); JS/TS usage is lexical; remote OpenAPI $ref not resolved; a bare version bump opens no campaign by default. See docs/campaigns.md, docs/watchers.md.

Security wording

Tracked files and the PR diff contain no detected secret material. This does not prove that secret material was never exposed locally or historically in logs. The new CI configuration no longer intentionally exposes key material; earlier logs are not retroactively cleaned.

🤖 Generated with Claude Code

… compatibility graph

Adds the campaign vocabulary (repo/campaign states, freshness, org classes),
the two pure state-derivation functions, the derived (never stored) org graph
and the bounded explicit-identity internal contract model. Includes the
Phase 0 audit/implementation map.
… gate

Uncertainty is never rounded to safe: stale, unknown, access-lost and
version-level-only repositories are UNKNOWN and keep the organization unsafe.
The pure publication gate (watch mode / verification requirement / publication
mode) lives here so its semantics are inspectable in the public engine.
…R support

Composes the existing M6->M9 functions per repository with structural isolation
(a failure is that repository's FAILED record, never another's), independent
per-repo publication under workspace policy, idempotent re-runs, and
reconciliation to RESOLVED only through fresh re-evaluation. Adds Alembic 0038,
the CampaignStore, the machine-readable dossier, and a backward-compatible
draft flag through the PR publisher/client/fake. Acme + internal-contract
fixtures and the end-to-end suite (real sandbox verification, fake GitHub).
…egistry, URL safety

Public-engine half of continuous watching: normalized snapshots, an opaque
JSON cursor, deterministic version/contract comparison with first-observation
baselines and duplicate suppression, adapters for PyPI, npm, GitHub Releases,
OpenAPI URLs and structured changelog feeds (no scraping), a manual-snapshot
path, a SSRF-safe fetcher, typed errors with pure backoff, and the watch
registry that de-duplicates fetches across workspaces. Hosted scheduling is
deliberately not here.
…he end-to-end demo

process_change_for_workspace applies the launch gate (structural proof by
default), loads the previous campaign, runs the per-repo M6->M9 flow, and
writes the campaign, the M6 event and the M7/M8 audit trail in one
transaction. Adds label-validated watcher/campaign/PR metrics, the
'patchfrog campaigns analyze|demo' commands, and the deterministic four-repo
demo (baseline -> change -> one campaign -> one PR -> no duplicates ->
RESOLVED). Fixes a latent crash: an SDK surface restored from a stored
snapshot now rebuilds its SdkSurface so it can be the old side of a diff.
The disposable GitHub App private key lived in the job-level env block, which
GitHub prints (resolved) in every step header. The key and the webhook secret
are now generated at run time, written to a file / $GITHUB_ENV, masked, and the
container check mounts the key file instead of passing its content. The new
workflow-hygiene test fails on the previous configuration and passes on this one.
This stops intentional plain-text display going forward; it does not erase
anything already present in historical logs.
…try-consulted evidence

Hosted pipelines detect a change in one process and evaluate it per workspace
in others: add a lossless ExternalChangeEvent JSON round trip. Add a
repository-scoped registry read (the existing one is intentionally global and
unsafe for multi-tenant callers; an empty scope never means 'all'). Let a
repository input say 'the registry was consulted' so a fresh discovery that
found nothing matching is NOT_AFFECTED without a checkout, while no evidence at
all stays UNKNOWN.
…resh evaluation

approved_repositories is threaded through run_campaign/process_change_for_workspace.
Approval never lowers the bar: the evaluation is recomputed from scratch, a
partially verified migration is still a draft, and an approval for a repository
whose migration is not eligible changes nothing.
…er classified from weaker evidence

Found while building the hosted runner: a failed checkout fell back to registry
evidence, which for an SDK without a built-in discovery adapter (package
declaration only, no call sites) reads as 'not affected'. RepositoryInput now
carries evidence_error; the evaluation marks such a repository FAILED, with
credentials scrubbed by the engine itself.
…ry, so it needs the sandbox (found by CI, where bwrap is unavailable)
@kadireren7
kadireren7 merged commit 9b9de85 into main Oct 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant