Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"approvals": [
{
"gate": "definition",
"actor": "Bruno",
"timestamp": 1790795582,
"digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"note": "Bruno explicitly answered Yes, use those criteria to EVIDENCE-1 and EVIDENCE-2 for issue #789. Scope approval only; implementation review is pending.",
"approved_scope": {
"schema_version": 1,
"change_id": "support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes",
"title": "Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes",
"description": "Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes",
"kind": "bug_fix",
"affected_specs": [
"change"
],
"affected_paths": [
"hi/evidence.md",
"src/change.rs",
"src/change_tests.rs"
],
"no_spec_change": false,
"no_spec_change_rationale": null,
"acceptance_criteria": [
"I can complete verification in a repository containing Git LFS assets. I can trust verification to detect changed asset content, whether assets are downloaded or represented by LFS pointers. Preserve whole-workspace coverage and fail closed for unsupported filters."
],
"dependencies": [],
"supersedes": [],
"answers": {
"architecture_risk": "yes",
"public_contract": "yes"
}
},
"approved_delta_digests": {
"change": "e3d6ae04b211b108bbcd4fab24f53ea493c90e47639cf6cbdcaaf3971844c366"
}
}
],
"reopenings": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
id: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
state: implementing
type: bug_fix
base_commit: 6a47f2cd0c6dc5dfa4168b79ac6dda960966bcfc
---

# Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes

## Intent

Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes

## Affected Canonical Specs

- `change`

## Acceptance Criteria

- I can complete verification in a repository containing Git LFS assets. I can trust verification to detect changed asset content, whether assets are downloaded or represented by LFS pointers. Preserve whole-workspace coverage and fail closed for unsupported filters.

## No-spec Rationale

Not applicable
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: context
---

# Context

Fix CorvidLabs/spec-sync#789. The current whole-workspace evidence collector rejects unchanged LFS assets even outside source scope and ignored_paths. Bruno confirmed EVIDENCE-1 and EVIDENCE-2 before implementation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Added

### REQUIREMENT REQ-change-103

Canonical evidence SHALL support ordinary SHA-256 Git LFS v1 files without dropping them from evidence or invoking LFS filters.

Acceptance Criteria
- A valid pointer and its hydrated content have the same canonical payload and digest, including in-scope assets.
- Modified content, pointer targets, executable modes and deletion remain detectable.
- Hydrated content is streamed with bounded memory; it cannot exceed the aggregate evidence buffer by its raw size.
- Unsupported pointer extensions, malformed pointer-like inputs and other Git content filters remain errors.
- No LFS download or external clean/smudge/process filter is needed to collect evidence.
- Existing sparse, topology, exact attribute-output and stable capture checks remain in force.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: design
---

# Design

Issue #789: support LFS v1 canonical evidence without filters or network calls.
- Keep entire evidence inventory; do not exclude asset paths through ignored_paths.
- Recognize filter=lfs explicitly and continue refusing custom filters/encoding/ident.
- Read attributed paths directly, including paths Git calls clean, to avoid hiding hydrated changes.
- Canonicalize plain SHA-256 LFS v1 pointers and streamed hydrated bytes to the same pointer payload; preserve index identity and file mode.
- Fail closed on unsupported pointer extensions and malformed pointer-like input; support the empty-file special case.
- Preserve sparse/missing/symlink guards and double-capture race detection.
- Disable LFS process/clean/smudge during evidence Git queries; do not invoke LFS or download content.
- Tests: pointer/hydrated parity, same-length changed bytes, edited pointers, staged changes, deletion, bad/extended pointers, other attributes, custom filter non-execution, full change-check with unchanged out-of-scope LFS.
- Full build/pre-push currently blocked by unavailable cached serde-saphyr; no fetch/install authorized.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: docs
---

# Docs

Document supported plain SHA-256 v1 pointers, streamed hydrated evidence, equivalent representation, the empty-file case, explicit errors for unsupported pointer formats, and unchanged ignored_paths semantics. No new CLI flag or dependency.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: plan
---

# Plan

Implement explicit LFS classification and streamed canonical payload collection, preserve existing guards, add unit and lifecycle regressions, update module contract and run offline checks. Do not claim full validation if cached dependencies are missing.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: requirements
---

# Requirements

### REQ-change-103

Canonical evidence SHALL support ordinary SHA-256 Git LFS v1 files without dropping them from evidence or invoking LFS filters.

Acceptance Criteria
- A valid pointer and its hydrated content have the same canonical payload and digest, including in-scope assets.
- Modified content, pointer targets, executable modes and deletion remain detectable.
- Hydrated content is streamed with bounded memory; it cannot exceed the aggregate evidence buffer by its raw size.
- Unsupported pointer extensions, malformed pointer-like inputs and other Git content filters remain errors.
- No LFS download or external clean/smudge/process filter is needed to collect evidence.
- Existing sparse, topology, exact attribute-output and stable capture checks remain in force.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: research
---

# Research

inspect_git_candidates validates attributes before substituting clean index blobs. It currently rejects filter=lfs, and dirty hydrated content would otherwise be buffered wholesale. Git LFS v1 specifies SHA-256, byte size and a bounded pointer; empty files pass through unchanged. Keep all governed paths rather than reinterpret ignored_paths.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"schema_version": 1,
"workflow_version": 2,
"workflow_origin_version": 2,
"id": "support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes",
"slug": "support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes",
"title": "Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes",
"description": "Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes",
"kind": "bug_fix",
"state": "verifying",
"canonical_applied": true,
"base_commit": "6a47f2cd0c6dc5dfa4168b79ac6dda960966bcfc",
"created_at": 1790795521,
"updated_at": 1790798366,
"affected_specs": [
"change"
],
"affected_paths": [
"src/change.rs",
"src/change_tests.rs",
"hi/evidence.md"
],
"no_spec_change": false,
"no_spec_change_rationale": null,
"acceptance_criteria": [
"I can complete verification in a repository containing Git LFS assets. I can trust verification to detect changed asset content, whether assets are downloaded or represented by LFS pointers. Preserve whole-workspace coverage and fail closed for unsupported filters."
],
"selected_artifacts": [
"context",
"testing",
"tasks",
"requirements",
"docs",
"research",
"design",
"plan"
],
"dependencies": [],
"answers": {
"architecture_risk": "yes",
"public_contract": "yes"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: tasks
---

# Tasks

- [x] Implement LFS evidence collection.
- [x] Add parity, mutation, failure and lifecycle regressions.
- [x] Synchronize specs and record actual validation.
- [x] Pass pre-push gate before publishing a PR.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
change: support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes
artifact: testing
---

# Testing

Test pointer/hydrated parity, same-length edits, staged changes, deletion, empty files, malformed and extended pointers, other filters, and ignored/out-of-scope LFS in the change lifecycle. Use offline builds only. Current dependency resolution is blocked by missing cached serde-saphyr; full gate cannot be claimed.

## Observed validation, 2026-09-30

- cargo fmt --check passed.
- Four pure tests, extracted unchanged with their production helpers into a temporary sha2-only crate, passed using existing offline cached dependencies. The streaming fixture exceeds 256 MiB while enforcing 64 KiB maximum read buffers.
- Installed SpecSync strict validation passed: 62 specs, zero warnings, 107/107 files (100%). Changed module score: 100/100.
- Full workspace cargo metadata and mandatory CARGO_NET_OFFLINE=true fledge lanes run pre-push stop on missing cached serde-saphyr. The pre-push formatter step passed; cargo check did not compile the project. Git/lifecycle integration regressions have been added but not run.
- No PR is published while the mandatory pre-push gate is blocked. No implementation review, full-test pass, finalization or provenance approval is claimed.
- HI sentences were recorded only after Bruno explicitly confirmed them; hi is unavailable locally, so hi check was not run.

## Requirement evidence

| Requirement | Evidence |
| --- | --- |
| REQ-change-103 | src/change_tests.rs::lfs_payload_pointer_and_hydrated_bytes_are_equivalent; src/change_tests.rs::lfs_payload_refuses_malformed_and_extended_pointers; src/change_tests.rs::lfs_payload_streams_large_content_in_bounded_chunks; src/change_tests.rs::lfs_attribute_exception_is_specific_and_keeps_exact_output_checks; src/change_tests.rs::lfs_workspace_and_scoped_evidence_detect_edits_without_filters; src/change_tests.rs::lfs_unchanged_out_of_scope_assets_allow_change_check |

The first four tests ran in isolation; the last two are pending full-project compilation. This table maps assertions and does not claim the pending tests passed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{
"schema_version": 1,
"attempts": [
{
"timestamp": 1790797641,
"commit": "8fae3e45e7938a5ffa9b391e0a1588e843ee6655",
"contract_digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"execution_digest": "77bc6f504f3886cd4b5252bacdaa6633cc8b79bcfcb00f3a3b9378c6590ca23e",
"workspace_digest": "962b5ac9558a8380ce086fac7036216738c168369f928c8bc54a0cf570757db4",
"passed": true,
"commands": [
{
"command": "specsync check --spec change",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-change-103"
]
},
{
"timestamp": 1790797655,
"commit": "cd4903853150e32e31e486b4e433ea8e7a8a272f",
"contract_digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"execution_digest": "77bc6f504f3886cd4b5252bacdaa6633cc8b79bcfcb00f3a3b9378c6590ca23e",
"workspace_digest": "962b5ac9558a8380ce086fac7036216738c168369f928c8bc54a0cf570757db4",
"passed": true,
"commands": [
{
"command": "specsync check --spec change",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-change-103"
]
},
{
"timestamp": 1790798360,
"commit": "ad6519ca71867d444b0138a209c2db797b48ded7",
"contract_digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"execution_digest": "77bc6f504f3886cd4b5252bacdaa6633cc8b79bcfcb00f3a3b9378c6590ca23e",
"workspace_digest": "f2f7eae63e57fd307a3cb4d0785e9717cd84ebe4214e666f5639ec3e11890e6e",
"passed": true,
"commands": [
{
"command": "specsync check --spec change",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-change-103"
]
},
{
"timestamp": 1790798365,
"commit": "419b5f1f12bd6e7069371e6fc90e02328cc40d75",
"contract_digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"execution_digest": "77bc6f504f3886cd4b5252bacdaa6633cc8b79bcfcb00f3a3b9378c6590ca23e",
"workspace_digest": "f2f7eae63e57fd307a3cb4d0785e9717cd84ebe4214e666f5639ec3e11890e6e",
"passed": true,
"commands": [
{
"command": "specsync check --spec change",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-change-103"
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"timestamp": 1790798365,
"commit": "419b5f1f12bd6e7069371e6fc90e02328cc40d75",
"contract_digest": "b48bcae372df50ad508a103108ee6cd299ac3a323e35a3d14f16eb5503f28d28",
"execution_digest": "77bc6f504f3886cd4b5252bacdaa6633cc8b79bcfcb00f3a3b9378c6590ca23e",
"workspace_digest": "f2f7eae63e57fd307a3cb4d0785e9717cd84ebe4214e666f5639ec3e11890e6e",
"passed": true,
"commands": [
{
"command": "specsync check --spec change",
"success": true,
"exit_code": 0
}
],
"requirement_ids": [
"REQ-change-103"
]
}
15 changes: 15 additions & 0 deletions hi/evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
hi: 1
families: [EVIDENCE]
---

# Evidence

## Intent

Trustworthy verification in repositories containing Git LFS assets. Bruno confirmed these wants in the conversation on 2026-09-30.

## Criteria

- **EVIDENCE-1** I can complete verification in a repository containing Git LFS assets
- **EVIDENCE-2** I can trust verification to detect changed asset content, whether assets are downloaded or represented by LFS pointers
5 changes: 4 additions & 1 deletion specs/change/change.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: change
version: 126
version: 128
status: active
files:
- src/change.rs
Expand Down Expand Up @@ -489,3 +489,6 @@ Acceptance Criteria
| 2026-09-09 | close-remaining-specsync-6-0-0-first-user-p1s-pre-commit-honors-config-toml-config-fail-closed-merge-git-sanitization: Close remaining SpecSync 6.0.0 first-user P1s: pre-commit honors config, TOML config fail-closed, merge git sanitization, and 5.x upgrade docs |
| 2026-09-09 | drop-interpolated-next-action-from-v1-verifying-assert-messages-so-codeql-cleartext-logging-is-not-a-required-check: Drop interpolated next_action from v1 verifying assert messages so CodeQL cleartext-logging is not a required-check failure |
| 2026-09-26 | lifecycle-commits-stage-only-what-the-change-owns-never-every-untracked-file: Lifecycle commits stage only what the change owns, never every untracked file |

| 2026-09-30 | LFS evidence (#789) | Support ordinary SHA-256 v1 pointers and streamed hydrated content without invoking filters; preserve full evidence inventory and mutation detection. |
| 2026-09-30 | support-git-lfs-pointers-and-hydrated-assets-in-canonical-change-evidence-without-hiding-content-changes: Support Git LFS pointers and hydrated assets in canonical change evidence without hiding content changes |
6 changes: 6 additions & 0 deletions specs/change/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -558,3 +558,9 @@ are never committed. It deliberately leaves out `affected_paths`. Those are pref
steps. The workflow-v2 baseline is on the list because `change new` writes it in a freshly adopted
project: the first test run of the fix left it out, which would have committed a change whose
origin anchor never reached history.

## Git LFS canonical evidence (#789)

Ordinary Git LFS v1 files are governed inputs, not ignored assets. Evidence recognizes `filter=lfs`, suppresses external LFS filters during worktree inspection, and uses the standard SHA-256/size pointer as the canonical payload. Pointer-only checkouts therefore agree with hydrated checkouts. Hydrated bytes are streamed directly even when Git reports the path clean; pointer edits, content edits and file mode changes remain visible. The empty-file convention is preserved. Unsupported extensions or malformed pointers fail closed; custom filters and encoding/ident transformations remain unsupported. No LFS download is attempted.

`ignored_paths` still controls meaningful-change policy; it does not omit assets from workspace evidence. Hydrated archives must be read to verify their actual bytes, so memory is bounded but I/O scales with content size. After Bruno authorized a one-time fetch of locked Cargo dependencies, local typechecking, all six LFS regression tests (including Git and lifecycle integration), and the mandatory pre-push gate passed. Full local verification also passed: 2,510 unit tests, 437 integration tests, release build, strict specs, 52 release-candidate checks and 29 CI-gate checks. Human implementation review remains pending.
12 changes: 12 additions & 0 deletions specs/change/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -1348,3 +1348,15 @@ Acceptance Criteria
- Canonical spec paths resolve through the same registry-aware resolver materialization writes through.
- Paths are project-relative with forward slashes, sorted and deduplicated, and a change that cannot be loaded is an error rather than an empty answer.


### REQ-change-103

Canonical evidence SHALL support ordinary SHA-256 Git LFS v1 files without dropping them from evidence or invoking LFS filters.

Acceptance Criteria
- A valid pointer and its hydrated content have the same canonical payload and digest, including in-scope assets.
- Modified content, pointer targets, executable modes and deletion remain detectable.
- Hydrated content is streamed with bounded memory; it cannot exceed the aggregate evidence buffer by its raw size.
- Unsupported pointer extensions, malformed pointer-like inputs and other Git content filters remain errors.
- No LFS download or external clean/smudge/process filter is needed to collect evidence.
- Existing sparse, topology, exact attribute-output and stable capture checks remain in force.
3 changes: 3 additions & 0 deletions specs/change/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ spec: change.spec.md
- [x] Clarify scoped-review claims versus separately enforced authentication without changing runtime review rules.

- [x] Name the untracked paths a lifecycle commit may stage in the domain (`lifecycle_commit_scope`), excluding `affected_paths` prefixes and whole spec directories, and name the lock and transaction journal as never-committed runtime files.

- [x] Draft explicit LFS pointer/hydrated canonicalization and focused regressions for #789.
- [ ] Run full-project LFS regressions and mandatory pre-push gate once existing dependency availability is resolved.
Loading
Loading