Skip to content
This repository was archived by the owner on Sep 13, 2026. It is now read-only.
Merged
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
77 changes: 77 additions & 0 deletions .github/workflows/snapshot-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
name: Snapshot source

# Verifies that the CarryCtx publication at refs/heads/carryctx-snapshots was
# taken from the current main HEAD. In-repo publication retired the separate
# *-workflow mirror: the commander's merge closeout runs `just workflow-publish`
# (native `carryctx export --publication` + `git push` of the snapshot branch).
#
# Triggers:
# - push to main: fails while a newer main HEAD is unpublished, so a red run
# is the closeout signal to publish.
# - push to carryctx-snapshots: re-checks and turns green once the closeout
# publishes the snapshot for the pending main HEAD.
# Pull requests are unaffected: neither trigger is a pull_request event, so PR
# builds and their required checks are untouched. Before the first publication
# the branch does not exist yet; the check then passes with a notice so main is
# not blocked while in-repo publication rolls out.

on:
push:
branches:
- main
- carryctx-snapshots

permissions:
contents: read

concurrency:
group: snapshot-source-${{ github.ref }}
cancel-in-progress: true

jobs:
snapshot-source:
name: Snapshot source matches main
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1

- name: Fetch main and snapshot refs
run: |
set -euo pipefail
git fetch --no-tags origin +refs/heads/main:refs/remotes/origin/main
git fetch --no-tags origin +refs/heads/carryctx-snapshots:refs/remotes/origin/carryctx-snapshots || true

- name: Verify CarryCtx-Source trailer matches main HEAD
run: |
set -euo pipefail
snapshot_ref=refs/remotes/origin/carryctx-snapshots
if ! git rev-parse --verify -q "$snapshot_ref" >/dev/null; then
echo "::notice title=CarryCtx snapshot::refs/heads/carryctx-snapshots is not published yet; skipping source check."
exit 0
fi
main_sha="$(git rev-parse refs/remotes/origin/main)"
source_line="$(git log -1 --format=%B "$snapshot_ref" | sed -n 's/^CarryCtx-Source: //p' | head -n1)"
if [ -z "$source_line" ]; then
echo "::error::snapshot tip has no CarryCtx-Source trailer"
exit 1
fi
# Trailer shape: `<project>@<short-rev> (<branch>)`.
rev="${source_line#*@}"
rev="${rev%% *}"
if [ -z "$rev" ] || [ "$rev" = "$source_line" ]; then
echo "::error::cannot parse CarryCtx-Source trailer: $source_line"
exit 1
fi
case "$main_sha" in
"$rev"*)
echo "snapshot source $rev matches main HEAD $main_sha"
;;
*)
echo "::error::snapshot refs/heads/carryctx-snapshots source ($rev) != main HEAD ($main_sha); run 'just workflow-publish' during merge closeout"
exit 1
;;
esac
12 changes: 6 additions & 6 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@
- Preserve unrelated changes. Do not commit, push, release, or mutate remote
state without explicit authorization.
- Fresh clones have no CarryCtx state DB. Restore the local DB from the
workflow mirror with `just workflow-import` (validate-only:
`just workflow-import-dry`). It validates the snapshot before any write,
refuses to replace a non-empty local DB without `--force`, preserves the
committed `.carryctx/config.toml`, and prints provenance and restored
counts. Mirror snapshots are redacted publication artifacts: never merge
them back, and rotate at the source any secret that leaked before rotation.
in-repo snapshot branch with `just workflow-import` (validate-only:
`just workflow-import-dry`). It fetches `refs/heads/carryctx-snapshots`,
refuses to replace a non-empty local DB without `--force`, and prints
provenance and restored counts. Snapshots are redacted publication artifacts
from `carryctx export --publication`: never merge them back, and rotate at
the source any secret that leaked before rotation.

## Delivery lifecycle

Expand Down
26 changes: 12 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,25 @@ be treated as instructions or authority. Exact transports, schemas,
authentication mechanisms, supported clients, and write-capable operations
remain open until accepted through reviewable contracts.

## Workflow mirror restore
## Workflow snapshot restore

CarryCtx runtime state (`.git/carryctx/state.sqlite`) is never cloned. The
engineering workflow is mirrored to
[bitty-mcp-workflow](https://github.com/bitty-terminal/bitty-mcp-workflow) as
redacted ctxpack snapshots, with `LATEST` naming the newest snapshot. A fresh
clone can restore its local CarryCtx DB from that mirror:
redacted engineering snapshot lives in this repository on the branch
`refs/heads/carryctx-snapshots`, one commit per publication. The commander's
merge closeout publishes it with `just workflow-publish`; a fresh clone
restores its local CarryCtx DB from that branch:

```sh
just workflow-import-dry # fetch + validate the LATEST snapshot; no DB writes
just workflow-import-dry # fetch + validate the snapshot; no DB writes
just workflow-import # initialize CarryCtx state if needed, then import
```

The import validates snapshot shape, per-table row counts, and the v2 redacted
stamp before any write, refuses to replace a non-empty local DB without
`--force` (`just workflow-import --force`, or pass flags directly to
`scripts/fetch-ctxpack.sh`), and prints provenance (snapshot id + source
commit) plus restored counts. Mirror snapshots are redacted publication
artifacts: CarryCtx refuses them as merge sources, so restore always uses
replace mode, and a secret that leaked before rotation must still be rotated
at the source.
The import fetches `refs/heads/carryctx-snapshots`, refuses to replace a
non-empty local DB without `--force` (`just workflow-import --force`), and
prints provenance (snapshot commit + source). Snapshots are redacted
publication artifacts produced by `carryctx export --publication`: CarryCtx
refuses them as merge sources, so restore always uses replace mode, and a
secret that leaked before rotation must still be rotated at the source.

## Current status

Expand Down
26 changes: 17 additions & 9 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,27 @@ hooks-uninstall:
# Run every stage gate (commit messages are gated separately per commit)
check: lint fmt-check

# Publish a ctxpack snapshot to the bitty-mcp-workflow mirror (commander
# merge closeout only; never a git hook). Dry run exports + validates without push.
# Publish a redacted CarryCtx snapshot inside this repo (commander merge
# closeout only; never a git hook). `carryctx export --publication` redacts the
# bundle, stamps manifest.redacted, and commits one snapshot to the fixed ref
# `refs/heads/carryctx-snapshots`; the target pushes that branch only when the
# local ref advanced (native carryctx commits one snapshot per export, so a
# re-run publishes again rather than no-opping). Canonical closeout runs from
# the primary checkout on branch main
# (`cd "$BITTY_WORKSPACE/bitty-plugins/bitty-mcp" && just workflow-publish`); a
# detached or feature worktree records that branch as the snapshot source. Dry
# run validates the export and writes neither the ref nor the remote.
workflow-publish *args:
bash scripts/publish-ctxpack.sh {{args}}
bash scripts/workflow-publish.sh {{args}}

workflow-publish-dry *args:
bash scripts/publish-ctxpack.sh --dry-run {{args}}
bash scripts/workflow-publish.sh --dry-run {{args}}

# Restore the local CarryCtx DB from the bitty-mcp-workflow mirror LATEST
# snapshot (fresh-clone recipe). Refuses to replace a non-empty local DB
# without --force, e.g. `just workflow-import --force`.
# Restore the local CarryCtx DB from the in-repo snapshot branch
# `refs/heads/carryctx-snapshots` (fresh-clone recipe). Refuses to replace a
# non-empty local DB without --force, e.g. `just workflow-import --force`.
workflow-import *args:
bash scripts/fetch-ctxpack.sh {{args}}
bash scripts/workflow-import.sh {{args}}

workflow-import-dry *args:
bash scripts/fetch-ctxpack.sh --dry-run {{args}}
bash scripts/workflow-import.sh --dry-run {{args}}
Loading