| name | shipment-reconcile |
| description | GI/GR reconciliation gate for shipment manifests — verifies every manifest item exists in queue (pre-mode) or archive (post-mode) with the expected status, and closes shipments with the single-artifact safe-close procedure that archives ONLY manifest item IDs with a verify-after-each invariant + git-revert-on-cascade instead of the destructive cascade backlogit_ship_shipment. |
Shipment Reconcile
Provides a double-entry (GI/GR) integrity check for shipment manifests. Run
mode: pre before closing a shipment and mode: post after the archive +
restore steps complete. Run mode: safe-close in place of the destructive
cascade backlogit_ship_shipment call to archive only the shipment manifest's
explicit item IDs one artifact at a time, verifying after each that the parent
feature and any unshipped sibling tasks survive.
Why safe-close exists. backlogit_ship_shipment treats a shipment as a
proxy for its covering feature and cascade-archives the whole feature subtree.
For partial-feature shipments (which intentionally exclude the parent
feature and some sibling tasks) this is destructive: it archives the parent
feature and orphans unshipped siblings that are not in the shipment
manifest. Safe-close prevents that corruption instead of merely detecting it
after the fact. See P-015 in workflow-policies for the governing policy.
When to Use
- Ship Step 6 closure (mandatory): run
mode: pre, then mode: safe-close
instead of the cascade backlogit_ship_shipment call, then mode: post.
Safe-close archives only the manifest item IDs individually; it never calls the
cascade op.
- Ship Step 0.5 (sanity check): pre-mode at intake with
expected_status: queued
(or active if the shipment was already claimed in a prior session)
to catch Stage-side over-inclusion before any build work begins.
- Ad-hoc audit: any time an operator suspects manifest drift.
Inputs
| Parameter | Required | Values | Notes |
|---|
mode | yes | pre | post | safe-close | Controls which check/close phase runs |
shipment_id | yes | e.g. 004-S | The shipment to reconcile |
expected_status | pre-mode only | queued | active | done | queued for fresh intake; active when shipment already claimed in a prior session; done for pre-ship check |
merge_commit_sha | post-mode and safe-close | git SHA | The merge commit that closed the PR; recorded on archived items for traceability |
Output
A structured reconciliation report stored at
.backlogit/reconcile/{shipment_id}-{mode}-{timestamp}.md.
Every item in the manifest is classified as one of:
| Classification | Pre-Mode Meaning | Post-Mode Meaning |
|---|
matched | Queue file present AND declared status matches expected_status | Archive file present for this item |
pre-archived | No queue file found but archive file exists — item already archived before this shipment ran; treated as valid | N/A (all items are expected in archive; use matched / missing) |
missing | No queue or archive file found for this manifest item | Archive file not found for this manifest item |
status-mismatch | Queue file present but declared status does not match expected_status | N/A (post-mode does not check status fields) |
orphan | Queue file declares this shipment_id in its frontmatter but is NOT in the manifest | N/A (post-mode does not scan queue files) |
Classification semantics are mode-dependent. Pre-mode checks the queue
for status correctness; post-mode checks the archive for file presence only.
The report ends with a recommendation:
PROCEED — all items are matched or pre-archived; no action needed
HALT — operator reconcile required — one or more missing, status-mismatch, or orphan items (pre-mode)
HALT — restore archives — missing archive files or unrestored deletions (post-mode)
CLOSED — safe-close archived every manifest item individually, archived the
shipment record itself, and the protected set (parent feature + unshipped siblings)
is intact
HALT — cascade detected, revert required — safe-close found a non-manifest artifact (parent feature or a sibling task) archived or deleted; the unintended change must be reverted before any commit
For mode: safe-close, the report also records the protected set (the parent
feature file and every unshipped sibling task file that must survive closure) and,
per manifest item, whether it was matched (archived by this run) or
pre-archived (already archived before this run; skipped to avoid
double-archival and false-positive cascade flags).
Behavioral Constraints
- Report-and-halt only. This skill NEVER modifies the shipment manifest or
queue/archive files outside the safe-close mode's manifest-scoped archival.
In pre- and post-mode it only reports; operators must manually reconcile via
existing backlog tools and re-invoke Ship Step 6.
- Manifest-scoped mutation only. In
mode: safe-close, the ONLY artifacts
this skill may move or archive are the shipment manifest's explicit item IDs and
the shipment record itself ({shipment_id}). It must NEVER archive the parent
feature or any sibling task that is not in the manifest. It never calls the cascade
backlogit_ship_shipment; the shipment record is closed as its own single artifact.
- No prune / no auto-repair. Auto-mutation of the manifest itself is reserved
for a future version. Safe-close never prunes the manifest and never
auto-deletes non-manifest artifacts; on cascade detection it reverts the
unintended change and halts.
- Single-writer lock. When invoked from Ship Step 6, this skill holds the
.backlogit/queue/{shipment_id}.md file lock (via the file-lock skill) for
the duration of pre-mode → safe-close → post-mode. See lock protocol in the
Required Protocol section below.
- Halt on RECONCILE_FAIL. Do not proceed to safe-close unless pre-mode
returns
PROCEED. Do not commit backlog state if safe-close returns
HALT — cascade detected, revert required. Surface the report path to the operator.
Required Protocol
Pre-Mode
-
Acquire single-writer lock (Ship Step 6 invocations only, not intake):
Invoke the file-lock skill to acquire .backlogit/queue/{shipment_id}.md.
If lock acquisition fails, count as a session stall (circuit-breaker protocol)
and prompt the operator.
-
Load manifest via backlogit_get_item(shipment_id).
Extract the items list.
-
Check each manifest item:
- Attempt to locate the file at
.backlogit/queue/{id}.*
- If found, read its frontmatter and compare
status to expected_status
— classify as matched or status-mismatch
- If NOT found in queue, check
.backlogit/archive/{id}.*
— if archive file exists, classify as pre-archived (valid; item already shipped)
— if no file in either location, classify as missing
-
Orphan scan:
Scan .backlogit/queue/ for any files whose YAML frontmatter declares
shipment_id: {shipment_id} but whose ID is NOT present in the manifest items list.
Classify each such file as orphan.
-
Produce report and store at
.backlogit/reconcile/{shipment_id}-{mode}-{timestamp}.md.
-
Gate decision:
- If all items are
matched or pre-archived and no orphans exist → recommendation: PROCEED
- If any
missing, status-mismatch, or orphan items exist →
recommendation: HALT — operator reconcile required
- On
HALT: emit the report path, release the lock, and halt with
RECONCILE_FAIL. Do NOT call backlogit_ship_shipment.
- On
PROCEED from Ship Step 6: retain the lock until post-mode completes.
Post-Mode
-
Verify archive presence:
List .backlogit/archive/ and confirm a file exists for the shipment itself
({shipment_id}.*).
-
Per-item archive check:
For every item in the manifest, verify a corresponding archive file exists.
If any are absent, flag them in the report.
-
Deleted-file guard (known backlogit_ship_shipment quirk — see P-007):
Run git status -- ".backlogit/archive/" and inspect for deletions.
If any archive files are reported as deleted, recommend
git restore .backlogit/archive/ before the commit step.
-
Produce post-mode report per the same schema.
-
Gate decision:
- If all archive files present and no deletions detected →
recommendation: PROCEED
- If missing archive files or unrestored deletions detected →
recommendation: HALT — restore archives
- On
HALT: release the lock and report. Ship must restore archives before committing.
-
Release lock (acquired in step 1 of pre-mode):
Invoke file-lock release for .backlogit/queue/{shipment_id}.md.
If release fails, log a warning — stale locks are operator-recoverable.
Safe-Close Mode
Runs in place of the destructive cascade backlogit_ship_shipment call.
Archives only the shipment manifest's explicit item IDs, one artifact at a time,
verifying after each archival that the parent feature and any unshipped sibling
tasks survive. Invoked between pre-mode (PROCEED) and post-mode, under the lock
pre-mode already holds. If invoked standalone, acquire the lock per pre-mode
step 1 first and release it on completion.
-
Load manifest via backlogit_get_item(shipment_id). Extract the
items list. These IDs are the only artifacts safe-close may move or
archive.
-
Compute the protected set (partial-feature detection):
- Derive the covering feature ID from the manifest item hierarchy
(e.g. a task
055.002-T belongs to feature 055-F).
- If the covering feature ID is not in the manifest
items, this is a
partial-feature shipment. Add the covering feature to the protected set.
- Enumerate every task sharing the covering feature's hierarchy prefix whose ID
is not in the manifest
items (the unshipped siblings) by scanning both
.backlogit/queue/ and .backlogit/archive/ (plus the
feature file's declared children when available). Add each to the protected set.
- The protected set is the parent feature plus every unshipped sibling task
that MUST remain in
.backlogit/queue/ after closure. It is computed
from expected IDs, not merely the files currently present in queue, so a
sibling or parent that was already wrongly archived is still detected.
-
Baseline integrity gate (before archiving anything): Run
git status --short -- ".backlogit/" and record the pre-closure
working-tree state so any later archival or deletion of a protected-set path can be
attributed to this procedure. Then confirm every protected-set member currently
exists in .backlogit/queue/. If any protected-set member is already in
.backlogit/archive/ or missing from the working tree, a cascade has
already occurred (or the shipment scope is wrong): halt immediately with
HALT — cascade detected, revert required, name the affected artifact IDs, and do
NOT archive any manifest item. The pre-archived exemption (step 4) applies to
manifest items only — never to the protected set.
-
Archive each manifest item individually (loop over items ONLY):
- If the item's file is in
.backlogit/queue/: move it to
done via backlogit_move_item, then archive that single artifact via
backlogit_archive_item (CLI fallback backlogit archive {id}). When the
backlog registry's archive_item operation supports commit metadata, record
the merge SHA using that tool's configured field (for backlogit, commit_sha);
otherwise record the merge SHA in the closure report. Classify matched.
- If the item's file is already in
.backlogit/archive/: classify
pre-archived and skip — do not re-archive. Reusing the pre-archived
classification prevents false-positive cascade flags on items that were
legitimately shipped earlier.
- If the item's file is in neither location: classify
missing, halt with
RECONCILE_FAIL, and do not continue archiving.
-
Verify-after-each invariant (run immediately after each item's archival):
- Confirm every protected-set member is still present in
.backlogit/queue/ — not moved to .backlogit/archive/,
not deleted from the working tree.
- Run
git status --short -- ".backlogit/" and confirm no
protected-set path appears as a deletion, rename into archive/, or new
archive/ addition beyond the baseline captured in step 3.
- The protected set was proven fully present in queue at the baseline gate
(step 3), so any protected-set member now found in
archive/ or missing from
the working tree is a cascade. There is no pre-archived exemption for the
protected set — the exemption in step 4 covers manifest items only.
-
git-revert-on-cascade: If the invariant fails (a protected-set artifact was
archived or deleted by the preceding archival):
- Cascade detected. Immediately restore the unintended change:
git restore -- .backlogit/queue/ .backlogit/archive/
for working-tree moves/deletions, or git revert <commit> if the cascade was
already committed.
- Re-run the invariant to confirm the protected set is intact again.
- Halt with
HALT — cascade detected, revert required, emit a P-005
violation event (naming the cascaded artifact IDs), and do NOT commit the
backlog state. Do not auto-prune the manifest.
-
Final invariant re-check: After the loop completes, re-confirm the full
protected set is intact in .backlogit/queue/.
-
Archive the shipment record itself (single artifact, non-cascading): archive
only the {shipment_id} artifact via backlogit_archive_item (CLI fallback
backlogit archive {id}), recording merge_commit_sha for traceability. Closing
the shipment record is the point of safe-close, so it is not in the protected
set — but it must be archived as its own single artifact, never via the cascade
backlogit_ship_shipment. Then re-run the verify-after-each invariant (step 5) to
confirm archiving the shipment record did not disturb the protected set.
-
Produce safe-close report per the same schema, recording the protected set,
each item's classification, the shipment-record archival, and the recommendation.
-
Gate decision:
- All manifest items
matched or pre-archived, the shipment record archived,
and the protected set intact → recommendation: CLOSED. Proceed to post-mode.
- Any cascade detected →
recommendation: HALT — cascade detected, revert required
(see step 6). Do not proceed to the commit step.
Lock-Conflict Scenario
If pre-mode cannot acquire the lock because another process holds it:
- Retry once after 30 seconds.
- If retry also fails, count as a session stall and prompt the operator:
Shipment lock conflict on {shipment_id}. Another process holds the lock.
- Do NOT proceed without the lock. Do NOT call
backlogit_ship_shipment.
Quality Criteria
mode: pre runs before closing a shipment in Ship Step 6
mode: pre with expected_status: queued (or active for already-claimed shipments) runs at Ship Step 0.5 intake
mode: safe-close runs in place of the cascade backlogit_ship_shipment call and archives only the manifest item IDs (one artifact at a time) plus the shipment record itself
- Safe-close computes the protected set (parent feature + unshipped siblings) from expected IDs and proves it is fully present in queue at a baseline gate before archiving anything
- Safe-close verifies the protected set survives after every single-item archival (verify-after-each invariant), with no pre-archived exemption for protected-set members
- Safe-close archives the shipment record as its own single artifact and never via the cascade op
- Safe-close reverts on cascade detection (
git restore/git revert) and halts with a P-005 violation; it never auto-prunes the manifest
mode: post runs after the safe-close archive sequence in Ship Step 6
- All five item classifications are represented in the schema
- Lock is acquired before pre-mode and released after post-mode (or on any halt)
- Report-and-halt in pre/post mode; safe-close mutation is strictly manifest-scoped with no auto-prune
Related Artifacts
.github/skills/file-lock/SKILL.md — lock acquisition/release primitives
.github/agents/.ship.agent.md — integration points (Step 0.5, Step 6 safe-close)
.github/agents/.stage.agent.md — scope guard (Step 5.5)
.github/policies/workflow-policies.md — P-007 archive integrity policy; P-015 single-artifact closure (cascade prohibition)
Model Routing
This skill operates at Tier 2 (Standard) — file scanning and frontmatter
comparison do not require frontier-level reasoning.
Generated by autoharness | Template: shipment-reconcile/SKILL.md.tmpl