Harden public docs gates

This commit is contained in:
jwinterm
2026-07-11 08:59:21 -04:00
parent f0d26933e6
commit d95296da5f
3 changed files with 46 additions and 8 deletions
+5 -3
View File
@@ -1,6 +1,6 @@
# Architecture # Architecture
> Status: draft | Updated 2026-07-07 | Applies to: AMPG core > Status: draft | Updated 2026-07-11 | Applies to: AMPG core
AMPG is a compiler plus an ingress manager. The compiler turns one site into a AMPG is a compiler plus an ingress manager. The compiler turns one site into a
protocol-neutral content graph, then renders protocol-specific outputs. The ingress protocol-neutral content graph, then renders protocol-specific outputs. The ingress
@@ -194,8 +194,10 @@ v1 is static-first, but the architecture reserves a path for interactive applica
Interactivity is declared per route group: Interactivity is declared per route group:
- `static`: files only. - `static`: files only.
- `forms`: server-rendered actions and full-page responses. - `interactive-lite`: deterministic games, status lookups, quote forms, and other flows
- `sessions`: authenticated flows with strict session policy. without account state or payment confirmation.
- `identity`: authenticated sessions, wallet sign-in, or signed capability links.
- `transactional`: server-confirmed orders, deposits, invoices, donations, or payment intents.
- `realtime`: rich browser/admin interfaces; HTTP transports only. - `realtime`: rich browser/admin interfaces; HTTP transports only.
- `internal`: workers, webhooks, private admin APIs; never published automatically. - `internal`: workers, webhooks, private admin APIs; never published automatically.
+9 -5
View File
@@ -1,6 +1,6 @@
# Autodocs And Drift Gates # Autodocs And Drift Gates
> Status: draft | Updated 2026-07-05 | Applies to: AMPG generated docs > Status: draft | Updated 2026-07-11 | Applies to: AMPG generated docs
AMPG should publish concise hand-written docs and generate the parts that can drift from AMPG should publish concise hand-written docs and generate the parts that can drift from
code. Generated artifacts are committed, then checked in CI by regenerating and diffing. code. Generated artifacts are committed, then checked in CI by regenerating and diffing.
@@ -38,13 +38,17 @@ git diff --exit-code docs/generated schemas openapi.json
python3 tools/docs_check.py python3 tools/docs_check.py
``` ```
`ampg docs check` should verify: `python3 tools/docs_check.py` verifies:
- every Markdown file starts with a `> Status:` header. - every Markdown file starts with a `> Status:` header.
- internal links resolve. - internal links resolve.
- generated docs are not edited by hand. - local links stay inside the repository.
- public docs contain no private keys, onion keys, I2P keys, Reticulum identities, or - public docs do not name private fixture targets.
local production secrets. - public docs do not contain obvious private-key blocks, common API tokens, or assigned
secret values.
`python3 -m ampg docs generate --check` verifies that generated docs and schemas match
the code-owned metadata.
## Public/private boundary ## Public/private boundary
+32
View File
@@ -11,6 +11,26 @@ from urllib.parse import unquote
ROOT = Path(__file__).resolve().parents[1] ROOT = Path(__file__).resolve().parents[1]
LINK_RE = re.compile(r"(?<!!)\[[^\]]+\]\(([^)]+)\)") LINK_RE = re.compile(r"(?<!!)\[[^\]]+\]\(([^)]+)\)")
FORBIDDEN_PUBLIC_TERMS = (
"wowrace",
"wowngeon",
"smirk-monorepo",
"medusa-multi-tenant-platform",
"ai-gen-bot",
)
SECRET_PATTERNS = (
("private key block", re.compile(r"-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----")),
("open ssh private key block", re.compile(r"-----BEGIN OPENSSH PRIVATE KEY-----")),
("aws access key", re.compile(r"\bAKIA[0-9A-Z]{16}\b")),
("github token", re.compile(r"\bgh[pousr]_[A-Za-z0-9_]{20,}\b")),
("slack token", re.compile(r"\bxox[baprs]-[A-Za-z0-9-]{10,}\b")),
(
"assigned secret",
re.compile(
r"(?i)\b(api[_-]?key|secret|password|token)\s*=\s*['\"][^'\"]{8,}['\"]"
),
),
)
def iter_markdown() -> list[Path]: def iter_markdown() -> list[Path]:
@@ -50,12 +70,24 @@ def main() -> int:
text = path.read_text(encoding="utf-8") text = path.read_text(encoding="utf-8")
if not has_status_header(text): if not has_status_header(text):
errors.append(f"{rel}: missing > Status: header in first 6 lines") errors.append(f"{rel}: missing > Status: header in first 6 lines")
lower_text = text.lower()
for term in FORBIDDEN_PUBLIC_TERMS:
if term in lower_text:
errors.append(f"{rel}: public docs contain private target term {term!r}")
for label, pattern in SECRET_PATTERNS:
if pattern.search(text):
errors.append(f"{rel}: public docs may contain {label}")
for match in LINK_RE.finditer(text): for match in LINK_RE.finditer(text):
target = local_link_target(path, match.group(1)) target = local_link_target(path, match.group(1))
if target is None: if target is None:
continue continue
checked_links += 1 checked_links += 1
try:
target.relative_to(ROOT)
except ValueError:
errors.append(f"{rel}: link escapes repo -> {match.group(1)}")
continue
if not target.exists(): if not target.exists():
errors.append(f"{rel}: broken link -> {match.group(1)}") errors.append(f"{rel}: broken link -> {match.group(1)}")