Skip to content

feat(ghost-drift): add --gate mode to compare for deterministic ledger reconciliation#78

Open
gnahCnayR wants to merge 2 commits into
block:mainfrom
gnahCnayR:rchang/ghost-drift-compare-gate
Open

feat(ghost-drift): add --gate mode to compare for deterministic ledger reconciliation#78
gnahCnayR wants to merge 2 commits into
block:mainfrom
gnahCnayR:rchang/ghost-drift-compare-gate

Conversation

@gnahCnayR
Copy link
Copy Markdown

@gnahCnayR gnahCnayR commented May 11, 2026

Summary

Adds a --gate mode to the existing ghost-drift compare command for deterministic ledger reconciliation suitable for CI, programmatic consumers, and Block's sq agents design adapter.

This is not a new orchestration verb — it composes the existing compareFingerprints, readSyncManifest, and checkBounds primitives into a stable contract surface. The verify.md skill recipe's "Ghost has no ghost verify CLI command" stance is intentional and unchanged; the gate is a deterministic signal, not a workflow.

What it does

When --gate is passed:

  1. Runs the existing pairwise comparison.
  2. Reads .ghost-sync.json (path overridable via --sync, default ./.ghost-sync.json).
  3. Reconciles per-dimension current distance vs the recorded ack stance using the existing checkBounds() helper.
  4. Emits a versioned per-dimension verdict (aligned / covered / reconverging / uncovered) in CLI or JSON form.
  5. Exits with a ledger-aware code: 0 aligned-or-covered, 1 uncovered drift, 2 hard error.

JSON schema (ghost.compare.gate/v1)

{
  "schema": "ghost.compare.gate/v1",
  "trackedFingerprintId": "market-theme",
  "localFingerprintId": "example-app",
  "overall": { "distance": 0.52, "verdict": "uncovered" },
  "dimensions": {
    "spacing":      { "distance": 0.73, "ackDistance": 0.73,  "stance": "accepted",  "verdict": "covered" },
    "palette":      { "distance": 0.95, "ackDistance": 0.875, "stance": "accepted",  "verdict": "uncovered", "reason": "current 0.95 exceeds acked 0.875 + tolerance 0.05" },
    "decisions":    { "distance": 0,    "ackDistance": 0,     "stance": "diverging", "verdict": "covered" },
    "newDimension": { "distance": 0.4,                                                "verdict": "uncovered", "reason": "no ack recorded" }
  }
}

Why this shape

The --gate work originated from a need at Block to integrate Ghost into our internal CI/review pipeline (sq agents design check --gate) and into a marketplace agent skill (ghost-drift-check). Both consumers were re-implementing the same reconciliation logic — read the manifest, walk dimensions, compare-with-tolerance, classify each dimension. Surfacing it as a flag on compare (rather than scattered downstream code) gives every consumer:

  • A versioned schema they can pin against.
  • A single exit code that means "uncovered drift exists" rather than "raw distance crossed a fixed threshold."
  • A per-dimension verdict vocabulary built from the existing stance vocabulary, not a new one.

Files

Architectural fit

Ghost's tools are calculators; agent workflows are skills. This change keeps that separation intact:

  • Calculator-only addition. --gate is a flag on the existing calculator (compare), not a new orchestration verb.
  • Skill recipe touched lightly. review.md gets one new subsection pointing at the flag for agents that want a deterministic signal. verify.md is untouched (its "no CLI verify command" framing is unchanged).
  • Composition over reimplementation. Reuses compareFingerprints, readSyncManifest, and checkBounds. No new ledger logic.
  • Forward-compatible. If Ghost ever ships ghost-drift loop (or similar), --gate becomes its gate stage; nothing thrown away.

Testing

  • pnpm install
  • pnpm build
  • pnpm test — 456 passed (40 files), including 14 new gate tests covering the spec cases.
  • pnpm check — biome check, typecheck, file-size limits (cli.ts 411 / gate.ts 328, both under the 500-line limit), docs frontmatter, cli-manifest drift.
  • pnpm fmt — clean.

Both cli.ts and the new gate.ts stay under the repo's 500-line file-size gate. The runGateCli runner sits in gate.ts (rather than inline in cli.ts) specifically to satisfy that gate while keeping the CLI runner re-exportable for programmatic consumers.

Notes for reviewers

  • No breaking changes. All existing compare invocations behave identically; --gate is opt-in.
  • Two intentional spec deviations, both documented in the implementation:
    1. aligned verdict requires ack.stance === "aligned" — a diverging ack with current distance 0 reads as covered, not aligned. This matches the spec test expectation for the diverging-covered case.
    2. The example JSON in the spec showed palette distance 0.92 vs ack 0.875 with tolerance 0.05 (which doesn't actually exceed: 0.875 + 0.05 = 0.925 > 0.92). The test fixture uses 0.95 so the case actually exercises the uncovered path. The reason-string format matches the spec.

gnahCnayR and others added 2 commits May 11, 2026 16:07
…r reconciliation

Compose the existing checkBounds() helper into a structured gate verdict
on top of the existing compare command. New flags: --gate, --sync, and
--max-divergence-days. Versioned JSON output as ghost.compare.gate/v1
suitable for CI and programmatic consumers; exit codes 0/1/2 reflect
aligned-or-covered, uncovered drift, and hard error respectively. No
new top-level orchestration verb; the existing review.md skill recipe
gets a short pointer to the gate flag for agents that want a
deterministic signal.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019e1874-9118-7527-b5e0-5bc0f8dcf913
Address sq agents review findings on the original gate scaffold:
- Replace `unknown` types in RunGateCliOptions with concrete string |
  undefined / number | undefined; validate at the runGateCli boundary.
- Use synchronous-flush stdout-write before process.exit so JSON output
  isn't truncated on piped or non-TTY runs.
- Consolidate the multiple process.exit branches in runGateCli to a
  single exit at the end with intermediate paths returning the exit
  code via a typed result.
- Remove (or wire up — verify intent) the unused effectiveTolerance
  in buildGateReport.

No public-API or behavioral changes; the ghost.compare.gate/v1 schema
and exit-code semantics are unchanged.

Amp-Thread-ID: https://ampcode.com/threads/T-019e1874-9118-7527-b5e0-5bc0f8dcf913
Co-authored-by: Amp <amp@ampcode.com>
@gnahCnayR gnahCnayR marked this pull request as ready for review May 12, 2026 19:03
@gnahCnayR gnahCnayR requested a review from nahiyankhan as a code owner May 12, 2026 19:03
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