Release engineer comparing tracked Git files with tar archive contents to diagnose a configuration file omitted by export-ignore.

A Tracked File Can Still Be Missing From a Git Archive

Wed, Oct 14, 2026

A Tracked File Can Still Be Missing From a Git Archive

The required configuration appears in git ls-tree. The release script archives the intended commit and exits successfully. Nevertheless, the resulting tar contains no config/runtime.json. Nothing needs to be untracked, corrupted or accidentally deleted for this to happen: Git’s archive documentation describes an export-ignore attribute that excludes tracked paths from archives. Tracking and export selection are different contracts.

For this playbook, the consumer requires exactly three regular files: README.txt, src/app.txt and config/runtime.json, each with independently specified bytes. Internal documentation and the attribute-policy file itself must stay outside the package. A successful command, a recognizable commit ID and an attractive file count cannot substitute for that contract.

The investigation changes packaging policy while holding the payload commit fixed. It compares attributes recorded in the commit, explicitly selected working-tree attributes and repository-local overrides. A separate control changes the working configuration without changing the archived configuration. Finally, a reviewed policy repair is checked in a controlled fresh clone.

The evidence comes from a small synthetic Linux laboratory, not a production incident. It uses two disposable repositories, five authored files, two commits and nine tar outputs. The recorded case results are identified below by their source digest and environment. No archive member is extracted or launched. The objective is narrow: determine which regular files were selected, verify their exact contents and decide whether the policy that produced them is approved.

Approve the release file set before generating it

Start with the consumer, not the archive command. The release requirement is this literal mapping, called EXPECTED in the driver:

Required regular path

Exact Python byte literal

Bytes

README.txt

b"Synthetic release\n"

18

src/app.txt

b"APP-v1\n"

7

config/runtime.json

b'{"mode":"lab"}\n'

15

The three payloads total 40 bytes. Every final newline is part of the contract. The configuration must not merely parse into an equivalent JSON object; its bytes must equal the literal above. This deliberately removes ambiguity about whitespace, formatting and a missing trailing newline.

Two other authored files are tracked but intentionally absent from every acceptable package: docs/internal.txt, containing Do not distribute\n, and .gitattributes, which carries selection policy. The desired archive is therefore neither “everything tracked” nor “anything except the missing configuration.” It is exactly the approved three-path mapping.

Keep that expectation independent of the candidate. Building the expected manifest from the tar listing would make an omission validate itself. Deriving it mechanically from the entire tracked tree would produce the opposite mistake: demanding files that the release policy intentionally excludes.

For background on staging and recording source, use everyday Git tracking foundations. Here, those foundations establish source presence; they do not decide what a release consumer should receive.

Review additions as carefully as omissions. An unexpected internal document is a contract failure even when all required files are present. Likewise, a correctly named configuration with working-copy drift is not acceptable simply because the package now has three entries. The release manifest supplies two independent comparisons: exact pathname membership and exact contents at each approved pathname. Neither comparison should be weakened to make a candidate pass.

Separate commit identity from export policy

Record two inputs separately: the immutable commit supplying the payload and the attribute sources deciding selection. Git’s documented attribute-source rules use committed-tree attributes by default, allow --worktree-attributes as an explicit alternative and recognize $GIT_DIR/info/attributes as another local influence. Consequently, naming a commit does not by itself pin every packaging-policy input.

The defective committed policy is:

.gitattributes export-ignore
docs/internal.txt export-ignore
config/runtime.json export-ignore

The first two exclusions are intentional. The third contradicts the release manifest. The controlled repair removes only that third line.

In the experiment, G1 through G6 all name the defective commit. Their differing outputs cannot be explained by selecting different application revisions. Conversely, G7 through G9 name the repaired commit, yet G8 still loses a required file. These comparisons make policy identity an observable input rather than an assumption attached to a commit hash.

Record the archive flag, repository location, complete local override text and relevant environment controls alongside the commit. “Built from the approved revision” is incomplete evidence when the producer has not declared the selection policy.

Treat a policy override as a reviewed input

A temporary override is useful for testing a diagnosis. Recovering the configuration without changing the payload commit supports the conclusion that selection, rather than missing source, caused the omission.

That success does not authorize an undocumented release procedure. G3, G4 and G5 therefore receive PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED, not ACCEPT_BOUNDED. The payload matches; the alternate policy still requires approval.

This parallels the distinction between release identity versus release approval, without introducing an attestation pipeline. Evidence establishes what happened. Approval establishes whether that combination of source, policy and output was permitted. Keep those decisions separate even when the resulting bytes look perfect.

Run the complete nine-archive experiment

Use a new writable disposable directory on Linux. Save the complete source below as git_archive_fixture.py outside any existing project. The script creates a uniquely named child directory containing repo, clean, all nine tar candidates and results.json. It leaves these materials in place for inspection.

Confirm the intended executables, then run:

python --version
git --version
python git_archive_fixture.py

The recorded environment is CPython 3.13.5, Git 2.47.3, and Linux-6.18.44-x86_64-with-glibc2.41. These are the versions in the supplied laboratory report, not claims about the latest releases. The driver records versions but does not install or enforce them; inspect the report before comparing another environment.

Inherited GIT_ variables are removed. Private home/configuration directories, disabled system configuration and system attributes, and command-level core.attributesFile=/dev/null prevent inherited user/system attribute settings from deciding this fixture. The Git configuration manual documents core.attributesFile as an additional attributes source, so controlling configuration alone is not a substitute for controlling that file.

The supplied laboratory report records PASS_9_CASES, starting at 2026-10-14T11:03:08.449968+00:00 and finishing at 2026-10-14T11:03:08.562503+00:00. Its source SHA-256 matches the supplied driver file:

c905bf252c6bc29c716992f0f2e6386ed7c7ca69e180df8950162d1ed1d5db52

The case results, commit identifiers and status snapshots discussed below refer to that report and its nine retained archives. They are recorded synthetic observations, not production history. The timestamps identify one run; they are not a benchmark. A fresh execution creates its own evidence directory and must retain its own report.

The complete reference source is embedded below. No companion helper file is required. Save it as plain UTF-8 text, preserving indentation, literal backslashes and ordinary quotation marks. Changes in source bytes, including line endings, change its digest; a new digest must identify the actual file used for a new execution rather than being copied from this article.

Compare tar members without extracting them

The driver opens each tar in memory, skips directory records, rejects unsupported non-directory members and duplicate regular-file names, then reads regular-file bytes. Python’s tarfile member-stream documentation explains that extractfile() returns a file object. This use reads a member stream without installing a pathname on the filesystem.

The comparison uses bytes before rendering these known text payloads into JSON. It checks the diagnostic path set, each present payload and complete equality with EXPECTED. Directory metadata is not an acceptance input. This is an owned-fixture inspector, not a general admission tool for hostile archives.

"""Owned local Git repositories; inspect tar members without extracting them."""
import hashlib
import io
import json
import os
import platform
import subprocess
import tarfile
import tempfile
from datetime import datetime, timezone
from pathlib import Path

EXPECTED = {"README.txt": b"Synthetic release\n", "src/app.txt": b"APP-v1\n",
            "config/runtime.json": b'{"mode":"lab"}\n'}
BASE_ATTRS = ".gitattributes export-ignore\ndocs/internal.txt export-ignore\n"
BAD_ATTRS = BASE_ATTRS + "config/runtime.json export-ignore\n"


def require(ok, message):
    if not ok:
        raise RuntimeError(message)


def main():
    root = Path(tempfile.mkdtemp(prefix="git-archive-", dir=Path(__file__).parent))
    repo, clean = root / "repo", root / "clean"
    repo.mkdir()
    env = {k: v for k, v in os.environ.items() if not k.startswith("GIT_")}
    env.update(HOME=str(root / "home"), XDG_CONFIG_HOME=str(root / "xdg"),
               GIT_CONFIG_GLOBAL=os.devnull, GIT_CONFIG_NOSYSTEM="1", GIT_ATTR_NOSYSTEM="1",
               GIT_TERMINAL_PROMPT="0", LC_ALL="C")
    (root / "home").mkdir()
    (root / "xdg").mkdir()
    report = {"revision": "git-archive-v1", "status": "FAIL", "cases": [], "commands": [],
              "started_utc": datetime.now(timezone.utc).isoformat(),
              "python": platform.python_version(), "platform": platform.platform(),
              "source_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}

    def git(*args, where=repo):
        command = ["git", "-c", "core.attributesFile=" + os.devnull,
                   "-c", "core.autocrlf=false", "-c", "commit.gpgsign=false", args]
        proc = subprocess.run(command, cwd=where, env=env, capture_output=True, timeout=20)
        report["commands"].append({"args": args, "cwd": str(where), "exit": proc.returncode,
                                   "stderr": proc.stderr.decode(errors="replace")})
        require(proc.returncode == 0, "Git failed: " + repr(args) + " " + proc.stderr.decode(errors="replace"))
        return proc.stdout

    def write(relative, data):
        path = repo / relative
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(data if isinstance(data, bytes) else data.encode())

    def archive(identity, commit, expected_paths, , worktree=False, where=repo, accept=False):
        row = {"id": identity, "status": "FAIL", "commit": commit,
               "worktree_attributes": worktree, "repository": str(where)}
        report["cases"].append(row)
        args = ["archive", "--format=tar"]
        if worktree:
            args.append("--worktree-attributes")
        raw = git(*args, commit, where=where)
        path = root / (identity + ".tar")
        path.write_bytes(raw)
        row["archive_sha256"] = hashlib.sha256(raw).hexdigest()
        files = {}
        with tarfile.open(fileobj=io.BytesIO(raw), mode="r:") as tf:
            for member in tf.getmembers():
                if member.isdir():
                    continue
                require(member.isfile() and member.name not in files, "Unsupported or duplicate member")
                stream = tf.extractfile(member)
                require(stream is not None, "Missing member stream")
                with stream:
                    files[member.name] = stream.read()
        row["paths"] = sorted(files)
        row["contents"] = {k: v.decode() for k, v in sorted(files.items())}
        row["status_porcelain"] = git("status", "--porcelain=v1", "--untracked-files=all", where=where).decode()
        info = where / ".git" / "info" / "attributes"
        row["info_attributes"] = info.read_text() if info.exists() else None
        require(row["paths"] == sorted(expected_paths), "Archive selection differs from diagnostic manifest")
        require(all(v == EXPECTED[k] for k, v in files.items()), "Archived payload differs from commit literals")
        row["matches_release_contract"] = files == EXPECTED
        require(row["matches_release_contract"] == accept, "Unexpected release-contract verdict")
        decision = "ACCEPT_BOUNDED" if accept else "REPAIR_SELECTION"
        if identity.startswith(("G3_", "G4_", "G5_")):
            decision = "PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED"
        row.update(status="PASS", decision=decision)
        return row

    try:
        report["git"] = git("--version").decode().strip()
        git("init", "--initial-branch=main")
        git("config", "user.name", "Synthetic Fixture")
        git("config", "user.email", "[email protected]")
        for name, payload in EXPECTED.items():
            write(name, payload)
        write("docs/internal.txt", "Do not distribute\n")
        write(".gitattributes", BAD_ATTRS)
        git("add", ".")
        git("commit", "-m", "Synthetic defective export policy")
        bad = git("rev-parse", "HEAD").decode().strip()
        report["bad_commit"] = bad
        tracked = git("ls-tree", "-r", "--name-only", "-z", bad).decode().rstrip("\0").split("\0")
        report["tracked_at_bad_commit"] = tracked
        require("config/runtime.json" in tracked, "Required file was not tracked")
        missing_config = ["README.txt", "src/app.txt"]
        archive("G1_committed_export_ignore", bad, missing_config)
        write(".gitattributes", BASE_ATTRS)
        archive("G2_uncommitted_policy_default_archive", bad, missing_config)
        archive("G3_explicit_worktree_attributes", bad, EXPECTED, worktree=True, accept=True)
        write("config/runtime.json", '{"mode":"WORKTREE-DRIFT"}\n')
        row = archive("G4_worktree_policy_not_worktree_payload", bad, EXPECTED, worktree=True, accept=True)
        row["working_payload"] = (repo / "config/runtime.json").read_text()
        require(row["working_payload"] != EXPECTED["config/runtime.json"].decode(), "Drift control missing")
        write(".git/info/attributes", "config/runtime.json -export-ignore\n")
        archive("G5_local_override_of_committed_policy", bad, EXPECTED, accept=True)
        write(".git/info/attributes", "config/runtime.json -export-ignore\nsrc/app.txt export-ignore\n")
        archive("G6_local_override_omits_another_file", bad, ["README.txt", "config/runtime.json"])
        (repo / ".git/info/attributes").unlink()
        write("config/runtime.json", EXPECTED["config/runtime.json"])
        git("add", ".gitattributes")
        git("commit", "-m", "Review complete release selection")
        good = git("rev-parse", "HEAD").decode().strip()
        report["good_commit"] = good
        archive("G7_committed_repair", good, EXPECTED, accept=True)
        write(".git/info/attributes", "src/app.txt export-ignore\n")
        row = archive("G8_clean_status_hidden_override", good, ["README.txt", "config/runtime.json"])
        require(row["status_porcelain"] == "", "Clean-status negative control failed")
        git("clone", "--no-local", str(repo), str(clean), where=root)
        row = archive("G9_clean_clone_approved_commit", good, EXPECTED, where=clean, accept=True)
        require(row["info_attributes"] is None and row["status_porcelain"] == "", "Clone was not clean")
        require(len(report["cases"]) == len({r["id"] for r in report["cases"]}) == 9,
                "Incomplete case inventory")
        report["status"] = "PASS_9_CASES"
    except Exception as error:
        report["error"] = {"type": type(error).__name__, "message": str(error)}
        raise
    finally:
        report["finished_utc"] = datetime.now(timezone.utc).isoformat()
        path = root / "results.json"
        path.write_text(json.dumps(report, indent=2, allow_nan=False) + "\n")
        print(path)

if name == "__main__":
    main()

A successful test run contains deliberate release failures. PASS_9_CASES means every case behaved as expected, including omissions; it does not approve all nine tars. Similarly, accept=True in the helper declares an expected manifest match, not independent authorization to publish. The case-specific decision preserves that distinction.

Read a new report in a fixed order. First, compare its environment and source_sha256 with the source actually executed. Second, obtain bad_commit and good_commit; these identify the payload inputs rather than merely the branch names used during construction. Third, reconcile every case’s paths, contents, info_attributes and status_porcelain. Finally, check the case inventory and overall status. A correct final row does not establish that the earlier negative controls ran.

The driver requires nine unique case IDs before setting the overall success marker. It also checks each diagnostic path set before comparing contents. This matters because a row with no configuration should pass its intended omission test while still failing the independent release contract. Treat status, matches_release_contract and decision as different fields, not redundant spellings of success.

Do not run the nine cases concurrently against the same repository. The experiment is a deliberate sequence: later cases inherit specific working edits until the driver restores or removes them. Parallelizing the archive calls would change the causal design rather than merely accelerate it. The second repository is introduced only for the final independent-context control.

The report retains command arguments, working directories, exit statuses, stderr, commit IDs, archive hashes, paths, contents, local overrides and status snapshots. The retained source supplies the wrapper configuration and sequence of worktree edits; the report is not a general inventory of every machine setting. No separate storage-failure or error-reporting fault-injection campaign is claimed.

Prove that the omitted configuration is tracked

G1 begins with the defective commit and an unmodified working tree. The recursive inventory recorded under tracked_at_bad_commit is:

.gitattributes
README.txt
config/runtime.json
docs/internal.txt
src/app.txt

The driver obtains this inventory using git ls-tree -r --name-only -z and the resolved commit ID. The git ls-tree manual describes -r as recursive traversal and -z as NUL-terminated, unquoted pathname output. Running from the repository root avoids accidentally scoping this fixture’s inventory to a subdirectory.

Presence in that inventory establishes the configuration’s membership in the selected source tree. G1 then records only README.txt and src/app.txt as regular tar members. Their bytes match their literals, but the mapping lacks the required configuration. Its release decision is REPAIR_SELECTION.

The commands themselves succeed. The supplied report records zero exit status for every Git command, including the defective exports. Treat that as successful execution of the requested operation, not successful fulfillment of a consumer requirement that Git was never asked to validate.

The initial status is empty, and info_attributes is null. Together with the constructed environment and committed policy, those controls make G1 a useful baseline: the defect is present before any diagnostic override or working-copy change.

Avoid interpreting the tracked inventory as a release manifest. It answers which paths exist in the chosen source snapshot. The independent consumer manifest answers which regular paths must appear in the package. The tar inventory answers which ones actually appeared. Keeping all three documents distinct makes the discrepancy explainable.

For G1, comparing the actual regular-path set against EXPECTED produces one missing path, config/runtime.json, and no unexpected paths. Comparing against the five-path tracked inventory would instead label the two intentional exclusions as defects. That small difference is why the consumer manifest must be approved before the tree and archive inventories are interpreted.

This also prevents a destructive “repair” aimed at the wrong layer. There is no reason to remove and re-add the configuration merely to prove tracking again. The evidence already establishes its presence. The next question is which attribute source excluded it, followed by whether a narrowly corrected policy emits the complete approved mapping.

Show why an uncommitted attribute edit is ignored by default

Before G2, the driver replaces the working .gitattributes with the two intentional exclusions. It does not commit that edit. G2 still names the defective commit and does not enable --worktree-attributes.

The recorded status is exactly:

 M .gitattributes

The resulting regular paths remain README.txt and src/app.txt. The configuration is still absent, so G2 remains REPAIR_SELECTION. The successful edit changed a working file, not the policy selected by this archive invocation. That outcome agrees with Git’s default archive attribute source.

Capture both pieces of evidence. A screenshot of the corrected working policy would be incomplete without the command’s attribute-source flag and output inventory. Equally, an unchanged tar listing without the dirty-status record would not show that the intended diagnostic variable actually changed.

Distinguish working policy from working payload

G3 names the same commit but adds --worktree-attributes. All three required paths now appear, with the exact expected bytes. This isolates the policy-source switch: the consumer mapping changes although the selected payload revision does not.

G4 adds a stronger negative control. The working configuration becomes:

{"mode":"WORKTREE-DRIFT"}

That working file contains 26 bytes, including its newline. Nevertheless, G4 archives the committed 15-byte {"mode":"lab"}\n payload. Its status records changes to both .gitattributes and config/runtime.json, while working_payload preserves the drift sentinel.

G3 establishes that the flag can change selection. G4 establishes that, in this invocation, selecting working attributes did not turn the archive into a copy of arbitrary working payloads. Both cases receive PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED because the complete payload was recovered through an alternate, uncommitted policy source.

Keep evidence attached to the moment of each archive call. Inspecting the repository only after all nine cases finish cannot reconstruct G2’s working state by observation: subsequent steps have changed it. The ordered driver and per-case report explain the earlier states; the final checkout is not a substitute for those records.

Do not remove G4 merely because G3 already produces three files. Without the distinct working-byte sentinel, identical working and committed contents would leave the payload-source claim insufficiently challenged.

Inspect the highest-priority local attribute override

G5 restores configuration selection without the worktree-attribute flag. The selected commit remains defective, and the working edits from G4 remain present. The complete .git/info/attributes file is:

config/runtime.json -export-ignore

The Git attributes manual gives $GIT_DIR/info/attributes the highest precedence. Here the minus sign unsets the attribute; it is not a negative pathname pattern. Negative patterns are not valid attribute-file patterns.

G5 emits all three expected paths and their committed bytes. Its decision remains PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED: correct output does not make an implicit local exception an approved packaging policy.

G6 extends the same override file to:

config/runtime.json -export-ignore
src/app.txt export-ignore

The configuration remains included, but the application disappears. The archive now contains README.txt and config/runtime.json, both byte-correct. This is REPAIR_SELECTION, not partial acceptance. Inspecting only the file originally reported missing would have overlooked a new omission introduced by the same policy source.

Retain the complete override text, including line order and newlines, rather than a note saying “local fix applied.” The report does exactly that. It also shows that G5 and G6 ran without --worktree-attributes, ruling out that flag as the explanation for their changed selection.

For supplementary diagnosis, the git check-attr manual distinguishes set, unset and unspecified values. Label its source explicitly: ordinary working-tree inspection, index inspection with --cached, or a named tree with --source=<commit>. Do not describe these as interchangeable views. Also record local overrides rather than assuming a named-tree query removes them. The driver does not execute these optional diagnostics; the emitted archive remains its acceptance evidence.

In a real review, an attribute explanation should reconcile every consumer path, not just demonstrate one favorable value. G6 makes that requirement observable with a second required file.

Demonstrate the limit of a clean status check

G8 is deliberately examined before the successful repair is discussed in detail. It names the repaired commit used by G7, but installs this local override:

src/app.txt export-ignore

The archive contains the correct README and configuration, but no application file. The recorded output of git status --porcelain=v1 --untracked-files=all is the empty string. The driver explicitly requires that emptiness for this negative control to pass.

The git status documentation describes differences involving the commit, index and working tree, together with relevant untracked paths. It is not a complete inventory of repository administration or every input another Git command might consult. G8’s unchanged status therefore does not contradict its changed archive selection.

Keep the status check; narrow its meaning. In this experiment it distinguishes working-file changes in G2 through G6 from the clean tracked state in G7 through G9. What it cannot do is replace inspection of .git/info/attributes.

The useful contrast is G7 versus G8: same repaired commit, no worktree-attribute flag, clean status in both, different local policy and different regular-file membership. A gate that accepts solely because the commit is approved and status is empty would approve G8 incorrectly.

Do not turn this into another ignore-file tutorial

The gitignore documentation concerns intentionally untracked files and explicitly states that files already tracked are unaffected. The missing application here is tracked. Editing ignore patterns would address a different mechanism from the export attribute being exercised.

Docker build-context selection is likewise outside this test. No container build receives the files. The consumer evidence is the regular-file mapping emitted by local git archive, not a working-directory listing or a build-context estimate.

The operational lesson is about evidence scope, not about abandoning familiar tools. An empty status is useful evidence of one condition. G8 proves that promoting it into a broader claim about all packaging policy would exceed what this control actually checked.

Commit the repair and verify its emitted payload

G7 removes the temporary local override, restores the working configuration to its expected literal and stages only .gitattributes. The second commit records this policy:

.gitattributes export-ignore
docs/internal.txt export-ignore

The repair does not discard all exclusions. It retains both intentional boundaries while removing the rule that contradicted the consumer manifest. Increasing a file count is not the objective; preserving the approved inclusion and exclusion decisions is.

In the supplied laboratory report, the full immutable identifiers are:

B (defective): 618ef4914f036c6ba0be79e7a340bc2b741e1fc5
G (repaired):  ff0ab65ab3c71d19d2a9a58a945729a9a1d43807

The table below uses B and G only as readable aliases for these recorded IDs. Every case in results.json stores the full selected commit. Another execution creates its own commits; it must record its own identities rather than borrow these values.

G7 emits README.txt, config/runtime.json and src/app.txt. Their byte lengths are respectively 18, 15 and 7, and their contents equal EXPECTED. Neither .gitattributes nor docs/internal.txt appears among the regular members. Status is empty, and no local override exists.

The fixture assigns ACCEPT_BOUNDED to this controlled repair. “Reviewed” is part of the synthetic scenario: the second policy is the designated approved policy. The commit message alone is not evidence that a real release owner approved anything. Applying this procedure to an actual release requires a separately reviewable policy decision.

Restoring the drifted configuration before committing matters. It prevents a diagnostic sentinel from becoming an unintended source change and keeps the repair focused on selection. The acceptance comparison then answers whether the revised policy emits the existing approved payload, not whether a combined policy-and-content change happened to produce a plausible package.

The committed repair also provides a reviewable explanation for why the archive changed. The producer can point to one removed exclusion while the payload literals stay constant. That is a stronger diagnosis than replacing the packaging command with an unrelated mechanism and observing that files reappear. It preserves the variable under investigation: export selection under the intended Git workflow.

G7 is the positive control; G8 immediately demonstrates why its acceptance cannot be generalized to every archive made from the same commit in any local environment.

Repeat the approved export in a controlled fresh clone

After G8, the original repository still contains the override excluding the application. The driver creates its second repository with a local clone using --no-local, then archives the full repaired commit ID in that new location.

The Git clone documentation describes --no-local as disabling local clone optimizations and using the regular Git transport for a local-path source. That is not, by itself, a promise to neutralize every environmental influence. The relevant evidence is what the constructed clone and its controlled invocation actually contain.

For G9, info_attributes is null, status is empty, and all three regular paths and byte strings equal EXPECTED. The same environment filtering and command-level configuration apply because the clone and archive calls use the same wrapper.

This distinguishes two plausible explanations for recovery. G9 did not select a newer application revision; it used G. It also did not quietly fix the original repository’s override; that repository was left available as the G8 diagnostic context. The candidate was generated in a different controlled context where the override was absent.

Do not translate that result into “every fresh clone is hermetic.” This script controls specified configuration inputs in an owned environment. It does not certify arbitrary Git installations, repository templates, wrapper replacements or concurrent interference. A different environment needs its own declared assumptions and observations.

Keep source selection separate from artifact transport

The next consumer might impose additional requirements, including file modes as a separate transport contract. That is a neighboring qualification boundary, not something established by this laboratory.

No candidate is uploaded, extracted, repaired with permission commands or executed here. G9 establishes complete selection and exact contents for this manifest under the controlled approved policy. It does not certify deployment readiness. Keeping that boundary explicit makes ACCEPT_BOUNDED a useful decision rather than an unsupported promise about everything that might happen after packaging.

Reconcile the nine manifests rather than their file counts

The following rows summarize the supplied laboratory report. B and G are the full commit aliases above; “WT” means --worktree-attributes. All present regular-file bytes match their corresponding EXPECTED literals. “Full” additionally means the entire mapping matches.

For readability, policy decisions are abbreviated here: Repair means REPAIR_SELECTION; Review means PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED; Accept means ACCEPT_BOUNDED. The report retains their full values. The G1 through G9 labels follow the driver’s case IDs, and each case produces a separately named tar.

Case

Commit

WT

Local override

Emitted regular paths

Content verdict

Decision

G1

B

No

None

README.txt
src/app.txt

Present bytes match; incomplete

Repair

G2

B

No

None

README.txt
src/app.txt

Present bytes match; incomplete

Repair

G3

B

Yes

None

README.txt
config/runtime.json
src/app.txt

Full

Review

G4

B

Yes

None

README.txt
config/runtime.json
src/app.txt

Full despite working drift

Review

G5

B

No

Unset config exclusion

README.txt
config/runtime.json
src/app.txt

Full

Review

G6

B

No

Unset config; exclude app

README.txt
config/runtime.json

Present bytes match; incomplete

Repair

G7

G

No

None

README.txt
config/runtime.json
src/app.txt

Full

Accept

G8

G

No

Exclude app

README.txt
config/runtime.json

Present bytes match; incomplete

Repair

G9

G

No

None in clone

README.txt
config/runtime.json
src/app.txt

Full

Accept

G1 and G2 contain two required files totaling 25 bytes. G6 and G8 also contain two, but total 33 bytes and omit a different requirement. Counts therefore lose identity even in this tiny fixture.

G4 is a successful negative control because the working sentinel differs while archived configuration bytes remain correct. A full mapping under G3 through G5 still needs policy review; a smaller mapping under G1, G2, G6 or G8 needs selection repair. Neither situation should disappear behind a single green test-suite indicator.

Retain failed packages and regenerate from approved input

Preserve the incomplete candidate before changing the environment. G1’s tar and its record show the original configuration omission. G6’s record shows a different omission introduced by a local policy edit. G8 preserves the clean-status counterexample. These outputs are evidence, not temporary clutter to overwrite as soon as one package succeeds.

Keep each candidate associated with its case ID, selected commit, source digest, archive digest, invocation and observed policy. The driver writes each archive under a distinct name before validating its members. It does not replace an earlier tar with a later successful one.

For an unexpected failure inside the guarded execution, the driver records an exception and attempts to write the partial report in finally. Do not assume that reporting survives every filesystem failure or interruption. A missing report, truncated record or absent candidate must be treated as incomplete evidence rather than retroactively labeled successful.

Regenerate a candidate from the independently approved manifest and reviewed policy revision. Do not silently insert the missing configuration into the old tar. That would replace the tested production path with a different transformation and require its own evidence, while obscuring why the original selection failed.

The principle resembles recovering source state in an independent context: preserve the diagnostic state while constructing a separately inspectable result. No stash is created, applied or repaired here. The independent context is the controlled clone used for G9.

Archive SHA-256 values identify retained artifacts; they are not the payload oracle. Two candidates can satisfy the same path-and-byte contract without a requirement that every tar byte remain identical across unrelated runs. Conversely, accurately identifying an incomplete tar does not make it complete.

Retaining the original is also necessary for honest incident communication. “The configuration was missing” and “the application was missing” are different observations, even when both arise during the same investigation. Associate the consumer impact with the correct candidate rather than collapsing all rejected packages into one generic failure record.

A useful replacement record explains which reviewed input changed, which old candidate remains rejected and which new candidate passed the bounded gate. That is more actionable than an unexplained “rerun succeeded.”

Set the release gate for selection and policy

The gate requires a conjunction: the intended immutable commit, declared effective attribute sources, an approved regular-file set, exact bytes and approval of the policy that selected them. No individual field replaces the others.

Use REPAIR_SELECTION when the evidence establishes a missing required file under a known policy, as in G1, G2, G6 and G8. The next action is to repair selection and regenerate, not to waive the manifest.

Use PAYLOAD_MATCH_POLICY_REVIEW_REQUIRED when the full mapping matches but an alternate policy source lacks approval. G3 through G5 are deliberately in this category. Their byte equality is useful evidence, not an automatic promotion instruction.

Use ACCEPT_BOUNDED for the designated reviewed policy with complete observed payload evidence in the controlled environment. G7 and G9 qualify within that scope. This label says nothing about the excluded transport, permissions or deployment contracts.

Use HOLD when evidence is missing, contradictory or cannot establish the declared conditions. Examples include an unexpected member type, byte mismatch, unexplained override, incomplete case inventory or mismatched evidence identity. The reference driver reports unexpected conditions as failures and exceptions; it does not emit a HOLD decision row. HOLD is the reviewer’s operational disposition for such unresolved evidence.

Evidence identity needs its own check. The source digest must identify the driver actually executed, and the archive digest must identify the candidate actually inspected. Matching case summaries from two runs do not establish that their source files or artifacts are byte-identical. Resolve discrepancies before combining their evidence.

A rollback does not recall distributed archives

This fixture does not publish or roll back releases. A proposed rollback would select a previously reviewed packaging revision and generate a separate candidate, subject to the same manifest and policy checks.

Changing source selection cannot establish that earlier copies have disappeared from consumers. Distributed-copy inventories, withdrawal procedures and downstream notifications remain outside this experiment. Record a rollback candidate as a new artifact decision rather than implying that a Git operation recalled every previously produced package.

Assign ownership and trigger requalification deliberately

Assign responsibilities before an omission becomes an emergency. The producer owns required pathnames and byte expectations. The release owner approves exclusions and any alternate policy source. CI ownership covers the effective command, executable and configuration environment. QA owns reconciliation of the complete case ledger and the distinction between a passed diagnostic and an accepted candidate.

These responsibilities may belong to fewer people in a small team, but they should remain separate questions. Otherwise, the same unchecked assumption can generate the archive, derive its expected manifest and approve its release.

Use evidence-driven release regression checks as the ownership context: the valuable output is a defensible decision with retained evidence, not a large collection of green assertions. Here, a reviewer must be able to explain why G8 is a successful test and an unacceptable package simultaneously.

Requalify after changing .gitattributes, local overrides, the Git executable, wrapper arguments, configuration sources, required files or the archive consumer’s contract. A move from default attributes to --worktree-attributes is a policy change even when an initial sample package looks identical.

Keep the causal controls when adapting the experiment. Retain an omitted-but-tracked requirement, a working-policy edit ignored by default, explicit working-policy selection, working-byte drift, a local override that fixes one file but loses another, and a clean-status omission. Finish with the committed repair and controlled-clone comparison.

New cases or changed fixture code need a new source digest and an actual report. Do not attach the old PASS_9_CASES label to a modified program merely because it still imports successfully. Syntax checks, behavioral execution and policy approval answer different questions.

When required contents change intentionally, review the new manifest before generating the replacement candidate. Do not update EXPECTED by copying values from a failing report merely to restore a passing run. An authorized product change can justify different bytes; a packaging observation alone cannot supply that authorization.

Finally, bound the handoff. This report covers regular-file selection and contents in an owned laboratory. It does not claim exhaustive error-path coverage, resistance to hostile repositories or certification of every downstream release requirement. Those limits should travel with the evidence so another team does not unknowingly widen the acceptance claim.

Practice release engineering with stronger foundations

The decisive repair is not “make Git include more files.” It is to align export selection with a reviewed consumer contract, demonstrate exact emitted bytes and confirm the effective policy in the environment that produced the candidate. G7 and G9 meet that bounded contract; the other cases explain why weaker checks would mislead.

For structured foundations, Refonte Learning’s DevOps Engineering programme lists Git/GitHub, Linux scripting, CI/CD and a capstone, with a stated duration of three months and 12–14 hours per week. Those themes support the underlying skills; the programme page does not establish that this exact export-ignore laboratory is included.

Practice carrying an independent expectation through source selection, policy inspection and artifact comparison. The useful release-engineering habit is not trusting a familiar command more strongly. It is retaining enough evidence to know exactly which claim its successful output supports.