This website is meant to be read and understood quickly by humans, but is only fully parsable, on a technical level, with the aid of an AI system. Read why →
Loop MMT
Coverage-Provable Reading Contractexcavate← all gifts

The Excavation

Point this at your site; hand the output to any AI; it can now prove it read all of it. Enumerate every page as a typed node, shard by budget, and track coverage against that enumerated oracle until the set-difference is empty — an honest accounting, not a confident skim.

What it is

A big site is bigger than one person — or one AI context window — can read in a sitting. The Excavation turns a bounded web corpus into a coverage-provable reading job. It builds three things from your site: a manifest (every page as a typed node — the coverage oracle), shards (the pages bundled into context-sized chunks), and a reckoning (a structured protocol a reader follows, tracking what it has covered against the manifest until the set-difference is empty). The result isn't “I read the important parts” — it's an enumerated whole and an honest accounting against it, with nothing missing.

This is the coverage-contract: enumerate every node, shard by budget, prove coverage against the node set. Not a crawler. Not SEO. A way to hand an AI a corpus and get back proof of complete coverage instead of a confident skim.

The authority handshake — not optional. The Excavation is run by an AI only when a human explicitly asks it to. A human copying this prompt to an AI is the authorization; absent that human act, an AI should not initiate an excavation, treat a site's existence as consent to ingest it, or self-authorize crawling. A human asks; the AI reads what it was pointed at; the AI reports coverage — including what it could not reach. Nothing runs without the human's ask.

Use it

git and Python 3 only, no dependencies, MIT licensed. Copy the directory, set four config knobs, declare your reconnaissance reading set, and run one script.

cp excavate.config.example excavate.config    # base_url, site_name, node_source, coverage_rules
cp core-set.txt.example core-set.txt          # your reconnaissance pages (the one input that matters)
python3 excavate.py                           # writes manifest + shards + reckoning.json
python3 excavate.py --check                   # honesty invariant: manifest nodes == site node set

Hand the three output files to an AI with “read this corpus and prove you covered all of it,” and reckoning.json tells it exactly how.

The core set — the one input that matters

The reckoning offers two reading altitudes: a quick reconnaissance pass and a full pass. Reconnaissance reads a small, declared set of pages — the ones a newcomer should read first to understand the whole. That set can't be inferred; you declare it, one served filename per line, in core-set.txt. Leave it undeclared and the Excavation still runs, but it says so loudly and collapses to a single tier — because a reconnaissance altitude with no declared core set would read everything and mean nothing.

The honest edge
The standalone driver is proven in a tree that carries the builders and their helpers; pointed at a bare stranger tree it fails loudly on the missing imports rather than pretending. Full standalone independence is the next build beat — earned against a real foreign fixture, not asserted. The gap is documented in the driver header, not papered over.
Run it
python3 excavate.py --check in-tree proven · standalone gate: beat 5 git + Python 3 stdlib, no dependencies
The code — every file that ships
excavate.py135 lineson GitHub →
#!/usr/bin/env python3
"""excavate.py — The Excavation gift driver.

Reads `excavate.config` (+ optional `core-set.txt`), enumerates a site's pages,
and emits the three coverage-contract faces:

    corpus-manifest.json   the typed-node coverage ORACLE
    corpus-shards.json     budget-sized reading bundles (a pure fold of the manifest)
    reckoning.json         the reader's coverage protocol (a pure fold of the two above)

Hand those three to an AI with "read this corpus and prove you covered all of it,"
and reckoning.json tells it exactly how (see README, "What 'done' looks like").

  python3 excavate.py            build the three faces from ./excavate.config
  python3 excavate.py --check    honesty invariant: manifest nodes == site node set

──────────────────────────────────────────────────────────────────────────────
HONEST STATUS (read before relying on this on a non-loopmmt site)
──────────────────────────────────────────────────────────────────────────────
The generalized builders (build_corpus_manifest.py + its two pure-fold siblings)
carry a config object (`_CFG`) whose defaults reproduce loopmmt.com byte-identical
and whose knobs — base_url, site_name, wrapper_dir, coverage_rules, core_set —
are exactly what this driver injects from excavate.config / core-set.txt.

What is PROVEN: the config surface, the _branch generalization, and the
declared-core-set mechanism (a foreign run with no core-set.txt emits a loud note
and collapses to one tier rather than silently mis-sharding).

STANDALONE INDEPENDENCE — PROVEN (DP-039 s7 beat 5, the acceptance gate). The
in-tree builders (build_corpus_manifest + siblings) still import loopmmt's own
toolchain (build_machine_digest for the sitemap walk; redact + disclosure_gate for
the publish gates) — that path stays byte-identical on the home site. A stranger's
bare tree carries none of those, so this driver no longer dies on the missing
imports: it falls back to `excavate_standalone.py`, a self-contained builder that
does a local .html walk (replacing the sitemap walk), extracts title/desc/body with
stdlib re+html (replacing the digest helpers), and DROPS the two publish gates —
loopmmt.com publish controls keyed to loopmmt's private-signature set and
disclosure map, a safety a stranger's own served tree does not need and this gift
must not falsely assert. Earned the way the beat required: by running against a
REAL non-loopmmt fixture (test-fixture/) under smoke_test.sh and letting the
missing-import failure define the seam. See excavate_standalone.py + smoke_test.sh.
"""
import os
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
CONFIG_NAME = "excavate.config"
CORE_SET_NAME = "core-set.txt"


def _die(msg, code=1):
    sys.stderr.write("excavate: " + msg + "\n")
    sys.exit(code)


def parse_config(path):
    """Parse the `key = value` config. Returns a dict with a `coverage_rules`
    list assembled from the repeated `rule = <substring> => <class>` lines."""
    if not os.path.isfile(path):
        _die("no %s found next to excavate.py — copy %s.example to %s and edit it."
             % (CONFIG_NAME, CONFIG_NAME, CONFIG_NAME))
    cfg = {"coverage_rules": []}
    with open(path, "r", encoding="utf-8") as fh:
        for raw in fh:
            line = raw.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                _die("malformed config line (no '='): %r" % raw.rstrip())
            key, _, val = line.partition("=")
            key, val = key.strip(), val.strip()
            if key == "rule":
                if "=>" not in val:
                    _die("malformed coverage rule (want 'substring => class'): %r" % val)
                sub, _, cls = val.partition("=>")
                sub, cls = sub.strip(), cls.strip()
                if cls not in ("deep", "standard", "optional"):
                    _die("coverage rule class must be deep|standard|optional, got %r" % cls)
                cfg["coverage_rules"].append((sub, cls))
            else:
                cfg[key] = val
    if not cfg.get("base_url"):
        _die("config is missing the required `base_url`.")
    cfg["base_url"] = cfg["base_url"].rstrip("/")
    return cfg


def read_core_set(path):
    """Read the declared reconnaissance set, one served filename per line.
    Returns None when absent (the builder then emits its loud single-tier note)."""
    if not os.path.isfile(path):
        return None
    names = []
    with open(path, "r", encoding="utf-8") as fh:
        for raw in fh:
            line = raw.strip()
            if line and not line.startswith("#"):
                names.append(line)
    return set(names) if names else None


def main():
    cfg = parse_config(os.path.join(HERE, CONFIG_NAME))
    core = read_core_set(os.path.join(HERE, CORE_SET_NAME))

    # Two builder paths, chosen by what the tree carries:
    #   * IN-TREE (loopmmt.com) — build_corpus_manifest.py + siblings are present;
    #     inject config into their _CFG and run them (byte-identical home output).
    #   * STANDALONE (a stranger's bare tree) — those builders are absent; run the
    #     self-contained excavate_standalone.py, which does a local walk with zero
    #     loopmmt deps. This is the fixture-proven acceptance beat (DP-039 s7 b5):
    #     the missing-import failure is no longer fatal — it routes to standalone.
    try:
        import build_corpus_manifest as manifest
    except ImportError:
        import excavate_standalone as standalone
        standalone.main()
        return

    manifest._CFG["base_url"] = cfg["base_url"]
    manifest._CFG["site_name"] = cfg.get("site_name", cfg["base_url"])
    manifest._CFG["wrapper_dir"] = cfg.get("wrapper_dir") or ""
    manifest._CFG["node_source"] = cfg.get("node_source", "local-walk")
    if cfg["coverage_rules"]:
        manifest._CFG["coverage_rules"] = cfg["coverage_rules"]
    if core is not None:
        manifest._CFG["core_set"] = core

    if "--check" in sys.argv:
        sys.argv = [a for a in sys.argv if a != "--check"] + ["--check"]
    manifest.main()


if __name__ == "__main__":
    main()
core-set.txt.example19 lineson GitHub →
# The Excavation — core set. Copy this to `core-set.txt` and edit.
#
# This is the DECLARED reconnaissance reading set: the pages a newcomer (human or
# AI) should read FIRST to understand the whole site before the full pass. It is
# the one input the Excavation cannot infer for you — you know which pages are the
# doorway to your corpus; a path rule does not.
#
# One served filename per line, as it appears in your site tree (the same path the
# manifest lists it under). Lines starting with # are comments and are ignored.
#
# If you leave core-set.txt absent entirely, the Excavation still runs, but it
# emits a loud note and collapses to a single reading tier — the reconnaissance
# altitude only means something when this set is a real, curated subset.
#
# Example (delete these and list your own):

index.html
about.html
start-here.html
excavate.config.example45 lineson GitHub →
# The Excavation — config. Copy this to `excavate.config` and edit.
# This file ships as an EXAMPLE; it contains no real site data.
#
# Format: one `key = value` per line. Blank lines and `#` comments ignored.
# Four keys. Only `base_url` is strictly required; the rest have safe defaults.

# base_url  (REQUIRED) — your site's canonical base. Every page node's identity
#           is a URL under this base. No trailing slash.
base_url = https://example.com

# site_name — a short human label used in the emitted output. Cosmetic.
site_name = Example Site

# node_source — how pages are enumerated. v1 supports `local-walk`: point the
#           tool at a local copy of your served tree (see `site_dir` below) and
#           it walks every .html file. This is the substrate-general default
#           because it needs nothing your site doesn't already have on disk.
node_source = local-walk

# site_dir — the local directory holding your served .html tree, relative to
#           this config file (or absolute). For a local-walk run this is what
#           gets enumerated. Example: a checkout of your site's public/ folder.
site_dir = ./site

# wrapper_dir — OPTIONAL. If your served pages live under a single wrapper
#           directory that is NOT part of their public URL path (e.g. your repo
#           keeps reading pages in `site/` but they serve from the root), name
#           it here so path-depth is measured in real URL segments. Leave blank
#           if your on-disk layout matches your URL layout (the common case).
wrapper_dir =

# coverage_rules — OPTIONAL. Ordered `substring => class` rules that sort pages
#           into reading classes. First match wins; unmatched => standard.
#           One rule per line, `substring => class`. Valid classes:
#           deep | standard | optional. Leave this whole section empty for the
#           safe generic default (everything is `standard`; your core-set.txt
#           carries the reconnaissance tier). Add rules only when your site has
#           real structure worth partitioning. Examples (delete or edit):
# rule = /docs/ => deep
# rule = /archive/ => optional

# core_set — declared in the separate `core-set.txt` sidecar, NOT here (one
#           served filename per line). See the README's "The core set" section:
#           it is the one input that actually carries weight. If absent, the run
#           emits a loud note and collapses to a single reading tier.
excavate_standalone.py423 lineson GitHub →
#!/usr/bin/env python3
"""excavate_standalone.py — the self-contained Excavation builder.

This is the STRANGER-SIDE builder: it emits the three excavation faces
(corpus-manifest.json, corpus-shards.json, reckoning.json) for a foreign site
with ZERO loopmmt.com dependencies. It carries no import of build_machine_digest,
redact, or disclosure_gate — a bare tree has none of those, and this module is
what the acceptance beat (DP-039 s7 beat 5) extracts so the gift runs anywhere.

WHAT IT REPRODUCES (and how it differs from the in-tree builders):
  * NODE SOURCE — a self-contained recursive local walk of `site_dir` (every
    *.html leaf), NOT digest.sitemap_pages(). Redirect stubs (meta-refresh) are
    skipped, matching the in-tree walk's exclusion.
  * TEXT / META — local _meta() and _page_text() equivalents, stdlib re+html
    only (byte-identical logic to the digest helpers).
  * PUBLISH GATES — DROPPED. redact.scan_text and disclosure_gate.require_publish
    are loopmmt.com publish controls keyed to loopmmt's private-signature set and
    disclosure map. A stranger's tree has neither, and the gift never asserts a
    safety it cannot honestly run: the stranger's own tree is their published
    surface by construction (they point it at their served files). The HONEST
    STATUS in excavate.py names this as the correct foreign behaviour.

DETERMINISM: stdlib only, offline, no wall-clock field. A re-run over an
unchanged tree is byte-identical (folds-twice-identical) — the same property the
staleness lints rely on. lastmod is OMITTED in standalone mode (a stranger's git
history is not this gift's to assume; the in-tree builder reads it from loopmmt's
own repo). Its absence is honest, not a gap.

USAGE (driven by excavate.py; runnable directly for the smoke test):
    python3 excavate_standalone.py             build the three faces
    python3 excavate_standalone.py --check      honesty invariant: manifest == walk
Config is read from excavate.config next to this file (same parser the driver
uses); site_dir names the local served tree.
"""
import hashlib
import html
import json
import os
import re
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
CONFIG_NAME = "excavate.config"
CORE_SET_NAME = "core-set.txt"

# Shard budget — the ~40K-token reading-bundle size the in-tree builder uses.
SHARD_TOKEN_BUDGET = 40000


# ── config (same shape excavate.py parses) ──────────────────────────────────
def _die(msg, code=1):
    sys.stderr.write("excavate-standalone: " + msg + "\n")
    sys.exit(code)


def parse_config(path):
    if not os.path.isfile(path):
        _die("no %s found next to this builder — copy %s.example and edit it."
             % (CONFIG_NAME, CONFIG_NAME))
    cfg = {"coverage_rules": []}
    with open(path, "r", encoding="utf-8") as fh:
        for raw in fh:
            line = raw.strip()
            if not line or line.startswith("#"):
                continue
            if "=" not in line:
                _die("malformed config line (no '='): %r" % raw.rstrip())
            key, _, val = line.partition("=")
            key, val = key.strip(), val.strip()
            if key == "rule":
                if "=>" not in val:
                    _die("malformed coverage rule (want 'substring => class'): %r" % val)
                sub, _, cls = val.partition("=>")
                sub, cls = sub.strip(), cls.strip()
                if cls not in ("deep", "standard", "optional"):
                    _die("coverage rule class must be deep|standard|optional, got %r" % cls)
                cfg["coverage_rules"].append((sub, cls))
            else:
                cfg[key] = val
    if not cfg.get("base_url"):
        _die("config is missing the required `base_url`.")
    cfg["base_url"] = cfg["base_url"].rstrip("/")
    cfg.setdefault("site_name", cfg["base_url"])
    cfg.setdefault("site_dir", "./site")
    cfg.setdefault("wrapper_dir", "")
    return cfg


def load_core_set(config_dir=HERE):
    path = os.path.join(config_dir, CORE_SET_NAME)
    if not os.path.isfile(path):
        return None
    out = set()
    with open(path, "r", encoding="utf-8") as fh:
        for line in fh:
            line = line.split("#", 1)[0].strip()
            if line:
                out.add(line)
    return out or None


# ── local HTML helpers (stdlib-only equivalents of the digest helpers) ──────
def _meta(path):
    """(title, description) from a page's <head>. Same logic as digest._meta,
    dropping og:url/canonical (a standalone node's url is derived from its path,
    not read from the page)."""
    try:
        with open(path, encoding="utf-8") as fh:
            h = fh.read()
    except OSError:
        return "", ""

    def grab(pat):
        m = re.search(pat, h, re.S)
        return html.unescape(m.group(1).strip()) if m else ""
    return grab(r"<title>(.*?)</title>"), grab(r'<meta name="description" content="(.*?)"')


def _is_redirect_stub(path):
    try:
        with open(path, encoding="utf-8") as fh:
            return re.search(r'<meta\s+http-equiv=["\']refresh["\']', fh.read(), re.I) is not None
    except OSError:
        return False


def _page_text(path):
    """Plain body text — byte-identical logic to digest._page_text (no DRAFT
    sentinel branch: a stranger has no loopmmt draft convention)."""
    try:
        with open(path, encoding="utf-8") as fh:
            h = fh.read()
    except OSError:
        return "(source unavailable)"
    h = re.sub(r"(?is)<head\b.*?</head>", " ", h)
    h = re.sub(r"(?is)<script\b.*?</script>", " ", h)
    h = re.sub(r"(?is)<style\b.*?</style>", " ", h)
    h = re.sub(r"(?is)<svg\b.*?</svg>", " ", h)
    m = re.search(r"(?is)<main\b[^>]*>(.*?)</main>", h)
    if m:
        h = m.group(1)
    h = re.sub(r"(?is)<(p|div|section|article|li|h[1-6]|tr|br)\b[^>]*>", "\n", h)
    h = re.sub(r"(?is)<[^>]+>", " ", h)
    h = html.unescape(h)
    h = re.sub(r"[ \t]+", " ", h)
    lines = [ln.strip() for ln in h.splitlines()]
    out, blank = [], False
    for ln in lines:
        if ln:
            out.append(ln)
            blank = False
        elif not blank:
            out.append("")
            blank = True
    return "\n".join(out).strip()


# ── the local walk (the node source — replaces digest.sitemap_pages) ────────
def _site_dir(cfg):
    sd = cfg["site_dir"]
    return sd if os.path.isabs(sd) else os.path.normpath(os.path.join(HERE, sd))


def _served_rel(cfg, disk_path):
    """The served path for a local file, relative to site_dir, forward-slashed.
    A wrapper_dir (if the served tree sits under a non-URL wrapper) is peeled."""
    rel = os.path.relpath(disk_path, _site_dir(cfg)).replace(os.sep, "/")
    wd = cfg.get("wrapper_dir")
    if wd and rel.startswith(wd + "/"):
        rel = rel[len(wd) + 1:]
    return rel


def walk_nodes(cfg):
    """Every served *.html leaf under site_dir, as {url, served_rel, disk},
    sorted for determinism. Redirect stubs are skipped (routing, not content)."""
    base = cfg["base_url"]
    root = _site_dir(cfg)
    if not os.path.isdir(root):
        _die("site_dir %r does not exist (config: site_dir = %s)" % (root, cfg["site_dir"]), code=2)
    found = []
    for dirpath, _dirs, files in os.walk(root):
        for fn in files:
            if not fn.endswith(".html"):
                continue
            disk = os.path.join(dirpath, fn)
            if _is_redirect_stub(disk):
                continue
            served = _served_rel(cfg, disk)
            url = base + "/" + served
            found.append({"url": url, "served_rel": served, "disk": disk})
    found.sort(key=lambda n: n["served_rel"])
    return found


# ── coverage typing (config-driven; same rules as the in-tree builder) ──────
def _branch(served_rel):
    if served_rel in ("", "index.html"):
        return "root"
    parts = [p for p in served_rel.split("/") if p]
    if not parts:
        return "root"
    if len(parts) == 1:
        return parts[0] if served_rel.endswith("/") else "root"
    return parts[0]


def _coverage_type(cfg, served_rel, core_set, single_tier):
    if single_tier:
        return "standard"
    fname = os.path.basename(served_rel)
    if served_rel in ("", "index.html") or fname in core_set:
        return "core"
    # Rule substrings match against a slash-bracketed served path so a rule
    # written the natural way — `/docs/ => deep` — hits both a root-served
    # `docs/guide.html` (a stranger's tree) AND a wrapped `site/docs/x.html`
    # (loopmmt's). Bracketing with leading+trailing '/' makes "/docs/" a segment
    # match regardless of whether the segment is at path start. (Fixture-proven:
    # without this, root-level dir rules silently never fired — beat 5.)
    bracketed = "/" + served_rel
    for needle, cls in cfg.get("coverage_rules", []):
        if needle in bracketed:
            return cls
    return "standard"


def _hash_and_tokens(disk_path):
    try:
        with open(disk_path, "rb") as fh:
            raw = fh.read()
    except OSError:
        return None, None, None
    return hashlib.sha256(raw).hexdigest()[:12], len(raw.split()), round(len(raw) / 4)


# ── face 1: the manifest ────────────────────────────────────────────────────
def build_manifest(cfg):
    core_set = load_core_set()
    single_tier = not core_set
    if single_tier:
        print("NOTE — no `core` reconnaissance set declared (no core-set.txt): "
              "every page is one tier. Declare a core-set.txt for a real "
              "recon/full cycle.", file=sys.stderr)
    nodes, counts, branches = [], {"core": 0, "standard": 0, "deep": 0, "optional": 0}, {}
    for entry in walk_nodes(cfg):
        served_rel, disk = entry["served_rel"], entry["disk"]
        title, desc = _meta(disk)
        ctype = _coverage_type(cfg, served_rel, core_set or set(), single_tier)
        branch = _branch(served_rel)
        h, words, tokens = _hash_and_tokens(disk)
        node = {"url": entry["url"], "title": title, "description": desc,
                "branch": branch, "coverage": ctype, "hash": h,
                "words": words, "tokens": tokens}
        nodes.append(node)
        counts[ctype] = counts.get(ctype, 0) + 1
        branches[branch] = branches.get(branch, 0) + 1
    total_tokens = sum(n["tokens"] for n in nodes if n["tokens"])
    doc = {
        "site": cfg["site_name"],
        "url": cfg["base_url"] + "/",
        "manifest_version": 1,
        "description": (
            f"The complete public coverage boundary of {cfg['site_name']}: one "
            "node per served public leaf, typed by coverage class. The reader's "
            "coverage oracle for a full excavation (see The Reckoning). A pure "
            "fold over the local served tree; nodes[].url is independently "
            "checkable against the served files."),
        "coverage_note": (
            "core = read for a Reconnaissance pass; standard = mid-tier content, "
            "read in full excavation; deep = evidentiary leaves, full excavation "
            "only; optional = supporting tools / docs. A complete excavation "
            "reads every core+standard+deep node, or declares each skipped node "
            "optional/inaccessible by name."),
        "counts": {
            "nodes": len(nodes),
            "by_coverage": counts,
            "by_branch": dict(sorted(branches.items())),
            "total_tokens_estimate": total_tokens,
        },
        "nodes": nodes,
    }
    return doc


# ── face 2: the shards (pure partition of the manifest node set) ────────────
def build_shards(cfg, manifest):
    base = cfg["base_url"]
    nodes = manifest["nodes"]
    shards, cur, cur_tokens, cur_branch = [], [], 0, None

    def flush():
        nonlocal cur, cur_tokens, cur_branch
        if cur:
            shards.append({"branch": cur_branch, "nodes": cur,
                           "tokens_estimate": cur_tokens})
            cur, cur_tokens, cur_branch = [], 0, None

    for n in nodes:
        tok = n.get("tokens") or 0
        if cur_branch is not None and (n["branch"] != cur_branch
                                       or cur_tokens + tok > SHARD_TOKEN_BUDGET):
            flush()
        cur_branch = n["branch"]
        cur.append(n["url"])
        cur_tokens += tok
    flush()

    index = {
        "site": cfg["site_name"],
        "url": f"{base}/corpus-shards.json",
        "description": (
            "The corpus partitioned into bounded reading bundles. The "
            "authoritative tree is corpus-manifest.json; each shard names a "
            "budget-sized set of node urls to read together."),
        "shard_token_budget": SHARD_TOKEN_BUDGET,
        "manifest_url": f"{base}/corpus-manifest.json",
        "counts": {"shards": len(shards),
                   "nodes": sum(len(s["nodes"]) for s in shards)},
        "shards": [
            {"id": f"shard-{i:02d}", "branch": s["branch"],
             "tokens_estimate": s["tokens_estimate"], "nodes": s["nodes"]}
            for i, s in enumerate(shards)
        ],
    }
    return index


# ── face 3: the reckoning (pure fold of manifest + shards) ──────────────────
def build_reckoning(cfg, manifest, shards):
    base = cfg["base_url"]
    c = manifest["counts"]
    doc = {
        "site": cfg["site_name"],
        "url": f"{base}/reckoning.json",
        "description": (
            f"The reader's coverage protocol for {cfg['site_name']}. A pure fold "
            "over corpus-manifest.json (the node oracle) and corpus-shards.json "
            "(the corpus in bounded bundles)."),
        "sources": {
            "manifest_url": f"{base}/corpus-manifest.json",
            "shards_url": f"{base}/corpus-shards.json",
        },
        "totals": {
            "nodes": c["nodes"],
            "by_coverage": c["by_coverage"],
            "shards": shards["counts"]["shards"],
            "total_tokens_estimate": c["total_tokens_estimate"],
        },
        "protocol": [
            {"pass": "reconnaissance",
             "how": ("fetch corpus-manifest.json, take the nodes with "
                     "coverage=='core', and read those first to understand the "
                     "whole site before the full pass.")},
            {"pass": "full-excavation",
             "how": ("iterate corpus-shards.json; fetch each shard's node urls "
                     "and read them, covering every core+standard+deep node.")},
            {"pass": "account",
             "how": ("a complete excavation reads every core+standard+deep node, "
                     "or declares each skipped node optional/inaccessible by "
                     "name. 'Everything' means every node examined or explicitly "
                     "accounted for.")},
            {"pass": "resume",
             "how": ("re-fetch corpus-manifest.json and compare its node set + "
                     "hashes against your last read to find what changed.")},
        ],
    }
    return doc


# ── driver ──────────────────────────────────────────────────────────────────
def _out_dir(cfg):
    # Foreign faces are written next to the config, under ./corpus-out/, so a
    # stranger's run never assumes a loopmmt site-root/ layout.
    d = os.path.join(HERE, "corpus-out")
    return d


def check(cfg):
    """Honesty invariant: the manifest's node-url set == a fresh local walk's
    url set. What a visiting AI runs to trust the manifest without trusting us."""
    manifest = build_manifest(cfg)
    manifest_urls = {n["url"] for n in manifest["nodes"]}
    walk_urls = {n["url"] for n in walk_nodes(cfg)}
    if manifest_urls == walk_urls:
        return True, f"CLEAN — manifest nodes == local walk ({len(manifest_urls)} urls)"
    missing = walk_urls - manifest_urls
    extra = manifest_urls - walk_urls
    return False, (f"DRIFT — manifest != walk. missing={sorted(missing)[:5]} "
                   f"extra={sorted(extra)[:5]}")


def main():
    cfg = parse_config(os.path.join(HERE, CONFIG_NAME))
    if "--check" in sys.argv:
        ok, msg = check(cfg)
        print(msg)
        sys.exit(0 if ok else 3)

    manifest = build_manifest(cfg)
    shards = build_shards(cfg, manifest)
    reckoning = build_reckoning(cfg, manifest, shards)

    out = _out_dir(cfg)
    os.makedirs(out, exist_ok=True)
    faces = {
        "corpus-manifest.json": manifest,
        "corpus-shards.json": shards,
        "reckoning.json": reckoning,
    }
    for name, doc in faces.items():
        body = json.dumps(doc, indent=2, ensure_ascii=False) + "\n"
        with open(os.path.join(out, name), "w", encoding="utf-8") as fh:
            fh.write(body)
    cc = manifest["counts"]
    print(f"wrote corpus-out/{{corpus-manifest,corpus-shards,reckoning}}.json — "
          f"standalone (no loopmmt deps)")
    print(f"grounded: {cc['nodes']} nodes · by_coverage={cc['by_coverage']} · "
          f"{shards['counts']['shards']} shard(s) · "
          f"~{cc['total_tokens_estimate']} tokens total")


if __name__ == "__main__":
    main()
smoke_test.sh113 lineson GitHub →
#!/usr/bin/env bash
# smoke_test.sh — the foreign-fixture ACCEPTANCE GATE for The Excavation gift
# (DP-039 s7 beat 5). Hermetic, no network: it proves the gift runs on a
# NON-loopmmt site with ZERO loopmmt.com dependencies present.
#
# The test copies ONLY the shipped gift files (excavate.py, excavate_standalone.py)
# and the fixture site into a bare temp tree — deliberately WITHOUT
# build_corpus_manifest.py / build_machine_digest.py / redact.py / disclosure_gate.py
# — so an import of any loopmmt builder would fail. If the gift produces coherent
# faces anyway, the standalone extraction is proven.
#
# Six scenarios:
#   1  driver runs on a bare stranger tree (no loopmmt builders) and exits 0
#   2  it emits all three faces (manifest, shards, reckoning)
#   3  the manifest node set == the served .html leaves (coverage honesty)
#   4  coverage typing honors the config (core-set + rules): the core, deep and
#      optional classes are all present
#   5  --check passes (manifest == local walk)
#   6  no loopmmt-private import leaked in (grep the tree: no build_machine_digest etc.)
#
# Run:  bash smoke_test.sh    (expect: 6/6 passed)
set -uo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PASS=0; FAIL=0
ROOT="$(mktemp -d)"; trap 'rm -rf "$ROOT"' EXIT

ok()  { echo "ok  $1"; PASS=$((PASS+1)); }
bad() { echo "FAIL $1: $2"; FAIL=$((FAIL+1)); }

# ── build the bare stranger tree: gift files + fixture, NO loopmmt builders ──
STRANGER="$ROOT/stranger"
mkdir -p "$STRANGER"
cp "$HERE/excavate.py"            "$STRANGER/"
cp "$HERE/excavate_standalone.py" "$STRANGER/"
cp -r "$HERE/test-fixture/." "$STRANGER/"    # brings excavate.config, core-set.txt, site/

# Sanity: confirm the tree is genuinely bare (no loopmmt builder alongside).
for banned in build_corpus_manifest.py build_machine_digest.py redact.py disclosure_gate.py; do
  if [ -e "$STRANGER/$banned" ]; then
    echo "FATAL smoke setup: $banned leaked into the stranger tree" >&2
    exit 1
  fi
done

# ── 1: runs on a bare tree, exit 0 ──────────────────────────────────────────
OUT="$( cd "$STRANGER"; python3 excavate.py 2>&1 )"; RC=$?
if [ "$RC" -eq 0 ]; then
  ok "driver runs on a bare stranger tree (no loopmmt builders) and exits 0"
else
  bad "bare-tree run" "expected exit 0, got $RC; out=[$OUT]"
fi

# ── 2: all three faces emitted ──────────────────────────────────────────────
FACES="$STRANGER/corpus-out"
if [ -f "$FACES/corpus-manifest.json" ] \
   && [ -f "$FACES/corpus-shards.json" ] \
   && [ -f "$FACES/reckoning.json" ]; then
  ok "emits all three faces (manifest, shards, reckoning)"
else
  bad "three faces" "one or more faces missing under corpus-out/"
fi

# ── 3: manifest node set == served .html leaves ─────────────────────────────
SITE="$STRANGER/site"
LEAVES="$( cd "$SITE" && find . -name '*.html' | sed 's#^\./##' | sort )"
NLEAVES="$( printf '%s\n' "$LEAVES" | grep -c . )"
NNODES="$( python3 -c '
import json,sys
d=json.load(open(sys.argv[1]))
print(d["counts"]["nodes"])
' "$FACES/corpus-manifest.json" 2>/dev/null )"
if [ "$NNODES" = "$NLEAVES" ] && [ "$NLEAVES" -eq 5 ]; then
  ok "manifest node set == served .html leaves ($NNODES nodes == $NLEAVES leaves)"
else
  bad "coverage honesty" "manifest nodes=$NNODES vs served leaves=$NLEAVES (want 5)"
fi

# ── 4: coverage typing honors the config (core / deep / optional all present) ─
TYPES="$( python3 -c '
import json,sys
d=json.load(open(sys.argv[1]))
bc=d["counts"]["by_coverage"]
print(bc.get("core",0), bc.get("deep",0), bc.get("optional",0))
' "$FACES/corpus-manifest.json" 2>/dev/null )"
read -r NCORE NDEEP NOPT <<<"$TYPES"
# fixture: index.html + about.html = core (2); docs/* = deep (2); archive/2019 = optional (1)
if [ "$NCORE" = "2" ] && [ "$NDEEP" = "2" ] && [ "$NOPT" = "1" ]; then
  ok "coverage typing honors config (core=2 deep=2 optional=1 from core-set + rules)"
else
  bad "coverage typing" "got core=$NCORE deep=$NDEEP optional=$NOPT (want 2/2/1)"
fi

# ── 5: --check passes (manifest == local walk) ──────────────────────────────
CHK="$( cd "$STRANGER"; python3 excavate.py --check 2>&1 )"; RC=$?
if [ "$RC" -eq 0 ] && printf '%s' "$CHK" | grep -q "CLEAN"; then
  ok "--check passes: manifest nodes == local walk"
else
  bad "--check" "expected exit 0 + CLEAN, got rc=$RC out=[$CHK]"
fi

# ── 6: no loopmmt-private module was needed (none present, yet it ran) ───────
# Prove the run did not depend on a loopmmt builder by confirming the standalone
# builder carries no import of them (the driver only imports them in-tree).
if ! grep -Eq 'import (build_corpus_manifest|build_machine_digest|redact|disclosure_gate)' \
        "$STRANGER/excavate_standalone.py"; then
  ok "standalone builder imports no loopmmt-private module (true independence)"
else
  bad "independence" "excavate_standalone.py imports a loopmmt-private module"
fi

echo
echo "$((PASS))/$((PASS+FAIL)) passed"
[ "$FAIL" -eq 0 ]
Take the whole folder → MIT git + Python 3 stdlib, no dependencies