Skip to content

feat: GitOps loop — plan on PR, gated apply, scheduled drift detection #24

Closed

Description

@2000game

Wrap the CLI in the GitOps loop that makes desired-state code actually reproducible in practice: every change is a PR with a visible plan, applies are gated, and drift is detected continuously instead of when someone remembers to run ct plan. Lives mostly in eqrm/ct-structure; ct-cli grows the machine-facing affordances. Part of the Phase 6 epic. Depends on environments + ct-structure bootstrap.

Scope

ct-cli (tool side):

  • Terraform-style --detailed-exitcode for ct plan: 0 = no changes, 1 = error, 2 = changes present. INCOMPLETE stays exit 1 (src/commands/plan.ts:102-113). Drift-only vs config-diff distinction surfaced in --json (summary.drifted separate from summary.hasChanges; per-item changes[].source = config/drift/config+drift). Tests: tests/plan-detailed-exitcode-command.test.ts (9 passing).
  • Non-interactive auth: already exists — CT_HOST + CT_LOGINTOKEN env vars bypass the Keychain (src/auth/tokenStore.ts). Remaining: document the CI usage in the workflow README.
  • Plan output rendering suitable for a PR comment. renderPlan/renderPermissionPlan use picocolors, which drops ANSI when stdout isn't a TTY or NO_COLOR is set; +/~/-/!/? prefixes carry the meaning with no color. Documented in README §"Posting a plan as a PR comment" incl. the CI=true-forces-color gotcha (set NO_COLOR=1) and a full GitHub Actions capture-into-<details> example.

ct-structure (repo side):

  • PR workflow: ct plan --env dev + --env prod, post/update the plan as a PR comment, fail the check on plan errors.
  • Apply workflow: merge to main → ct apply --env dev automatically; ct apply --env prod behind a GitHub Environment with required reviewers (the confirmation prompt's CI equivalent).
  • Scheduled drift job (nightly): ct plan --detailed-exitcode per env; on exit 2, open/update a "drift detected" issue with the plan output; close it when clean.
  • Secrets: per-env CT token as environment-scoped repo secrets.

Acceptance criteria

  • A structure change lands as: PR with visible plans → review → merge → dev applied → prod applied after approval. No local CLI run needed.
  • Manual change in the CT UI to a managed resource → drift issue appears within 24h, with the exact field diff.
  • ct plan --detailed-exitcode exit codes covered by tests (tests/plan-detailed-exitcode-command.test.ts: 0/1/2 incl. INCOMPLETE-stays-1 and permission-only-changes→2).

Activity

  1. added theissue type on Jul 8, 2026
  2. 2000game commented on Jul 9, 2026

    @2000game
    MemberAuthor

    Status (autonomous wave, 2026-07-09): both halves are merged.

    • Tool side (PR feat(plan): --detailed-exitcode, drift-aware --json, CI usage docs #63): ct plan --detailed-exitcode (0/1/2, INCOMPLETE stays 1, permission-only changes → 2, covered by tests), --json gains per-change source: config|drift|config+drift + summary (attribution traced correct in review), NO_COLOR guidance (picocolors force-colors under CI=true), README CI-usage docs.
    • Repo side (eqrm/ct-structure#2): plan.yml (sticky PR comment, both envs, fails only on exit 1), apply.yml (dev auto → prod behind production environment gate with --confirm-env prod; state write-back commits each env's ct-state file back to main even on partial apply failure, rebase-retry push, paths-ignore retrigger guard), drift.yml (nightly + manual; exit 2 → create/update a drift issue with source attribution; exit 0 → close; hard-fails on unexpected exit codes). Three review rounds; zero outstanding findings.

    Remaining (user setup, then a supervised first run — exact steps in eqrm/ct-structure#2's body): repo secrets CT_DEV_TOKEN + CT_PROD_TOKEN, GitHub Environment production with required reviewers, branch-protection bypass for the Actions bot's state write-back pushes. Acceptance (drift issue within 24h, merge-to-applied with no local CLI) is then provable live.

  3. 2000game commented on Jul 10, 2026

    @2000game
    MemberAuthor

    Live setup update (2026-07-10):

    • ✅ production GitHub Environment created via API, deployment branch policy = main only.
    • ❌ Required reviewers are impossible on this billing plan: environment protection rules on PRIVATE repos need GitHub Enterprise Cloud (Free/Pro/Team = public repos only; API returns 422 — verified live). Adaptation in ct-structure PR feat: Phase 2 — Read/Adopt (ct adopt) + state file #4: apply-prod runs only on manual workflow_dispatch (the dispatch is the human gate, honestly documented as weaker), with a success()-guarded gate so a failed dev rehearsal can never unlock prod.
    • 🐛 PR feat: Phase 2 — Read/Adopt (ct adopt) + state file #4 also fixes a blocker its own CI run exposed: workflows couldn't npm ci the private ct-cli git dependency (no SSH key; GITHUB_TOKEN is repo-scoped) → an auth step rewrites ssh→token-https via a new CT_CLI_READ_TOKEN secret.

    Remaining (user): create secrets CT_DEV_TOKEN, CT_PROD_TOKEN, CT_CLI_READ_TOKEN (fine-grained PAT, contents:read on eqrm/ct-cli) → PR #4's plan check should go green → merge PR #4 → supervised first run of each workflow. Setup checklist is in the PR body / README.

  4. 2000game commented on Jul 10, 2026

    @2000game
    MemberAuthor

    Handoff state (2026-07-10): everything mergeable is merged except one PR, and one secret is missing.

  5. 2000game commented on Jul 10, 2026

    @2000game
    MemberAuthor

    ct-cli tool-side is complete — all three tool checkboxes ticked. Verified in this session:

    • --detailed-exitcode implemented (src/commands/plan.ts), 9 exit-code tests pass (tests/plan-detailed-exitcode-command.test.ts), incl. INCOMPLETE→1, permission-only-changes→2, drift-alone→0.
    • Non-interactive auth (CT_HOST/CT_LOGINTOKEN) documented in README §CI usage.
    • PR-comment rendering documented (README §"Posting a plan as a PR comment"), including the CI=true picocolors gotcha and a GitHub Actions example that captures the plan into a <details> block.

    Everything remaining on #24 is ct-structure-side (separate private repo): the plan/apply/drift workflows and env-scoped secrets. Per the roadmap that is handoff item #3 — it needs the CT_CLI_READ_TOKEN fine-grained PAT secret so ct-structure can npm install ct-cli, then ct-structure PR #4 goes green. No further ct-cli code changes are required for #24.

  6. added a commit that references this issue on Jul 10, 2026
  7. 2000game commented on Jul 10, 2026

    @2000game
    MemberAuthor

    ct-cli now publishes to GitHub Packages — merged PR #83 (87eb5d9). This is the no-PAT cross-repo path (your GitHub-Packages recollection was right; it applies to registries, not raw git deps).

    Validated live: the release.yml run on the merge (29094017769) went fully green — semantic-release reached analyzeCommits, i.e. the new @semantic-release/npm verifyConditions (GitHub Packages auth) passed. The ci: commit is non-releasing, so nothing published yet; the package first publishes on the next releasable feat/fix merged to main.

    Consumer side (eqrm/ct-structure — separate PR there):

    • package.json: depend on @eqrm/ct-cli instead of the ssh://…/ct-cli.git git URL.
    • workflows: permissions: { packages: read, contents: read }; actions/setup-node with registry-url: https://npm.pkg.github.com, scope: '@eqrm'; npm ci with env: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}. No PAT.
    • one-time click: in ct-cli's package settings, grant eqrm/ct-structure Actions access to the @eqrm/ct-cli package (after it first publishes).

    Reviewed by an agent across 7 wiring dimensions (version bump, plugin order, auth/whoami blast-radius, dist packaging, no-public-npm-leak, repository field, YAML) — all clean. One pre-existing follow-up filed: the GitHub Release ct-cli.tgz asset stays at 0.1.0.

  8. 2000game commented on Jul 11, 2026

    @2000game
    MemberAuthor

    GitOps loop is live and validated end-to-end (2026-07-11).

    The last blocker — ct-structure's CI could not install the private ct-cli dependency — is resolved by consuming @eqrm/ct-cli from GitHub Packages with the built-in GITHUB_TOKEN (no PAT). Chain: #84/PR #85 fixed the release tarball and triggered the first Packages publish (@eqrm/ct-cli@1.2.2) → eqrm/ct-structure#8 switched the consumer + all three workflows to it → #7 (dev state) and #4 (prod gate) merged in a safe order so no push ever triggered an unsupervised apply.

    All three workflows proven live:

    • plan.yml — verified on eqrm/ct-structure#8: posts the sticky dual-env plan comment; passes on exit 0/2.
    • apply.yml — first apply-dev ran on merge: healed one real dev drift (a stray ct-cli bug: dynamic ruleset round-trip assumption unverified — CT-recomputed fields would cause perpetual diffs #36 test probe on group #1698 → the config's real ruleset), apply-prod skipped (dispatch-gated). Prod apply is manual workflow_dispatch — required-reviewer env protection needs Enterprise Cloud on private repos (422, verified live), so the dispatch is the documented (weaker) human gate; if: success() && workflow_dispatch keeps a failed dev rehearsal from ever unlocking prod.
    • drift.yml — manual dispatch: dev and prod both clean (exit 0), no false drift issues opened.

    Acceptance: PR → plan comment → merge → dev applied ✅; drift detection live (nightly + on-demand, proven green) ✅; --detailed-exitcode tests ✅; prod behind a gate ✅ (dispatch, not reviewer — honest caveat). No PAT anywhere; secrets are CT_DEV_TOKEN + CT_PROD_TOKEN only.

    Tool side was already complete (PR #63). Nothing further required.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions