Metadata-Version: 2.4
Name: aiep-mirror
Version: 2.1.0
Summary: AIEP Machine Mirror — produces .well-known/aiep/ artefacts from HTML source. Kernel-bound. Schema v2.0.0.
License-Expression: Apache-2.0
Project-URL: Repository, https://github.com/NeilGrassby/aiep-mirror
Project-URL: Changelog, https://github.com/NeilGrassby/aiep-mirror/blob/main/CHANGELOG.md
Project-URL: Patent, https://www.ipo.gov.uk/p-ipsum/Case/ApplicationNumber/GB2519711.2
Project-URL: AIEP Hub, https://aiep.protocol
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: validation
Requires-Dist: jsonschema>=4.0; extra == "validation"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: jsonschema>=4.0; extra == "dev"
Dynamic: license-file

# aiep-mirror

**Zero-friction `.well-known/aiep/` pipeline for any website.**

[![Version](https://img.shields.io/badge/version-2.1.0-blue)](https://aiep.dev/downloads/repos/aiep-mirror-v2.1.0.zip)
[![Schema v2.0.0](https://img.shields.io/badge/Schema-v2.0.0%20LOCKED-green)](https://aiep.dev/schemas/v2.0.0/)
[![Tests](https://img.shields.io/badge/tests-90%20passed-brightgreen)](tests/)
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)](pyproject.toml)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE)
[![Zero deps](https://img.shields.io/badge/dependencies-zero-success)](pyproject.toml)

---

## What it does

Converts any HTML website into a **machine-readable AIEP mirror surface** — a set of structured, cryptographically-bound JSON artefacts that AI systems, crawlers, validators, and automated agents can consume with full verifiability.

Three commands. Works with any static site generator. Zero runtime dependencies.

---

## Install

```bash
pip install aiep-mirror
```

Or with `pipx` (isolated global install, recommended for CLI use):

```bash
pipx install aiep-mirror
```

---

## Quick start

```bash
# 1. Initialise — creates .aiep-mirror.json in the current directory
aiep-mirror init

# 2. Build your site (Astro, Next.js, Hugo, Jekyll, or plain HTML)
npm run build    # or: hugo, jekyll build, make build, etc.

# 3. Build mirror artefacts (reads .aiep-mirror.json automatically)
aiep-mirror build

# 4. Verify the output
aiep-mirror verify
```

**That's it.** Your `.well-known/aiep/` surface is ready to deploy.

---

## Configuration file (`.aiep-mirror.json`)

`aiep-mirror init` creates this file. Edit `site_base` and `in_dir` to match your project:

```json
{
  "site_base": "https://yourdomain.com",
  "in_dir": "./dist",
  "out_dir": ".",
  "visibility_default": "PRIVATE",
  "public_prefixes": ["/blog", "/docs", "/products"],
  "private_prefixes": ["/admin", "/account", "/api"]
}
```

| Field | Default | Meaning |
|-------|---------|---------|
| `site_base` | — | Your deployed domain (required) |
| `in_dir` | `./dist` | Directory containing your HTML build output |
| `out_dir` | `.` | Root where `.well-known/` will be written |
| `visibility_default` | `PRIVATE` | Page visibility if no prefix matches (FC §7) |
| `public_prefixes` | `[]` | URL prefixes to mark PUBLIC |
| `private_prefixes` | `[]` | URL prefixes to mark PRIVATE (takes precedence) |

---

## Framework integrations

| Framework | in_dir | Integration |
|-----------|--------|-------------|
| **Astro** | `./dist` | `postbuild` script in `package.json` |
| **Next.js** (static) | `./out` | `postbuild` + `output: 'export'` |
| **Hugo** | `./public` | Add after `hugo` command |
| **Jekyll** | `./_site` | Add to Makefile or CI |
| **Plain HTML** | any dir | Set `in_dir` to your folder |

### Add to `package.json` (any Node.js project)

```json
{
  "scripts": {
    "build": "astro build",
    "postbuild": "aiep-mirror build",
    "aiep:verify": "aiep-mirror verify",
    "aiep:status": "aiep-mirror status"
  }
}
```

Full guides: [Astro](examples/astro-integration.md) · [Next.js](examples/nextjs-integration.md) · [GitHub Actions](examples/github-action.yml)

---

## What it produces

```
.well-known/
  aiep-manifest.json              ← WellKnownManifest (P61)
  aiep-index.json                 ← SiteIndexEntry list, searchable (P62)
  aiep/
    pages/{content_hash}.json     ← MirrorPage per page (P60)
    proofs/{content_hash}.json    ← cryptographic proof per page
    proofs/manifest.json          ← manifest-level proof
    mirror_policy.json            ← visibility policy (FC §4)
```

Every artefact is **self-verifiable** — all hashes are recomputable from the
artefact's own fields. No external database, no token, no secret.

---

## CLI reference

```
aiep-mirror init           Create .aiep-mirror.json in current directory
aiep-mirror build          Build mirror artefacts (reads .aiep-mirror.json)
aiep-mirror verify         Verify artefacts
aiep-mirror status         Show config and last build info
aiep-mirror --self-test    Verify LOCKFILE + Canon self-test, then exit
```

Build flags (all optional when `.aiep-mirror.json` is present):

```
--in-dir PATH        HTML source directory
--site-base URL      Deployed site base URL
--out-dir PATH       Output directory
--public             Set all pages PUBLIC
--public-prefix P    Mark URL prefix PUBLIC (repeatable)
--private-prefix P   Mark URL prefix PRIVATE (repeatable)
--config PATH        Use alternate config file
```

---

## Python API

```python
from aiep_mirror import build_mirror, verify_manifest, run_self_test

# Startup health check — fail fast if Canon is broken
run_self_test()

# Build from your HTML build output
result = build_mirror(
    in_dir="./dist",
    site_base="https://example.com",
    out_dir=".",
    visibility_default="PRIVATE",
    private_prefixes=["/admin"],
    public_prefixes=["/blog"],
)
print(result["manifest_hash"])    # 64-char hex
print(result["page_count"])       # int
print(result["capability_hash"])  # FC §3 binding — on every artefact
print(result["policy_hash"])      # FC §4 binding — on every artefact

# Verify (no HTML source needed — anyone can verify)
vr = verify_manifest("./.well-known/aiep-manifest.json")
assert vr["verified"]
# vr["findings"]  ← list of ✓/✗ per check
```

### Custom adapter (FC §6)

Output to S3, GCS, a CDN, a database — plug in any transport:

```python
from aiep_mirror import MirrorAdapter, build_mirror

class S3Adapter(MirrorAdapter):
    def write_page(self, page_id: str, record: dict) -> None:
        self.s3.put_object(
            Key=f".well-known/aiep/pages/{page_id}.json",
            Body=json.dumps(record),
            ContentType="application/json",
        )
    # override write_proof, write_manifest, write_index, write_policy

build_mirror(..., adapter=S3Adapter(bucket="my-cdn"))
```

---

## Run the example

```bash
python examples/run_example.py
```

Full build + verify against a 3-page sample site. Prints every hash and every check.
Output: 3 pages, 24 verification checks, all PASS.

---

## Deployment checklist

1. `aiep-mirror build`
2. `aiep-mirror verify`
3. Deploy your site root. Ensure `.well-known/` is served with:
   ```
   Content-Type: application/json
   Access-Control-Allow-Origin: *
   ```
4. Confirm: `curl https://yourdomain.com/.well-known/aiep-manifest.json`

CORS header config for [Vercel](examples/astro-integration.md) and [Netlify](examples/astro-integration.md) is in the integration guides.

---

## Flexibility Contract bindings

Every artefact carries three provenance bindings from the GENOME governance architecture:

| Field | Formula | FC ref |
|-------|---------|--------|
| `lockfile_version` | Pinned `"1.0.0"` | FC §2, AIEP-NMR-001 |
| `capability_hash` | `sha256(canonical_json(cap_core))` | FC §3 |
| `policy_hash` | `sha256(canonical_json(policy_core))` | FC §4 |

These fields appear on every MirrorPage, every proof, the manifest, and the index.
A third party can verify exactly what capability set and policy governed any artefact
without needing the original HTML.

---

## Hash invariants

| Hash | Formula | Spec |
|------|---------|------|
| `content_hash` | `sha256(canonical_json(data))` | P60 |
| `page_id` | `== content_hash` | P60 |
| `proof_hash` | `concat_hash(page_id, content_hash, manifest_hash)` | P60 |
| `manifest_hash` | `concat_hash(canonical_manifest, aiep_version, site)` | P61 |
| `index_hash` | `concat_hash(url, content_hash, last_modified)` | P62 |
| `policy_hash` | `sha256(canonical_json(policy_core))` | FC §4 |
| `capability_hash` | `sha256(canonical_json(cap_core))` | FC §3 |

`concat_hash(...)` = SHA-256 of length-prefixed (8-byte big-endian) parts (R7).
All hashes are recomputable from fields in the same JSON record.

---

## Canonicalisation rules (R1–R8)

| Rule | Definition |
|------|-----------|
| R1 | Keys sorted lexicographic UTF-8 byte order |
| R2 | Compact — no whitespace between tokens |
| R3 | Unicode NFC normalised; `ensure_ascii=False` |
| R4 | SHA-256, lowercase hex, 64 chars |
| R5 | No scientific notation; trailing zeros trimmed; minus-zero forbidden |
| R6 | UTF-8 strict encoding |
| R7 | Multi-part concat: 8-byte big-endian length prefix per part |
| R8 | Timestamps are data — never `datetime.now()` in hash inputs |

---

## Tests

```bash
pip install "aiep-mirror[dev]"
pytest
# 90 passed in 3.5s
```

---

## Licence

Apache License 2.0. See [LICENSE](LICENSE).

Priority filings: GB2519711.2 · GB2519798.9 (filed 20 November 2025)
