Skip to content

Implement 0.2 lockfile schema with per-file integrity records, exact version dispatch, and legacy-alpha migration - #454

Merged
eXamadeus merged 1 commit into
mainfrom
julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_
Jul 24, 2026
Merged

Implement 0.2 lockfile schema with per-file integrity records, exact version dispatch, and legacy-alpha migration#454
eXamadeus merged 1 commit into
mainfrom
julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_

Conversation

@eXamadeus

@eXamadeus eXamadeus commented Jul 23, 2026

Copy link
Copy Markdown
Member

Why

Tasks 9.1 and 9.2 of the non-asset-files spec: replace the legacy numeric-ordered lockfile version handling with exact version dispatch, and derive per-file integrity records from verified on-disk bytes rather than copying self-declared values from build manifests.

Details

Exact version dispatch (design D10). loadLockfile now delegates to parseLockfileDocument in the protocol package, which selects the legacy-alpha 1 schema or the current 0.2 schema by exact equality. An unknown version produces a structured unsupported-lockfile-version failure with an actionable "Upgrade the CLI" message. A malformed 0.2 document is never silently reinterpreted as legacy 1. The loaded version is surfaced as result.version so the orchestrator can branch on it without re-parsing the raw document.

Current lockfile version is 0.2. The version constant is renamed from LOCKFILE_VERSION to CURRENT_LOCKFILE_VERSION (value 0.2), with LEGACY_LOCKFILE_VERSION (1) introduced for the compatibility window. Version dispatch is by exact equality, so 0.2 is "current" even though it is numerically less than 1.

Per-file integrity via buildVerifiedAssetPlan. A new verified-asset-plan.ts module is the single producer of files[] records for every resolve path (registry, git, local). It classifies inner-archive entries using the shared protocol planArchiveEntries derivation (design D3/D12) and recomputes each file's hash from the verified on-disk bytes via computeDirIntegrity — never from a build manifest's self-declared value. The plan carries only paths, hashes, and ownership; no bytes.

Migration behavior. A normal install always writes the current 0.2 schema. When a legacy 1 lockfile is loaded and the content reproduces the locked integrity, the entry's identity (source/version/integrity) is preserved from the lockfile but assets[].files is re-derived from the verified slot, migrating the entry to 0.2. Frozen mode never rewrites the lockfile, so a legacy entry is inherited verbatim and the written lockfileVersion stays 1.

Asset ordering. Assets are sorted skills → agents → commands, then by name; each asset's files array is sorted lexicographically by path. This keeps lockfile diffs stable across installs.

Verification

CI. New and updated tests cover: fresh installs writing 0.2 lockfiles with recomputed per-file records; round-trip loading of both 0.2 and legacy 1 documents; exact version dispatch rejecting unsupported versions; migration of a legacy 1 lockfile to 0.2 on a normal (non-frozen) install; and frozen installs retaining the loaded version verbatim.


Note

Medium Risk
Changes core install lockfile I/O and how every facet source builds lockfile asset metadata; behavior diverges between normal migration and frozen no-rewrite paths, with broad test coverage but receipt/materialization follow-ups still pending.

Overview
Install now treats facets.lock 0.2 as current: each asset carries a files[] list of inner-archive paths with recomputed sha256 hashes, not identity-only rows. loadLockfile uses protocol parseLockfileDocument with exact version dispatch (1 vs 0.2, not numeric ordering), surfaces result.version, and bootstraps missing files at 0.2.

buildVerifiedAssetPlan is the single producer for lockfile asset records on registry, git, and local resolve paths: it classifies entries via planArchiveEntries and hashes verified on-disk bytes via computeDirIntegrity, replacing computeAssetList at commit time. loadFacetContent also returns the raw FacetManifest for that classification.

Migration: a normal install always writes lockfileVersion: 0.2 while preserving locked facet identity (source/version/integrity) and re-deriving files[] from the verified slot. Frozen installs inherit lockfile entries verbatim (legacy 1 stays 1) and keep the loaded top-level version.

Tests and the CLI install happy path assert 0.2 output, legacy 1 load, unsupported-version errors, and git cache-hit migration to per-file records.

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

Summary by CodeRabbit

  • New Features
    • Lockfiles now record per-file integrity information for installed assets.
    • New installations generate the current lockfile format with deterministic asset details.
  • Bug Fixes
    • Existing legacy lockfiles are automatically migrated during normal installations.
    • Frozen installs preserve locked asset information without rewriting it.
    • Lockfile validation now provides clearer errors for malformed or unsupported versions.
  • Compatibility
    • Legacy lockfiles remain readable during the compatibility window while new lockfiles use the current schema.

@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7a385fb

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

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: lockfile 0.2, exact version dispatch, and migration behavior.
Description check ✅ Passed The description matches the template with Why, Details, and Verification sections and covers the key implementation and testing points.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_

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.

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: fde47edfbd

ℹ️ 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 +146 to +148
if (key === undefined) {
throw new Error(`internal: companion "${entry.path}" references undeclared skill "${entry.skill}"`)
}

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 Handle companions that sort before SKILL.md

A valid skill companion such as README.md or .meta sorts before skills/<name>/SKILL.md in planArchiveEntries, so this branch runs before the corresponding primary-asset has populated skillKeyByName. The resulting throw escapes buildVerifiedAssetPlan (and therefore runInstall, which only releases its lock in finally) instead of returning the advertised BUILD_FAILED result, causing installation of otherwise valid facets to crash. Build the primary-asset lookup independently of iteration order, or return a discriminated failure here.

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

Useful? React with 👍 / 👎.

Comment thread packages/engine/src/install/commit/resolve-git.ts
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR implements exact version dispatch for lockfile I/O (replacing numeric-ordering with exact equality checks), introduces the 0.2 schema with per-file integrity records, and migrates a new buildVerifiedAssetPlan module as the single producer of files[] records across all four resolve paths (registry, git, local, and the frozen reproduction path).

  • Lockfile dispatch (lockfile-io.ts): loadLockfile now delegates to parseLockfileDocument, which selects legacy 1 or current 0.2 by exact equality; unknown versions produce a structured unsupported-lockfile-version failure; the loaded version is surfaced on the result so run-install.ts can write CURRENT_LOCKFILE_VERSION on normal installs and retain the on-disk version on frozen installs.
  • buildVerifiedAssetPlan (verified-asset-plan.ts): New module that classifies archive entries via the shared planArchiveEntries derivation and recomputes per-file hashes from verified on-disk bytes, replacing the old computeAssetList in all three source resolvers. It returns a VerifiedAssetPlan with assets[] (for the lockfile) and archiveOnly[] (reserved for task 9.3 reconciliation).
  • Migration: Normal installs always write 0.2 (re-deriving files[] from the verified slot even for audited cache hits so a legacy 1 lockfile migrates); frozen installs inherit entries verbatim and never rewrite the lockfile version.

Confidence Score: 4/5

The PR is safe to merge for skill bundles with no companions or companions whose names sort after SKILL.md; a skill with any companion path that sorts before SKILL.md (e.g. README.md, AGENTS.md) will throw during plan construction and abort the install.

The companion-ordering panic in verified-asset-plan.ts will throw on any real-world skill bundle that declares a companion whose inner-archive path sorts before skills/<name>/SKILL.md. Because all four resolve paths now use this function, the failure affects every source kind (registry, git, local). Everything else in the PR — version dispatch, frozen-mode inheritance, migration logic, and the test coverage — is correctly implemented.

packages/engine/src/install/verified-asset-plan.ts — the single-pass companion grouping loop.

Reviews (3): Last reviewed commit: "Migrate lockfile to `0.2` with per-file ..." | Re-trigger Greptile

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fccd5d08-68f0-4d11-b616-80b7c41a640f

📥 Commits

Reviewing files that changed from the base of the PR and between 8840e4d and fde47ed.

📒 Files selected for processing (13)
  • openspec/changes/support-non-asset-files/tasks.md
  • packages/cli/src/commands/install/__tests__/install-cli.test.ts
  • packages/engine/src/__tests__/run-install.test.ts
  • packages/engine/src/install/__tests__/lockfile-io.test.ts
  • packages/engine/src/install/__tests__/run-remove.test.ts
  • packages/engine/src/install/commit/finalize-facet.ts
  • packages/engine/src/install/commit/resolve-facet.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/lockfile-io.ts
  • packages/engine/src/install/run-install.ts
  • packages/engine/src/install/verified-asset-plan.ts

Comment on lines +4 to +11
import {
CURRENT_LOCKFILE_VERSION,
type LEGACY_LOCKFILE_VERSION,
type Lockfile,
type LockfileParseFailure,
parseLockfileDocument,
SUPPORTED_LOCKFILE_VERSIONS,
} from '@agent-facets/protocol'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Locate relevant files:"
fd -a 'lockfile-io\.ts$|.*protocol.*' . | sed 's#^\./##' | head -100

echo
echo "File outline:"
ast-grep outline packages/engine/src/install/lockfile-io.ts --view expanded || true

echo
echo "Relevant lockfile-io.ts lines:"
cat -n packages/engine/src/install/lockfile-io.ts | sed -n '1,80p'

echo
echo "Search lockfile version definitions:"
rg -n "LEGACY_LOCKFILE_VERSION|CURRENT_LOCKFILE_VERSION|SUPPORTED_LOCKFILE_VERSIONS|type LoadedLockfileVersion" packages -S

Repository: agent-facets/facets

Length of output: 12545


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "Protocol lockfile version exports:"
cat -n packages/protocol/src/schemas/lockfile.ts | sed -n '1,45p'

echo
echo "Protocol index exports around constants:"
cat -n packages/protocol/src/index.ts | sed -n '138,152p'

echo
echo "TypeScript availability:"
type tsc || true

if command -v tsc >/dev/null 2>&1; then
  tmp="$(mktemp -d)"
  cat > "$tmp/import-type-about.ts" <<'TS'
import { LEGACY_LOCKFILE_VERSION, CURRENT_LOCKFILE_VERSION } from './@agent-facets/protocol'
export type LoadedLockfileVersion = typeof LEGACY_LOCKFILE_VERSION | typeof CURRENT_LOCKFILE_VERSION
declare const _: LoadedLockfileVersion
console.log(_)
TS
  (cd "$tmp" && tsc --noEmit --strict import-type-about.ts) || true
fi

echo
echo "Standalone TypeScript import-type about typeof semantics:"
if command -v tsc >/dev/null 2>&1; then
  tmp="$(mktemp -d)"
  cat > "$tmp/lib.ts" <<'TS'
export const LEGACY_LOCKFILE_VERSION = 1
export const CURRENT_LOCKFILE_VERSION = 0.2
TS
  cat > "$tmp/main.ts" <<'TS'
import { LEGACY_LOCKFILE_VERSION, CURRENT_LOCKFILE_VERSION } from './lib'
import { type LEGACY_LOCKFILE_VERSION as TypeOnlyLEGACY, CURRENT_LOCKFILE_VERSION as CurrentImport } from './lib'
export type LoadedLockfileVersion = typeof LEGACY_LOCKFILE_VERSION | typeof CURRENT_LOCKFILE_VERSION
export type LoadedLockfileVersionFromTypeImport = typeof TypeOnlyLEGACY | typeof CurrentImport
TS
  (cd "$tmp" && tsc --noEmit --strict main.ts && echo "compile ok") || (cd "$tmp" && tsc --noEmit --strict main.ts) >/tmp/tsc_out.txt 2>&1
  cat /tmp/tsc_out.txt
fi

Repository: agent-facets/facets

Length of output: 3332


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

tmp="$(mktemp -d)"
cat > "$tmp/lib.ts" <<'TS'
export const LEGACY_LOCKFILE_VERSION = 1
export const CURRENT_LOCKFILE_VERSION = 0.2
TS

cat > "$tmp/main.ts" <<'TS'
import { LEGACY_LOCKFILE_VERSION, CURRENT_LOCKFILE_VERSION } from './lib'
import type { LEGACY_LOCKFILE_VERSION as TypeOnlyLegacy, CURRENT_LOCKFILE_VERSION as TypeOnlyCurrent } from './lib'

export type LoadedLockfileVersionFromValueImports = typeof LEGACY_LOCKFILE_VERSION | typeof CURRENT_LOCKFILE_VERSION
export type LoadedLockfileVersionFromTypeImports = typeof TypeOnlyLegacy | typeof TypeOnlyCurrent
export type LoadedLockfileVersionMixed = typeof TypeOnlyLegacy | typeof CURRENT_RUNTIME_IMPORT
import { CURRENT_LOCKFILE_VERSION as CURRENT_RUNTIME_IMPORT } from './lib'
TS

(
  cd "$tmp"
  echo "compiler:"
  tsc --version
  echo
  tsc --noEmit --strict main.ts
) || true

Repository: agent-facets/facets

Length of output: 182


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

tmp="$(mktemp -d)"
cat > "$tmp/lib.ts" <<'TS'
export const LEGACY_LOCKFILE_VERSION = 1
export const CURRENT_LOCKFILE_VERSION = 0.2
TS

cat > "$tmp/main.ts" <<'TS'
import { LEGACY_LOCKFILE_VERSION, CURRENT_LOCKFILE_VERSION } from './lib'
import type { LEGACY_LOCKFILE_VERSION as TypeOnlyLegacy, CURRENT_LOCKFILE_VERSION as TypeOnlyCurrent } from './lib'

export type LoadedLockfileVersionFromValueImports = typeof LEGACY_CHECK | typeof CURRENT_CHECK
const TYPE_ONLY_LEGACY_CHECK = TypeOnlyLegacy
const TYPE_ONLY_CURRENT_CHECK = TypeOnlyCurrent
export type LoadedLockfileVersionMixed = typeof LEGACY_CHECK | typeof CURRENT_CHECK

import { LEGACY_LOCKFILE_VERSION as LEGACY_CHECK, CURRENT_LOCKFILE_VERSION as CURRENT_CHECK } from './lib'
TS

(
  cd "$tmp"
  echo "compiler:"
  tsc --version
  echo
  tsc --noEmit --strict main.ts
) || true

Repository: agent-facets/facets

Length of output: 417


Use a value import for LEGACY_LOCKFILE_VERSION.

LEGACY_LOCKFILE_VERSION is declared as export const in protocol, but in lockfile-io.ts it is imported with the per-specifier type modifier while LoadedLockfileVersion uses typeof LEGACY_LOCKFILE_VERSION. Drop the type modifier on that import specifier; the import itself is not used as a runtime value here, and the type import can remain if LEGACY_LOCKFILE_VERSION is imported as a value from protocol for this file.

@eXamadeus
eXamadeus force-pushed the julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_ branch from fde47ed to 92429da Compare July 24, 2026 04:03

@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 2 total unresolved issues (including 1 from previous review).

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 92429da. Configure here.

scope: 'project',
type: entry.assetType,
name: entry.name,
files: [{ path: entry.path, integrity: hashFor(entry.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.

Migration resets asset scope to project

Medium Severity

The buildVerifiedAssetPlan function hardcodes asset scope to 'project'. This overwrites existing user or system scopes during lockfile migration or re-derivation, which can change materialization targets and drift deletion keys.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 92429da. Configure here.

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:49 AM UTC: Graphite rebased this pull request as part of a merge.
  • Jul 24, 4:49 AM UTC: @eXamadeus merged this pull request with Graphite.

@eXamadeus
eXamadeus changed the base branch from julian/07-20-replace_positional_adapter_asset_contract_with_tagged_request_result_unions_and_atomic_skill-bundle_helpers to graphite-base/454 July 24, 2026 04:47
@eXamadeus
eXamadeus changed the base branch from graphite-base/454 to main July 24, 2026 04:47
…ion dispatch, and `buildVerifiedAssetPlan`
@eXamadeus
eXamadeus force-pushed the julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_ branch from 92429da to 7a385fb Compare July 24, 2026 04:48
@mintlify

mintlify Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
AgentFacets 🟢 Ready View Preview Jul 24, 2026, 4:49 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@eXamadeus
eXamadeus merged commit 5f4a77d into main Jul 24, 2026
7 checks passed
@eXamadeus
eXamadeus deleted the julian/07-22-migrate_lockfile_to_0.2_with_per-file_integrity_records_exact_version_dispatch_and_buildverifiedassetplan_ branch July 24, 2026 04:49

@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: 7a385fb878

ℹ️ 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".

// verbatim, and it never reaches this build branch (an audited hit is
// required for frozen reproduction; a frozen rebuild would fail the
// one-check reproduction guard rather than rewrite).
const plan = buildVerifiedAssetPlan(content.manifest, sourceDir)

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 Verify every planned file before recording its hash

For a git facet that declares a skill companion or top-level supplementary file, runBuildPipeline still produces buildManifest.assets from the legacy primary-only collector, so the cache sidecar and its later self-audit do not cover that declared file. This new call then reads that unverified file from the cache and writes its hash into the 0.2 lockfile; a companion modified after caching therefore passes the cached integrity check and is silently re-pinned on a normal install. Include the complete archive plan in the build/cache verification set (or verify this plan against a complete trusted archive) before deriving files[].

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