Shared guidance ownership lock¶
guidance-lock/v1 is the cross-product ownership contract for files physically
materialized by BaseCoat, Sheen, and Adhesion.
Canonical location¶
The single lock is .github/base-coat/guidance-lock.json in the consumer
repository. Sheen and Adhesion must read and update that file even when their
own product payload is installed elsewhere. The reference JSON Schema is
installed at .github/base-coat/schemas/guidance-lock-v1.schema.json, and the
portable PowerShell reader/writer is installed at
.github/base-coat/scripts/guidance-lock.ps1.
The lock covers files below these physical shared destinations:
.github/skills/.github/agents/.github/agent-references/.github/instructions/.github/prompts/.agents/skills/
.github/agent-references/ is listed explicitly as a shared destination, outside
.github/agents/ because Copilot CLI parses every markdown file under that tree as a
custom agent (#3766). Sync removes a legacy .github/agents/references/ file only
when it can verify the file is unchanged BaseCoat content, by matching the previous
asset-manifest SHA or the current source. Modified or unverifiable files are kept and
left unmanaged, so Copilot CLI may still warn about them until you remove or move them.
Contract¶
Each entry records:
path: normalized repository-relative path using/owner: stable lowercase product identifier (basecoat,sheen, oradhesion)guidanceUnit: optional source unit or source-relative asset pathsourceVersion: optional product or unit versionsha256: lowercase SHA-256 of UTF-8 text with CRLF and CR line endings canonicalized to LF
Entries are sorted by path. Paths must be files, must be under an allowed
shared destination, and must not be rooted, contain . or .. segments, or
escape the repository through normalization. Duplicate paths and malformed
locks fail closed with GUIDANCE_LOCK_INVALID.
Paths use the portable [A-Za-z0-9._/+@()-] vocabulary. Optional metadata uses
[A-Za-z0-9._/+:-] (maximum 512 characters), so PowerShell and
dependency-free Bash readers produce the same deterministic representation.
Required synchronization behavior¶
Before changing any shared destination, a product must preflight the complete write and removal plan:
- A different owner already claiming a path blocks with
GUIDANCE_PATH_COLLISION; no planned shared file is changed. - An existing unclaimed path also blocks as owner
unmanaged. - A same-owner update is allowed only when the installed SHA-256 equals the
lock's expected hash. A legacy raw-byte hash may migrate only when the
current destination is canonically identical to the exact planned owner
payload. Every other mismatch blocks with
GUIDANCE_CONTENT_MODIFIED, including expected and actual hashes. - A product may remove a stale path only when the lock names that product as owner and the installed hash still matches. Foreign stale entries and files are preserved.
- A cross-platform exclusive lease at
.github/base-coat/guidance-lock.lease/serializes the read, preflight, shared-file mutation, and lock publication transaction. Contenders wait up to 30 seconds, then fail withGUIDANCE_LOCK_BUSY. The lease contains a unique ownership token and acquisition epoch. A lease older than 10 minutes is reclaimed by atomically renaming it before deletion; release removes a lease only when the token still matches its owner. - The lock is written by same-directory temporary file plus atomic replace after all planned file operations succeed. If a file operation fails first, the old lock remains and the next run fails closed on any partial content change.
Products must retain foreign entries unchanged and must never maintain a second ownership tracker.
BaseCoat migration¶
When the JSON lock does not exist, BaseCoat imports
.github/base-coat/.overlay-managed-files as owner: basecoat. Existing file
bytes become the migration baseline hash because the legacy tracker did not
record hashes. BaseCoat also retains the post-#3422 fallback that reconstructs
legacy paths from the previously installed asset manifest when neither state
file exists.
After a successful sync, BaseCoat atomically writes guidance-lock.json and
removes .overlay-managed-files. If both files exist, the JSON lock is
authoritative and the legacy tracker is removed only after successful lock
publication. This prevents two unsynchronized ownership sources.
PowerShell reference¶
. .github/base-coat/scripts/guidance-lock.ps1
$lock = Read-GuidanceLock -RepoRoot (git rev-parse --show-toplevel)
$entry = New-GuidanceLockEntry `
-RepoRoot (git rev-parse --show-toplevel) `
-Path '.github/agents/example.agent.md' `
-Owner 'adhesion' `
-GuidanceUnit 'dist/agents/example.agent.md' `
-SourceVersion '0.7.0' `
-Sha256 '<64 lowercase hex characters>'
Write-GuidanceLock -RepoRoot (git rev-parse --show-toplevel) `
-Entries (@($lock.entries) + $entry)
Callers remain responsible for the preflight rules above; the reusable functions provide strict normalization, validation, hashing, reading, and atomic writing.