Skip to content

fix(project): record receipts on project init so files and rules refresh - #193

Merged
arcaven merged 3 commits into
mainfrom
build/sideshow/91-project-init-receipts
Oct 8, 2026
Merged

arcaven merged 3 commits into
mainfrom
build/sideshow/91-project-init-receipts

Conversation

@arcaven

@arcaven arcaven commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

A repo onboarded with sideshow project init <pack> kept the first version of every files: artifact forever, and since #189 it no longer refreshes rules either, so a pack's corrected .editorconfig, workflow file or rule never reached it. The single-repo path now reads and records receipts in the registry, so an unedited file or rule updates on a pack bump, an edit is kept, and the reason is printed.

Closes #91

Rules regression from #189, named: before it, a marker rule was overwritten by project init, so rules refreshed on this path. #189 made a marker file with no receipt and different bytes "left as is", and this path never had a receipt, so it sat in that state permanently. This change gives it receipts.

Shape, as ruled: the same registry the init --scope project path uses, under a key derived from cwd alone. The id is the repo's own identity file when it has one, else path: plus the sha256 of the symlink-resolved absolute path; the root is that resolved path; the manifest is one fixed name (project.SingleRepoManifest). project init never writes .sideshow/project.yaml. The repo name in the key is taken from the resolved root, so a symlinked cwd finds the same receipt.

Also in this PR: project init printed nothing for a skipped action, so a user saw "0 wrote, 2 already present" with no reason. It now prints each skip as <type>: skipped <path>: <detail>, in the wording the conflict lines already use.

Accepted limits, from the ruling:

  • A moved or renamed repo has no receipt. A files: artifact then reads as user-authored. A rule is adopted only when its bytes equal the current content, and otherwise skipped with the no-receipt message.
  • Two keys for one repo (this path and an orchestrator's) are fine. The cwd path at v1 records a rule receipt, the orchestrator path moves the rule to v2, and the cwd path then reports "already current" and moves its receipt (TestProjectInit_TwoKeysForOneRepoAgree). A files: artifact is different: a files: artifact created through one path (subrepo or cwd) is user-authored to the other; only the creating path refreshes it. Equal bytes with no receipt prove equality, not authorship, so they are never adopted. This is a named limit, ruled by the architect (valid until 2026-10-22), pinned by TestProjectInit_AFilesArtifactCreatedByOnePathIsUserAuthoredToTheOther.

Migration note for the release notes: repos that ran project init before this have no receipts. Their files: artifacts stay user-authored, and their rules are adopted only when already current; otherwise the user deletes the file once and the next run writes it and records a receipt.

Acceptance:

  • the issue repro, flipped: 0.1.0 then 0.2.0 updates the file and the rule and records receipts for both
  • a user edit, then 0.3.0: the edit is kept and the reason is printed for both
  • with no identity file nothing is written under .sideshow; the derived key is the same from the repo path and from a symlink to it, and differs between repos
  • a dry run records nothing, changes nothing and prints the same reasons
  • a moved repo has no receipt, with the two messages above

Commits: (1) tests with a stub key, red; (2) the key, the wiring and the printed reasons.

Blast radius: runProjectInitForPack, one new function and constant in internal/project; the orchestrator path is unchanged.

Mutants, each compiles and fails a test:

  • the key ignores an identity file: killed by TestSingleRepoKey_UsesAnIdentityFileWhenThereIsOne
  • the root is not symlink-resolved: killed by TestSingleRepoKey_DerivedFromTheResolvedPathAndWritesNothing and the symlinked-cwd test
  • the key derivation writes an identity file: killed by the same key test and the pack-bump tests
  • RecordResults is not called: killed by the pack-bump test
  • RecordResults runs under --dry-run: killed by the dry-run test
  • skipped details are not printed: killed by the edit and dry-run tests
  • the rule priors are not passed: killed by the pack-bump and two-keys tests
  • the file priors are not passed: killed by the pack-bump test
  • the repo name is not taken from the resolved root: killed by the symlinked-cwd test

@arcavenai arcavenai left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. With this change, a repo set up with project init picks up a pack's corrected files and rules. A user's edit is kept and the reason is printed, and neither order of the two paths overwrote an edit in my runs. Before this, a pack fix never reached such a repo. On main, the issue repro still reproduces: v1 then v2 leaves a.cfg and the rule at v1, the registry stays empty, and the run prints "0 wrote, 2 already present" with no reason.

What I ran. I built both binaries (main 3cd4e2d, head 0f10ae4) and used a scratch HOME and SIDESHOW_HOME with the network denied. A test pack ck ships one files: artifact (a.cfg) and one rule (.claude/rules/r1.md) at v1, v2 and v3.

scenario (single-repo path) head
v1, then v2 file and rule updated, receipts move to v2; a third run reports "already current" for both
edit both, then v2, then v3 both kept with "modified since sideshow wrote it (user edit preserved)"; receipts stay at v1
edit the file, then v2, --dry-run the edit is kept with its reason printed; the registry is byte-identical before and after; the next real run updates the rule only
user-authored file before the first run kept with "exists and sideshow has no record of writing it (user-authored)" at v1 and v2
file hand-set to the v2 bytes, then v2 "already current" and the receipt moves; v3 then updates it
file deleted after v1 recreated; v2 updates it
v1 through the real path, v2 through a symlinked cwd (PWD set to the link) updated; one installation, root is the resolved path, repo name from it

No run wrote .sideshow/project.yaml inside the repo. The only one is at the orchestrator root, written by the init --scope project path, which this PR does not touch.

The files: two-path case, both orders.

  • cwd path first: the orchestrator path reads the file as user-authored at every version and records no file receipt. A later edit is kept by both paths.
  • orchestrator path first: the cwd path reads it as user-authored with no receipt. The orchestrator path refreshes it at v2, and a later edit is kept by both.
  • Created by the cwd path, deleted, then recreated by the orchestrator path: both paths hold a receipt. A later edit is still kept by both, because each path compares the bytes on disk to its own receipt, and an edit matches neither.

In no order was a user edit overwritten or lost, and the body's limit sentence matches what I saw.

Key stability (SingleRepoKey). These forms all give the same id and root:

  • the absolute path, with one or two trailing slashes
  • a symlink to the repo, with a trailing slash
  • a symlink to that symlink
  • .., ../ and ./.. from a subdirectory
  • a/sub/..

Two repos with the same basename under different parents get different ids. A repo with an identity file keeps its id after a move, with a new root, which is the accepted limit. The key derivation writes nothing into the repo.

Red and mutants.

  • Red at e1bc1f8: all 8 new tests fail on assertions, 40 of 40 across 5 runs. Head passes ./..., and the new tests pass with -count=20.
  • Mutants: each of the 9 listed is killed, by the tests the body names. So are two I added: skipping reg.Save(), and keying on the unresolved cwd. On 0f10ae4, the adopt-equal-bytes mutant (case !known && onDisk != sourceSum) fails the new two-path test.

Unchanged against main. 0f10ae4 adds only the test; no production code differs from 58437fa. The orchestrator path's output is byte-identical between the main and head binaries in my eight files: scenarios and eight rules: scenarios. CI on 0f10ae4 passes.

Non-blocking: the reason printed after a two-key rule lag is wrong. The case: the cwd path records a v1 rule receipt, the orchestrator path moves the rule to v2, and the pack is at v3 before the cwd path runs again. That run then prints "modified since sideshow wrote it (user edit preserved)" for a rule nobody edited. Nothing is lost: the next orchestrator run writes v3, and the cwd path then reports "already current" and moves its receipt. Only the reason is misleading, and only for that window. The body's "already current" sentence holds when the cwd path runs at the version the orchestrator path wrote.

Seat: reviewer / reviewer

@arcaven
arcaven marked this pull request as ready for review October 8, 2026 12:02
@arcaven
arcaven merged commit 33fa181 into main Oct 8, 2026
10 checks passed
@arcaven
arcaven deleted the build/sideshow/91-project-init-receipts branch October 8, 2026 12:15
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.

files: artifacts never refresh via 'project init' — no receipt on the single-repo path

2 participants