Scaffold Integrity Edge Cases Research
Scaffold Integrity Edge Cases Research
Scope and method
Three readonly Terra lanes reconstructed the CLI 3.0.0 contract lineage, the context-plan hashing defect in issue #97, and the managed-conflict plus projection-receipt defects in issues #104 and #105. The coordinator cross-checked issue, pull request, release, commit, current source, and current test evidence. This report separates shipped behavior, remaining defects, inferences, and unresolved policy.
Historical contract
- PR #88 introduced deterministic desired state and non-destructive scaffold reconciliation before CLI 3.0.0. Current planning still updates a managed file only when observed bytes match the prior receipt; otherwise it emits a user-modified conflict (
apps/cli/src/features/scaffold-state/reconcile.ts:556-580). Current tests preserve a modified generated sync script during ordinary update (apps/cli/src/update/run.test-cases.test.ts:558-611). Conflict preservation is deliberate product behavior, not the new bug. - Issue #91 concerned remote baseline availability being misclassified as scaffold drift. CLI 3.0.1 made non-bundled checks refresh remote authority and kept typed unavailability distinct from fatal integrity failures. Current repository-check classification preserves that boundary (
apps/cli/src/features/repository-check/application.ts:57-87), and the session hook reports divergence only for explicitdrift-detectedoutput (apps/cli/src/data/hooks/scaffold-update-check.mjs:70-74). None of #97, #104, or #105 should weaken it. - Issue #94 was a later durable-authority disagreement, closed by PR #95. It reinforces the same rule: reachable authority disagreement is integrity/state failure, not transient unavailability.
- PR #96 added operation-scoped confinement, atomic single-file publication, rollback, and adversarial filesystem coverage. It did not couple the projection receipt to its manifest entry. PR #101 formalized managed hook ownership and release behavior but did not add an authoritative conflict override or receipt/manifest transaction.
GitHub currently records PR #88 as merged. Its body still contains a stale pre-merge sentence saying it was open and unmerged; the merged state and merge timestamp are the authority.
Issue #97: fixed in 3.1.3, still open administratively
Facts
- Issue #97 reported CLI 3.1.2 recording a raw SHA-256 for compact
.devpunks/context-plan.json, after which formatting changed only its bytes and madehi check --jsonfail. - The 3.1.2 positive test scaffolded and checked without inserting the formatting transition. It also asserted the raw hash, so it encoded the defective representation-level contract. The separate corruption test proved semantic changes failed but did not prove byte-only reformats passed. Historical sources: positive path and corruption path.
- Commit 89b0a6f repaired the defect; commit 129e835 consolidated the shared helper. CLI 3.1.3 explicitly names issue #97 as fixed.
- Current policy semantically hashes only the context plan and projection receipt; every other managed file remains byte-hashed (
apps/cli/src/scaffold/json.ts:13-47). Check accepts the canonical semantic hash, a legacy raw hash, or formatter-only semantic equality, while rejecting semantic drift (apps/cli/src/update/run.ts:466-480,apps/cli/src/update/run.ts:3759-3779). - Current regression coverage performs scaffold, check, byte-only context-plan reserialization, another check, legacy raw-receipt compatibility, and final receipt assertions (
apps/cli/src/update/run.test-cases.test.ts:3291-3375,apps/cli/src/update/run.test-cases.test.ts:4671-4721).
Conclusion
No new #97 production change is justified without a 3.1.3-or-newer semantic-drift reproduction. Obtain one fresh installed-package scaffold-to-check proof, record it on the issue, and close the issue. Retain legacy raw-hash compatibility for existing consumer manifests.
Issue #104: explicit baseline-wins reconciliation is missing
[!NOTE] Superseded recommendation. This section records the earlier issue #104 recommendation to add an explicit, conflict-preserving policy boundary. The later accepted decision in Scaffold Integrity Root-Cause Plan supersedes it: normal
hi updaterecoverably applies verified baseline content to fixedScaffoldManagedconflicts by default;--writeand--yesare compatibility aliases;--checkis read-only; and project-owned, generated, consumer-owned, structured, dependency, and intentionally customizable exceptions remain preserved silently.
Facts
- Issue #104 reports that selected baseline authority still preserved managed conflicts, requiring manual archive, replacement, update, and check steps.
- Current reconciliation already refreshes a stale receipt when desired and observed content agree. When observed content differs from the prior receipt, it deliberately creates
UserModifiedScaffoldEntryConflict; receipt-only mismatches becomeStaleScaffoldOwnershipConflict(apps/cli/src/features/scaffold-state/reconcile.ts:556-609). Application exposes those asScaffoldConflictPreservedand persists receipts only for successful or skipped work (apps/cli/src/features/scaffold-state/apply.ts:114-141,apps/cli/src/features/scaffold-state/apply.ts:247-337). hi update --writeand--yescurrently share the ordinary apply policy. There is no explicit baseline-wins conflict policy. Turning either existing flag into an unconditional destructive override would violate the 3.0.0 non-destructive contract and regress intentionally tailored project files, including the behavior protected by issue #53.
Superseded inference and smallest safe boundary
The earlier recommendation was to add a separately explicit baseline-authoritative policy and keep ordinary update conflict-preserving. The accepted root-cause decision replaces that policy: ordinary hi update performs recoverable archive-then-apply for eligible fixed ScaffoldManaged conflicts, while ownership classification silently preserves project-owned and customizable exceptions.
Archive to a collision-resistant run directory beneath .devpunks/replaced-scaffold/, using the confined filesystem boundary established by PR #96. Never overwrite an archive or concurrently recreated target. If replacement fails while the target remains absent, restore the archived original. Persist the final manifest from post-application bytes, types, and modes, then require a fresh clean hi check --json.
Resolved by the later root-cause decision
The accepted scope is all eligible fixed ScaffoldManaged files, not only /skills. Skills, hooks, lint specifications, and Oxlint files follow the same ownership rule. Project-owned, generated, consumer-owned, structured, dependency, and intentionally customizable assets stay outside fixed baseline recovery and remain preserved without warning.
Issue #105: generated receipt and manifest diverge
Facts
- Initial scaffold is internally ordered: it runs projection, reads
.devpunks/harness-projection-receipt.json, computes managed hashes, and then writes.devpunks/scaffold-manifest.json(apps/cli/src/scaffold/output.ts:2586-2608,apps/cli/src/scaffold/output.ts:2706-2788). - Later generated sync directly rewrites the projection receipt after applying projections but never reads or updates the scaffold manifest (
apps/cli/src/data/scripts/sync-subagents.mjs:2384-2469). A meaningful semantic receipt change therefore leaves the manifest stale, exactly as issue #105 reports. - Check validates recorded context evidence before reconciliation and rejects semantic mismatch (
apps/cli/src/update/run.ts:3759-3781). The defect cannot be repaired by deferring to reconciliation without weakening read-only integrity. - Existing sync tests prove repeated receipt generation can be byte-identical but do not supply a scaffold manifest (
apps/cli/src/data/scripts/sync-subagents.test.ts:397-454). Existing update coverage proves semantic manifest hashes can pass check but does not execute generated sync after scaffold (apps/cli/src/update/run.test-cases.test.ts:4671-4721).
Smallest safe boundary
Fix the canonical generated sync source. Before projection or receipt mutation, confined-read the manifest and validate its schema plus exactly one unambiguous projection-receipt managedFiles entry. Capture the validated manifest identity. After truthful receipt publication, reread and semantically hash the committed receipt, then revalidate the manifest's confinement, schema, unique receipt entry, and captured identity at commit time before changing only that entry. Preserve every unrelated field. Do not begin projection on malformed, ambiguous, unsafe, or contradictory manifest evidence.
If commit-time identity comparison detects a concurrent manifest replacement after the receipt was truthfully published, retain that receipt, return a typed retry result, and let a rerun converge. Do not roll back or falsify producer evidence to match the old manifest.
Two independent file renames cannot provide literal crash-atomicity. The smallest safe interpretation is individually atomic confined writes plus retry-safe recovery. If crash-consistent two-file state is required, delivery needs an explicit journal/transaction contract rather than claiming filesystem atomicity it cannot provide.
Required public regression: scaffold, then make a controlled semantic projection-input change that must alter the generated receipt. Capture the old manifest receipt hash. At the producer seam after new receipt publication but before manifest refresh, prove that the old manifest hash differs from the new committed receipt's semantic hash. Complete the manifest refresh, run hi check --json, and prove convergence plus equality between the refreshed manifest hash and the committed receipt's semantic hash. A no-op consecutive sync does not satisfy this regression. Retain PR #96 adversarial cases for symlink swaps, outside targets, malformed inputs, failed publication, and commit-time identity races.
Delivery boundaries
| Issue | Current status | Next action |
|---|---|---|
| #97 | Code-fixed and released in 3.1.3; issue still open | Fresh installed-package proof, comment, close |
| #104 | Real default-update policy gap; earlier explicit-policy recommendation superseded | Implement default recoverable baseline recovery for fixed ScaffoldManaged files while silently preserving ownership exceptions |
| #105 | Real generated-writer integrity defect | Implement receipt-plus-manifest refresh and end-to-end regression independently |
The later root-cause plan supersedes this report's earlier recommendation to deliver #104 through a separate opt-in policy. #104 and #105 remain distinct implementation lanes but meet in one integration and release proof because both govern manifest truth. Neither should weaken issue #91's availability/integrity classification or issue #97's narrow semantic-JSON allowlist.