mirror of
https://github.com/Such-Software/adaptive-multi-protocol-gateway.git
synced 2026-10-17 07:31:32 +02:00
Emit AMPB fixture manifests
This commit is contained in:
@@ -78,6 +78,7 @@ internal even when the public site is available on Tor or I2P.
|
|||||||
- [Architecture](docs/architecture.md)
|
- [Architecture](docs/architecture.md)
|
||||||
- [Configuration](docs/configuration.md)
|
- [Configuration](docs/configuration.md)
|
||||||
- [Daemon management](docs/daemon-management.md)
|
- [Daemon management](docs/daemon-management.md)
|
||||||
|
- [Fixture manifests](docs/fixture-manifests.md)
|
||||||
- [Interactive applications](docs/interactive-apps.md)
|
- [Interactive applications](docs/interactive-apps.md)
|
||||||
- [Wownero integration](docs/wownero-integration.md)
|
- [Wownero integration](docs/wownero-integration.md)
|
||||||
- [Autodocs and drift gates](docs/autodocs.md)
|
- [Autodocs and drift gates](docs/autodocs.md)
|
||||||
@@ -97,6 +98,7 @@ The current implementation is dependency-free Python:
|
|||||||
python3 -m ampg --config examples/wownero.gateway.toml plan
|
python3 -m ampg --config examples/wownero.gateway.toml plan
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml plan --write-artifacts
|
python3 -m ampg --config examples/wownero.gateway.toml plan --write-artifacts
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml build
|
python3 -m ampg --config examples/wownero.gateway.toml build
|
||||||
|
python3 -m ampg --config examples/wownero.gateway.toml manifest
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml audit
|
python3 -m ampg --config examples/wownero.gateway.toml audit
|
||||||
python3 -m ampg --config examples/i2p-only.gateway.toml build
|
python3 -m ampg --config examples/i2p-only.gateway.toml build
|
||||||
python3 -m ampg docs generate
|
python3 -m ampg docs generate
|
||||||
@@ -116,4 +118,5 @@ passed. They are reviewable snippets, not installed daemon config.
|
|||||||
- [ ] Select protocols and daemon policy.
|
- [ ] Select protocols and daemon policy.
|
||||||
- [ ] Run `ampg plan`; review output roots, ports, and config changes.
|
- [ ] Run `ampg plan`; review output roots, ports, and config changes.
|
||||||
- [ ] Run `ampg build`; inspect generated variants.
|
- [ ] Run `ampg build`; inspect generated variants.
|
||||||
|
- [ ] Run AMPB fixture checks against generated manifests.
|
||||||
- [ ] Run `ampg apply`; publish through adopted or managed daemons.
|
- [ ] Run `ampg apply`; publish through adopted or managed daemons.
|
||||||
|
|||||||
+22
@@ -8,6 +8,7 @@ from .audit import audit_gateway
|
|||||||
from .build import build_gateway
|
from .build import build_gateway
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
from .docsgen import generate_docs
|
from .docsgen import generate_docs
|
||||||
|
from .manifest import write_fixture_manifests
|
||||||
from .plan import plan_gateway, write_plan_artifacts
|
from .plan import plan_gateway, write_plan_artifacts
|
||||||
|
|
||||||
|
|
||||||
@@ -27,6 +28,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
help="Write generated config snippets to the configured plan root.",
|
help="Write generated config snippets to the configured plan root.",
|
||||||
)
|
)
|
||||||
subcommands.add_parser("build", help="Build enabled protocol outputs.")
|
subcommands.add_parser("build", help="Build enabled protocol outputs.")
|
||||||
|
subcommands.add_parser("manifest", help="Write AMPB fixture manifests.")
|
||||||
audit_parser = subcommands.add_parser("audit", help="Audit source HTML quality.")
|
audit_parser = subcommands.add_parser("audit", help="Audit source HTML quality.")
|
||||||
audit_parser.add_argument(
|
audit_parser.add_argument(
|
||||||
"--fail-on-warn",
|
"--fail-on-warn",
|
||||||
@@ -52,6 +54,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||||||
return _cmd_plan(config, write_artifacts=args.write_artifacts)
|
return _cmd_plan(config, write_artifacts=args.write_artifacts)
|
||||||
if args.command == "build":
|
if args.command == "build":
|
||||||
return _cmd_build(config)
|
return _cmd_build(config)
|
||||||
|
if args.command == "manifest":
|
||||||
|
return _cmd_manifest(config)
|
||||||
if args.command == "audit":
|
if args.command == "audit":
|
||||||
return _cmd_audit(config, fail_on_warn=args.fail_on_warn)
|
return _cmd_audit(config, fail_on_warn=args.fail_on_warn)
|
||||||
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
except Exception as exc: # noqa: BLE001 - CLI should print concise failures.
|
||||||
@@ -102,6 +106,24 @@ def _cmd_build(config) -> int:
|
|||||||
f" skipped={result.files_skipped}"
|
f" skipped={result.files_skipped}"
|
||||||
f"{extra}"
|
f"{extra}"
|
||||||
)
|
)
|
||||||
|
for manifest in write_fixture_manifests(config):
|
||||||
|
print(
|
||||||
|
"AMPG_MANIFEST "
|
||||||
|
f"site={manifest.site_id} "
|
||||||
|
f"path={manifest.path} "
|
||||||
|
f"fixtures={manifest.fixture_count}"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _cmd_manifest(config) -> int:
|
||||||
|
for manifest in write_fixture_manifests(config):
|
||||||
|
print(
|
||||||
|
"AMPG_MANIFEST "
|
||||||
|
f"site={manifest.site_id} "
|
||||||
|
f"path={manifest.path} "
|
||||||
|
f"fixtures={manifest.fixture_count}"
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ DEFAULT_DAEMONS = {
|
|||||||
"i2p": "i2pd",
|
"i2p": "i2pd",
|
||||||
"gemini": "agate",
|
"gemini": "agate",
|
||||||
"reticulum": "rnsd",
|
"reticulum": "rnsd",
|
||||||
|
"ipfs": "ipfs",
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_POLICIES = {
|
DEFAULT_POLICIES = {
|
||||||
@@ -20,6 +21,7 @@ DEFAULT_POLICIES = {
|
|||||||
"i2p": "auto",
|
"i2p": "auto",
|
||||||
"gemini": "auto",
|
"gemini": "auto",
|
||||||
"reticulum": "auto",
|
"reticulum": "auto",
|
||||||
|
"ipfs": "auto",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from .config import GatewayConfig, ProtocolConfig, SiteConfig
|
||||||
|
|
||||||
|
|
||||||
|
SCHEMA = "ampg.fixture-manifest.v1"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ManifestWriteResult:
|
||||||
|
site_id: str
|
||||||
|
path: Path
|
||||||
|
fixture_count: int
|
||||||
|
|
||||||
|
|
||||||
|
def fixture_manifest(site: SiteConfig) -> dict[str, Any]:
|
||||||
|
fixtures = [
|
||||||
|
_fixture_entry(site, protocol)
|
||||||
|
for protocol in site.protocols.values()
|
||||||
|
if protocol.enabled
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
"schema": SCHEMA,
|
||||||
|
"site": {
|
||||||
|
"id": site.id,
|
||||||
|
"domain": site.domain,
|
||||||
|
"canonical_url": site.source.canonical_url or "",
|
||||||
|
},
|
||||||
|
"fixtures": fixtures,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def write_fixture_manifests(config: GatewayConfig) -> list[ManifestWriteResult]:
|
||||||
|
results: list[ManifestWriteResult] = []
|
||||||
|
for site in config.sites:
|
||||||
|
manifest = fixture_manifest(site)
|
||||||
|
path = manifest_path(site)
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text(
|
||||||
|
json.dumps(manifest, indent=2, sort_keys=True) + "\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
results.append(
|
||||||
|
ManifestWriteResult(
|
||||||
|
site_id=site.id,
|
||||||
|
path=path,
|
||||||
|
fixture_count=len(manifest["fixtures"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
def manifest_path(site: SiteConfig) -> Path:
|
||||||
|
return site.outputs.root / "ampg-fixture-manifest.json"
|
||||||
|
|
||||||
|
|
||||||
|
def _fixture_entry(site: SiteConfig, protocol: ProtocolConfig) -> dict[str, Any]:
|
||||||
|
url, address_status = _fixture_url(site, protocol)
|
||||||
|
transport = _transport_for(protocol.name)
|
||||||
|
return {
|
||||||
|
"protocol": protocol.name,
|
||||||
|
"renderer": protocol.renderer,
|
||||||
|
"url": url,
|
||||||
|
"address_status": address_status,
|
||||||
|
"output_path": protocol.name,
|
||||||
|
"checks": {
|
||||||
|
"transport": transport,
|
||||||
|
"profile": transport,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _transport_for(protocol_name: str) -> str:
|
||||||
|
if protocol_name in {"clearnet", "tor", "i2p", "gemini", "reticulum", "ipfs"}:
|
||||||
|
return protocol_name
|
||||||
|
return protocol_name
|
||||||
|
|
||||||
|
|
||||||
|
def _fixture_url(site: SiteConfig, protocol: ProtocolConfig) -> tuple[str, str]:
|
||||||
|
configured_url = protocol.options.get("fixture_url") or protocol.options.get("browser_url")
|
||||||
|
if configured_url:
|
||||||
|
return _with_trailing_slash(str(configured_url)), "configured"
|
||||||
|
|
||||||
|
if protocol.name == "clearnet":
|
||||||
|
if site.source.canonical_url:
|
||||||
|
return _with_trailing_slash(site.source.canonical_url), "configured"
|
||||||
|
return f"https://{site.domain}/", "derived"
|
||||||
|
|
||||||
|
if protocol.name == "tor":
|
||||||
|
onion_url = protocol.options.get("onion_url")
|
||||||
|
if onion_url:
|
||||||
|
return _with_trailing_slash(str(onion_url)), "configured"
|
||||||
|
onion_location = str(protocol.options.get("onion_location", "auto"))
|
||||||
|
if onion_location and onion_location != "auto":
|
||||||
|
return _with_scheme(onion_location, "http"), "configured"
|
||||||
|
return f"http://{site.id}.onion/", "placeholder"
|
||||||
|
|
||||||
|
if protocol.name == "i2p":
|
||||||
|
i2p_url = protocol.options.get("i2p_url") or protocol.options.get("i2p_hostname")
|
||||||
|
if i2p_url:
|
||||||
|
return _with_scheme(str(i2p_url), "http"), "configured"
|
||||||
|
return f"http://{site.id}.i2p/", "placeholder"
|
||||||
|
|
||||||
|
if protocol.name == "gemini":
|
||||||
|
gemini_url = protocol.options.get("gemini_url")
|
||||||
|
if gemini_url:
|
||||||
|
return _with_scheme(str(gemini_url), "gemini"), "configured"
|
||||||
|
return f"gemini://{site.domain}/", "derived"
|
||||||
|
|
||||||
|
if protocol.name == "reticulum":
|
||||||
|
rns_url = protocol.options.get("rns_url")
|
||||||
|
if rns_url:
|
||||||
|
return _with_scheme(str(rns_url), "rns"), "configured"
|
||||||
|
return f"rns://{site.id}", "placeholder"
|
||||||
|
|
||||||
|
if protocol.name == "ipfs":
|
||||||
|
ipfs_url = protocol.options.get("ipfs_url")
|
||||||
|
if ipfs_url:
|
||||||
|
return _with_scheme(str(ipfs_url), "ipfs"), "configured"
|
||||||
|
cid = protocol.options.get("cid")
|
||||||
|
if cid:
|
||||||
|
return f"ipfs://{cid}", "configured"
|
||||||
|
return f"/ipfs/{site.id}", "placeholder"
|
||||||
|
|
||||||
|
return f"https://{site.domain}/", "derived"
|
||||||
|
|
||||||
|
|
||||||
|
def _with_scheme(value: str, scheme: str) -> str:
|
||||||
|
if "://" in value:
|
||||||
|
return _with_trailing_slash(value)
|
||||||
|
return _with_trailing_slash(f"{scheme}://{value}")
|
||||||
|
|
||||||
|
|
||||||
|
def _with_trailing_slash(value: str) -> str:
|
||||||
|
if value.startswith(("rns://", "lxmf://", "nomad://", "ipfs://", "ipns://")):
|
||||||
|
return value
|
||||||
|
if value.endswith("/"):
|
||||||
|
return value
|
||||||
|
return value + "/"
|
||||||
@@ -188,4 +188,14 @@ DAEMON_ADAPTERS = (
|
|||||||
"Certificate and key paths are plan values until install/apply support exists.",
|
"Certificate and key paths are plan values until install/apply support exists.",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
DaemonAdapter(
|
||||||
|
daemon=DEFAULT_DAEMONS["ipfs"],
|
||||||
|
protocols=("ipfs",),
|
||||||
|
default_policy=DEFAULT_POLICIES["ipfs"],
|
||||||
|
generated_artifacts=("fixture manifest route expectations",),
|
||||||
|
notes=(
|
||||||
|
"IPFS output is static web content for a local gateway or later pinning.",
|
||||||
|
"IPFS is content-addressed distribution, not an anonymity layer.",
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ source site
|
|||||||
-> render profiles
|
-> render profiles
|
||||||
-> output roots
|
-> output roots
|
||||||
-> daemon adapters
|
-> daemon adapters
|
||||||
-> clearnet / Tor / I2P / Gemini / Reticulum
|
-> clearnet / Tor / I2P / Gemini / IPFS / Reticulum
|
||||||
```
|
```
|
||||||
|
|
||||||
## Source adapters
|
## Source adapters
|
||||||
@@ -69,6 +69,10 @@ own output.
|
|||||||
: Gemini output. Converts headings, paragraphs, lists, code blocks, and links to `.gmi`.
|
: Gemini output. Converts headings, paragraphs, lists, code blocks, and links to `.gmi`.
|
||||||
Inline links become block links near the paragraph by default.
|
Inline links become block links near the paragraph by default.
|
||||||
|
|
||||||
|
`clearnet` for IPFS
|
||||||
|
: Static web output suitable for a local IPFS gateway or later pinning. IPFS is treated
|
||||||
|
as content-addressed distribution, not an anonymity layer.
|
||||||
|
|
||||||
`micron`
|
`micron`
|
||||||
: Reticulum/Nomad output. Produces terminal-oriented Micron markup with small pages,
|
: Reticulum/Nomad output. Produces terminal-oriented Micron markup with small pages,
|
||||||
plain links, and no heavy media by default.
|
plain links, and no heavy media by default.
|
||||||
@@ -82,6 +86,7 @@ Daemon adapters provide a common interface:
|
|||||||
- `render_config`: produce config snippets and complete managed configs.
|
- `render_config`: produce config snippets and complete managed configs.
|
||||||
- `apply`: write approved managed config and start or reload services.
|
- `apply`: write approved managed config and start or reload services.
|
||||||
- `health`: verify the selected transport can serve the generated output.
|
- `health`: verify the selected transport can serve the generated output.
|
||||||
|
- `manifest`: emit AMPB route/profile expectations for generated fixtures.
|
||||||
|
|
||||||
Adapters must be conservative. They do not modify non-AMPG config without showing a
|
Adapters must be conservative. They do not modify non-AMPG config without showing a
|
||||||
plan first, and managed config lives under AMPG-owned state directories.
|
plan first, and managed config lives under AMPG-owned state directories.
|
||||||
|
|||||||
+12
-1
@@ -66,6 +66,13 @@ daemon_policy = "auto"
|
|||||||
port = 1965
|
port = 1965
|
||||||
max_asset_bytes = 1048576
|
max_asset_bytes = 1048576
|
||||||
|
|
||||||
|
[site.protocols.ipfs]
|
||||||
|
enabled = false
|
||||||
|
renderer = "clearnet"
|
||||||
|
daemon = "ipfs"
|
||||||
|
daemon_policy = "auto"
|
||||||
|
cid = ""
|
||||||
|
|
||||||
[site.protocols.reticulum]
|
[site.protocols.reticulum]
|
||||||
enabled = false
|
enabled = false
|
||||||
renderer = "micron"
|
renderer = "micron"
|
||||||
@@ -86,6 +93,7 @@ workflow.
|
|||||||
| tor | `privacy-html` | `tor` | `auto` |
|
| tor | `privacy-html` | `tor` | `auto` |
|
||||||
| i2p | `privacy-html` | `i2pd` | `auto` |
|
| i2p | `privacy-html` | `i2pd` | `auto` |
|
||||||
| gemini | `gemtext` | `agate` | `auto` |
|
| gemini | `gemtext` | `agate` | `auto` |
|
||||||
|
| ipfs | `clearnet` | `ipfs` | `auto` |
|
||||||
| reticulum | `micron` | `rnsd` | `auto` |
|
| reticulum | `micron` | `rnsd` | `auto` |
|
||||||
|
|
||||||
## Renderer defaults
|
## Renderer defaults
|
||||||
@@ -178,7 +186,7 @@ omits it or renders a safe alternate page.
|
|||||||
## Protocol-only deployments
|
## Protocol-only deployments
|
||||||
|
|
||||||
Enabled protocols are independent. A config can omit clearnet entirely and publish only
|
Enabled protocols are independent. A config can omit clearnet entirely and publish only
|
||||||
to I2P, Tor, Gemini, Reticulum, or any combination of supported targets.
|
to I2P, Tor, Gemini, IPFS, Reticulum, or any combination of supported targets.
|
||||||
|
|
||||||
For an existing HTML site that should publish only to I2P:
|
For an existing HTML site that should publish only to I2P:
|
||||||
|
|
||||||
@@ -209,6 +217,9 @@ script_policy = "strip"
|
|||||||
|
|
||||||
This builds only the I2P output root and only the I2P/nginx plan snippets.
|
This builds only the I2P output root and only the I2P/nginx plan snippets.
|
||||||
|
|
||||||
|
For IPFS, use the `clearnet` renderer to create a static output tree that can be served
|
||||||
|
through a local gateway or pinned by a later publishing step.
|
||||||
|
|
||||||
## Plan artifacts
|
## Plan artifacts
|
||||||
|
|
||||||
`ampg plan` prints expected artifact paths. `ampg plan --write-artifacts` writes
|
`ampg plan` prints expected artifact paths. `ampg plan --write-artifacts` writes
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Fixture Manifests
|
||||||
|
|
||||||
|
> Status: draft | Updated 2026-07-06 | Applies to: AMPG and AMPB compatibility checks
|
||||||
|
|
||||||
|
AMPG writes deterministic fixture manifests so AMPB can verify that generated site
|
||||||
|
variants route to the expected browser transport and profile.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
`ampg build` writes one manifest per site:
|
||||||
|
|
||||||
|
```text
|
||||||
|
<site.outputs.root>/ampg-fixture-manifest.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The manifest contains public route expectations only:
|
||||||
|
|
||||||
|
- site id, domain, and canonical URL.
|
||||||
|
- enabled protocol fixtures.
|
||||||
|
- protocol renderer and relative output path.
|
||||||
|
- route URL for AMPB checks.
|
||||||
|
- expected AMPB transport and profile.
|
||||||
|
|
||||||
|
It must not contain private host inventory, hidden-service keys, tunnel keys, credentials,
|
||||||
|
or deployment notes.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python3 -m ampg --config examples/wownero.gateway.toml build
|
||||||
|
python3 -m ampg --config examples/wownero.gateway.toml manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
`build` emits the manifest after writing protocol outputs. `manifest` rewrites only the
|
||||||
|
manifest and does not rebuild protocol outputs.
|
||||||
|
|
||||||
|
## Address Status
|
||||||
|
|
||||||
|
Some transports may not have final public addresses when a fixture is generated. In those
|
||||||
|
cases AMPG uses route-valid placeholders such as `http://wownero.onion/` so AMPB can still
|
||||||
|
verify transport selection without contacting the network.
|
||||||
@@ -44,3 +44,13 @@ Notes:
|
|||||||
Notes:
|
Notes:
|
||||||
- Gemini serves generated Gemtext output directly.
|
- Gemini serves generated Gemtext output directly.
|
||||||
- Certificate and key paths are plan values until install/apply support exists.
|
- Certificate and key paths are plan values until install/apply support exists.
|
||||||
|
|
||||||
|
## ipfs
|
||||||
|
|
||||||
|
- Protocols: ipfs
|
||||||
|
- Default policy: `auto`
|
||||||
|
- Generated artifacts: fixture manifest route expectations
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- IPFS output is static web content for a local gateway or later pinning.
|
||||||
|
- IPFS is content-addressed distribution, not an anonymity layer.
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ transport-specific variants with defaults:
|
|||||||
- Tor: privacy-hardened static HTML.
|
- Tor: privacy-hardened static HTML.
|
||||||
- I2P: privacy-hardened static HTML.
|
- I2P: privacy-hardened static HTML.
|
||||||
- Gemini: text-first `.gmi` pages.
|
- Gemini: text-first `.gmi` pages.
|
||||||
|
- IPFS: static output tree for gateway or pinning workflows.
|
||||||
- Reticulum: compact Micron `.mu` pages.
|
- Reticulum: compact Micron `.mu` pages.
|
||||||
|
|
||||||
## Source Adapter
|
## Source Adapter
|
||||||
@@ -51,6 +52,10 @@ must not be required for AMPG adoption.
|
|||||||
index.gmi
|
index.gmi
|
||||||
mining.gmi
|
mining.gmi
|
||||||
emission.gmi
|
emission.gmi
|
||||||
|
ipfs/
|
||||||
|
index.html
|
||||||
|
css/
|
||||||
|
img/
|
||||||
reticulum/
|
reticulum/
|
||||||
index.mu
|
index.mu
|
||||||
mining.mu
|
mining.mu
|
||||||
@@ -84,6 +89,11 @@ Use `gemtext`:
|
|||||||
- convert large media to plain download links.
|
- convert large media to plain download links.
|
||||||
- skip repeated navigation noise.
|
- skip repeated navigation noise.
|
||||||
|
|
||||||
|
### IPFS
|
||||||
|
|
||||||
|
Use `clearnet` static output for content-addressed publishing. AMPG emits route
|
||||||
|
expectations for AMPB, but pinning and CID publication remain explicit operator actions.
|
||||||
|
|
||||||
### Reticulum
|
### Reticulum
|
||||||
|
|
||||||
Use `micron`:
|
Use `micron`:
|
||||||
@@ -114,6 +124,11 @@ daemon_policy = "auto"
|
|||||||
enabled = false
|
enabled = false
|
||||||
daemon_policy = "auto"
|
daemon_policy = "auto"
|
||||||
|
|
||||||
|
[site.protocols.ipfs]
|
||||||
|
enabled = false
|
||||||
|
renderer = "clearnet"
|
||||||
|
daemon_policy = "auto"
|
||||||
|
|
||||||
[site.protocols.reticulum]
|
[site.protocols.reticulum]
|
||||||
enabled = false
|
enabled = false
|
||||||
daemon_policy = "auto"
|
daemon_policy = "auto"
|
||||||
@@ -123,6 +138,7 @@ daemon_policy = "auto"
|
|||||||
|
|
||||||
- `ampg plan` identifies the Wownero source tree and selected protocols.
|
- `ampg plan` identifies the Wownero source tree and selected protocols.
|
||||||
- `ampg build` creates clearnet, Tor, I2P, and Gemini output without requiring Markdown.
|
- `ampg build` creates clearnet, Tor, I2P, and Gemini output without requiring Markdown.
|
||||||
|
- `ampg build` writes a fixture manifest that AMPB can check.
|
||||||
- Tor output contains no `<script>` tags or inline event handlers.
|
- Tor output contains no `<script>` tags or inline event handlers.
|
||||||
- Generated Gemini output has readable headings, paragraphs, and links.
|
- Generated Gemini output has readable headings, paragraphs, and links.
|
||||||
- Generated Micron output fits terminal-first browsing.
|
- Generated Micron output fits terminal-first browsing.
|
||||||
@@ -133,6 +149,7 @@ daemon_policy = "auto"
|
|||||||
```sh
|
```sh
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml plan
|
python3 -m ampg --config examples/wownero.gateway.toml plan
|
||||||
python3 -m ampg --config examples/wownero.gateway.toml build
|
python3 -m ampg --config examples/wownero.gateway.toml build
|
||||||
|
python3 -m ampg --config examples/wownero.gateway.toml manifest
|
||||||
python3 -m ampg --config examples/i2p-only.gateway.toml plan
|
python3 -m ampg --config examples/i2p-only.gateway.toml plan
|
||||||
python3 -m ampg --config examples/i2p-only.gateway.toml build
|
python3 -m ampg --config examples/i2p-only.gateway.toml build
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ampg.config import load_config
|
||||||
|
from ampg.manifest import fixture_manifest, write_fixture_manifests
|
||||||
|
|
||||||
|
|
||||||
|
class FixtureManifestTest(unittest.TestCase):
|
||||||
|
def test_manifest_contains_route_checks_for_enabled_protocols(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
source = root / "site"
|
||||||
|
source.mkdir()
|
||||||
|
(source / "index.html").write_text("<h1>Hi</h1>", encoding="utf-8")
|
||||||
|
config_path = root / "gateway.toml"
|
||||||
|
config_path.write_text(
|
||||||
|
"""
|
||||||
|
[[site]]
|
||||||
|
id = "example"
|
||||||
|
domain = "example.test"
|
||||||
|
|
||||||
|
[site.source]
|
||||||
|
kind = "static-html"
|
||||||
|
path = "./site"
|
||||||
|
canonical_url = "https://example.test"
|
||||||
|
|
||||||
|
[site.outputs]
|
||||||
|
root = "./out"
|
||||||
|
plan_root = "./plan"
|
||||||
|
|
||||||
|
[site.protocols.clearnet]
|
||||||
|
enabled = true
|
||||||
|
renderer = "clearnet"
|
||||||
|
|
||||||
|
[site.protocols.tor]
|
||||||
|
enabled = true
|
||||||
|
renderer = "privacy-html"
|
||||||
|
onion_location = "auto"
|
||||||
|
|
||||||
|
[site.protocols.i2p]
|
||||||
|
enabled = true
|
||||||
|
renderer = "privacy-html"
|
||||||
|
|
||||||
|
[site.protocols.gemini]
|
||||||
|
enabled = true
|
||||||
|
renderer = "gemtext"
|
||||||
|
|
||||||
|
[site.protocols.ipfs]
|
||||||
|
enabled = true
|
||||||
|
renderer = "clearnet"
|
||||||
|
cid = "bafyexample"
|
||||||
|
""",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
|
||||||
|
config = load_config(config_path)
|
||||||
|
manifest = fixture_manifest(config.sites[0])
|
||||||
|
written = write_fixture_manifests(config)
|
||||||
|
|
||||||
|
self.assertEqual("ampg.fixture-manifest.v1", manifest["schema"])
|
||||||
|
self.assertEqual(["example"], [result.site_id for result in written])
|
||||||
|
self.assertEqual(5, written[0].fixture_count)
|
||||||
|
|
||||||
|
path = root / "out/ampg-fixture-manifest.json"
|
||||||
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
|
by_protocol = {fixture["protocol"]: fixture for fixture in data["fixtures"]}
|
||||||
|
|
||||||
|
self.assertEqual("https://example.test/", by_protocol["clearnet"]["url"])
|
||||||
|
self.assertEqual("http://example.onion/", by_protocol["tor"]["url"])
|
||||||
|
self.assertEqual("placeholder", by_protocol["tor"]["address_status"])
|
||||||
|
self.assertEqual("gemini://example.test/", by_protocol["gemini"]["url"])
|
||||||
|
self.assertEqual("ipfs://bafyexample", by_protocol["ipfs"]["url"])
|
||||||
|
self.assertEqual({"transport": "ipfs", "profile": "ipfs"}, by_protocol["ipfs"]["checks"])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user