Skip to content

Complete per-file skill materialization, reconciliation, receipt 0.2 ownership, and offline companion cleanup - #455

Merged
eXamadeus merged 1 commit into
mainfrom
julian/07-23-plumb_companion_bytes_owned-path_sets_and_pre-materialization_reconciliation_through_install_receipt_and_cli_rendering
Jul 24, 2026
Merged

Complete per-file skill materialization, reconciliation, receipt 0.2 ownership, and offline companion cleanup#455
eXamadeus merged 1 commit into
mainfrom
julian/07-23-plumb_companion_bytes_owned-path_sets_and_pre-materialization_reconciliation_through_install_receipt_and_cli_rendering

Conversation

@eXamadeus

@eXamadeus eXamadeus commented Jul 23, 2026

Copy link
Copy Markdown
Member

Why

Completes the non-asset file materialization work (tasks 9.3–9.11): skill companion files (e.g. references/api.md, assets/logo.bin) are now fully materialized, tracked, repaired, and removed alongside their primary SKILL.md, with all state changes committed transactionally and all failure modes surfaced with exact diagnostic output.

Details

Pre-materialization reconciliation (9.3). Before any adapter write, when the same artifact is being reproduced (identical facet-level integrity), the previously-locked 0.2 entry is checked against the freshly-derived verified asset plan. Mismatches produce four structured failure codes — RECONCILE_FACET_INTEGRITY, RECONCILE_ASSET_IDENTITY, RECONCILE_OWNED_PATH_SET, and RECONCILE_PER_FILE_INTEGRITY — each carrying the exact path or identity that diverged. Because reconciliation runs before materialization, a mismatch leaves all project, lockfile, receipt, and adapter state untouched. Legacy (1) entries skip per-file reconciliation (migration is intentionally permissive at the file level).

Receipt 0.2 ownership (9.4). The receipt now carries a files: string[] array per asset recording the exact inner-archive paths materialized for it. Legacy (1) receipts are refined on load to primary-only ownership (one conventional path per asset). Owned file paths are validated for path traversal on load; a bad path drops the whole asset record rather than partially trusting it.

Transactional commit and frozen consistency gate (9.5). The frozen consistency gate (lockfile drift detection) now explicitly completes before receipt-driven cleanup begins. A frozen install that would reject an orphaned lockfile entry fails before any materialized asset is deleted, leaving adapter state untouched.

Companion materialization with skip/repair and rollback (9.6). materialize now receives companion bytes (keyed skill:<name>) from the resolver's verified asset plan and passes them through to adapter install/read/delete requests with the engine-verified owned-path set. Skip-if-identical compares the full bundle (primary + every companion); a single drifted companion forces an atomic bundle replacement. Rollback preimages capture the complete prior bundle so an undo restores the exact previous state.

Path-specific drift and offline removal (9.7). Drift reporting names the exact companion paths that changed (drifted, added, or removed). Removal passes the receipt's owned-path set to the adapter delete request so every owned companion is deleted offline without cache or network access, while unowned files in the skill directory are never touched.

Archive compatibility table (9.8). A single archiveCompatibilityGuidance utility maps a known archive format to the minimum agent-facets release that supports it, or advises updating to latest for an unknown future format without inventing a minimum version. The UNSUPPORTED_ARCHIVE registry error path now uses this table. The CLI renders all four reconciliation failure codes with exact paths and both hash values.

readSkillCompanionBytes. A new helper reads companion bytes verbatim from the verified directory, converting full inner-archive paths to skill-root-relative form for the adapter contract. It is called by all three resolvers (local, git, registry) immediately after buildVerifiedAssetPlan and threaded through ResolvedFacet alongside the plan.

Verification

New and updated tests cover: per-file reconciliation abort before any write; full companion install with binary file round-trip; skip-identical bundle; single-companion drift detection and repair with exact log output; unowned file preservation across update and removal; offline receipt-driven multi-file removal; archive-only file withholding (verified but never materialized or locked); interrupted-install convergence; frozen orphan rejection before cleanup; legacy receipt refinement; owned-path escape containment; and the archive compatibility table for known, unknown, and missing format versions. All focused install, materialization, receipt, lockfile, cache, registry, and CLI install tests pass.


Note

Medium Risk
Changes core install commit, materialization, and receipt semantics (multi-file deletes and frozen ordering); mistakes could corrupt adapter trees or skip cleanup, but failures are fail-closed with extensive test coverage.

Overview
Finishes tasks 9.3–9.11 for non-asset / multi-file skill support: companions are installed, reconciled, repaired, removed offline, and surfaced in the CLI with structured errors.

Engine — before any adapter write, reconcileLockedAgainstPlan compares a reproduced 0.2 lockfile entry to the freshly derived verified asset plan (asset identities, owned path sets, per-file hashes). Mismatches return RECONCILE_* failures and leave lockfile, receipt, and adapter state unchanged. Resolvers thread plan and readSkillCompanionBytes through ResolvedFacet.

Materialization passes companion bytes and engine-owned path sets into tagged adapter skill requests; skip-if-identical covers the full bundle; rollback journals restore primary + companions; drift logs name specific companion paths; deletes use receipt/lockfile owned paths and leave unowned files alone.

Receipt 0.2 stores per-asset files[], validates untrusted paths on load, refines legacy 1 to primary-only ownership, and mirrors lockfile paths in tri-write bootstrap.

Frozen installs run lockfile drift (including orphans) before receipt-driven cleanup so assets are not deleted when frozen would reject the state.

CLI adds archiveCompatibilityGuidance for UNSUPPORTED_ARCHIVE, plus install UI for all four reconcile failure variants (path + hashes where applicable).

Tests cover reconciliation abort, multi-file skill lifecycle, offline removal, archive-only withholding, partial-install convergence, frozen orphan ordering, receipt legacy/escape handling, and compatibility guidance.

Reviewed by Cursor Bugbot for commit 82a46cf. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@eXamadeus, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 1 minute

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 504eff85-ac2b-4223-9e8b-1ee183c293ae

📥 Commits

Reviewing files that changed from the base of the PR and between 5f4a77d and 82a46cf.

📒 Files selected for processing (22)
  • openspec/changes/support-non-asset-files/tasks.md
  • packages/cli/src/__tests__/install-view.test.tsx
  • packages/cli/src/tui/views/install/facet-row.tsx
  • packages/cli/src/tui/views/install/failure-block.tsx
  • packages/cli/src/util/__tests__/archive-compatibility.test.ts
  • packages/cli/src/util/archive-compatibility.ts
  • packages/cli/src/util/registry-errors.ts
  • packages/engine/src/__tests__/run-install.test.ts
  • packages/engine/src/install/__tests__/receipt.test.ts
  • packages/engine/src/install/__tests__/run-install.receipt.test.ts
  • packages/engine/src/install/commit/install-loop.ts
  • packages/engine/src/install/commit/reconcile.ts
  • packages/engine/src/install/commit/resolve-git.ts
  • packages/engine/src/install/commit/resolve-local.ts
  • packages/engine/src/install/commit/resolve-registry.ts
  • packages/engine/src/install/commit/tri-write.ts
  • packages/engine/src/install/commit/types.ts
  • packages/engine/src/install/materialize.ts
  • packages/engine/src/install/receipt.ts
  • packages/engine/src/install/run-install.ts
  • packages/engine/src/install/types.ts
  • packages/engine/src/install/verified-asset-plan.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julian/07-23-plumb_companion_bytes_owned-path_sets_and_pre-materialization_reconciliation_through_install_receipt_and_cli_rendering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 82a46cf

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

eXamadeus commented Jul 23, 2026

Copy link
Copy Markdown
Member Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30f2eb4996

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +187 to +188
const companions = opts.companionBytes?.get(assetKey(asset)) ?? {}
const ownedCompanionPaths = oldOwnedByKey.get(assetKey(asset)) ?? []

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve companion bytes during frozen reproduction

For every frozen reproduction path, companionBytes is left undefined; this fallback therefore sends an empty companion bundle while still passing the lockfile's existing owned paths. The adapter treats that as a replacement and deletes every owned companion absent from {}. Consequently, facet install --frozen-lockfile for an already-installed 0.2 skill with companions can silently strip all of its companion files even though the verified artifact and lockfile have not changed.

Useful? React with 👍 / 👎.

// converges the bundle back to its prior state without touching unowned
// files. Companion-less skills and single-file assets restore exactly
// as before.
const restoreOwned = previous ? Object.keys(previous.companions) : ownedCompanionPaths

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include newly written companions in rollback ownership

When an existing skill update adds a companion and a later operation fails, restoreOwned contains only the companions that existed before the update. The rollback install restores the old companion map but does not list the newly added path as owned, so the adapter preserves that new file rather than removing it. This leaves a partial new bundle after a reported rollback; the ownership set must include paths from both the prior and newly installed bundles.

Useful? React with 👍 / 👎.

Comment on lines 102 to +105
adapters: [...adapters],
oldAssets,
newAssets: entry.assets,
companionBytes,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use receipt ownership when replacing an installed skill

This replacement path derives oldAssets solely from the checked-out lockfile, despite the receipt being the record of what this machine actually materialized. For example, after a pull changes a skill from v1 (with references/old.md) to v2 (without it), the current lockfile no longer supplies old.md as an owned path, so the v2 install preserves that v1 companion and then rewrites the receipt to v2, losing the only cleanup record. Feed the matching receipt asset ownership into replacement materialization so obsolete companions are removed.

Useful? React with 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR completes tasks 9.3–9.11 of the non-asset file materialization work, wiring skill companion files (e.g. references/api.md, assets/logo.bin) through the full install pipeline — reconciliation, materialization, receipt ownership, and offline removal — transactionally and with exact diagnostic output.

  • Reconciliation (9.3): reconcileLockedAgainstPlan runs before any adapter write when the same artifact is reproduced, emitting RECONCILE_ASSET_IDENTITY, RECONCILE_OWNED_PATH_SET, and RECONCILE_PER_FILE_INTEGRITY on mismatch without touching lockfile, receipt, or adapter state. A fourth declared code (RECONCILE_FACET_INTEGRITY) is defined in types and rendered by the CLI but is never emitted — VerifiedAssetPlan carries no facet-level integrity to compare against the locked entry.
  • Receipt 0.2 (9.4): The receipt now stores per-asset owned file paths; legacy 1 receipts refine to primary-only ownership on load, and untrusted path traversal drops the whole asset record rather than partially trusting it.
  • Companion materialization (9.6/9.7): materialize installs full skill bundles using adapter-verified companion bytes, compares the full bundle for skip-if-identical, names exact drifted companion paths in verbose logs, and uses the receipt's owned-path set for offline multi-file removal without touching unowned files.

Confidence Score: 5/5

Safe to merge. All three install path variants (local, git, registry) correctly thread companion bytes and owned-path sets; fail-closed reconciliation leaves state untouched on mismatch; legacy receipt refinement is backward-compatible.

The core install commit path (lockfile, receipt, materialize, frozen ordering) is covered by comprehensive new tests spanning binary round-trips, drift detection, offline removal, legacy migration, and interrupted-install convergence. The one gap — RECONCILE_FACET_INTEGRITY defined but never emitted — does not cause incorrect runtime behavior and does not affect the correctness of the three implemented reconcile checks.

packages/engine/src/install/commit/reconcile.ts — the RECONCILE_FACET_INTEGRITY failure code is rendered in the CLI but unreachable from the engine.

Reviews (3): Last reviewed commit: "Plumb companion bytes, owned-path sets, ..." | Re-trigger Greptile

Comment thread packages/engine/src/install/materialize.ts
Comment thread packages/engine/src/install/commit/resolve-registry.ts
Comment thread packages/engine/src/install/materialize.ts
* - `per-file-integrity` — a locked per-file hash does not equal the
* recomputed archive-entry hash for that exact path.
*/
| { code: 'RECONCILE_FACET_INTEGRITY'; facet: string; expected: string; actual: string }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused reconcile facet failure code

Low Severity

RECONCILE_FACET_INTEGRITY is part of RunInstallFailure and fully rendered in the CLI, but reconcileLockedAgainstPlan never constructs it. Facet integrity only gates whether reconciliation runs; mismatches surface elsewhere or are skipped. The dead variant adds maintenance cost and implies a diagnostic path that cannot occur.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 30f2eb4. Configure here.

Comment thread packages/engine/src/install/verified-asset-plan.ts

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f43bc42e40

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Every skill companion path is derived by `planArchiveEntries` to live
// below the skill root, so this prefix strip is total.
const relative = file.path.startsWith(skillRoot) ? file.path.slice(skillRoot.length) : file.path
companions[relative] = new Uint8Array(readFileSync(join(verifiedDir, file.path)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return a structured failure when companion reads fail

If a companion becomes unreadable after buildVerifiedAssetPlan hashes it—for example, a local source is edited concurrently or a cache file is removed—this synchronous read throws. Every resolver invokes this helper without catching that exception, so runInstall rejects instead of returning its documented RunInstallResult failure and cannot report or roll back cleanly. Convert this read failure into a discriminated result that the resolver maps to an install failure.

AGENTS.md reference: AGENTS.md:L172-L178

Useful? React with 👍 / 👎.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f43bc42. Configure here.

Comment thread packages/engine/src/install/materialize.ts

eXamadeus commented Jul 24, 2026

Copy link
Copy Markdown
Member Author

Merge activity

  • Jul 24, 4:47 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 24, 4:51 AM UTC: Graphite rebased this pull request as part of a merge.
  • Jul 24, 4:51 AM UTC: @eXamadeus merged this pull request with Graphite.

@eXamadeus
eXamadeus changed the base branch from julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_ to graphite-base/455 July 24, 2026 04:48
@eXamadeus
eXamadeus changed the base branch from graphite-base/455 to main July 24, 2026 04:49
…ciliation through install, receipt, and CLI rendering
@eXamadeus
eXamadeus force-pushed the julian/07-23-plumb_companion_bytes_owned-path_sets_and_pre-materialization_reconciliation_through_install_receipt_and_cli_rendering branch from f43bc42 to 82a46cf Compare July 24, 2026 04:50
@eXamadeus
eXamadeus merged commit a395ebe into main Jul 24, 2026
7 checks passed
@eXamadeus
eXamadeus deleted the julian/07-23-plumb_companion_bytes_owned-path_sets_and_pre-materialization_reconciliation_through_install_receipt_and_cli_rendering branch July 24, 2026 04:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82a46cf149

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

if (asset.type !== 'skill') continue
const skillRoot = `skills/${asset.name}/`
const primary = `skills/${asset.name}/SKILL.md`
const companions: SkillCompanionBytes = {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Store companion bytes in a null-prototype map

A valid companion path can be exactly __proto__: the archive-plan path grammar permits it, but assigning it to this ordinary {} object invokes the inherited __proto__ setter instead of creating an own property. Object.entries(companions) in the adapter then omits that companion, so installing a skill that declares files: ['__proto__'] records it in the lockfile/receipt but never writes it to the adapter. Initialize this map with Object.create(null) (as the adapter's read path does) so every validated path is represented.

Useful? React with 👍 / 👎.

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