PhaseDrift

Merged-code baselines

Connect main-branch code measurements alongside local feedback and improvements.

PhaseDrift has two different jobs. Local tracking measures how work happens: agent effort, edit-to-check feedback, check results and improvement reports. The GitHub baseline measures the code that actually reached the repository's default branch. Both appear in the same dashboard; their measurements keep their origins.

The GitHub App connects repository identity and reads its current default-branch revision and Actions outcomes. A pinned reusable Actions workflow supplies the scan compute. Installing the App alone does not run a scan. A baseline of main is not proof of a production deployment.

When collection runs

Automatic baselines run only when the default branch changes (main in the generated workflow for our projects). A PR merge, squash or rebase updates main and triggers one exact-commit scan. Feature-branch pushes and PR updates do not trigger this collector. A direct push to main also triggers it. A push with several commits measures the pushed tip, not every intermediate commit.

Manual dispatch is a recovery path for the same main branch. The cloud verifies the run, repository, branch and exact commit before accepting results. Local collection still observes agent work and check timings across worktrees; it does not replace the merged-code baseline.

What the setup agent does

Requires PhaseDrift 0.4.41 or later and an account-connected local project. Baseline setup is currently qualified for macOS Apple Silicon; other full CLI platforms retain their earlier release and local/account functionality. The separate CI collector supports Linux x64 and macOS ARM64. Preserve existing WIP, consent and pauses.

  1. Verify phasedrift status, phasedrift cloud status and an acknowledged results-only upload for the actual repository. Git worktrees share one cloud project; do not create another project per checkout.
  2. Inspect existing workflows, repository permissions, runner availability, Actions policy and actual remaining runner allowance. Never assume that a public reusable workflow makes a private caller free. Do not enable this workflow while Actions are paused without owner authorization for this collector. Do not change billing, plans, unrelated workflows, backup policy or paid analysis.
  3. Run phasedrift baseline connect from that repository. An existing selected-repository App installation is reused through GitHub authorization; otherwise the owner selects and approves the read-only App connection. Pairing requires the GitHub user to have write permission to that repository; a guessed installation ID cannot bind a project. Reuse phasedrift baseline status when already connected instead of reauthorizing.
  4. Run phasedrift baseline workflow. Review its generated YAML, permissions and exact collector commit. It prints a proposed file; it does not write to the repository. With authority to enable this collector, add it as .github/workflows/phasedrift-baseline.yml through the project's normal release process. Preserve unrelated workflows and WIP.
  5. The generated file defaults to the dedicated labels ["self-hosted","phasedrift-baseline"]. This deliberately queues instead of silently buying hosted minutes. A runner must be explicitly registered, suitable for trusted private main-branch code, and available. Only choose a hosted label after checking and authorizing its cost. The public collector supports Linux x64 and macOS ARM64; Git, Node.js and Perl must exist on the runner. It includes its own runtime and pinned analyzers.
  6. Verify a genuine default-branch run, its exact commit and complete/partial status, a matching cloud acknowledgement, and matching dashboard values. The collector repeats the frozen upload once to verify idempotent recovery; this does not perform a second scan. Then verify a later real main push arrives automatically. A local scan, generated file, queued job or App installation is not this proof.
  7. Explain what is configured, running and still unverified. Give the owner the GitHub run link and the latest merged-code revision. Their everyday job is normal project work and feedback; the setup agent owns these operations.

What runs and what is uploaded

Default-branch push Pinned reusable workflow Fresh exact-SHA checkout and structural scan Bounded metric rows and provenance Short-lived upload authorization Atomic metric storage and receipt Shared local and cloud charts GitHub App Current head and run verification Local tracking Effort, feedback and improvement results
Diagram source
flowchart TD
  A[Default-branch push] --> B[Pinned reusable workflow]
  B --> C[Fresh exact-SHA checkout and structural scan]
  C --> D[Bounded metric rows and provenance]
  D --> E[Short-lived upload authorization]
  E --> F[Atomic metric storage and receipt]
  F --> G[Shared local and cloud charts]
  H[GitHub App] --> I[Current head and run verification]
  I --> E
  I --> G
  J[Local tracking] --> K[Effort, feedback and improvement results]
  K --> G

The default structural scan measures source/test size, complexity and duplication. It does not execute project builds, tests, lifecycle scripts, model calls or deployments. Full-profile results can be partial when dependencies or generated inputs are unavailable; full dependency preparation is not part of this reusable workflow. Existing CI check outcomes remain separately identifiable GitHub evidence. Scan wall time measures the collector, not a laptop check or edit-to-feedback delay.

Source and original analyzer outputs stay on the runner. The cleanup step removes the collector's temporary directory and analyzer reports; it does not delete the repository checkout. A persistent self-hosted runner retains its workspace until its operator cleans it. Use a dedicated ephemeral runner or an explicitly reviewed workspace-cleanup policy. Neither source nor raw reports are uploaded to PhaseDrift cloud or Actions artifacts. Postgres stores metric values, units, methods, completeness, exact commit and tree, collector/workflow versions, environment digest, attempt state and acknowledgement. The App retains numeric repository/owner/installation IDs, current repository name/default branch and last verified head. Webhook bodies, repository source, transcripts, raw reports and App user tokens are not archived. R2 is not used for CI evidence.

Authentication and delivery

The caller grants contents: read and id-token: write. Checkout credentials are not persisted. No PhaseDrift laptop credential, personal access token or billing key belongs in Actions secrets. PhaseDrift verifies GitHub's OIDC signature, issuer, audience, time, numeric repository/owner, run attempt, default branch and exact pinned reusable workflow SHA, then independently reads the GitHub run using the App. An authenticated account and paired repository are required.

A resulting upload capability lasts ten minutes and is scoped to one source epoch, run attempt, project and commit. It cannot read source or upload for another repository. --resume-upload obtains a fresh capability for the same genuine GitHub run and retries the frozen bytes. The receipt identity and digest cannot change. Conflicting evidence fails; it is never overwritten. Recovery authorization is limited to runs started in the last 24 hours. Pausing, revoking, re-pairing, project deletion and recovery fences invalidate old grants.

Signed webhooks update the current expected revision. Duplicate deliveries retain only bounded delivery hashes; out-of-order deliveries read the current GitHub ref rather than rolling it back. A bounded five-minute reconciliation repairs missed head/run events. It checks at most four sources per tick, so head verification can lag with several sources or upstream failure. Last verified time remains visible.

Charts and improvement evidence

The same baseline status component and existing code-health charts serve local and cloud dashboards. Choose Local scans or GitHub baseline in cloud code-health results. Methods, source, profile, environment, population and units separate comparison groups; a compiler/runner/configuration change creates a new group. Original methods are not rewritten to make history look continuous.

Setup status identifies a missing/disabled workflow, an awaiting first scan and a queued runner. The dashboard distinguishes the current expected commit, its latest attempt, and the last complete baseline. Pending, partial and failed measurements never become a successful current baseline. Old successful results remain available when a refresh fails.

Recorded improvement implementation dates appear as chart markers. Where retained complete CI points bracket the recorded change with matching method/environment/profile/source, improvement details show a qualified before/after comparison with exact commits. This is a temporal association, not proof the improvement caused the change or an automatic Keep decision. Missing compatible evidence stays missing. Local task feedback still requires real local edits/checks and cannot be reconstructed from CI runtime.

Storage and request bounds

BoundBehavior
One uploadOne code scan, at most 64 rows and 32 KiB of canonical metric bundle
One sourceAt most 64 admitted run attempts per rolling 24 hours
CI historyLatest 30 result groups per repository; older metric groups retire atomically
CI account shareAt most 6,000 retained rows / 4 MiB
Combined accountRetained local results plus retained CI cannot exceed 25,000 rows / 16 MiB
Recovery recordsAll unexpired attempts plus at most 128 older retired attempts per source
RetryThree bounded transient upload attempts, then explicit frozen replay
App reconciliationFour sources per five-minute tick; sixteen sources per account

These are admitted logical result bounds, not an exact Postgres disk-size prediction. Indexes and database system data add overhead. Local superseded revisions retire on new admission. Under account pressure, the oldest local metric groups retire while retaining the newest kind per project; improvement definitions and learning records remain. Local ACK receipts retain seven days, independently capped at 50,000 per account. Timestamped frozen uploads expire after seven days; an already-consented automatic collector renews expired uploads with a new identity without changing the original frozen body. Older v1 collectors remain compatible; their frozen receipts retain their identities within the separate 50,000-receipt cap instead of silently expiring an unupgradable platform. Retiring CI groups can remove an old improvement comparison. Local evidence and retained Git commits remain the original sources; cloud is not a backup.

Check account capacity in the merged-code details or phasedrift baseline status. At a limit, collection fails visibly; it does not increase storage or buy capacity. CI usage and retries consume runner time. Reusing a public workflow charges the caller's runner allowance. GitHub billing currently lists self-hosted execution as free; operating the machine can still cost money. No runner is provisioned or hosting plan changed by these commands.

CLI, local API and MCP

phasedrift baseline status --repo /path/to/checkout
phasedrift baseline connect
phasedrift baseline workflow
phasedrift baseline refresh
phasedrift baseline pause
phasedrift baseline resume
phasedrift baseline revoke

refresh checks GitHub metadata; it does not execute a scan. Source pause controls cloud admission and invalidates capabilities; disable the specific repository workflow as well to stop runner execution. Resume does not retroactively dispatch old jobs. Local tracking remains separate. collect --source UUID --state-dir PATH --profile structural is the CI entry point and requires genuine GitHub Actions identity. Never manufacture its environment to label a local scan as CI.

Local read-only HTTP: GET /api/health/baseline. Project-scoped stdio MCP: phasedrift_baseline_status and phasedrift_baseline_workflow. Neither executes checks or enables Actions. The existing phasedrift mcp --repo PATH serves these alongside effort, timings and learning tools.

Cloud API under /api/cloud/v1/baseline:

EndpointAuthority / purpose
GET /configPublic App availability and exact collector workflow pin
POST /pairExisting paired collector/account; creates expiring App pairing challenge
GET /callbackGitHub user authorization and repository installation proof
POST /authorizeGitHub signed workload; returns one short-lived upload capability
POST /resultsScoped capability; validates bounded results and atomically acknowledges
POST /webhookOriginal-byte App HMAC; bounded metadata reconciliation
GET /status?projectId=...Verified account owner or paired collector; head, attempt and capacity
POST /refresh, /pause, /resume, /revokePaired local collector, project scoped

The transport contract is github-baseline/v1; inspect src/baseline/contract.ts and the migration for exact field types. New tables use RLS and service-only RPCs with explicit account/project/producer checks. The normal results APIs return CI metric rows through the same bounded dashboard pagination as local results.

Operator release setup

Register the reviewed docs/github-baseline/app-manifest.json via bun scripts/register-github-app.ts. The browser approves read-only Contents, Metadata and Actions permissions. Secrets go directly to the existing history Worker's secret store; never commit or print them. Install only selected repositories. The App does not need workflow-write, checks-write, administration, deployment or billing permission.

Release builds use Bun 1.4.3. Published collector archives and their workflow commits remain immutable; updating development Bun does not replace a running collector or change its recorded environment. Upgrade a collector through a reviewed workflow revision.

Build collector targets with the pinned Bun 1.4.3 runtime:

BUN_BE_BUN=1 /path/to/release/runtime scripts/package-baseline.ts linux x64
BUN_BE_BUN=1 /path/to/release/runtime scripts/package-baseline.ts darwin arm64

Publish immutable archive paths on the existing downloads service. Populate the reviewed public workflow template with those exact archive hashes; publish only that workflow/docs in FrontierNomad/phasedrift-actions, not the private product source. Set GITHUB_COLLECTOR_WORKFLOW to its exact 40-character commit pin. Apply the tested additive cloud migration and deploy the history Worker/dashboard manually while unrelated Actions remain paused. The six App secret bindings are GITHUB_APP_ID, GITHUB_APP_PRIVATE_KEY, GITHUB_APP_WEBHOOK_SECRET, GITHUB_APP_CLIENT_ID, GITHUB_APP_CLIENT_SECRET, GITHUB_APP_SLUG. No App secrets belong in CI or browser code.

Before claiming release acceptance, record the published collector hash, App installation, paired numeric repository, actual main run, acknowledged digest and matching cloud values for each pilot. Then exercise the next main push. An App registration or a local/native database test alone does not establish live collection.

Runner operation

A dedicated self-hosted runner can use a fresh official GitHub runner container for each trusted main-branch job. Keep its repository-scoped identity private. Do not mount host source, the Docker socket, personal access tokens or PhaseDrift account credentials into jobs. Remove the job container and workspace after execution. Set CPU, memory and disk headroom from a real scan of the repository, and verify it again after an analyzer update. A small repository's resource limit can produce an incomplete measurement on a larger one.

Keep the runner service persistent across reboots and verify it is online in GitHub. Review and pin official runner updates before GitHub's mandatory update window. An offline or outdated runner must remain visibly queued; never silently switch to paid hosted execution.

Everyday use

Work normally and merge reviewed changes through your repository's release process. Open the account Projects page and choose GitHub baseline for merged code, or Local scans for workstation evidence. Ask your agent to investigate a concrete improvement, record it, implement it during authorized work and compare compatible before/after results. You do not need to dispatch scans or refresh subscriptions manually.

On this page