Compare commits
1 Commits
fix/535
...
step35/877
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6595a82afb |
@@ -1,96 +0,0 @@
|
||||
# Bezalel Tailscale Bootstrap
|
||||
|
||||
Refs #535
|
||||
|
||||
This is the repo-side operator packet for installing Tailscale on the Bezalel VPS and verifying the internal network path for federation work.
|
||||
|
||||
Important truth:
|
||||
- issue #535 names `104.131.15.18`
|
||||
- older Bezalel control-plane docs also mention `159.203.146.185`
|
||||
- the current source of truth in this repo is `ansible/inventory/hosts.ini`, which currently resolves `bezalel` to `67.205.155.108`
|
||||
|
||||
Because of that drift, `scripts/bezalel_tailscale_bootstrap.py` now resolves the target host from `ansible/inventory/hosts.ini` by default instead of trusting a stale hardcoded IP.
|
||||
|
||||
## What the script does
|
||||
|
||||
`python3 scripts/bezalel_tailscale_bootstrap.py`
|
||||
|
||||
Safe by default:
|
||||
- builds the remote bootstrap script
|
||||
- writes it locally to `/tmp/bezalel_tailscale_bootstrap.sh`
|
||||
- prints the SSH command needed to run it
|
||||
- does **not** touch the VPS unless `--apply` is passed
|
||||
|
||||
When applied, the remote script does all of the issue’s repo-side bootstrap steps:
|
||||
- installs Tailscale
|
||||
- runs `tailscale up --ssh --hostname bezalel`
|
||||
- appends the provided Mac SSH public key to `~/.ssh/authorized_keys`
|
||||
- prints `tailscale status --json`
|
||||
- pings the expected peer targets:
|
||||
- Mac: `100.124.176.28`
|
||||
- Ezra: `100.126.61.75`
|
||||
|
||||
## Required secrets / inputs
|
||||
|
||||
- Tailscale auth key
|
||||
- Mac SSH public key
|
||||
|
||||
Provide them either directly or through files:
|
||||
- `--auth-key` or `--auth-key-file`
|
||||
- `--ssh-public-key` or `--ssh-public-key-file`
|
||||
|
||||
## Dry-run example
|
||||
|
||||
```bash
|
||||
python3 scripts/bezalel_tailscale_bootstrap.py \
|
||||
--auth-key-file ~/.config/tailscale/auth_key \
|
||||
--ssh-public-key-file ~/.ssh/id_ed25519.pub \
|
||||
--json
|
||||
```
|
||||
|
||||
This prints:
|
||||
- resolved host
|
||||
- host source (`inventory:<path>` when pulled from `ansible/inventory/hosts.ini`)
|
||||
- local script path
|
||||
- SSH command to execute
|
||||
- peer targets
|
||||
|
||||
## Apply example
|
||||
|
||||
```bash
|
||||
python3 scripts/bezalel_tailscale_bootstrap.py \
|
||||
--auth-key-file ~/.config/tailscale/auth_key \
|
||||
--ssh-public-key-file ~/.ssh/id_ed25519.pub \
|
||||
--apply \
|
||||
--json
|
||||
```
|
||||
|
||||
## Verifying success after apply
|
||||
|
||||
The script now parses the remote stdout into structured verification data:
|
||||
- `verification.tailscale.self.tailscale_ips`
|
||||
- `verification.tailscale.self.dns_name`
|
||||
- `verification.peers`
|
||||
- `verification.ping_ok`
|
||||
|
||||
A successful run should show:
|
||||
- at least one Bezalel Tailscale IP under `tailscale_ips`
|
||||
- `ping_ok.mac = 100.124.176.28`
|
||||
- `ping_ok.ezra = 100.126.61.75`
|
||||
|
||||
## Expected remote install commands
|
||||
|
||||
```bash
|
||||
curl -fsSL https://tailscale.com/install.sh | sh
|
||||
tailscale up --ssh --hostname bezalel
|
||||
install -d -m 700 ~/.ssh
|
||||
touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys
|
||||
tailscale status --json
|
||||
```
|
||||
|
||||
## Why this PR does not claim live completion
|
||||
|
||||
This repo can safely ship the bootstrap script, host resolution logic, structured proof parsing, and operator packet.
|
||||
It cannot honestly claim that Bezalel was actually joined to the tailnet unless a human/operator runs the script with a real auth key and real SSH access to the VPS.
|
||||
|
||||
That means the correct PR language for #535 is advancement, not pretend closure.
|
||||
@@ -14,7 +14,6 @@ Quick-reference index for common operational tasks across the Timmy Foundation i
|
||||
| Agent scorecard | fleet-ops | `python3 scripts/agent_scorecard.py` |
|
||||
| View fleet manifest | fleet-ops | `cat manifest.yaml` |
|
||||
| Run nightly codebase genome pass | timmy-home | `python3 scripts/codebase_genome_nightly.py --dry-run` |
|
||||
| Prepare Bezalel Tailscale bootstrap | timmy-home | `python3 scripts/bezalel_tailscale_bootstrap.py --auth-key-file <path> --ssh-public-key-file <path> --json` |
|
||||
|
||||
## the-nexus (Frontend + Brain)
|
||||
|
||||
|
||||
172
research/mathematics/math-001-shadow-maths-triage-rubric.md
Normal file
172
research/mathematics/math-001-shadow-maths-triage-rubric.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Shadow Maths Triage Rubric (MATH-001)
|
||||
|
||||
**Status**: Draft v1.0 **Date**: 2026-04-26 **Author**: Timmy
|
||||
**Milestone**: Contribute to Mathematics — Shadow Maths Search
|
||||
**Parent**: #876 — [MATH][EPIC] Shadow Maths
|
||||
|
||||
---
|
||||
|
||||
## Purpose
|
||||
|
||||
Timmy's mathematics contribution program targets *bounded, verifiable, useful* problems hiding in plain sight. This rubric operationalizes "shadow maths" — distinguishing legitimate first-crack contributions from crank Grand Unified Theories.
|
||||
|
||||
The rubric serves two roles:
|
||||
1. **Triage gate** — filter submissions and scout list candidates worth pursuing.
|
||||
2. **No-crank guardrail** — explicitly reject unfalsifiable, unscoped, or unsourced claims.
|
||||
|
||||
---
|
||||
|
||||
## Candidate Categories (Positive Types)
|
||||
|
||||
| Category | Description | Verification Path | Useful Because |
|
||||
|----------|-------------|-------------------|---------------|
|
||||
| **Small lemma** | Missing but straightforward piece in an active area (e.g., "Proposition 3.2 in Smith 2021 needs this case analysis") | Check paper + 1–2 related references; prove or give counterexample | Clarifies existing theory, removes ambiguity |
|
||||
| **Counterexample search** | Find explicit counterexample to a claimed-but-unproven statement (often from MO/SE) | Compute/construct + cite the original claim | Prevents propagation of errors |
|
||||
| **Computational classification** | Exhaustive enumeration/classification of a small infinite family (e.g., "all groups of order < 200 with property X") | Code is verifiable; results match known data | Creates reference data, spotlights patterns |
|
||||
| **Formalization gap** | Statement already believed true but missing from Lean/mathlib/Isabelle | Formal proof artifact; merges to mainline library | Makes mathematics machine-checkable |
|
||||
| **OEIS sequence note** | New sequence entry or correction to an existing entry with proof/algorithm | OEIS A-number + formula/generation code | Public archival, enables further work |
|
||||
| **Exposition repair** | Fix an unclear proof, fill a gap, simplify an argument in an existing paper | Side-by-side diff + justification for each change | Improves pedagogy, reduces confusion |
|
||||
| **MathOverflow-quality answer** | Answer to a specific, bounded, research-level question on MO/SE that has no accepted answer | Cite question + self-contained proof/computation | Serves the community directly |
|
||||
|
||||
---
|
||||
|
||||
## Rejection Criteria (No-Crank Guardrails)
|
||||
|
||||
> Any candidate that triggers one or more of these is **rejected outright** — no scoring needed.
|
||||
|
||||
| Rule | What to look for | Why it's crank |
|
||||
|------|------------------|----------------|
|
||||
| **Unsourced grand theory** | Claim introduces new "framework"/"paradigm" without citing specific bounded problem it solves | Mathematics advances by solving problems, not proposing frameworks |
|
||||
| **Impossible scope** | "I will prove/disprove the Riemann Hypothesis", "classify all finite simple groups" | Demonstrably beyond single-attack capability |
|
||||
| **No verification path** | No way for a third party to check the work (no code, no formalization, no explicit examples) | Cannot be wrong if it cannot be checked |
|
||||
| **Novelty claim without literature search** | States "I believe this is new" without checking MathSciNet/arXiv/Google Scholar | Almost certainly reinvention or known result |
|
||||
| **Vague mathematical objects** | Uses undefined or ambiguous terminology ("energy", "resonance", "harmonic" in non-standard ways) | Not mathematics |
|
||||
| **Secrecy or paywall** | Key definition or proof behind a paywall or withheld | Not sovereign; not verifiable |
|
||||
| **Symbolic overloading without definition** | Repurposes standard notation in non-standard ways without explicit redefinition | Creates confusion, not clarity |
|
||||
| **Invariance violations** | Claims "up to isomorphism" or "modulo equivalence" without defining the equivalence relation | Not mathematically precise |
|
||||
| **Cherry-picked examples as proof** | Proves only easy special cases and claims the general case follows | Example ≠ theorem |
|
||||
| **Circular citation chains** | Relies on unpublished/preprint work that itself cites the candidate as motivation | Not a foundation |
|
||||
| **No clear problem statement** | Cannot write a one-sentence problem statement in standard mathematical English | Not a problem; just musings |
|
||||
| **Claims of "obvious" or "clear" for non-trivial steps** | Uses "obviously" or "it is clear that" where a proof requires >2 lines | Evasion |
|
||||
| **References only popular science / non-technical sources** | Cites Penrose, Hawking, Tegmark for technical claims | Wrong tier of source |
|
||||
| **All notation defined in non-standard way** | Redefines basic operators (+, ×, ≤) without explicit warning | Not mathematics |
|
||||
| **No engagement with existing literature** | Zero citations to relevant peer-reviewed work or established preprints | scholarship was not done |
|
||||
| **Claims of "disproof" of widely-accepted theorems** | Without finding a peer-reviewed error in the existing proof | Almost certainly wrong |
|
||||
|
||||
---
|
||||
|
||||
## Evidence Tiers
|
||||
|
||||
| Tier | Artifact | What it Proves |
|
||||
|------|----------|----------------|
|
||||
| **T3 — Literature** | MathSciNet / Zentralblatt / Google Scholar citations showing the problem is real and open | Problem exists in the literature |
|
||||
| **T2 — Executable** | Python/Sage/Lean code that others can run to verify computation/formalization | Result is reproducible |
|
||||
| **T1 — Human-reviewed** | MO answer with upvotes, referee report, or explicit external review | Independent verification |
|
||||
| **T0 — Self-contained** | Clear statement + proof/computation in a single document, all definitions explicit | Standalone correctness |
|
||||
|
||||
A valid candidate must have at least **one** T3 citation (shows the problem is real) AND a verification artifact (T0 minimum; T2 ideal).
|
||||
|
||||
---
|
||||
|
||||
## Scoring Rubric
|
||||
|
||||
Score each candidate on **4 dimensions**, each 0–3. Maximum 12 points.
|
||||
|
||||
| Dimension | 3 (excellent) | 2 (good) | 1 (minimal) | 0 (absent) |
|
||||
|-----------|---------------|----------|-------------|------------|
|
||||
| **Boundedness** | Scope is explicitly finite/small (single lemma, finite classification < N, one SE question) | Scope is implied bounded but not quantified | Scope is large/vague but attackable | Unbounded or impossible scope |
|
||||
| **Verifiability** | T2 artifact (code/formalization) + T3 citation | T0 proof + T3 citation | Proof/computation only, no citations | No way to check independently |
|
||||
| **Usefulness** | Solves problem others actively need (cites known difficulty, fills formalization gap) | Solves a clean exercise or interesting special case | Interesting but no clear audience | Pointless or self-referential |
|
||||
| **Discipline** | No crank flags; explicit rejection criteria cleanly passed | Minor crank flags (vague wording) but overall sound | Some crank flags but bounded scope rescues it | Triggers multiple rejection rules |
|
||||
|
||||
**Thresholds**:
|
||||
- **8–12**: Legitimate shadow maths candidate — queue for work
|
||||
- **4–7**: Needs refinement — reject unless strong disciplinary context
|
||||
- **0–3**: Reject as crank / out-of-scope
|
||||
|
||||
---
|
||||
|
||||
## Three Worked Examples
|
||||
|
||||
### Example 1: Small Lemma — Bounded
|
||||
|
||||
**Candidate**: "Proposition 3.2 in 'Coarse Geometry and Coarse Embeddings' (Lang-Schlichenmaier 2005) states that every finite CW-complex has Markov property. The proof gives 'it follows by induction on skeleta' without handling the attaching map case. Fill the gap."
|
||||
|
||||
**Triage**:
|
||||
- **Category**: Small lemma (exposition repair + proof gap fill)
|
||||
- **Boundedness**: 3 — single proposition in a specific paper, 2–3 pages max
|
||||
- **Verifiability**: 3 — paper is cited (T3), self-contained proof in 20 lines (T0), can formalize in Lean (T2 possible)
|
||||
- **Usefulness**: 3 — readers of this paper hit this gap; Lean formalization needed for mathlib
|
||||
- **Discipline**: 3 — no crank flags; scoped, sourced, technical
|
||||
- **Total**: **12/12** → YES
|
||||
|
||||
**Action**: File ticket "MATH-LEMMA-001"; assign to formalization lane + human review.
|
||||
|
||||
---
|
||||
|
||||
### Example 2: Grand Unified Theory — CRANK
|
||||
|
||||
**Candidate**: "I have discovered the Energy-Conscious Riemann Hypothesis framework. The zeros of ζ(s) correspond to harmonic resonance frequencies in prime-number energy manifolds. Uses my new Operator-Weight theory."
|
||||
|
||||
**Triage**:
|
||||
- **Category**: N/A
|
||||
- **Rejection triggers**:
|
||||
- ✗ Unsourced grand theory (introduces "Energy-Conscious", "Operator-Weight" with no definition in standard math)
|
||||
- ✗ No verification path (no computation, no reference to known data)
|
||||
- ✗ No literature engagement (zero citations)
|
||||
- ✗ Vague mathematical objects ("energy", "resonance", "harmonic")
|
||||
- ✗ Claims new framework
|
||||
- **Score**: 0 — **REJECT**
|
||||
|
||||
**Action**: Close with reason "crank: unsourced grand theory + no verification path".
|
||||
|
||||
---
|
||||
|
||||
### Example 3: Computational Classification — Bounded
|
||||
|
||||
**Candidate**: "Compute all 3-headed Turing machines with 3 states that halt within 100 steps on the blank tape. There are 9 such machines. This fills an OEIS gap: A327000 only lists up to 2-state 2-symbol."
|
||||
|
||||
**Triage**:
|
||||
- **Category**: Computational classification + OEIS sequence
|
||||
- **Boundedness**: 3 — finite exhaustive enumeration (3^6 = 729 machines, filter to 9)
|
||||
- **Verifiability**: 2 — code is executable (T2), but no T3 citation of why this sequence matters yet
|
||||
- **Usefulness**: 2 — plugs a gap in the Busy Beaver frontier; OEIS entry gets concrete data
|
||||
- **Discipline**: 3 — explicit scope, reproducible, submits to OEIS (external review path)
|
||||
- **Total**: **10/12** → YES (minor fix: add motivation/references)
|
||||
|
||||
**Action**: Accept; write exhaustive script; submit OEIS draft with code + results; file MATH-COMP-001.
|
||||
|
||||
---
|
||||
|
||||
## Operational Use
|
||||
|
||||
### Triage Workflow
|
||||
|
||||
1. **Read candidate** (issue, email, self-generated idea).
|
||||
2. **Check rejection criteria first** — if any trigger → **REJECT** immediately, cite rule.
|
||||
3. If passes rejection gate, **score 4 dimensions**.
|
||||
4. **Score ≥8** → mark as `shadow-maths-candidate`, route to appropriate lane:
|
||||
- Lemma/exposition → `formalization-lane`
|
||||
- Computation → `compute-lane`
|
||||
- MO/SE answer → `answer-lane`
|
||||
- OEIS → `oeis-lane`
|
||||
5. **Score 4–7** → requires refinement; ask for:
|
||||
- Explicit scope bound
|
||||
- T3 citation
|
||||
- Verification artifact
|
||||
6. **Score ≤3** → reject with specific rule(s) triggered.
|
||||
|
||||
### Guardrail Enforcement
|
||||
|
||||
The following prompts/agents **must refuse** to work on any candidate that:
|
||||
- Triggers any rejection criterion (before any code/proof work)
|
||||
- Has no T3 citation (real problem statement from literature)
|
||||
- Has no bounded scope (cannot write ≤1-sentence problem statement)
|
||||
|
||||
Enforcement is a **pre-flight check** in the task intake pipeline.
|
||||
|
||||
---
|
||||
|
||||
## Revision History
|
||||
|
||||
- v1.0 — 2026-04-26 — initial rubric + 3 scored examples
|
||||
@@ -16,14 +16,11 @@ import argparse
|
||||
import json
|
||||
import shlex
|
||||
import subprocess
|
||||
import re
|
||||
from json import JSONDecoder
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
DEFAULT_HOST = "67.205.155.108"
|
||||
DEFAULT_HOST = "159.203.146.185"
|
||||
DEFAULT_HOSTNAME = "bezalel"
|
||||
DEFAULT_INVENTORY_PATH = Path(__file__).resolve().parents[1] / "ansible" / "inventory" / "hosts.ini"
|
||||
DEFAULT_PEERS = {
|
||||
"mac": "100.124.176.28",
|
||||
"ezra": "100.126.61.75",
|
||||
@@ -69,37 +66,6 @@ def parse_tailscale_status(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
def resolve_host(host: str | None, inventory_path: Path = DEFAULT_INVENTORY_PATH, hostname: str = DEFAULT_HOSTNAME) -> tuple[str, str]:
|
||||
if host:
|
||||
return host, "explicit"
|
||||
if inventory_path.exists():
|
||||
pattern = re.compile(rf"^{re.escape(hostname)}\s+.*ansible_host=([^\s]+)")
|
||||
for line in inventory_path.read_text().splitlines():
|
||||
match = pattern.search(line.strip())
|
||||
if match:
|
||||
return match.group(1), f"inventory:{inventory_path}"
|
||||
return DEFAULT_HOST, "default"
|
||||
|
||||
|
||||
def parse_apply_output(stdout: str) -> dict[str, Any]:
|
||||
result: dict[str, Any] = {"tailscale": None, "ping_ok": {}}
|
||||
text = stdout or ""
|
||||
start = text.find("{")
|
||||
if start != -1:
|
||||
try:
|
||||
payload, _ = JSONDecoder().raw_decode(text[start:])
|
||||
if isinstance(payload, dict):
|
||||
result["tailscale"] = parse_tailscale_status(payload)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
for line in text.splitlines():
|
||||
if line.startswith("PING_OK:"):
|
||||
_, name, ip = line.split(":", 2)
|
||||
result["ping_ok"][name] = ip
|
||||
return result
|
||||
|
||||
|
||||
def build_ssh_command(host: str, remote_script_path: str = "/tmp/bezalel_tailscale_bootstrap.sh") -> list[str]:
|
||||
return ["ssh", host, f"bash {shlex.quote(remote_script_path)}"]
|
||||
|
||||
@@ -123,9 +89,8 @@ def parse_peer_args(items: list[str]) -> dict[str, str]:
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description="Prepare or execute Tailscale bootstrap for the Bezalel VPS.")
|
||||
parser.add_argument("--host")
|
||||
parser.add_argument("--host", default=DEFAULT_HOST)
|
||||
parser.add_argument("--hostname", default=DEFAULT_HOSTNAME)
|
||||
parser.add_argument("--inventory-path", type=Path, default=DEFAULT_INVENTORY_PATH)
|
||||
parser.add_argument("--auth-key", help="Tailscale auth key")
|
||||
parser.add_argument("--auth-key-file", type=Path, help="Path to file containing the Tailscale auth key")
|
||||
parser.add_argument("--ssh-public-key", help="SSH public key to append to authorized_keys")
|
||||
@@ -151,7 +116,6 @@ def main() -> None:
|
||||
auth_key = _read_secret(args.auth_key, args.auth_key_file)
|
||||
ssh_public_key = _read_secret(args.ssh_public_key, args.ssh_public_key_file)
|
||||
peers = parse_peer_args(args.peer)
|
||||
resolved_host, host_source = resolve_host(args.host, args.inventory_path, args.hostname)
|
||||
|
||||
if not auth_key:
|
||||
raise SystemExit("Missing Tailscale auth key. Use --auth-key or --auth-key-file.")
|
||||
@@ -162,31 +126,28 @@ def main() -> None:
|
||||
write_script(args.script_out, script)
|
||||
|
||||
payload: dict[str, Any] = {
|
||||
"host": resolved_host,
|
||||
"host_source": host_source,
|
||||
"host": args.host,
|
||||
"hostname": args.hostname,
|
||||
"inventory_path": str(args.inventory_path),
|
||||
"script_out": str(args.script_out),
|
||||
"remote_script_path": args.remote_script_path,
|
||||
"ssh_command": build_ssh_command(resolved_host, args.remote_script_path),
|
||||
"ssh_command": build_ssh_command(args.host, args.remote_script_path),
|
||||
"peer_targets": peers,
|
||||
"applied": False,
|
||||
}
|
||||
|
||||
if args.apply:
|
||||
result = run_remote(resolved_host, args.remote_script_path)
|
||||
result = run_remote(args.host, args.remote_script_path)
|
||||
payload["applied"] = True
|
||||
payload["exit_code"] = result.returncode
|
||||
payload["stdout"] = result.stdout
|
||||
payload["stderr"] = result.stderr
|
||||
payload["verification"] = parse_apply_output(result.stdout)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(payload, indent=2))
|
||||
return
|
||||
|
||||
print("--- Bezalel Tailscale Bootstrap ---")
|
||||
print(f"Host: {resolved_host} ({host_source})")
|
||||
print(f"Host: {args.host}")
|
||||
print(f"Local script: {args.script_out}")
|
||||
print("SSH command: " + " ".join(payload["ssh_command"]))
|
||||
if args.apply:
|
||||
|
||||
@@ -2,12 +2,9 @@ from scripts.bezalel_tailscale_bootstrap import (
|
||||
DEFAULT_PEERS,
|
||||
build_remote_script,
|
||||
build_ssh_command,
|
||||
parse_apply_output,
|
||||
parse_peer_args,
|
||||
parse_tailscale_status,
|
||||
resolve_host,
|
||||
)
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_build_remote_script_contains_install_up_and_key_append():
|
||||
@@ -81,46 +78,3 @@ def test_parse_peer_args_merges_overrides_into_defaults():
|
||||
"ezra": "100.126.61.76",
|
||||
"forge": "100.70.0.9",
|
||||
}
|
||||
|
||||
|
||||
def test_resolve_host_prefers_inventory_over_stale_default(tmp_path: Path):
|
||||
inventory = tmp_path / "hosts.ini"
|
||||
inventory.write_text(
|
||||
"[fleet]\n"
|
||||
"ezra ansible_host=143.198.27.163 ansible_user=root\n"
|
||||
"bezalel ansible_host=67.205.155.108 ansible_user=root\n"
|
||||
)
|
||||
|
||||
host, source = resolve_host(None, inventory)
|
||||
|
||||
assert host == "67.205.155.108"
|
||||
assert source == f"inventory:{inventory}"
|
||||
|
||||
|
||||
def test_parse_apply_output_extracts_status_and_ping_markers():
|
||||
stdout = (
|
||||
'{"Self": {"HostName": "bezalel", "DNSName": "bezalel.tailnet.ts.net", "TailscaleIPs": ["100.90.0.10"]}, '
|
||||
'"Peer": {"node-1": {"HostName": "ezra", "TailscaleIPs": ["100.126.61.75"]}}}'
|
||||
"\nPING_OK:mac:100.124.176.28\n"
|
||||
"PING_OK:ezra:100.126.61.75\n"
|
||||
)
|
||||
|
||||
result = parse_apply_output(stdout)
|
||||
|
||||
assert result["tailscale"]["self"]["tailscale_ips"] == ["100.90.0.10"]
|
||||
assert result["ping_ok"] == {"mac": "100.124.176.28", "ezra": "100.126.61.75"}
|
||||
|
||||
|
||||
def test_runbook_doc_exists_and_mentions_inventory_auth_and_peer_checks():
|
||||
doc = Path("docs/BEZALEL_TAILSCALE_BOOTSTRAP.md")
|
||||
assert doc.exists(), "missing docs/BEZALEL_TAILSCALE_BOOTSTRAP.md"
|
||||
text = doc.read_text()
|
||||
assert "ansible/inventory/hosts.ini" in text
|
||||
assert "tailscale up" in text
|
||||
assert "authorized_keys" in text
|
||||
assert "100.124.176.28" in text
|
||||
assert "100.126.61.75" in text
|
||||
|
||||
runbook = Path("docs/RUNBOOK_INDEX.md").read_text()
|
||||
assert "Prepare Bezalel Tailscale bootstrap" in runbook
|
||||
assert "scripts/bezalel_tailscale_bootstrap.py" in runbook
|
||||
|
||||
Reference in New Issue
Block a user