Skip to content

T-5IG1-lease-primitives-validate-git-actually-acted

Status: closed/superseded · Impact: medium · Complexity: small

Auto-generated from a /sdlc:task-work post-mortem. Review and promote to open/ready before picking up.

cas_create already learned a hard lesson: git push --force-with-lease exits 0 with “Everything up-to-date” when the SHA pushed equals what the ref already points at, silently masking a CAS loss that the protocol contract requires us to treat as a failure. The same family of short-circuits (no-op fast-forwards, already-present objects, already-deleted refs) almost certainly exists for the other primitives too. This task audits all five primitives in plugin/lib/lease/primitives.py to confirm each one validates the post-state, not just the exit code. Cite T-S0PK-add-lease-protocol-library-and-schemas for the originating fix.

LocationRole today
plugin/lib/lease/primitives.pycas_create scans stdout/stderr for the “Everything up-to-date” short-circuit and raises CASFailed; the other four primitives (cas_replace, cas_delete, fetch_ref, fetch_namespace) rely on git’s exit code alone.
plugin/lib/lease/tests/test_primitives.pyHas the cas_create same-SHA regression test; no equivalent test for the other primitives.

Each primitive validates that the ref’s post-state matches the intended operation, not just that git exited 0. Concretely:

  • cas_replace re-fetches the ref after push and confirms its SHA equals the new SHA the caller asked us to write.
  • cas_delete re-fetches the namespace and confirms the ref is genuinely absent.
  • fetch_ref confirms the ref exists in the post-fetch local refs (not just that fetch exited 0 with nothing to do).
  • fetch_namespace confirms the namespace prefix was queried (no silent empty result when the remote has refs the local pruned away).

Each primitive gets a regression test mirroring the cas_create same-SHA test, exercising the no-op / short-circuit path against the local-bare-repo fixture.

  1. Read cas_create’s defensive check as the reference implementation, note the exact short-circuit string git emits.
  2. For each of the other four primitives, identify the analogous short-circuit (git docs + manual experimentation against the bare-repo fixture). Write a failing test first that asserts the primitive raises the appropriate exception on the short-circuit path.
  3. Update each primitive to post-validate.
  4. Run the full lease test suite; confirm coverage stays >90%.
LocationKindChange
plugin/lib/lease/primitives.pymodifyAdd post-state validation to cas_replace, cas_delete, fetch_ref, fetch_namespace.
plugin/lib/lease/tests/test_primitives.pymodifyAdd short-circuit / no-op regression tests for the four primitives.
plugin/lib/lease/exceptions.pymodifyAdd any new exception types if the post-state failure mode differs from existing CASFailed / RefNotFound (likely not — reuse).
  • AC-1: Each of the five primitives in primitives.py post-validates the ref state, not just git’s exit code. Code review confirms the validation step on each function.
  • AC-2: pytest plugin/lib/lease/tests/test_primitives.py includes a regression test for each primitive that exercises the no-op / short-circuit path and asserts the correct exception is raised.
  • AC-3: Combined line coverage on primitives.py remains >90% after the new tests land.
  • Rewriting the primitives to avoid git push --force-with-lease entirely (e.g. using libgit2). The wrapper-validates-post-state approach is sufficient.

Spawned by /sdlc:task-work post-mortem of T-S0PK-add-lease-protocol-library-and-schemas on 2026-05-23.

Bullet: git push —force-with-lease exits 0 on same-SHA no-op. Every primitive’s wrapper needs to validate did git actually do what I asked, or did it short-circuit? CAS-CREATE silently masks a CAS loss. Keywords searched: force-with-lease, short-circuit, cas-create, primitive, same-sha, validate, actually, silently Excluded: 2026-05-23-add-lease-protocol-library-and-schemas Top candidates (score / status / headline):

  • 6 / closed/done / 2026-05-19-templates-html-comment-after-frontmatter — Move template HTML comment block after frontmatter
  • 6 / closed/done / 2026-05-20-task-spec-flags-schema-rejecting-placeholders — /sdlc:task-define surfaces schema-rejecting placeholders at authoring time
  • 5 / closed/done / 2026-05-20-task-close-out-verifies-pr-on-main — task-close-out verifies merged PR’s mergeCommit is reachable from origin/main
  • 5 / planning/backlog / 2026-05-21-task-ensure-ready-verifies-named-consuming-skills — task-ensure-ready verifies named consuming-skills lists match codebase
  • 4 / closed/done / 2026-05-19-extract-start-task-script — Extract task-work Step 5b into scripts/start_task.py Decision: SPAWNED

← Back to Tasks