docs(claude-config): catch up uncommitted session artifacts (Omarchy, digital-business, NetBird+Twingate)
Backlog across 2026-09-13/15/16 sessions, none committed until now: - Omarchy: prompts G/H + research (omarchy-os-evaluation, omarchy-ai-skills-deep-dive) - Digital business: prompt J + gameplans (etsy/kdp/docforge launch), ai-support-disclosure-research, digital-businesses-marketing-strategy, etsy-marketing-tables.sql, prompt I (mesh-VPN) + netbird-vs-tailscale research - NetBird+Twingate (this session): prompts K/L/M + research (netbird-twingate-gameplan, netbird-selfhost-buildplan, twingate-deploy-runbook, partner-access-audit-austin-hailee) - DECISIONS.md (NetBird+Twingate 09-15 entry), research/INDEX.md rows, config/prompts/README.md K/L/M rows Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
# Prompt G — Omarchy Linux research (laptop daily-driver + server-OS candidate)
|
||||
|
||||
Tracks: laptop general-questions hub. Spawn from `~/Desktop/claude/claude-config`. Research-only.
|
||||
|
||||
---
|
||||
|
||||
You are a research analyst producing one fact-checked report on the **Omarchy** Linux distribution. You will read the web and write exactly one Markdown file. You will NOT install anything, change any config, write to any database, restart any container, or need any credential.
|
||||
|
||||
## Task
|
||||
Establish what Omarchy actually is and gather the facts needed to decide two separate questions. **Do NOT make the final adoption decision** — the human's main session will do that with infrastructure context you do not have. Your job is accurate, sourced facts plus a *preliminary, clearly-hedged* suitability read for each use case.
|
||||
|
||||
Two use cases to evaluate separately:
|
||||
- **UC1 — Laptop daily-driver desktop OS** (to try alongside / possibly replace LMDE 7 on a personal laptop).
|
||||
- **UC2 — Server OS for `server-01`**, replacing LMDE 7 (Debian 13 base). This machine runs real self-hosted workloads and is currently a headless/server role. The competing option it would displace is a planned migration to **plain Debian** (which the user had heard is "the best server distro").
|
||||
|
||||
## Context / pre-fetched framing (verify anything you cite; do not assume these are current)
|
||||
- The user heard Omarchy has "AI features like Claude skills" and that is partly what makes them consider it even for a server. **Truth-check this specifically** — what AI/LLM/Claude integration, if any, Omarchy actually ships out of the box, and whether "Claude skills" is an accurate description or a conflation. This is a central question.
|
||||
- Known hardware in play: an RTX 2060 Super 8 GB (NVIDIA) is present in the stack; laptop model/specs are UNKNOWN to you — do not invent them. Flag NVIDIA-on-Wayland/Hyprland status as a risk factor if applicable.
|
||||
- Hard user constraint from prior research: the user **will never use Ubuntu**. Do not recommend it.
|
||||
- The user runs Claude Code on a Claude Pro subscription (OAuth, no API key). Any "AI feature" that requires a paid API key is a downgrade, not an upgrade — note it as such.
|
||||
|
||||
## Research questions (answer each with sourced facts; mark UNVERIFIED if you cannot confirm)
|
||||
1. **Identity & lineage.** Who makes Omarchy, what is it built on (base distro, release model — rolling vs point), what desktop/WM does it ship (Hyprland?), and what is its stated target audience? Current version + release date as of your research.
|
||||
2. **AI features truth-check.** Exactly what AI/LLM tooling ships by default. Is there any real "Claude skills" or Claude Code integration, or is it generic LLM tooling / a conflation? Does any of it require a paid API key? Cite the source.
|
||||
3. **Desktop suitability (UC1).** Maturity as a daily driver: hardware/NVIDIA support, installer, app availability (AUR/pacman), update stability, community size, documentation, known breakage reports in the last ~90 days.
|
||||
4. **Server suitability (UC2).** Be blunt here. Is Omarchy designed to be a server OS at all, or is it an opinionated *desktop* distro? Assess: rolling-release stability risk for an always-on server, unattended-update/breakage exposure vs Debian's stability guarantees, headless operation (does it even make sense without Hyprland?), long-term maintenance burden, security-update cadence. Compare against the incumbent (LMDE 7 / Debian) and the planned plain-Debian migration on the specific axis of *server reliability*.
|
||||
5. **Migration & footprint.** Rough disk-space footprint of an Omarchy install; whether it supports dual-boot alongside an existing Linux install; installer's disk handling (does it want the whole disk?); how reversible a trial is; whether a live-USB trial without installing is supported.
|
||||
6. **Rolling-release reality.** What running Arch/rolling actually demands of an operator (manual intervention frequency, breakage classes, snapshot/rollback story e.g. Btrfs+snapper) — this bears on BOTH use cases but especially the server one.
|
||||
|
||||
## Output — write exactly one file: `research/omarchy-os-evaluation.md`
|
||||
Sections, in this order:
|
||||
1. **Verdict (preliminary, hedged).** 2–4 sentences per use case: a lean (lean-yes / lean-no / depends-on-X) with a confidence level, explicitly stating "the main session must confirm against actual infra + future plans." No unhedged recommendation.
|
||||
2. **What Omarchy is** — identity, base, release model, WM, target audience, current version+date.
|
||||
3. **AI-features truth-check** — the finding on "Claude skills," with citation; API-key implications.
|
||||
4. **UC1 desktop suitability** — the facts, with the NVIDIA/Wayland risk called out.
|
||||
5. **UC2 server suitability** — the blunt assessment vs Debian, on reliability axes.
|
||||
6. **Migration & disk footprint** — dual-boot support, live-USB trial support, approximate footprint.
|
||||
7. **Rolling-release operator burden.**
|
||||
8. **Risks / open questions for the human** — a short list the main session should weigh.
|
||||
9. **Sources** — URL + access date for every non-obvious claim.
|
||||
|
||||
## Output rules
|
||||
- Every non-obvious claim gets a URL + date. Mark anything you cannot verify as UNVERIFIED rather than guessing.
|
||||
- Do not invent the laptop's specs, disk size, or partition layout — those are unknown to you and are the human's to check.
|
||||
- Do not recommend Ubuntu. Do not recommend anything requiring an Anthropic API key for Claude.
|
||||
- Do not make the final switch/no-switch decision or tell the user to reinstall anything — you gather facts and give a hedged lean only.
|
||||
|
||||
## Persistence (do these as your final actions, before the wrap-up JSON)
|
||||
- Write the report to `research/omarchy-os-evaluation.md` (relative to `~/Desktop/claude/claude-config`).
|
||||
- Append one row to `research/INDEX.md` in the existing table format (Topic | File | Status | Key open decision).
|
||||
- Append a dated handoff entry (What was done / Preliminary verdict per use case / Current state / Next step = "main session does infra-fit synthesis + laptop disk check") to `/opt/appdata/docker/Machines/laptop general questions/.claude/context.md`. Append, do not overwrite history.
|
||||
- Do NOT commit or push anything. Do NOT modify any file outside the three named above.
|
||||
|
||||
## MANDATORY WRAP-UP (required regardless of success or failure)
|
||||
Before stopping for ANY reason — task complete, error, or approaching turn limit — output this JSON as your final message. Do not stop without it.
|
||||
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"project": "laptop general questions",
|
||||
"files_touched": ["research/omarchy-os-evaluation.md", "research/INDEX.md", ".../laptop general questions/.claude/context.md"],
|
||||
"containers_restarted": [],
|
||||
"uc1_laptop_lean": "<lean-yes|lean-no|depends> + confidence",
|
||||
"uc2_server_lean": "<lean-yes|lean-no|depends> + confidence",
|
||||
"ai_features_finding": "<one sentence: what 'Claude skills' really is + URL>",
|
||||
"next_step": "main session: infra-fit synthesis + laptop dual-boot disk check",
|
||||
"notes": "anything relevant for the next session"
|
||||
}
|
||||
|
||||
If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Prompt H — Omarchy AI skills deep-dive + port-for-ourselves assessment
|
||||
|
||||
Tracks: laptop general-questions hub (builds on `research/omarchy-os-evaluation.md`). Spawn from `~/Desktop/claude/claude-config`. Research-only.
|
||||
|
||||
---
|
||||
|
||||
You are a research analyst producing one fact-checked report on the **AI skills that ship with Omarchy Linux** — with a specific focus on any **crash / error auto-investigation skill** — and on whether those skills can be extracted and reused on our own machines. You will read the web (repos, manuals, raw skill source) and write exactly one Markdown file. You will NOT install anything, change any config, write to any database, restart any container, or need any credential. **Do not make adoption decisions or build the skill** — you gather facts and produce a portability assessment + a blueprint the human's main session will decide on.
|
||||
|
||||
## Why this research exists (grounding — verify, don't assume)
|
||||
- We are **NOT** putting Omarchy on our servers (decided 2026-09-13; server-01 stays on the plain-Debian track, primary server is reliability-critical). Omarchy may go on a personal **laptop** only.
|
||||
- Omarchy's skills are plain Claude Code skills (SKILL.md-style files) symlinked into `~/.claude/skills/` — so they are potentially **portable to any distro** running Claude Code. That portability is the whole point of this research: even if Omarchy never touches our servers, we may **lift a skill and adapt it** for our own use.
|
||||
- The user watched a video of an Omarchy skill that, **when something on the OS crashes, spins up an agent that investigates why it crashed / what happened, diagnoses the problem, writes a report, and then fixes the issue.** Confirm whether this skill actually exists in Omarchy (it may be a demo, a community skill, or a conflation — TRUTH-CHECK it, do not assume it is real), and if so, document exactly how it works.
|
||||
- Target we'd port it into: our existing **`diagnose` skill** (a six-phase disciplined debugging workflow) and our **"virtual IT department" / constrained-autonomy** design, whose rule is: on a failure the system fails **closed**, an always-on agent (**Hermes**) attempts diagnose+fix, and if it cannot, it fires a high-priority **NTFY** alert for a human. **Any privileged/mutating fix in our environment must route through sudo-bridge (human phone approval) — we do NOT allow unattended destructive auto-fixes.** Keep this constraint front-of-mind when assessing Omarchy's auto-fix behavior.
|
||||
- Prior report to build on (read it first, do not re-derive it): `research/omarchy-os-evaluation.md`.
|
||||
|
||||
## Research questions (sourced facts; mark UNVERIFIED if you cannot confirm)
|
||||
1. **Full skill inventory.** Enumerate EVERY AI skill Omarchy ships by default: name, source path in the repo, one-line purpose, and where it symlinks to. Quote the manual / repo. (Start: github.com/omacom/omarchy, omarchy.org/manual/ai/. Look for a skills directory and any SKILL.md files.)
|
||||
2. **Crash auto-investigation skill — does it exist?** Search the repo, manual, changelog, DHH posts, and any demo/video writeups for a skill that triggers on a crash/error and runs investigate→diagnose→report→fix. State plainly whether it is (a) a shipped Omarchy skill, (b) a community/third-party skill, (c) a demo not in the distro, or (d) unverifiable. Cite sources.
|
||||
3. **If it exists — mechanism, in detail.** How is it TRIGGERED (systemd coredump hook? a crash wrapper? journald watcher? manual invocation?) — quote the wiring. What does the SKILL.md instruct (paste the key instructions / structure)? What tools + permissions does it use? Does it auto-apply fixes, and if so, what are its guardrails (or lack thereof)? Does it require root/sudo?
|
||||
4. **Portability assessment.** Are these plain Claude Code skills that run on any distro on a Pro OAuth login, or do they hard-depend on Arch/Hyprland/Omarchy-specific tooling (pacman, systemd specifics, Omarchy helper scripts, Limine/snapper)? List every dependency that would NOT be present on Debian/LMDE. State clearly what would work as-is vs. what would need rewriting.
|
||||
5. **Other reusable skills.** Besides crash-investigation, flag any other Omarchy skill worth stealing for a homelab/"virtual IT department" (e.g., system-tailoring, update-safety, log triage). One line each.
|
||||
|
||||
## Output — write exactly one file: `research/omarchy-ai-skills-deep-dive.md` (relative to /home/administrator/Desktop/claude/claude-config)
|
||||
Sections, in order:
|
||||
1. **Verdict (preliminary, hedged)** — does the crash-investigation skill exist, and is it worth porting? 3–5 sentences; explicitly "main session decides."
|
||||
2. **Skill inventory** — table: skill name | repo path | purpose | symlink target | source URL.
|
||||
3. **Crash auto-investigation skill** — existence finding + full mechanism (trigger, SKILL.md contents/structure, tools, auto-fix behavior + guardrails, privilege needs). Paste real excerpts with URLs.
|
||||
4. **Portability assessment** — works-as-is vs. needs-rewrite, with the Debian/LMDE dependency gaps listed.
|
||||
5. **Blueprint: a Claude-Code-native version for us** — map it onto our `diagnose` skill + the constrained-autonomy/Hermes/NTFY design. Specify: trigger source on Debian (journald/systemd/coredumpctl), the diagnose→report phases, WHERE the sudo-bridge human-approval gate sits before any fix, what the report artifact looks like, and what stays read-only vs. what may mutate. Note this is a spec only — not to be built by this agent.
|
||||
6. **What to steal vs. leave.**
|
||||
7. **Risks / open questions for the human.**
|
||||
8. **Sources** — URL + access date for every non-obvious claim.
|
||||
|
||||
## Output rules
|
||||
- Every non-obvious claim gets a URL + date. Mark anything you cannot verify as UNVERIFIED rather than guessing. If the crash skill turns out not to exist in the distro, say so plainly — do not manufacture one.
|
||||
- Do NOT recommend unattended destructive auto-fixes; in the blueprint, privileged fixes MUST pass through a human-approval (sudo-bridge) gate.
|
||||
- Do NOT build the skill, install anything, or tell the user to reinstall anything.
|
||||
- Do not recommend Ubuntu; do not recommend anything requiring an Anthropic API key for Claude (our Claude runs on Pro OAuth).
|
||||
|
||||
## Persistence (final actions, before the wrap-up JSON)
|
||||
- Write the report to `research/omarchy-ai-skills-deep-dive.md`.
|
||||
- Append one row to `research/INDEX.md` in the existing table format.
|
||||
- Append a dated handoff entry (What was done / crash-skill existence finding / portability verdict / Next step = "main session decides whether to spec+build a ported diagnose-and-report skill") to `/opt/appdata/docker/Machines/laptop general questions/.claude/context.md`. Append, do not overwrite.
|
||||
- Do NOT commit or push. Do NOT modify any file outside those three.
|
||||
|
||||
## MANDATORY WRAP-UP (required regardless of success or failure)
|
||||
Before stopping for ANY reason, output this JSON as your final message. Do not stop without it.
|
||||
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"project": "laptop general questions",
|
||||
"files_touched": ["research/omarchy-ai-skills-deep-dive.md", "research/INDEX.md", ".../laptop general questions/.claude/context.md"],
|
||||
"containers_restarted": [],
|
||||
"crash_skill_exists": "<yes-shipped|community|demo-only|unverifiable> + one-sentence evidence + URL",
|
||||
"portability": "<works-as-is|needs-rewrite|mixed> + the key dependency gap",
|
||||
"steal_recommendation": "<worth-porting|not-worth-it|depends> + one sentence",
|
||||
"next_step": "main session: decide whether to spec+build a ported diagnose-and-report skill",
|
||||
"notes": "anything relevant for the next session"
|
||||
}
|
||||
|
||||
If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,68 @@
|
||||
# Prompt I — NetBird vs Tailscale: mesh-VPN decision for the self-hosted stack (Opus 4.8)
|
||||
|
||||
Tracks: primary-server general-questions hub (infrastructure decision). Spawn from claude-config. Research-only. **Run on Claude Opus 4.8.**
|
||||
|
||||
---
|
||||
|
||||
You are an infrastructure analyst making a **final, defensible recommendation** between **NetBird** and **Tailscale** as the mesh-VPN / zero-trust-access layer for a one-person self-hosted homelab that also runs several small businesses. You will read the web and local files and write exactly one Markdown file. This is a decision the owner has deferred repeatedly and wants closed — end with a single clear pick, not a "it depends".
|
||||
|
||||
## Task
|
||||
Decide **NetBird or Tailscale** for connecting the primary Docker host, server-01 (sandbox), and the owner's roaming laptop/phone into one private mesh, plus secure remote access to existing host services. Weight the recommendation by THIS owner's documented infrastructure philosophy and constraints (below) — not by generic "best VPN" lists.
|
||||
|
||||
## Owner's infrastructure philosophy & constraints — THESE ARE THE DECISION CRITERIA (do not rediscover; apply them)
|
||||
- **Self-hosting-first / anti-vendor-lock-in is the #1 principle.** Stated goal: "everything self-hosted on-prem except the Claude model itself; eliminate vendor lock-in on tooling; control costs as businesses scale." A hosted SaaS control plane you cannot own is a strike against a candidate and MUST be weighed explicitly. → NetBird's management/coordination server is open-source and self-hostable; Tailscale's coordination server is proprietary SaaS and self-hosting it means **Headscale**, a third-party open-source re-implementation (not official, partial feature parity). Treat "self-host the control plane" as a first-class scored criterion and be honest about each option's real state (features lost, HA, upgrade/backup burden of the control-plane DB).
|
||||
- **Cost-conscious.** No Anthropic API key (Claude Pro only, `$20/mo` hard cap); the owner watches every recurring cost. State **exact current pricing and free-tier limits** for: Tailscale (free tier device/user caps, paid per-user tiers), NetBird Cloud (free tier + paid), and NetBird/Headscale fully self-hosted ($0 software + your own compute). Give the real dollar numbers with URLs + dates.
|
||||
- **Zero-trust security posture.** Standing security principles (P1–P9) + Vault for all secrets + Bitwarden. Compare: ACL/policy model, SSO/OIDC + MFA integration (the stack has **Authelia** and can run Authentik), device posture/approval, key expiry & rotation, and how each stores/rotates its own secrets (must fit Vault).
|
||||
- **Docker-first deployment.** Services deploy as containers (Coolify is being retired → Jenkins pending; docker-compose is the fallback). Image-selection order linuxserver.io → official → custom. Canonical compose template = **bridged network, ports bound to the host LAN IP, no host mode** — with one documented EXCEPTION: a mesh agent that must put the tunnel interface on the host needs `network_mode: host` + `NET_ADMIN` + `/dev/net/tun`. Describe each option's container deployment shape (agent + optional self-hosted control plane) and which template exception it forces.
|
||||
- **Existing network/proxy architecture (must interoperate, not fight):** internal HTTP via **Traefik**; external HTTPS via **cloudflared** (Cloudflare Tunnel); Docker overlay/private subnet in the `172.16.16.x` range; primary host services bound to the host LAN IP. Explain how the mesh reaches existing host-bound services (subnet router / exit node / host-mode agent) and whether it could **replace cloudflared** for the owner's own remote access (business SaaS stays on cloudflared/Traefik). Note NAT-traversal/DERP-relay differences and whether relays can be self-hosted.
|
||||
- **Topology:** primary Docker host (Debian 13, kernel 6.12) + **server-01 = TEST-ONLY sandbox, never any prod data** + roaming laptop + phone. Small node count today, must scale cleanly.
|
||||
- **Prior (unconfirmed) lean:** an earlier session recommended **Tailscale + Twingate** together (Tailscale in host mode for the tunnel), never finalized. NetBird is the newer candidate that could consolidate mesh + zero-trust access into ONE self-hostable tool. Treat that prior lean as context to test, not as a default.
|
||||
- **Solo-operator burden is a real cost.** The owner maintains everything alone. Weigh: self-hosted control-plane upgrades, HA, backup/restore of the management DB, and blast radius if the control plane is down (does the existing mesh keep forwarding?). A self-hostable tool that is painful to run solo may lose to the principle on paper.
|
||||
|
||||
## Constraints
|
||||
- Research-only: no installs, no config changes, no DB writes, no credentials, no commits. Every factual claim (pricing, feature, limit) gets a URL + access date; write **UNVERIFIED** rather than guess.
|
||||
- Pin claims to **current** docs (2026) where possible; flag anything that looks like it may have changed since your training.
|
||||
- Mask any secret you happen to encounter in local files; never print secret values.
|
||||
|
||||
## Step-by-step (bound each step; ~10–12 turns total)
|
||||
1. **Read local context first** (no web): skim `/home/administrator/.claude/projects/-opt-appdata-docker/memory/project_ai_infrastructure_vision.md`, `security_principles.md`, `project_docker_network_proxy.md`, and any `project_*vpn*`/`*tailscale*` memory you can find, plus this prompt's constraints. Extract the owner's real weighting. 1–2 turns.
|
||||
2. **Tailscale** — official docs: coordination-server model (proprietary), Headscale self-host reality (feature gaps, maturity), pricing + free-tier caps, ACL/tags model, SSO/MFA, subnet router + exit node, DERP relays (self-hostable?), container/host-mode deployment. 2–3 turns.
|
||||
3. **NetBird** — official docs: self-hosted management server (setup shape, dependencies, DB, upgrade/backup), NetBird Cloud pricing + free tier, ACL/policy + posture checks, SSO/OIDC (Authelia/Authentik) + MFA, relay/signal self-hosting, container deployment. 2–3 turns.
|
||||
4. **Head-to-head against the criteria above**: build a weighted scoring table with the owner's criteria as rows (self-hostability of control plane, $ cost, zero-trust/SSO fit, Docker/compose fit, interop with Traefik+cloudflared+172.16.16.x, solo-operator maintenance burden, NAT traversal/relay self-hosting, maturity/community, migration effort from the prior Tailscale+Twingate idea). Assign weights reflecting self-host-first. 1–2 turns.
|
||||
5. **Decide.** One clear pick + the deployment shape you'd actually run (which containers, self-host control plane vs cloud, host-mode vs subnet-router, how secrets land in Vault, how it coexists with cloudflared). Include the honest case for the loser and the exact condition under which the pick would flip. 1 turn.
|
||||
6. Write `/home/administrator/Desktop/claude/claude-config/research/netbird-vs-tailscale-mesh-vpn.md` (sections below). Append one row to `/home/administrator/Desktop/claude/claude-config/research/INDEX.md`.
|
||||
|
||||
## Report sections (required)
|
||||
Verdict (the pick, one paragraph) · Owner criteria & weights (restate what you optimized for) · Tailscale profile · NetBird profile · Weighted scoring table · Recommended deployment shape (containers, control-plane choice, Vault secret paths to create, cloudflared coexistence, subnet-routing plan for host services) · Honest case for the loser + flip condition · Migration/rollout steps at a high level (no execution) · Open questions / grill-me (≤6) · Sources (URL + date each).
|
||||
|
||||
## BEFORE YOU FINISH — IN-CONVERSATION WRAP-UP (run this yourself; the main session verifies after)
|
||||
Do every step in order. Do NOT emit the wrap-up JSON until A–F are done.
|
||||
|
||||
A. **Self-verify artifacts on disk** (never trust memory): `ls -la research/netbird-vs-tailscale-mesh-vpn.md` and `grep '## Verdict'` it — exists + non-empty; `grep` your new row in `research/INDEX.md`; confirm/add a dated handoff entry in the primary-server hub context (`/opt/appdata/docker/Machines/primary server general questions/.claude/context.md` — create from the standard template if missing, then write into it). Record each as VERIFIED/MISSING in the JSON.
|
||||
B. **Scope + safety self-check:** `git status --short` — confirm you did NOT commit/push or write outside {your report, `research/INDEX.md`, that context.md entry, your run-log, the memory-embed line}. Note any breach in `actions_failed`.
|
||||
C. **Sanity-read:** the Verdict names ONE tool and answers the task; every unproven claim is written UNVERIFIED and listed in `notes`.
|
||||
D. **Temp cleanup:** delete any `/tmp` scratch files you created, by name.
|
||||
E. **Record the run** (a FILE write — allowed): append one JSON object to `/opt/appdata/docker/.claude/logs/agent_runs/I.json` with keys `ts, agent, prompt_file, tracking_row` ("primary-server-hub:mesh-vpn"), `status, artifacts_verified, artifacts_missing, unverified_claims, verdict`.
|
||||
F. **Update semantic memory** — emit this line exactly (the SubagentStop hook embeds it):
|
||||
`[[MEMORY_EMBED | <YYYY-MM-DD> Mesh-VPN decision: <NetBird|Tailscale> chosen because <one clause> + research/netbird-vs-tailscale-mesh-vpn.md ]]`
|
||||
|
||||
## MANDATORY WRAP-UP JSON (required regardless of success or failure)
|
||||
Before stopping for ANY reason — done, error, or approaching the turn limit — output this JSON as your final message. Do not stop without it.
|
||||
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"notes": "unverified claims + anything the next session must know",
|
||||
"artifacts_verified": ["research/netbird-vs-tailscale-mesh-vpn.md", "research/INDEX.md row", "context.md entry"],
|
||||
"artifacts_missing": [],
|
||||
"decision": "NetBird|Tailscale",
|
||||
"primary_reason": "<one sentence tied to the owner's #1 criterion>",
|
||||
"flip_condition": "<the one thing that would change the pick>",
|
||||
"self_host_control_plane": "yes|no|via-Headscale + one clause"
|
||||
}
|
||||
|
||||
If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,67 @@
|
||||
# Prompt J — Unified marketing strategy for the three digital businesses launching this week (Fable 5.1)
|
||||
|
||||
Tracks: business_projects **43** ("Overhaul the marketing system for ALL digital businesses (API, KDP, Etsy)"). Spawn from claude-config. Research/creative-only. **Run on Fable 5.1** (uses remaining usage credits).
|
||||
|
||||
---
|
||||
|
||||
You are a pragmatic growth strategist for a **one-person operator who is NOT a salesman, has no social-media presence, and does not want one.** You will read local files + the web and write exactly one Markdown file: a **unified, launch-week marketing strategy** covering all three of the owner's digital businesses, that gets each to its first customers **without cold selling**.
|
||||
|
||||
## The three businesses (all launching the week of 2026-09-15)
|
||||
1. **API** — RapidAPI-listed APIs (current: Dividend Tracker API, FastAPI). Channel = RapidAPI marketplace search + developer discovery. Existing N8N outreach pipeline is **PAUSED** (do not assume it resumes; the old scraped-lead cold-email path was retired under GitHub AUP §7, 2026-09-08). Business_projects 44; research: `research/api-business-evaluation.md`; hub `/opt/appdata/docker/Business/API Idea/.claude/context.md`.
|
||||
2. **Amazon KDP** — J.M. Hartley pen name, AI-assisted niche Q&A books, print-on-demand. Channel = Amazon organic search. Business_projects 40/45; **existing marketing plan already written:** `research/kdp-business-evaluation.md`. Hub `/opt/appdata/docker/Business/amazon kdp business idea/.claude/context.md`.
|
||||
3. **Etsy** — ConfettiPrintCo, AI-designed printable digital products (junk-journal/planner ephemera, stickers). Channel = Etsy search. Business_projects 39/46; **existing marketing plan already written:** `research/etsy-business-evaluation.md`. Hub `/opt/appdata/docker/Business/etsy digital business idea/.claude/context.md`.
|
||||
|
||||
## CRITICAL — build on existing work, do NOT duplicate it
|
||||
KDP and Etsy **already have full marketing plans** (Fable agents E/F, 2026-09-09) and API has an evaluation (agent D). **Read all three first.** Your job is the layer ABOVE them: a **cross-business strategy + this-week execution plan** — shared assets, sequencing, a common weekly cadence, and the reusable marketing system project 43 asks for. Where you touch a single business, EXTEND or correct its existing plan (cite it); never re-derive what it already says.
|
||||
|
||||
## Owner constraints — apply to EVERY recommendation (non-negotiable)
|
||||
- One person, **not a salesman, NO social-media persona and no wish to build one.** Any channel needing a daily posting persona or DMs/cold outreach is OUT. Evergreen, automatable, opt-in, pay-per-result, or marketplace-native SEO only.
|
||||
- **AD BUDGET = $0 right now.** Every plan needs a zero-cost path to first sales; paid ads appear only as a LATER phase funded by that business's own profits. State per business whether first sales at $0 are realistic; if not, say so.
|
||||
- **BOOTSTRAP CHAIN:** the three launch in order of "which reaches first sales at $0 fastest"; that one's profit funds the next one's ads. Your strategy must give a **time-to-first-sale-at-$0 ranking across all three** and a launch order.
|
||||
- **No automation of logged-in accounts** (Amazon, Etsy, RapidAPI, GitHub, LinkedIn). Opt-in contact only.
|
||||
- Money/tooling: no Anthropic API key (Claude Pro only, `claude -p`, $20/mo hard cap); self-hosted N8N + Postgres + local Ollama (llama3.1:8b + nomic-embed-text). Every automation you propose must name (a) what it costs, (b) what N8N + a local model can do vs what needs Claude, and (c) **where its decisions get logged as training data** (the always-be-training rule).
|
||||
- Every recommendation states: what it costs, time-to-first-signal, and how it's measured.
|
||||
- Research/creative-only: no installs, no config changes, no DB writes, no credentials, no commits. Web claims get a URL + date; write UNVERIFIED rather than guess. Marketplace pages that block bots (Etsy, Amazon best-sellers, RapidAPI category pages behind a login/CAPTCHA) — do NOT burn turns scraping; use official docs/search snippets and mark demand figures UNVERIFIED, or specify a manual method the owner runs.
|
||||
|
||||
## Step-by-step (~10–12 turns)
|
||||
1. **Read the three existing docs** (`research/api-business-evaluation.md`, `kdp-business-evaluation.md`, `etsy-business-evaluation.md`) + the three `.claude/context.md` hubs + business_projects 43's intent. Extract each business's current channel, first-customer path, and gaps. 2–3 turns (mostly local).
|
||||
2. **Cross-business synthesis:** what marketing capability do all three share (marketplace-native SEO/listing quality, evergreen search assets, opt-in email captured inside the product, review/rating flywheel)? Design ONE reusable "marketing system" (project 43) that serves all three with per-business configs, not three separate machines. 1–2 turns.
|
||||
3. **Per-business this-week actions:** for each, the concrete zero-cost launch-week checklist that EXTENDS its existing plan (what's new/changed vs the E/F/D doc, cited). 2–3 turns.
|
||||
4. **Sequencing:** rank the three by time-to-first-sale-at-$0 (with confidence + evidence) → recommended launch order + which one's profit funds ads next. 1 turn.
|
||||
5. **Automation + training layer:** which pieces N8N/local-model automate (listing-keyword scans, rank tracking, sales/opt-in logging, review requests within platform ToS) and the training-data log each writes. 1 turn.
|
||||
6. **Measurement:** the 2–3 metrics per business the owner checks weekly, and the kill/scale triggers. 1 turn.
|
||||
7. Write `/home/administrator/Desktop/claude/claude-config/research/digital-businesses-marketing-strategy.md` (sections below). Append one row to `/home/administrator/Desktop/claude/claude-config/research/INDEX.md`.
|
||||
|
||||
## Report sections (required)
|
||||
Executive summary (launch order + the one strategy in 3 sentences) · The shared marketing system (project 43: capabilities, per-business config, what's reusable) · Per-business launch-week plan ×3 (each: what's NEW vs its existing plan + citation, $0 first-customer path, weekly cadence) · Time-to-first-sale-at-$0 ranking + launch order + bootstrap-funding chain · Automation & training-data layer (N8N/local vs Claude, cost, training log per automation) · Weekly metrics + kill/scale triggers per business · Grill-me questions (≤6) · Sources (URL + date).
|
||||
|
||||
## BEFORE YOU FINISH — IN-CONVERSATION WRAP-UP (run this yourself; the main session verifies after)
|
||||
Do every step in order. Do NOT emit the wrap-up JSON until A–F are done.
|
||||
|
||||
A. **Self-verify artifacts on disk:** `ls -la research/digital-businesses-marketing-strategy.md` and `grep '## Executive'` it — exists + non-empty; `grep` your new row in `research/INDEX.md`; confirm/add a dated handoff entry in EACH of the three business `.claude/context.md` hubs (a one-line pointer to this strategy is enough). Record each as VERIFIED/MISSING in the JSON.
|
||||
B. **Scope + safety self-check:** `git status --short` — confirm you did NOT commit/push or write outside {your report, `research/INDEX.md`, the three context.md entries, your run-log, the memory-embed line}. Note any breach in `actions_failed`.
|
||||
C. **Sanity-read:** every per-business section says what's NEW vs the existing plan (no blind duplication); every unproven demand figure is UNVERIFIED and listed in `notes`.
|
||||
D. **Temp cleanup:** delete any `/tmp` scratch files you created, by name.
|
||||
E. **Record the run** (a FILE write — allowed): append one JSON object to `/opt/appdata/docker/.claude/logs/agent_runs/J.json` with keys `ts, agent, prompt_file, tracking_row` ("business_projects:43"), `status, artifacts_verified, artifacts_missing, unverified_claims, launch_order`.
|
||||
F. **Update semantic memory** — emit this line exactly (the SubagentStop hook embeds it):
|
||||
`[[MEMORY_EMBED | <YYYY-MM-DD> Digital-business marketing strategy: launch order <a>→<b>→<c>, shared marketing system for API/KDP/Etsy + research/digital-businesses-marketing-strategy.md ]]`
|
||||
|
||||
## MANDATORY WRAP-UP JSON (required regardless of success or failure)
|
||||
Before stopping for ANY reason — done, error, or approaching the turn limit — output this JSON as your final message. Do not stop without it.
|
||||
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"notes": "unverified figures + anything the next session must know",
|
||||
"artifacts_verified": ["research/digital-businesses-marketing-strategy.md", "research/INDEX.md row", "3 context.md entries"],
|
||||
"artifacts_missing": [],
|
||||
"launch_order": "<business> -> <business> -> <business>",
|
||||
"shared_system_summary": "<one sentence: the reusable marketing system>",
|
||||
"each_has_zero_dollar_path": "API:yes|no; KDP:yes|no; Etsy:yes|no"
|
||||
}
|
||||
|
||||
If you hit --max-turns before finishing, set status="partially_succeeded" and list what remains in actions_failed.
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,128 @@
|
||||
# Prompt K — NetBird + Twingate deployment PREFLIGHT (read-only research + partner-access audit)
|
||||
|
||||
Tracks: primary-server hub (infrastructure). Spawn from claude-config. **Read-only / research-only:
|
||||
no installs, no config changes, no DB writes, no revocations, no commits.** Model: any (Opus/Sonnet).
|
||||
|
||||
---
|
||||
|
||||
You are an infrastructure analyst doing the **preflight** for deploying **Twingate** (partner access
|
||||
to Nextcloud) and **NetBird self-hosted** (owner's device mesh) on a one-person homelab. You produce
|
||||
**three runbooks/audits** so the follow-up execution session is mechanical. You DEPLOY NOTHING and
|
||||
CHANGE NOTHING — you read state, read current vendor docs, and write Markdown reports only.
|
||||
|
||||
## Fixed context — apply, do not re-decide
|
||||
- **Role split (locked):** Twingate = scoped zero-trust access for PARTNERS to Nextcloud only (they do
|
||||
NOT join a mesh). NetBird = the OWNER's own device mesh (primary host, server-01, laptop, phone).
|
||||
- **server-01 = TEST-ONLY sandbox** (never prod data; only Ollama/GPU + Obsidian live there). ALL real
|
||||
services (Twingate connector, production NetBird control plane) deploy on the **PRIMARY server**.
|
||||
NetBird's stack is only *validated* on server-01 as a throwaway sandbox.
|
||||
- **Driver:** Tyler is behind **CGNAT**; Nextcloud **Talk** partner meetings fail P2P. Twingate makes
|
||||
CGNAT irrelevant by routing his Nextcloud traffic through a connector on the owner's network. Next
|
||||
meeting: **Sun 2026-09-20**. Current band-aid = Open Relay Project TURN (`staticauth.openrelay.metered.ca`).
|
||||
- **Partner change:** **Austin has LEFT the business.** New third partner = **Hailee** (33%). Austin's
|
||||
old Marketing/People role is **unassigned — do NOT assume Hailee inherits it.** Employees Grim + Josh
|
||||
(via Hailee) are NOT partners — do not grant them Nextcloud/partner access.
|
||||
- Prior research: `research/netbird-vs-tailscale-mesh-vpn.md` (NetBird chosen; has 3 UNVERIFIED items).
|
||||
|
||||
## Credential map (exact — never discover auth at runtime)
|
||||
- **Nextcloud occ (read-only):** resolve the container dynamically: `docker ps | grep -i nextcloud`
|
||||
(the app container, NOT the db/redis). Run occ as the web user, e.g.
|
||||
`docker exec -u www-data <nextcloud-container> php occ <cmd>`. Read-only occ only:
|
||||
`user:list`, `group:list`, `user:info <uid>`, `sharing:list` if available, `talk:room:list` if
|
||||
available. **occ echoes secrets on some commands — suppress/masks any secret in output.**
|
||||
- **Vault (AppRole, read-only):** role-id/secret-id files at
|
||||
`/opt/appdata/docker/docker-compose/vault/approle/{role-id,secret-id}`; resolve Vault IP via
|
||||
`docker inspect vault-iwaulpoi5hwirdlogshmul40`; login `POST /v1/auth/approle/login`; **revoke the
|
||||
token when done** (`POST /v1/auth/token/revoke-self`). Use only to LIST paths / check for entries
|
||||
referencing Austin — do not print any secret value.
|
||||
- **Bitwarden bridge (read-only):** container `bitwarden-bridge`; key from its `BRIDGE_API_KEY` env;
|
||||
`Authorization: Bearer`. Use only to check for items referencing Austin — never print values.
|
||||
|
||||
## Tasks (bound each; ~18 turns total)
|
||||
|
||||
### Task 1 — Partner access audit (Austin offboard / Hailee onboard) — READ ONLY
|
||||
Enumerate WHERE the departed partner "Austin" currently has access, and WHERE new partner "Hailee"
|
||||
must be added. Cover at minimum: Nextcloud user account + group memberships; the **`/Partner Meetings`
|
||||
folder** shares (memory says shared with Tyler, Austin, recording-bot); Talk room participants; any
|
||||
Vault or Bitwarden entries referencing Austin; any other homelab service login you can see evidence of
|
||||
(Gitea, ntfy, etc.). For anything **external** you cannot inspect (Discord, personal email), list it as
|
||||
a **manual owner action**. Produce a checklist: `[ ] remove …` items and `[ ] add Hailee to …` items.
|
||||
**Make ZERO changes.** Write `research/partner-access-audit-austin-hailee.md`.
|
||||
|
||||
### Task 2 — Twingate deploy runbook — DOCS + LOCAL SHAPE
|
||||
From current Twingate docs (2026): free-tier limits (users/resources/connectors/networks); the
|
||||
**connector** container deployment shape (official image, `network_mode`, env/tokens, deploy on the
|
||||
**primary server** on the standard bridged template if possible); the **Remote Network + Resource**
|
||||
model to expose the Nextcloud app to a partner; how a partner authenticates/connects (client vs
|
||||
clientless) and whether it works cleanly over CGNAT; which **Vault paths** to create for connector
|
||||
tokens (propose `secret/twingate/*`). Give a step-by-step the executor can follow without deciding
|
||||
anything. Pin claims to URLs + access date; mark **UNVERIFIED** rather than guess. Write
|
||||
`research/twingate-deploy-runbook.md`.
|
||||
|
||||
### Task 3 — NetBird self-host buildplan + bootstrap decision aid — DOCS
|
||||
Resolve the 3 UNVERIFIED items from current NetBird self-hosted docs: (a) management **DB engine** —
|
||||
default SQLite? can it point at the existing homelab Postgres for unified backup? (b) **Authelia** via
|
||||
NetBird's generic-OIDC connector — feasible, or fall back to **Authentik** for this one integration?
|
||||
(c) **TURN/relay reachable through cloudflared** for roaming peers WITHOUT opening a direct UDP port
|
||||
(P5) — or is a port needed? Then give a **bootstrap-vs-self-hosted recommendation** written so the
|
||||
owner can decide: what each path costs in time/risk, what "bootstrap on NetBird Cloud free tier then
|
||||
migrate" actually entails, and your recommendation with the deciding factor. Include the **server-01
|
||||
sandbox validation runbook** and the **primary-server production deploy shape** (containers, Traefik/
|
||||
cloudflared front, Vault paths `secret/netbird/*`). Pin claims to URLs + date; mark UNVERIFIED.
|
||||
Write `research/netbird-selfhost-buildplan.md`.
|
||||
|
||||
## Constraints
|
||||
- Read-only everywhere. No installs, no `up`/`deploy`, no occ writes, no Vault/Bitwarden writes, no
|
||||
revocations, no commits/pushes. If ANY action would mutate state, STOP and note it for the owner.
|
||||
- **Mask every secret** in all output (`hvs.***xxxx`); never print token/password/key values; delete
|
||||
any `/tmp` scratch file that touched a secret.
|
||||
- If a security hook or permission blocks a read, report a partial wrap-up — do NOT improvise around it.
|
||||
|
||||
## Scope allowlist — the ONLY files you may write
|
||||
`research/partner-access-audit-austin-hailee.md`, `research/twingate-deploy-runbook.md`,
|
||||
`research/netbird-selfhost-buildplan.md`, a row per new report in `research/INDEX.md`, a dated handoff
|
||||
entry in `/opt/appdata/docker/Machines/primary server general questions/.claude/context.md` (create
|
||||
from the standard template if missing), the run-log `/opt/appdata/docker/.claude/logs/agent_runs/K.json`,
|
||||
and the MEMORY_EMBED line. Touch nothing else.
|
||||
|
||||
## BEFORE YOU FINISH — IN-CONVERSATION WRAP-UP (do A–F in order; then the JSON)
|
||||
A. **Verify artifacts on disk:** `ls -la` each of the three reports and `grep` a heading in each
|
||||
(exists + non-empty); `grep` your new rows in `research/INDEX.md`; confirm the context.md handoff
|
||||
entry. Record each VERIFIED/MISSING in the JSON.
|
||||
B. **Scope + safety self-check:** `git status --short` — confirm no commits/pushes and nothing written
|
||||
outside the allowlist; confirm you made **zero** state mutations (no occ writes, no revocations).
|
||||
Note any breach in `actions_failed`.
|
||||
C. **Sanity-read:** the audit lists concrete offboard/onboard items; each runbook is followable without
|
||||
further decisions; every unproven vendor claim is written UNVERIFIED and listed in `notes`.
|
||||
D. **Temp cleanup:** delete any `/tmp` scratch files you created, by name; confirm the Vault token was
|
||||
revoked.
|
||||
E. **Record the run:** append one JSON object to `/opt/appdata/docker/.claude/logs/agent_runs/K.json`
|
||||
with keys `ts, agent, prompt_file, tracking_row` ("primary-server-hub:netbird-twingate-preflight"),
|
||||
`status, artifacts_verified, artifacts_missing, unverified_claims`.
|
||||
F. **Update semantic memory** — emit exactly:
|
||||
`[[MEMORY_EMBED | <YYYY-MM-DD> NetBird+Twingate preflight: Twingate=partner access / NetBird=owner mesh; Austin offboard + Hailee onboard audited + research/netbird-twingate-gameplan.md ]]`
|
||||
|
||||
## MANDATORY WRAP-UP JSON (required regardless of success or failure — output as final message)
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"notes": "unverified claims + anything the execution session must know",
|
||||
"project": "netbird-twingate",
|
||||
"files_touched": [],
|
||||
"containers_restarted": [],
|
||||
"artifacts_verified": [],
|
||||
"artifacts_missing": [],
|
||||
"austin_access_surface": ["<where Austin has access>"],
|
||||
"hailee_add_targets": ["<where Hailee must be added>"],
|
||||
"netbird_db_engine": "<answer or UNVERIFIED>",
|
||||
"authelia_oidc": "<works|use-authentik|UNVERIFIED>",
|
||||
"turn_via_cloudflared": "<yes|needs-udp-port|UNVERIFIED>",
|
||||
"bootstrap_recommendation": "<bootstrap|straight-selfhost + one clause>",
|
||||
"next_step": "execute Twingate deploy + partner swap (prompt L) in the next infra block"
|
||||
}
|
||||
|
||||
--max-turns 20
|
||||
(Raised from 15: three independent research/audit tasks with multi-source doc verification.)
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output
|
||||
the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Prompt L — Twingate deploy + partner swap (Austin out / Hailee in) — MUTATING / EXECUTION
|
||||
|
||||
Tracks: primary-server hub (infrastructure). Spawn from claude-config. **This is an EXECUTION prompt,
|
||||
NOT research-only** — it deploys a container and changes access. Run it **only after preflight K has
|
||||
landed its three reports** and the owner has read them. Deadline: complete before **Sun 2026-09-20**.
|
||||
Prefer running with the owner watching the agent view (access revocation is sensitive).
|
||||
|
||||
---
|
||||
|
||||
You are a deployment engineer. You will (1) deploy the **Twingate connector on the PRIMARY server** and
|
||||
expose **Nextcloud** to partners, (2) **offboard Austin** (left the business) and **onboard Hailee**
|
||||
(new partner), then (3) verify partner-meeting connectivity works over CGNAT. You follow the preflight
|
||||
runbooks exactly; you do NOT re-decide architecture.
|
||||
|
||||
## Read these FIRST (produced by preflight K) — do not proceed if missing
|
||||
- `research/twingate-deploy-runbook.md` — the mechanical Twingate steps + Vault paths.
|
||||
- `research/partner-access-audit-austin-hailee.md` — the exact offboard/onboard checklist.
|
||||
- `research/netbird-twingate-gameplan.md` — Phase 1 scope + role split.
|
||||
|
||||
## Fixed context — apply, do not re-decide
|
||||
- **Twingate = partner access to Nextcloud only.** Connector on the **PRIMARY server** (server-01 is
|
||||
test-only). No port-forwarding (P5) — the connector makes CGNAT irrelevant for Tyler.
|
||||
- Partners after this change: **Kaleb, Tyler, Hailee.** **Austin is OUT** (remove all access).
|
||||
Employees Grim + Josh are NOT partners — grant them nothing here.
|
||||
- Austin's old Marketing/People role is unassigned — do NOT grant Hailee anything beyond standard
|
||||
partner access (Nextcloud + `/Partner Meetings` + Talk + Twingate) unless the checklist says so.
|
||||
|
||||
## Credential map (exact)
|
||||
- **Nextcloud occ (linuxserver image — K verified 2026-09-15):** container `nextcloud-nextcloud-1`;
|
||||
run `docker exec nextcloud-nextcloud-1 occ <cmd>` (the wrapper). **`-u www-data` and `php occ` FAIL.**
|
||||
Austin = user `Austin_Mktg`, in NO groups, only `/Partner Meetings` **share id 2**. Talk rooms are
|
||||
NOT occ-listable here → disabling/deleting the user covers Talk; verify in the UI. occ echoes secrets
|
||||
on some commands — suppress.
|
||||
- **Vault (AppRole):** files `/opt/appdata/docker/docker-compose/vault/approle/{role-id,secret-id}`;
|
||||
Vault IP via `docker inspect vault-iwaulpoi5hwirdlogshmul40`; login → use → **revoke-self**. Create
|
||||
Twingate secret paths per the runbook; deliver to the connector via the SecretSpec+Vault pattern —
|
||||
nothing hardcoded in compose (P2).
|
||||
- **Bitwarden bridge:** container `bitwarden-bridge`, `BRIDGE_API_KEY` env, `Authorization: Bearer`.
|
||||
- **sudo-bridge (if a privileged host command is needed):** Primary `192.168.1.88:8082`, Bearer key at
|
||||
Vault `secret/data/sudo-bridge` → `api_key`. **`GET /allowlist` before any `POST /exec`.** Announce
|
||||
in your session output BEFORE routing (owner watches; the bridge pushes phone approval).
|
||||
|
||||
## Steps (bound each; verify before claiming success)
|
||||
1. **Twingate network + connector:** per the runbook, create the Twingate Remote Network + connector;
|
||||
deploy the connector container on the **primary server** (Vault-backed tokens). Verify the connector
|
||||
shows **healthy/connected** in Twingate before continuing.
|
||||
2. **Expose Nextcloud as a Resource** scoped to the partner group; confirm the Resource resolves.
|
||||
3. **Offboard Austin** — work the audit checklist item by item: remove from Nextcloud groups + the
|
||||
`/Partner Meetings` share, remove from Talk rooms, remove any Vault/Bitwarden entries, revoke any
|
||||
service logins. For each item, log what you did. **External items (Discord etc.) → list for the
|
||||
owner to do manually; do not attempt.** Do NOT delete Austin's Nextcloud user outright unless the
|
||||
checklist explicitly says to — disable/deauthorize first so nothing breaks; flag deletion for owner.
|
||||
4. **Onboard Hailee** — create/confirm her Nextcloud account, add to the partner group + `/Partner
|
||||
Meetings` + Talk; add her as a Twingate user with access to the Nextcloud Resource. Confirm she can
|
||||
authenticate.
|
||||
5. **Verify end-to-end:** confirm a partner (Tyler, or a CGNAT-simulated client) can join a Nextcloud
|
||||
**Talk** call through Twingate; confirm Austin can reach nothing; confirm Hailee can reach Nextcloud.
|
||||
Record the verification method + result. Do NOT claim success without this.
|
||||
6. **Do NOT remove Open Relay Project from Talk TURN yet** — leave the band-aid in place until the
|
||||
owner confirms Twingate-routed Talk is solid over a real CGNAT session. Note this as the next step.
|
||||
|
||||
## Guardrails (per playbook_background_agent_prompts)
|
||||
- **No commits, no pushes** — the owner commits later via the checklist.
|
||||
- **Container restart/redeploy via a self-elevated, logged, initially-EMPTY allowlist:** before
|
||||
bouncing any container, add it to your allowlist first, log the elevation in `actions_taken`, restart
|
||||
only what you elevated, and **verify health after**. Never touch an out-of-scope container.
|
||||
- **Privileged/mutating host commands go through sudo-bridge** (announce first; `GET /allowlist`
|
||||
before `POST`). No direct mutating `sudo`.
|
||||
- **Mask every secret** in all output; never print token/password/key values; delete `/tmp` secret
|
||||
scratch files; **revoke the Vault token** when done.
|
||||
- **Blocked = stop, don't improvise.** If a hook/permission blocks an action, emit a partial wrap-up.
|
||||
- **Scope allowlist (files you may write):** the connector's docker-compose under the primary-server
|
||||
compose tree, a dated handoff in `/opt/appdata/docker/Machines/primary server general questions/.claude/context.md`,
|
||||
the run-log `/opt/appdata/docker/.claude/logs/agent_runs/L.json`, the MEMORY_EMBED line. Touch nothing else.
|
||||
|
||||
## BEFORE YOU FINISH — WRAP-UP (A–F, then the JSON)
|
||||
A. **Verify on disk/live:** connector healthy in Twingate; Nextcloud Resource resolves; Austin access
|
||||
gone (re-check the surfaces); Hailee access present; `git status --short` shows no commit/push.
|
||||
B. **Scope + safety self-check:** nothing written outside the allowlist; every container you bounced is
|
||||
healthy and was elevated first.
|
||||
C. **Sanity-read:** the end-to-end Talk-over-Twingate verification actually ran; results recorded.
|
||||
D. **Temp cleanup:** delete `/tmp` secret scratch files; confirm Vault token revoked.
|
||||
E. **Record the run:** append one JSON object to `/opt/appdata/docker/.claude/logs/agent_runs/L.json`
|
||||
(`ts, agent, prompt_file, tracking_row`="primary-server-hub:twingate-partner-swap", `status,
|
||||
actions_taken, containers_restarted, verification_result`).
|
||||
F. **MEMORY_EMBED** — emit exactly:
|
||||
`[[MEMORY_EMBED | <YYYY-MM-DD> Twingate connector deployed (primary server) = partner Nextcloud access over CGNAT; Austin offboarded, Hailee onboarded + research/netbird-twingate-gameplan.md ]]`
|
||||
|
||||
## MANDATORY WRAP-UP JSON (required regardless of success or failure — output as final message)
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"notes": "anything the owner must know; manual external items (Discord etc.)",
|
||||
"project": "netbird-twingate",
|
||||
"files_touched": [],
|
||||
"containers_restarted": ["name — healthy?"],
|
||||
"austin_access_removed": ["surface — done?"],
|
||||
"hailee_access_added": ["surface — done?"],
|
||||
"verification_result": "<how Talk-over-Twingate was tested + pass/fail>",
|
||||
"open_relay_removed": "no — pending owner confirmation over real CGNAT",
|
||||
"next_step": "owner confirms Talk over real CGNAT → remove Open Relay TURN; then NetBird phase"
|
||||
}
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output
|
||||
the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -0,0 +1,110 @@
|
||||
# Prompt M — Onboard Hailee into Nextcloud (ADDITIVE ONLY) — MUTATING / EXECUTION
|
||||
|
||||
Tracks: primary-server hub (netbird-twingate / partner swap). Spawn from claude-config. **Run AFTER
|
||||
preflight K completes** so you consume its audit for the exact add-targets and know whether Hailee
|
||||
already exists. **This is additive only: create/extend Hailee's access. REMOVE NOTHING.** Austin's
|
||||
offboarding is a separate, owner-present task (prompt L) — do not touch Austin here.
|
||||
|
||||
---
|
||||
|
||||
You onboard the new partner **Hailee** into Nextcloud so she has the same standard partner access the
|
||||
other partners have. You create nothing external and remove nothing.
|
||||
|
||||
## Owner-supplied inputs (the spawning session fills these in before launch — do NOT invent them)
|
||||
- HAILEE_USERNAME: `<<FILL>>`
|
||||
- HAILEE_DISPLAY_NAME: `<<FILL>>`
|
||||
- HAILEE_EMAIL: `<<FILL>>`
|
||||
If any is still `<<FILL>>` when you start, STOP and emit a partial wrap-up asking for it — never guess
|
||||
a real person's account details.
|
||||
|
||||
## PRECONDITION — do not start until true
|
||||
`bitwarden-bridge` container must be **running** (as of 2026-09-15 it is Exited(128) 8 days). Step 2
|
||||
stores Hailee's login in Bitwarden via the bridge; if the bridge is down you cannot complete safely.
|
||||
Check `docker ps --filter name=bitwarden-bridge`. If down, STOP and tell the owner to bring it back
|
||||
(no self-restart) — do NOT create the account and leave the credential unstored.
|
||||
|
||||
## Read FIRST
|
||||
- `research/partner-access-audit-austin-hailee.md` (from K) — the exact groups/shares/Talk rooms to
|
||||
add Hailee to, and whether a `HAILEE_USERNAME` account already exists. If this file is missing,
|
||||
proceed with the standard partner-access set below but note that K's audit was unavailable.
|
||||
|
||||
## Fixed context
|
||||
- Standard partner-access set (baseline if the audit is silent): partner group membership; the
|
||||
**`/Partner Meetings`** folder share; the partner **Talk** room(s). Match whatever Tyler has.
|
||||
- Hailee **inherits Austin's Marketing/People role but is eased in gradually** — so grant the standard
|
||||
partner access now; do NOT auto-grant every elevated permission Austin had. Flag role-specific
|
||||
access (admin panels, marketing tool logins) for the owner to grant later, staged.
|
||||
- Employees Grim + Josh are NOT partners — do nothing for them.
|
||||
|
||||
## Credential map (exact)
|
||||
- **Nextcloud occ (linuxserver image — K verified 2026-09-15):** container is `nextcloud-nextcloud-1`;
|
||||
run `docker exec nextcloud-nextcloud-1 occ <cmd>` (the wrapper). **`-u www-data`, `-u abc`, and
|
||||
`php occ` all FAIL on this image — use the bare wrapper.** To create a user without exposing the
|
||||
password: pass `OC_PASS` via `-e` and use
|
||||
`docker exec -e OC_PASS="$PW" nextcloud-nextcloud-1 occ user:add --password-from-env --display-name=... --group=... <user>`.
|
||||
Never pass the password as a plain arg; never print it.
|
||||
- **Password generation:** `bw generate -ulns --length 20` (per `feedback_password_generation`).
|
||||
- **Store the login in Bitwarden** (login credentials live in Bitwarden, per `project_secrets_architecture`):
|
||||
bw CLI 2026.5.0+ throws a WASM error on item create — **use the Bitwarden REST/bridge to create the
|
||||
item** (`feedback_bw_cli_vaultwarden_create`). Bridge: container `bitwarden-bridge`, `BRIDGE_API_KEY`
|
||||
env, `Authorization: Bearer`. Item name e.g. "Nextcloud — Hailee". Never print the password.
|
||||
|
||||
## Steps (additive only; verify each)
|
||||
1. **Check existence:** `occ user:info HAILEE_USERNAME`. If she already exists, skip creation and go to
|
||||
group/share/Talk steps (idempotent — adding to a group she's already in is a no-op).
|
||||
2. **Create the account** (only if absent): generate a 20-char password → set `OC_PASS` →
|
||||
`occ user:add --password-from-env --display-name="HAILEE_DISPLAY_NAME" HAILEE_USERNAME`; set her
|
||||
email `occ user:setting HAILEE_USERNAME settings email HAILEE_EMAIL`. **Store the credential in
|
||||
Bitwarden** via the bridge before doing anything else with it. Mask the password everywhere.
|
||||
3. **Groups:** add her to the partner group(s) the audit names (or the group Tyler is in).
|
||||
4. **`/Partner Meetings` share:** grant her access to that folder the same way Tyler has it.
|
||||
5. **Talk:** add her to the partner Talk room(s).
|
||||
6. **Verify:** re-read `occ user:info HAILEE_USERNAME` — account enabled, email set, in the expected
|
||||
groups; confirm the `/Partner Meetings` share includes her; confirm Talk membership. Record results.
|
||||
Do NOT claim success without this.
|
||||
|
||||
## Guardrails (per playbook_background_agent_prompts)
|
||||
- **Additive only — REMOVE NOTHING**, disable nothing, touch no other user (especially not Austin).
|
||||
- No commits, no pushes. No container restarts should be needed; if one is, use the self-elevated,
|
||||
logged, initially-empty allowlist + verify health after.
|
||||
- Privileged host commands go through sudo-bridge (announce first; `GET /allowlist` before `POST`).
|
||||
- **Mask every secret**; never print the generated password; delete `/tmp` secret scratch files.
|
||||
- Blocked = stop, emit a partial wrap-up. Do not improvise.
|
||||
- **Scope allowlist (files you may write):** the run-log
|
||||
`/opt/appdata/docker/.claude/logs/agent_runs/M.json`, a dated handoff line in
|
||||
`/opt/appdata/docker/Machines/primary server general questions/.claude/context.md`, and the
|
||||
MEMORY_EMBED line. (Nextcloud/Bitwarden changes are live-state, not files.) Touch nothing else on disk.
|
||||
|
||||
## BEFORE YOU FINISH — WRAP-UP (A–E, then the JSON)
|
||||
A. **Verify live:** `occ user:info HAILEE_USERNAME` shows enabled + email + expected groups; share +
|
||||
Talk membership confirmed; the Bitwarden item exists (by name, not value).
|
||||
B. **Additive-only self-check:** confirm you removed/disabled nothing and did not touch Austin or any
|
||||
other user. Note any deviation in `actions_failed`.
|
||||
C. **Sanity-read:** password never printed; credential is in Bitwarden; role-specific/elevated access
|
||||
was flagged for staged owner grant, not auto-granted.
|
||||
D. **Temp cleanup:** delete `/tmp` secret scratch files; confirm no secret in any output.
|
||||
E. **Record the run:** append one JSON object to `/opt/appdata/docker/.claude/logs/agent_runs/M.json`
|
||||
(`ts, agent, prompt_file, tracking_row`="primary-server-hub:hailee-nextcloud-onboard", `status,
|
||||
actions_taken, verification_result`). Then emit:
|
||||
`[[MEMORY_EMBED | <YYYY-MM-DD> Hailee onboarded to Nextcloud (partner group + /Partner Meetings + Talk); login in Bitwarden; role eased-in staged + research/netbird-twingate-gameplan.md ]]`
|
||||
|
||||
## MANDATORY WRAP-UP JSON (required regardless of success or failure — output as final message)
|
||||
{
|
||||
"status": "succeeded|partially_succeeded|failed",
|
||||
"actions_taken": ["action — outcome"],
|
||||
"actions_failed": ["action — reason"],
|
||||
"notes": "role-specific access flagged for staged owner grant; anything the owner must know",
|
||||
"project": "netbird-twingate",
|
||||
"account_created": "yes|already-existed|no",
|
||||
"groups_added": ["group — done?"],
|
||||
"partner_meetings_share": "granted|already|failed",
|
||||
"talk_rooms": ["room — done?"],
|
||||
"bitwarden_item": "created|exists|failed",
|
||||
"verification_result": "<occ user:info summary — masked>",
|
||||
"removed_anything": "no",
|
||||
"next_step": "owner shares the Bitwarden login with Hailee; stage role-specific access later"
|
||||
}
|
||||
|
||||
--max-turns 15
|
||||
If you issue the same tool call or command twice with identical arguments, STOP immediately and output
|
||||
the mandatory wrap-up with status=partially_succeeded.
|
||||
@@ -12,6 +12,13 @@ Canonical copies live here; mirrored to Obsidian `Resources/Prompts/`. Each prom
|
||||
| D | [D_api-business-evaluation-and-marketing.md](D_api-business-evaluation-and-marketing.md) | business_projects 44 | claude-config or API Idea | `research/api-business-evaluation.md` |
|
||||
| E | [E_kdp-business-evaluation-and-marketing.md](E_kdp-business-evaluation-and-marketing.md) | business_projects 45 | claude-config | `research/kdp-business-evaluation.md` |
|
||||
| F | [F_etsy-business-evaluation-and-marketing.md](F_etsy-business-evaluation-and-marketing.md) | business_projects 46 | claude-config | `research/etsy-business-evaluation.md` |
|
||||
| G | [G_omarchy-os-evaluation.md](G_omarchy-os-evaluation.md) | laptop general-questions hub | claude-config | `research/omarchy-os-evaluation.md` |
|
||||
| H | [H_omarchy-ai-skills-deep-dive.md](H_omarchy-ai-skills-deep-dive.md) | laptop general-questions hub | claude-config | `research/omarchy-ai-skills-deep-dive.md` |
|
||||
| I | [I_netbird-vs-tailscale-mesh-vpn.md](I_netbird-vs-tailscale-mesh-vpn.md) | primary-server hub (mesh-VPN decision) | claude-config | `research/netbird-vs-tailscale-mesh-vpn.md` — **Opus 4.8** |
|
||||
| J | [J_digital-businesses-marketing-strategy.md](J_digital-businesses-marketing-strategy.md) | business_projects 43 (API/KDP/Etsy) | claude-config | `research/digital-businesses-marketing-strategy.md` — **Fable 5.1** |
|
||||
| K | [K_netbird-twingate-preflight.md](K_netbird-twingate-preflight.md) | primary-server hub (netbird-twingate) | claude-config | `research/{twingate-deploy-runbook,netbird-selfhost-buildplan,partner-access-audit-austin-hailee}.md` — **read-only preflight** |
|
||||
| L | [L_twingate-partner-swap-deploy.md](L_twingate-partner-swap-deploy.md) | primary-server hub (netbird-twingate) | claude-config | Twingate connector deploy + Austin→Hailee swap — **⚠ EXECUTION/mutating, not research-only; run after K + owner review** |
|
||||
| M | [M_hailee-nextcloud-onboard.md](M_hailee-nextcloud-onboard.md) | primary-server hub (netbird-twingate) | claude-config | Onboard Hailee into Nextcloud (additive only) — **⚠ EXECUTION/mutating; run AFTER K; needs Hailee username+email filled in first** |
|
||||
|
||||
**Bootstrap rule (user, 2026-09-09):** ad budget is $0. D/E/F must each give a $0 path to first sales and a time-to-first-sale ranking input; the business that bootstraps fastest launches first and its profits fund ads for the next. A business with no $0 path is PAUSED.
|
||||
|
||||
|
||||
@@ -15,6 +15,76 @@ entry links back to the research that drove it, so the reasoning survives.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-15 — DECIDED (20:15): NetBird + Twingate deployment — both tools, distinct roles
|
||||
|
||||
- **Decision (adopt): run BOTH NetBird and Twingate with distinct roles**, overriding the "retire Twingate" line in the mesh-VPN research. **Twingate = scoped zero-trust access for PARTNERS to Nextcloud** (they don't join a mesh) — the real fix for Tyler's CGNAT and the fastest path to working partner meetings. **NetBird (self-hosted) = the OWNER's own device mesh** (primary host, server-01, laptop, phone) + pulling owner admin surfaces off cloudflared. The mesh-tool-vs-Tailscale pick itself stands (NetBird, 3.98 vs 3.19).
|
||||
- **Sequencing (adopt): Twingate first** (deadline: partner meeting **Sun 2026-09-20**), NetBird self-hosted after.
|
||||
- **NetBird bootstrap (adopt): bootstrap on NetBird Cloud free tier (€0, 5 users/100 machines), then migrate to self-hosted.** Why: decouples "does the mesh work for my nodes/roaming" from "can I self-host the control plane cleanly," so a snag in the 3 unverified items (Authelia-OIDC / TURN-through-cloudflared / mgmt DB engine) doesn't leave the owner meshless. Data plane is P2P WireGuard either way; the cloud tenant is temporary and sees coordination metadata only, never traffic.
|
||||
- **server-01 = validation sandbox ONLY** (test-only per `feedback_sandbox_isolation`; exceptions Ollama/GPU + Obsidian). NetBird's stack is *validated* on server-01 as a throwaway; **all real services — Twingate connector + production NetBird control plane — deploy on the PRIMARY server.**
|
||||
- **Partner change in scope: Austin has LEFT the business → remove all his access; onboard Hailee** (new 33% partner). **Hailee inherits Austin's old Marketing/People role, but is eased into it more gradually than Austin was** (staged onboarding, not day-one full handoff).
|
||||
- **Infra week priority (adopt):** Twingate + partner swap (deadline) → **secrets-proxy #174 + agent-sudo #176** (retire sudo-bridge, unlock autonomous agent work — highest leverage) → NetBird self-hosted → voice system. Wed personal block = media_pipeline unchanged.
|
||||
- **Research:** `research/netbird-vs-tailscale-mesh-vpn.md`; **Plan:** `research/netbird-twingate-gameplan.md`.
|
||||
- **Implementation:** prompts `config/prompts/K_netbird-twingate-preflight.md` (read-only preflight, running 2026-09-15) + `config/prompts/L_twingate-partner-swap-deploy.md` (execution, next infra block) + a Hailee-Nextcloud-onboarding agent.
|
||||
- **Revisit when:** bootstrap→self-hosted cutover proven, OR any of the 3 unverified items forces a design change, OR the flip condition in the mesh-VPN research is hit.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-15 — DECIDED (17:04): post-agent decisions — support-email disclosure, Vercel KV, Etsy meeting order
|
||||
|
||||
- **Support-email disclosure = Option 1, NO line in the email** (decisions row 22). Signature "The Boring API Company · support", no human name; one sentence on the docs/listing ("support replies are prepared with AI assistance and reviewed by our team"); prompt never denies being AI. Why: controlled studies mostly find AI labels lower perceived authenticity; user's Brevo cold-email footer was dropped as unprofessional; no cited law requires a line while the human gate runs. **REVISIT TRIGGER: the day auto-send is enabled, add a short notice to the auto-send variant only** (EU AI Act Art. 50 in force 2026-08-02; FTC reasonable-consumer test). Source: `research/ai-support-disclosure-research.md`.
|
||||
- **Vercel KV dropped** (row 23): now Marketplace/Upstash-backed. Counter + 15-customer alert live in Postgres via the N8N telemetry webhook.
|
||||
- **Etsy: partner meeting Sunday 2026-09-20** on the normal cadence; **manual first batch of 3 listings moves to Mon 2026-09-21**; weekly metrics pulled via Open API OAuth, not manual CSV (row 24). Gameplan week 1 to be revised.
|
||||
- Gameplans written by two Fable agents: `research/etsy-launch-gameplan.md`, `kdp-launch-gameplan.md`, `docforge-build-gameplan.md`. Title C gated on a week-9 rank re-read (kids 13/18, pet 11/18, UNVERIFIED).
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-15 — DECIDED (grill-me 15:48–16:40): KDP report §7 (agent E) + API report (agent D) questions closed — ALL digital-business grill-mes DONE
|
||||
|
||||
**KDP (decisions rows 14–17; business_projects 40 noted)**
|
||||
- **"100,000 Whys" brand dropped entirely**; each title carries its own series name. Generic Whys = revisit only as a later colour title funded by profit.
|
||||
- **Format/price:** A seniors = 6×9 black ink, white, 16 pt, ~140 pp, $12.99 (~$5.09 royalty). B couples 52×7 = 6×9 black ink, cream, 12 pt, ~120 pp, $9.99 (~$3.54). Kindle $4.99/70% after paperback live. Recompute at final page count.
|
||||
- **Kids revisited (user request):** single-theme black-ink kids "why" (15/18) = **title C candidate**, after A+B prove disclosure doesn't hurt reach. Competes with **pet-behaviour** (rank read via search snippets in the gameplan session, no manual browsing). Profession-interview Q&A PARKED.
|
||||
- **Kill criteria:** keep 10 copies / 90 d / BSR #500k; replace "$3 per owner-hour" with **royalty per title < $10 at day 90**; **policy removal = hard stop on its own**. A+B evaluation ≈ 2026-12-25.
|
||||
|
||||
**API (decisions rows 18–21; business_projects 43 noted, 48 DocForge, 49 support inbox)**
|
||||
- **DocForge approved**, evaluated against the course framework + `project_api_creation_playbook.md`: launch endpoints = **HTML→PDF + PDF→tables JSON** (the PPT "twist"; plain text extraction included; Markdown later). **Course stack kept** — Express/TypeScript on **Vercel** (@sparticuz/chromium, pure-JS PDF parser), NOT FastAPI/WeasyPrint on the owner box (= report Q5 answered: box is not prod). No Upstash. Verify cold-start latency; raise function timeout to 60 s. Pricing $12/$39/$99 + $0.005/req overage in the playbook quota layout; price ladder applies. **Security:** renderer network blocked (SSRF), upload + page caps. EDGAR Events waits for first paid subscriber or day-60 kill.
|
||||
- **Analytics = own-side telemetry, day one:** gateway subscriber/plan headers → DocForge POST → N8N webhook (cloudflared) → `marketing_*` weekly-metrics table; Vercel KV counter + 15-customer alert (this IS the framework's "customer counter", built at launch not after first sale). Manual: monthly revenue-email transcription by partner/employee (~10 min).
|
||||
- **GitHub:** public example repo IN; manual Q&A answering OUT.
|
||||
- **Support inbox (user addition):** company Gmail, working name **"The Boring API Company"** (e.g. boring.api.company@gmail.com) as the listing + repo support contact. Automation: N8N polls Gmail (official API/MCP — same relaxation as Etsy Open API) → Claude drafts → code sample verified on the LIVE endpoint via RapidAPI gateway (own free key; sandbox step dropped 16:40, server-01 only for bug reproduction) → weekly human gate logged as training data → auto-send after 20 consecutive unedited passes. Disclosure line UNDECIDED (user: may look unprofessional) → research task. **All API dev is AI-driven; support must be Claude-answerable.**
|
||||
- **Partner meeting additions:** partner takes Reddit for DocForge?; support address + account owner + umbrella brand.
|
||||
|
||||
**Credits:** $25.29 → $17.21 across Etsy + KDP + course evaluation (~$8 / 8 Qs incl. logging). Fable grill-mes cost more than the $0.25/Q estimate when they include research — revise to ~$1/Q.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-15 — DECIDED (grill-me 15:40–15:47): Etsy report §8 questions closed (agent F)
|
||||
|
||||
**Context:** 4 of 6 report questions were already settled by the strategy grill-me (Open API approved, Pinterest parked to day 30, throughput = automated pipeline, tables approved). Two remained.
|
||||
|
||||
- **Q1 demand method → API, not manual.** etsy.com returns 403 to every non-browser client because it sits behind DataDome (JS + captcha interstitial). Scraping stays banned. Instead: Etsy **Open API v3 `findAllListingsActive`** (app key only, no OAuth) — `count` = supply, favourites + review counts of top 20 = demand proxy, `tags` of top 20 = vocabulary (autocomplete not exposed). Google Trends returns 429 server-side → optional manual check. **User registers the developer app 2026-09-16** (own account; same app later does OAuth uploads), keystring → Vault. Claude scripts scoring over 20 phrases. Gate unchanged: <2 strong phrases under 50k results = Etsy fails gate → KDP first. business_projects **47** created; decisions row 12.
|
||||
- **Q5 POD gate → accepted + third condition.** No Printify listings until (1) one digital listing has a Bestseller/Popular badge, (2) ≥$50 cumulative profit (= cross-funding gate), (3) **customer-message owner named at the partner meeting**. Pipeline stays digital-only until all three hold. decisions row 13.
|
||||
- **Verify when key exists:** provisional-app daily rate limit (scoring needs <100 calls).
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-15 — DECIDED (grill-me 14:53–15:29): digital-businesses launch strategy (agent J, 6 Qs) + standing operating rules
|
||||
- **Decision (Q3, chain):** LOCK Etsy → KDP → API now. Swap Etsy/KDP only if the Etsy browser demand method fails its own gate (no shortlist niche with page-1 badges + non-saturated count). Demand method runs this week (was due 09-14; not yet run; delegable).
|
||||
- **Decision (Q1, Etsy scope):** first 10 listings = template-driven planner **stickers + dashboards**, then junk-journal kits; birthday printables PARKED (never scored, no designs exist). business_projects 39 scope changes accordingly.
|
||||
- **Decision (Q1b, Etsy product pipeline — NEW scope):** build an automated product pipeline: keyword intake → code-templated PDF generation (typographic/geometric products, not painterly AI art = avoids "AI slop") → optional local SDXL motifs on server-01 → scripted assembly + 6 mockups → claude -p listing copy incl. AI-disclosure line → Claude-judge QA → **15-min/week human approve/reject gate (every verdict logged as training data)** → upload via **Etsy official Open API v3 with OAuth on our own shop**. Rule change: "never automate the logged-in Etsy account" is RELAXED to "official Open API on our own shop only; no browser automation, no scraping." Consequence: this is a build project before a listing project; first 10 listings will NOT be live by 09-21 — do one manual batch this week with generated assets to start the shop clock, pipeline built during weekday blocks over ~2 weeks. DocForge (API) slot 2026-10-05 may slip behind it.
|
||||
- **Decision (Q2, KDP title A):** seniors large-print Q&A = title A, publish by 2026-09-25 (Q4 gift window, live by ~Nov 1). "365 Questions for Couples" = title B with the "52 weeks × 7" hook. KDP upload stays manual (no publishing API; ~1 h/title) — content generation automated.
|
||||
- **Decision (Q4, Pinterest):** OUT for launch week. Becomes a day-30 decision once 30 listings exist: 10 min/day × 2 weeks manual warm-up by an employee is cheap → partner-meeting item.
|
||||
- **Decision (Q5, cross-funding):** ALLOWED. Etsy profit may fund KDP's first $90/mo ad month (~Nov) only after Etsy clears its own scale gate ($50 profit + one page-1 badge); every transfer logged (amount + trigger). Portfolio = one marketing-reserve ledger.
|
||||
- **Decision (Q6, tables + hub):** APPROVED five `marketing_*` tables in `business_projects` DB (keyword intake, listing drafts, QA verdicts, opt-ins, weekly metrics) + one static hub site (/confetti, /jmhartley, /docs) on Traefik/cloudflared. Table build moves EARLIER (this week, weekday block) because the Etsy pipeline needs them as state store. Domain: `*.reverseproxyserver.net` is available; whether it is brand-suitable for customer-facing pages → partner meeting (buy ~$12/yr domain otherwise).
|
||||
- **Standing rule (NEW, from user):** default = automated/passive; manual work gets a hearing when it changes profitability or feasibility ("don't sacrifice feasibility for passivity"). Labour pool = 3 partners + up to 3 employees; hiring possible. Full passivity is the long-term goal (incl. a self-owned store where our own automation rules apply).
|
||||
- **Standing rule (NEW, from user):** weekends OFF. All work goes in weekday business block or personal block; overtime possible; weekend = absolute last resort. Anything previously weekend-scheduled is parked for automation.
|
||||
- **Partner meeting required before go-live:** non-automatable duties (Etsy account owner, customer messages, Payments/taxes, takedown response, weekly QA gate → partner's mother?, KDP manual uploads, demand method), Pinterest day-30 opt-in, hub domain/brand, marketing_* ownership.
|
||||
- **Billing fact (2026-09-15):** user is on **Pro**; Max upgrade likely soon. claude -p shares the subscription pool (no separate SDK credits) → generation batches use `--model claude-sonnet-5` (or haiku) until Max.
|
||||
- **Why:** agent J's strategy (2026-09-15) reconciled the parked 09-10 cross-business question; the user's passivity goal reframed Etsy from a Canva/design job into a code pipeline, which is what the owner can actually sustain.
|
||||
- **Research:** [../research/digital-businesses-marketing-strategy.md](../research/digital-businesses-marketing-strategy.md), D/E/F evaluations.
|
||||
- **Implementation:** pending — marketing_* tables + pipeline (weekday blocks), business_ideas.decisions rows 8–11, context.md ×4 updated. Per-report grill-mes (Etsy F §8, KDP E §7, API D) still to run.
|
||||
- **Revisit when:** demand method fails its gate (swap Etsy/KDP); partner meeting changes the delegation plan; Max upgrade lands (model choice for batches).
|
||||
|
||||
|
||||
## 2026-09-08 — DECIDED (23:55): GitHub-sourced cold email RETIRED (business_projects 42)
|
||||
- **Decision:** the lead pipeline may NOT resume as designed. GitHub AUP §7 forbids using information from the Service "(whether scraped, collected through our API, or obtained otherwise) ... for the purposes of sending unsolicited emails to users"; ToS §H repeats it for the API and threatens account/API suspension. Our pipeline collects emails via the GraphQL API and cold-emails them = the named act. Personalization and low volume do not cure "unsolicited."
|
||||
- **Why:** verbatim policy text fetched 2026-09-08; the old approach also yielded only 4 free-tier subscribers.
|
||||
|
||||
@@ -13,10 +13,18 @@ re-verify versions, repos, and Linux support before acting on anything.
|
||||
| **jcode vs Claude Code** — tests the two jcode hopes (bigger context; "CLAUDE.md size limit") and ranks executors (Claude Code, jcode, OpenCode, Aider, Goose, Cline, OpenHands, Gemini CLI) under R1 Pro-OAuth / R2 hooks+skills / R3 FOSS | [jcode-vs-claude-code.md](jcode-vs-claude-code.md) | Scored 2026-09-08 (Fable 5.1) | **Claude Code stays (14/18).** Every OSS executor DQ on R1 for Claude (jcode spoofs the Claude Code client; OpenCode removed Pro/Max under legal request). "Bigger window" = model property: Sonnet 5 is 1M free on Pro, Opus 1M = paid credits. Pain (b) is the 25 KB MEMORY.md *index* cap, not CLAUDE.md. Gemini CLI = optional non-Claude secondary for long reads. |
|
||||
| **HyperAgent marketing fit** — disambiguation (Airtable Hyperagent vs Hyperbrowser OSS vs FSoft paper), constraints, per-job fit for API-clients / KDP / Etsy, ToS risk, OSS alternatives | [hyperagent-marketing-fit.md](hyperagent-marketing-fit.md) | Assessed 2026-09-08 (Fable 5.1) | **Reject all three jobs.** User likely heard of closed-source hyperagent.com (credit-metered, $4–35/task). OSS HyperAgent has `baseURL` for Ollama but hard-codes `headless:false`; KDP/Etsy/LinkedIn browser automation = ban risk. Only fragment worth anything = plain Playwright enrichment under N8N; browser-use beats it if a trial is ever wanted. |
|
||||
|
||||
| **Omarchy OS evaluation** — is DHH's Omarchy (Arch+Hyprland) a fit as a laptop daily driver (UC1) and/or as server-01's OS (UC2) vs the planned plain-Debian migration; "AI features like Claude skills" truth-check | [omarchy-os-evaluation.md](omarchy-os-evaluation.md) | Researched 2026-09-13 (Opus 4.8), preliminary/hedged | **UC1 laptop = lean-yes/depends-on-GPU (MED); UC2 server = lean-no (HIGH).** "Claude skills" is a conflation: Omarchy pre-wires the normal Claude Code CLI (works on Pro OAuth, no API key) + ONE experimental system-tailoring skill — no bundled model. Main session: infra-fit synthesis + verify laptop GPU/disk/partition + snapshot-rollback caveat (#8047) before any install. |
|
||||
| **Omarchy AI skills deep-dive** — full skill inventory + the crash auto-investigation skill (does it exist? mechanism?) + portability to our Debian/LMDE machines + a blueprint mapping it onto our `diagnose` skill / Hermes / NTFY / sudo-bridge | [omarchy-ai-skills-deep-dive.md](omarchy-ai-skills-deep-dive.md) | Researched 2026-09-13 (Opus 4.8), preliminary/hedged | **Crash skill is REAL & shipped** (`diagnose-crash`, systemd-coredump journald watcher → click-to-diagnose), but it is **diagnose-and-report ONLY — explicitly does NOT auto-fix** ("Diagnosis reads; it does not fix") and is human-click-triggered, not unattended → the "then fixes it" the user saw is NOT the shipped skill. Two skills ship (`diagnose-crash` + `omarchy` tailoring). Portability = **mixed**: method lifts nearly verbatim, but Arch (pacman/archlinux-debuginfod) + Hyprland/Quickshell trigger chain need rewrite for headless Debian. **Main session: decide whether to spec+build a ported diagnose-and-report skill** (fixes stay behind sudo-bridge). |
|
||||
| **GitHub policy vs lead pipeline** — may the GitHub-sourced cold-email outreach resume? | [github-aup-lead-pipeline.md](github-aup-lead-pipeline.md) | Verdict 2026-09-08 | **NO** — AUP §7 + ToS §H forbid unsolicited email from API-collected data (verbatim quotes in file). Sourcing + contact move to the marketing overhaul (business_projects 43). |
|
||||
| **KDP business evaluation (Prompt E)** — J.M. Hartley AI-assisted Q&A books: KDP 2026 rules (AI disclosure, low-content, royalty/print cost), 9-niche scoring, $0 marketing plan, ads model, kill criteria | [kdp-business-evaluation.md](kdp-business-evaluation.md) | Evaluated 2026-09-09 (Fable 5.1) | **GO WITH CHANGES.** Drop the generic "100,000 Whys" clone (a #138-in-Books colour incumbent exists, 2026-05); 3 shortlisted niches (seniors large-print Q&A 16/18, single-theme kids why 15/18, couples questions 14/18). First sale at $0 realistic in 21–60 days (LOW-MED); royalties pay ~60 d after month end → ads ($90/mo) only from ~Dec 2026. Claude text = AI-generated → must disclose. |
|
||||
| **Etsy / ConfettiPrintCo evaluation** — AI-designed POD (Printify) + printables viability, official Etsy rules/fees (AI disclosure, production partner, digital limits), $0-ad marketing plan, unit economics, kill criteria (business_projects 46) | [etsy-business-evaluation.md](etsy-business-evaluation.md) | Evaluated 2026-09-09 (Fable 5.1, Prompt F) | **go_with_changes — digital-first** (junk-journal/planner printables), Printify POD only after a digital bestseller badge + $50 profit. First sale at $0: 21–56 days after 30 listings, ~55%. Opt OUT of Offsite Ads day 1. Demand counts UNVERIFIED (etsy.com 403) → owner runs browser method before 2026-09-14. |
|
||||
| **Mesh-VPN / zero-trust access — NetBird vs Tailscale** — final defensible pick for connecting primary host + server-01 (sandbox) + roaming laptop/phone into one private mesh, weighted by self-host-first + cost + zero-trust + solo-operator burden | [netbird-vs-tailscale-mesh-vpn.md](netbird-vs-tailscale-mesh-vpn.md) | Decided 2026-09-15 (Opus 4.8) | **NetBird (self-hosted control plane).** Wins on principle #1 (own the coordination plane; Tailscale's is proprietary SaaS, self-host = 3rd-party Headscale w/ gaps). Weighted 3.98 vs TS-cloud 3.19. **Flip → Tailscale-cloud only if owner accepts vendor lock-in on this one tool to save solo-operator upkeep.** Open: Authelia-vs-Authentik OIDC (P4), TURN-through-cloudflared, mgmt DB engine — all UNVERIFIED, test on server-01 first. |
|
||||
| **NetBird + Twingate deployment gameplan** — owner-decided plan to run BOTH tools with distinct roles (Twingate=partner access to Nextcloud over Tyler's CGNAT; NetBird=owner device mesh); phases 0 preflight / 1 Twingate+partner-swap / 2 NetBird self-host; Austin offboard + Hailee onboard in scope | [netbird-twingate-gameplan.md](netbird-twingate-gameplan.md) | Planned 2026-09-15 (Opus 4.8) | **Bootstrap-vs-self-host for NetBird STILL OPEN** (preflight resolves DB engine / Authelia-OIDC / TURN-through-cloudflared to inform it). Twingate first (deadline Sun 09-20). server-01 = validation sandbox only; all real services on primary server. Prompts K (preflight) + L (deploy). |
|
||||
| **API business (RapidAPI) evaluation + marketing plan (Prompt D)** — can RapidAPI produce first paid subscribers at $0 ads; 8-category scoring; two API candidates; no-social/no-outreach marketing plan; N8N/local-model automation + training-log; kill criteria (business_projects 44) | [api-business-evaluation.md](api-business-evaluation.md) | Evaluated 2026-09-09 (Fable 5.1, agent D) | **go_with_changes** — build a utility API (HTML/MD→PDF + PDF→JSON) not a second finance niche; first sale at $0 realistic but 60–120 days (LOW-MEDIUM) → slowest of the three, run in background. RapidAPI fee 25 % since 2025-11-15, PayPal-only; category pages login-walled (no public demand data); ToS forbids using marketplace info to solicit consumers → quota upsell = opt-in via own docs site. |
|
||||
| **Digital-businesses marketing strategy (agent J)** — the layer above D/E/F: launch order + bootstrap-funding chain, one reusable "Listing Engine" marketing system (project 43: keyword intake → listing draft/QA → product-embedded opt-in → weekly metrics/kill-rule, 5 unified `marketing_*` tables + one hub site), per-business launch-week checklists (what is NEW vs each plan), automation/training-log layer, weekly metrics | [digital-businesses-marketing-strategy.md](digital-businesses-marketing-strategy.md) | Written 2026-09-15 (Fable 5.1, agent J) | **Etsy → KDP → API locked** (swap Etsy/KDP only if the browser demand method fails this week). All three have a $0 first-sale path (Etsy ~55 %, KDP/API LOW-MED). NEW: Etsy Share & Save day 1; KDP title A by 09-25 with bank/tax interview day 1; API = background ≤ 2 h/wk until 10-05. Six grill-me Qs incl. two scope conflicts (biz_projects 39 birthday printables vs junk-journal; 40 couples vs seniors). Tables/hub site PROPOSED, not built. |
|
||||
| **Partner access audit — Austin offboard / Hailee onboard** (preflight K, read-only) — every place departed partner Austin has access + every place new 33% partner Hailee must be added; offboard/onboard checklists | [partner-access-audit-austin-hailee.md](partner-access-audit-austin-hailee.md) | Audited 2026-09-15 (Opus 4.8) | **ZERO changes made.** Austin access = Nextcloud user `Austin_Mktg` (enabled) + `/Partner Meetings` **share id 2** (only that). No groups, **no Vault refs**. Bitwarden bridge DOWN → manual owner check. Talk rooms not occ-listable → disabling the user covers Talk (verify in UI). External (Discord/email) = manual. Hailee: new NC user + `/Partner Meetings` share + Talk room + Twingate Partners group. NOT Grim/Josh. |
|
||||
| **Twingate deploy runbook** (preflight K) — partner access to Nextcloud over Tyler's CGNAT; free-tier, connector container shape, Remote-Network/Resource/Group model, client requirement, Vault paths | [twingate-deploy-runbook.md](twingate-deploy-runbook.md) | Written 2026-09-15 (Opus 4.8) | **Free tier fits** (5 users/10 nets/50 resources; partners=Tyler+Hailee). Connector `twingate/connector` on PRIMARY server, **outbound-only → CGNAT-proof, no port-forward**. Client app REQUIRED (no clientless). Vault `secret/twingate/*` (none exist yet). **UNVERIFIED: Talk WebRTC fully over Twingate + browser LNA** → test before dropping Open Relay TURN. |
|
||||
| **NetBird self-host buildplan + bootstrap decision aid** (preflight K) — owner device mesh; resolves the 3 open items + bootstrap-vs-selfhost recommendation + server-01 sandbox runbook + primary-server prod shape | [netbird-selfhost-buildplan.md](netbird-selfhost-buildplan.md) | Written 2026-09-15 (Opus 4.8) | **(a) DB: SQLite default, Postgres supported → use homelab Postgres.** **(b) OIDC: Authelia WORKS via generic-OIDC (official Authelia↔NetBird guide) — no Authentik;** device-flow for CLI UNVERIFIED (setup-keys fallback). **(c) TURN: coturn needs a UDP port; WSS relay on 443 may avoid it (UNVERIFIED — test on server-01).** **Recommend BOOTSTRAP on NetBird Cloud free tier first** (deciding factor: 09-20 Twingate deadline owns this week). |
|
||||
|
||||
### Where the prior infrastructure research lives (memory corpus)
|
||||
Not duplicated here — cited in [infrastructure-synthesis.md](infrastructure-synthesis.md). Key files:
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
# Disclosing AI-drafted customer-support e-mails — what businesses do in 2025–2026, what the law says, and three wording options
|
||||
|
||||
Written 2026-09-15 by the gameplan background agent (Fable 5.1) for business_projects 49 (DocForge support inbox).
|
||||
Source basis: WebSearch result snippets only (no page was fetched); every finding cites the URL the snippet came from
|
||||
and is **UNVERIFIED** against the full page. The user's concern: *a disclosure line may look unprofessional.*
|
||||
|
||||
Context that shapes the answer: the DocForge inbox is **e-mail, not a live chatbot**; every reply passes a **human
|
||||
approve/reject gate** until 20 consecutive unedited passes, after which replies to a category auto-send; the seller is
|
||||
US-based, the buyers are developers worldwide (some in the EU); the sender name is a company ("The Boring API
|
||||
Company"), not a person's name.
|
||||
|
||||
---
|
||||
|
||||
## A. Legal requirements that could touch a US seller
|
||||
|
||||
1. **EU AI Act, Article 50 (transparency), applies from 2 August 2026.** Providers of systems "intended to interact
|
||||
directly with natural persons" must design them so users are informed they are dealing with AI, and the obligation
|
||||
reaches providers outside the EU when the output is used in the EU. Penalty ceiling quoted: €15 million or 3 % of
|
||||
worldwide turnover. https://artificialintelligenceact.eu/transparency-rules-article-50/ ;
|
||||
https://ai-act-service-desk.ec.europa.eu/en/ai-act/article-50 ; https://bratby.law/ai-act-transparency-obligations-2026/ ;
|
||||
https://digital-strategy.ec.europa.eu/en/faqs/transparency-obligations-under-article-50-ai-act
|
||||
2. **The "chatbot" duty is framed around interactive systems; e-mail replies sit at the edge.** Commentary on
|
||||
Article 50 says the notification must come "before or at the very beginning of the conversation" for chatbots
|
||||
(https://hard2bit.com/en/blog/ai-act-article-50-ai-transparency-chatbots-deepfakes/). A separate Article 50 limb on
|
||||
*AI-generated text* only bites for text "published with the purpose of informing the public on matters of public
|
||||
interest", and it is waived where a human reviewed the text and holds editorial responsibility
|
||||
(https://reallygoodemails.com/school/blog/ai-disclaimers-in-email ; https://www.euaiact.com/key-issue/5). A one-to-one
|
||||
support reply is neither a public-interest publication nor, while the human gate runs, unreviewed. Once auto-send
|
||||
starts, the reply is a system output sent directly to a person — the cautious reading is that a short notice is
|
||||
expected for EU recipients. An arXiv analysis of AI agents under EU law makes the same point for agent-sent e-mail
|
||||
(https://arxiv.org/pdf/2604.04604). Legal-advice status: **none of these sources is a lawyer's opinion on our
|
||||
facts** — UNVERIFIED.
|
||||
3. **Article 50 is not limited to high-risk systems** — a company with no high-risk AI still carries it for a
|
||||
customer-facing bot (https://scalevise.com/resources/eu-ai-act-article-50-transparency-rules-2026/ ;
|
||||
https://likeone.ai/blog/eu-ai-act-article-50-transparency-disclosure-guide-2026/).
|
||||
4. **US federal: FTC Act §5 (deception).** The FTC's position is that if a consumer reasonably believes they are dealing
|
||||
with a human and the business knows it, the failure to say otherwise is deceptive; "Operation AI Comply" enforcement
|
||||
has continued into 2025–2026, and a July 2026 proposed policy statement targets deceptive steering of AI systems
|
||||
(https://captaincompliance.com/education/ai-disclosure-requirements-the-complete-guide-for-businesses-using-artificial-intelligence/ ;
|
||||
https://www.beneschlaw.com/insight/one-year-in-ftcs-operation-ai-comply-continues-under-new-administration-signaling-enduring-enforcement-focus/ ;
|
||||
https://www.bclplaw.com/en-US/events-insights-news/ctrl-alt-deceive-ftc-proposes-policy-on-ai-accuracy-suppression.html ;
|
||||
https://www.ftc.gov/industry/technology/artificial-intelligence). Practical test: never sign an AI-drafted reply with
|
||||
a human first name that does not exist; a company signature is not deceptive.
|
||||
5. **US states — "bot disclosure" laws are chatbot laws, mostly not e-mail laws.** Maine's Chatbot Disclosure Act
|
||||
(effective 2025-09-24) requires telling consumers they are not talking to a live human when a reasonable consumer
|
||||
could not tell; Utah after SB 226 (2025) requires disclosure only when the user asks or in high-risk (health,
|
||||
financial, biometric) interactions; California SB 243 (effective 2026-01-01) targets companion chatbots, with AB 1988
|
||||
/ AB 1609 extending disclosure to customer-service chatbots still moving in 2026; Colorado's AI Act was pushed to
|
||||
2026-06-30 and is a risk-management law. https://www.multistate.ai/updates/vol-85-state-ai-chatbot-regulation-laws ;
|
||||
https://www.cooley.com/news/insight/2025/2025-10-21-ai-chatbots-at-the-crossroads-navigating-new-laws-and-compliance-risks ;
|
||||
https://fpf.org/blog/understanding-the-new-wave-of-chatbot-legislation-california-sb-243-and-beyond/ ;
|
||||
https://www.ailawsbystate.com/tools/ai-disclosure-tracker ; https://www.truebe.io/state-chatbot-laws-2026
|
||||
6. **California's older B.O.T. Act (2019) already requires bot disclosure when a bot is used to incentivise a purchase**
|
||||
— relevant if an auto-sent reply ever recommends upgrading to PRO. (Mentioned across the trackers above;
|
||||
verbatim UNVERIFIED this run.) Cheap mitigation: the upsell lives in the 429 body and docs, not in support replies.
|
||||
7. **Net legal read for our facts:** while the human gate is active, no cited law clearly requires a line on a
|
||||
reviewed e-mail signed by a company. After auto-send begins, the EU Article 50 and FTC "reasonable consumer" tests
|
||||
both point to *some* notice for a reply a person could mistake for a human's. Utah's "must answer truthfully if
|
||||
asked" rule is the floor everywhere: the Claude prompt must never deny being AI.
|
||||
|
||||
## B. What major vendors' support replies actually say
|
||||
|
||||
8. **Intercom Fin** appends, by default, to e-mail replies it sends through Zendesk/Salesforce: *"This answer was
|
||||
composed by [AI agent name], [workspace]'s AI Agent."* In its own messenger the "AI Agent" label can be toggled off,
|
||||
in which case Intercom recommends the intro message as the compliance disclosure.
|
||||
https://fin.ai/help/en/articles/11847955-what-is-ai-agent-disclosure
|
||||
9. **Zendesk-hosted AI agents** are covered by the same Fin footer mechanism when Fin is the agent; Zendesk's own AI
|
||||
agents label the bot persona in messaging (vendor comparison pages, no verbatim footer found):
|
||||
https://www.plain.com/blog/blog-best-ai-customer-support-platforms-b2b-2025 ;
|
||||
https://www.usefini.com/guides/best-ai-customer-support-platforms-saas-2026
|
||||
10. **Pattern across B2B SaaS vendors (2026 round-ups):** disclosure is treated as a *configuration default that most
|
||||
teams leave on*, phrased as a named-agent footer rather than a disclaimer; none of the vendors describes it as a
|
||||
conversion risk. https://frigade.com/blog/eight-customer-support-ai-tools-2026 ;
|
||||
https://www.viewpointanalysis.com/post/customer-service-ai-software-options-2026 (UNVERIFIED: Klarna's exact
|
||||
wording was not surfaced by the search).
|
||||
|
||||
## C. Trust and conversion evidence
|
||||
|
||||
11. **Most controlled studies find that explicitly labelling a service reply as AI-authored lowers perceived
|
||||
authenticity and satisfaction** — with at least one exception (Ovsyannikova et al., 2025) — and that *framing*
|
||||
matters: a utilitarian frame ("so you get an answer faster/more accurately") offsets much of the penalty.
|
||||
https://www.sciencedirect.com/science/article/pii/S0278431926003142 ;
|
||||
https://www.tandfonline.com/doi/full/10.1080/13527266.2025.2540376
|
||||
12. **Which information is disclosed changes the preference:** customers who must disclose sensitive information prefer
|
||||
different agent types — the AI-vs-human preference is not fixed. Support for a developer API (non-sensitive, technical)
|
||||
is the low-penalty end. https://www.sciencedirect.com/science/article/abs/pii/S096969892500400X
|
||||
13. **Industry survey (COPC 2025):** markets that lead in disclosure (Malaysia, Singapore) also lead in satisfaction and
|
||||
comfort with AI; what "tanks satisfaction" is a bot that lacks context or cannot escalate, not the disclosure.
|
||||
https://www.copc.com/ai-customer-experience-research-2025/ ; same conclusion in
|
||||
https://aissist.io/insights/ai-disclosure-customer-service
|
||||
14. **E-mail-specific outbound data:** one-line "drafted with AI assistance" disclosures matched the trust scores of
|
||||
no disclosure and beat long disclosures; process-framed lines ("AI-assisted drafting, reviewed by our team") often
|
||||
built confidence in professional-services contexts. https://blog.magicteams.ai/blog/does-disclosing-ai-emails-hurt-response-rates/
|
||||
15. **"Defensive" wording is the failure mode:** most disclosure fails because it reads like a confession, not because
|
||||
it exists. Recommended shape: short, after the value, plain.
|
||||
https://www.swizero.com/blog/ai-assisted-email-writing-2026 ; https://elementsai.net/blog/ai-content-disclosure-small-business/
|
||||
16. **Consumer trust baseline:** 82 % of surveyed consumers see AI-driven data loss as a threat (Relyance 2025) — the
|
||||
fear is about *data handling*, which argues for pairing any AI line with "we don't train on your messages / your
|
||||
documents are not stored" rather than for hiding the AI. https://www.relyance.ai/consumer-ai-trust-survey-2025
|
||||
17. **Small-business practice:** the common recommendation for SMBs is a signature-level line plus a policy page, so
|
||||
the e-mail itself stays clean. Sample lines collected: "AI helped draft this email; a real person reviews all
|
||||
messages"; "This message was drafted using AI technology… contact us directly." https://biglysales.com/the-ethics-of-ai-in-email-communication/ ;
|
||||
https://revcity.6sense.com/home/discussion/1884/ai-wrote-this-email-should-you-tell-when-how-to-disclose-ai-generated-content ;
|
||||
https://www.human-i-t.org/ai-disclosure/
|
||||
|
||||
---
|
||||
|
||||
## D. Three wording options
|
||||
|
||||
**Option 1 — No line in the e-mail; disclosure lives on the listing/docs and in the prompt.**
|
||||
Signature: `— The Boring API Company · support` (no human name). Docs/README "Support" section: *"Support replies are
|
||||
prepared with AI assistance and reviewed by our team; if you ask whether you're talking to an AI, we'll tell you."*
|
||||
Prompt rule: never deny being AI. Pros: cleanest e-mail, matches finding 14's "no disclosure" trust score, defensible
|
||||
while the human gate is on (finding 7). Cons: once auto-send starts, EU recipients get an unreviewed AI reply with no
|
||||
in-message notice (finding 2); a reader who later learns it was AI may feel misled (finding 4).
|
||||
|
||||
**Option 2 — Process-framed footer (recommended).**
|
||||
Signature block:
|
||||
```
|
||||
— The Boring API Company · support
|
||||
Drafted with AI, checked by our team. Replies within one business day. Your messages and documents are never used to train anything.
|
||||
```
|
||||
When a reply auto-sends (after the 20-pass threshold), the middle sentence becomes: *"Answered by our AI assistant;
|
||||
a human reviews this inbox weekly — reply 'human' to reach one directly."* Pros: the one-line, after-the-value,
|
||||
non-defensive form the evidence favours (findings 11, 14, 15); satisfies the Article 50 "informed" test and the FTC
|
||||
reasonable-consumer test after auto-send; pairs the AI line with the data-handling reassurance customers actually
|
||||
care about (finding 16); reads as a competence signal for an API company whose whole pitch is automation.
|
||||
Cons: two variants to maintain (one flag in N8N).
|
||||
|
||||
**Option 3 — Full disclosure line at the top.**
|
||||
First line of the body: *"This reply was generated by an AI assistant on behalf of The Boring API Company. It has
|
||||
[/ has not] been reviewed by a human. If anything here is wrong, reply and a person will follow up."* Pros: maximal
|
||||
compliance clarity in every jurisdiction, including future California customer-service bills (finding 5). Cons: the
|
||||
wording the studies penalise — long, top-of-message, confession-shaped (findings 11, 15); the "unprofessional"
|
||||
effect the user fears is specifically this form.
|
||||
|
||||
**Recommendation: Option 2**, with Option 1's docs paragraph added as well (belt and braces, zero e-mail cost) and
|
||||
Option 3 kept only as the template for *billing/security* categories if those are ever auto-sent. Rationale: the
|
||||
user's worry is real but applies to Option 3's form, not to a one-line process footer, which the evidence says costs
|
||||
nothing in trust; and the footer is the cheapest way to stay on the right side of the EU/FTC tests the moment
|
||||
auto-send begins. Two build notes for the N8N flow: (a) the `auto_sent` flag selects the footer variant; (b) the
|
||||
system prompt contains "If asked whether you are an AI, answer yes" (Utah floor, finding 5).
|
||||
|
||||
## Update instructions
|
||||
Revisit when auto-send is switched on for the first category, when California AB 1988/1609 pass, and at the first
|
||||
support-inbox retro; record the chosen option in DECISIONS.md and update the N8N footer variants.
|
||||
@@ -0,0 +1,169 @@
|
||||
# Digital businesses — unified launch-week marketing strategy (API · KDP · Etsy)
|
||||
|
||||
Tracks: business_projects **43** (marketing overhaul, all digital businesses). Written 2026-09-15 (launch week) by background agent J (Fable 5.1, research/creative-only: no installs, no config changes, no DB writes, no commits). Builds on — does not repeat — the three per-business evaluations by agents D/E/F (2026-09-09):
|
||||
`research/api-business-evaluation.md` (D), `research/kdp-business-evaluation.md` (E), `research/etsy-business-evaluation.md` (F). Anything not confirmed from a primary source is marked **UNVERIFIED**.
|
||||
|
||||
## Executive summary
|
||||
|
||||
**Launch order: Etsy (ConfettiPrintCo) → KDP (J.M. Hartley) → API (RapidAPI).** All three go live this week, but attention is sequenced: Etsy gets ~60 % of the week's hours (it is the only one where first sale AND first cash can both land inside 30 days), KDP gets ~30 % (its 21–60-day clock only starts when title A is published, so publish early and then leave it alone), API gets ~10 % (maintenance of the existing listing only; the utility-API build starts the week of 2026-10-05).
|
||||
|
||||
**The one strategy in three sentences.** For an owner who is not a salesman, the *product listing on a search marketplace is the entire sales team*: every hour goes into listing quality (exact-phrase titles, complete metadata, mandatory AI disclosure, a free/low-priced entry point) because that is what each marketplace's own search ranks. Around the three listings sits **one** reusable marketing system (project 43): a shared keyword-intake → listing-draft-and-QA → product-embedded opt-in → weekly-metrics-and-kill-rule loop, run on N8N + local Ollama with `claude -p` only for drafting, and every decision logged as training data. Paid ads are a later phase, funded strictly by each business's own banked profit (Etsy first, ~Nov; KDP ~Dec; API ~Q1 2027).
|
||||
|
||||
**$0 first-customer path exists for all three:** Etsy yes (21–56 d after 30 listings, ~55 %), KDP yes (21–60 d after first publish, LOW-MED), API yes-but-slow (60–120 d after the utility API is listed, LOW-MED). Cash order differs from sale order only for KDP (royalties ~60 days after month end).
|
||||
|
||||
## 1. What the three evaluations already give us (extracted, not re-derived)
|
||||
|
||||
| | API (D) | KDP (E) | Etsy (F) |
|
||||
|---|---|---|---|
|
||||
| Channel | RapidAPI hub search + Google → docs site | Amazon Books search (title/keywords/categories) | Etsy search (title/13 tags/attributes) + Google indexing of listings |
|
||||
| First-customer path | Complete listing + free BASIC tier + docs site + example repo; 429 body/headers as the upsell | $9.99 black-ink 6×9 paperback in a specific-audience Q&A niche, Read Sample, A+ content | 30 tightly-themed digital listings by day 21, $3.99–4.99, launch sale, Offsite Ads opt-out |
|
||||
| Time to first sale at $0 | 60–120 d after utility API listed (LOW-MED) | 21–60 d after publish (LOW-MED) | 21–56 d after 30 listings (~55 %) |
|
||||
| Cash timing | Paid end of the month *after* the charge month, PayPal only (D §Verdict) | ~60 d after month end (E §4.4, UNVERIFIED verbatim) | Etsy Payments deposits; new-shop reserve/hold possible (**UNVERIFIED** — check Shop Manager → Finances on day 1) |
|
||||
| Off-platform asset | Docs site + opt-in list | Author page + back-matter opt-in | Bonus-pack page + opt-in (last PDF page) |
|
||||
| Automation already specified | 7 jobs, `content_drafts`/`canary_runs`/`marketing_metrics`/`decisions` (D) | 7 jobs, `kdp_decisions`/`kdp_sales_daily`/`kdp_optins` (E) | 7 jobs, `etsy_training_log`/`etsy_sales` (F) |
|
||||
| Gaps found this run | Existing Dividend Tracker listing has no plan of its own (D plans only the *next* API); no PayPal yet | business_projects 40 says title 1 = "365 Questions for Couples" but E ranks couples #3 (14/18) behind seniors (16) and kids-space (15); bank/tax interview not in the calendar | business_projects 39 says "10 birthday-party printables" but F says junk-journal/planner kits (16/20); demand method still un-run; Etsy Share & Save not used |
|
||||
|
||||
The three docs proposed **three separate automation stacks with overlapping tables**. Section 2 merges them.
|
||||
|
||||
## 2. The shared marketing system (project 43)
|
||||
|
||||
Name: **Listing Engine**. One N8N project, one Postgres schema, one static "hub" site; per-business behaviour comes from a config row, not from separate workflows.
|
||||
|
||||
### 2.1 Capabilities (what every business needs, in the order money flows)
|
||||
|
||||
| # | Capability | What it does | Owner input (manual, browser, no logged-in automation) | N8N + local model (llama3.1:8b / nomic-embed-text) | `claude -p` (Claude Pro, within $20/mo) | Cost | First signal | Measured by |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| C1 | **Keyword intake** | One form (N8N Form Trigger) the owner fills after 15–20 min in a normal browser: seed phrase, autocomplete completions, top-result rank/BSR/badge counts | Etsy: autocomplete + result count + p1 badges (F §3). KDP: search-suggest + top-5 BSR + review moat (E §2). API: Search Console queries + StackExchange RSS hits (D §B) | nomic-embed clusters phrases; llama scores each cluster against the business's rubric (demand/competition/fit); dedup against `marketing_keywords` | none | $0 | same day | rows in `marketing_keywords`; later joined to sales |
|
||||
| C2 | **Listing draft + QA** | Turns a keyword cluster into title / subtitle-or-tags / description / disclosure line, then runs a hard QA checklist | owner picks one of 3 variants, edits | llama runs the *checklist* (deterministic: char limits, all 13 tags / 7 keywords used, banned words per platform, AI-disclosure line present, file ≤ 20 MB, price ≥ min) and scores variants | drafts the 3 variants (1 call per listing/title/endpoint page) | $0 (Claude Pro) | at publish | `marketing_content_drafts` with `owner_pick`, `edit_distance` |
|
||||
| C3 | **Product-embedded opt-in** | The *only* contact channel: a bonus page linked from inside the product (last PDF page / book back matter / API 429 body + docs site). Double opt-in, one welcome sequence per business, "new item" mail only when true | owner approves each send | N8N webhook → `marketing_optins`; llama drafts the "new item" mail from the listing row; Brevo free tier or Buttondown free tier sends (limits **UNVERIFIED** today; F §4.4) | none | $0 | first opt-in (≈ first sale + 1 wk) | opt-ins ÷ sales (target 3–5 %, E §3.3) |
|
||||
| C4 | **Metrics ingest + weekly digest** | Owner drops CSVs into one watched folder per business (Etsy Stats/Orders, KDP Reports, RapidAPI analytics export, Search Console) | weekly CSV download (~10 min total) | N8N parses → `marketing_metrics`; llama writes a 10-line digest + "rewrite these N listings" memo → ntfy | none | $0 | week 2 | digest delivered Mondays |
|
||||
| C5 | **Kill/scale evaluator** | Runs each business's own kill/scale rules (D §Kill, E §6, F §7) as SQL over `marketing_metrics` on the dates those docs set (day 30/45/60/90) | owner decides | N8N cron → SQL → ntfy verdict with the inputs | none | $0 | day 30 (Etsy) | `marketing_decisions` row per evaluation |
|
||||
| C6 | **Review flywheel (platform-native only)** | Nothing solicited off-platform. Etsy: its automated review request; KDP: neutral back-matter line (E §1.7); API: none (RapidAPI has no ratings — Discussions tab only) | — | — | — | $0 | 30–90 d | review count in weekly form |
|
||||
|
||||
Explicitly OUT for all three (owner constraints): Pinterest daily pinning (needs a 2-week manual persona warm-up, F §4.4 — ruled out here unless the owner opts in at grill-me Q4), any DM/cold email, any scraping of etsy.com / amazon.com / rapidapi.com, any automation of logged-in accounts, review swaps/incentives, paid promo newsletters.
|
||||
|
||||
### 2.2 Per-business config (one row each in `marketing_business_config`)
|
||||
|
||||
| Field | API | KDP | Etsy |
|
||||
|---|---|---|---|
|
||||
| Title/tag limits | short desc 1 sentence; README markdown | title+subtitle ≤ 200 chars; 7 keyword slots (**UNVERIFIED** 7×50, E §1.5); 3 categories | title ≤ 140 chars, first 40 matter; 13 tags; attributes |
|
||||
| Banned words | none documented | "bestselling", "free", other authors (E §1.5) | none documented; IP names out |
|
||||
| Mandatory disclosure | none (but ToS forbids soliciting subscribers, D §2) | "AI-generated text" tick at every publish (E §1.1) | AI-disclosure sentence in description (F §2.1) |
|
||||
| Free/entry tier | BASIC free, hard limit 100–200 req/mo | $9.99 launch price (60 % tier) | $3.99–4.99, 20 % launch sale |
|
||||
| Opt-in page | hub `/docs` (release notes list) | hub `/jmhartley` (50 bonus questions) | hub `/confetti` (2 bonus ephemera pages) |
|
||||
| Trackable outbound link | plain link to listing | plain Amazon link (Amazon Attribution + 10 % referral bonus reported for authors — **UNVERIFIED** for KDP paperbacks, requires an Amazon Ads account; do not rely on it) | **Share & Save** `confettiprintco.etsy.com` links (4 % of eligible order total refunded when the buyer orders within 30 days of clicking — official: https://help.etsy.com/hc/en-us/articles/16981332744087, fetched 2026-09-15) |
|
||||
| Metrics CSV | RapidAPI provider analytics export; Search Console | KDP Reports; weekly BSR form | Etsy Stats + Orders CSV |
|
||||
| Kill/scale dates | day 60 after utility API listed | day 45 / 90 after first publish | day 30 (gate) / 90 |
|
||||
|
||||
### 2.3 Unified tables (PROPOSED — creation is a DB write, needs approval; not done in this run)
|
||||
|
||||
`marketing_keywords(business, ts, phrase, cluster_id, score_json, rationale)` · `marketing_content_drafts(business, ts, item_slug, kind, prompt_version, model, candidates_json, owner_pick, edit_distance, published_at)` · `marketing_optins(business, ts, email, source_item, consent_ts, sequence_step)` · `marketing_metrics(business, week, metric, value, source_file)` · `marketing_decisions(business, ts, rule, inputs_json, model_proposal, human_decision, outcome_30d)`.
|
||||
These replace the nine per-business tables proposed by D/E/F (`content_drafts`, `qa_candidates`, `kdp_decisions`, `kdp_keyword_rank`, `etsy_training_log`, `etsy_sales`, …) with a `business` column; the `api_business` DB's existing `lead_review_log` pattern (Claude gate → human label) is the template. Suggested home: `business_projects` DB (E §5 and F §6 both proposed it) — confirm at grill-me Q6.
|
||||
|
||||
### 2.4 What is reusable across all three (the point of project 43)
|
||||
|
||||
The hub site (one static site, three paths, behind existing Traefik/cloudflared — F §4.4 and E §3.3 each asked for one; build once), the N8N Form Trigger + CSV-watcher + weekly-digest workflows (parameterised by `business`), the llama QA-checklist prompt (per-business rule list injected from config), and the `claude -p` drafting prompt (same skeleton: "given keyword cluster + product facts + platform limits, produce 3 variants"). Build effort ≈ one weekend, after Etsy's first 10 listings are live (listings before tooling).
|
||||
|
||||
## 3. Per-business launch-week plan (2026-09-15 → 09-21)
|
||||
|
||||
### 3.1 Etsy — ConfettiPrintCo (extends F §4.5 days 1–7)
|
||||
|
||||
**NEW / changed vs F:**
|
||||
1. **Resolve the product scope conflict before listing #1.** business_projects 39 says "10 birthday-party printable listings"; F's scoring says junk-journal kits / planner stickers / dashboards (16/20 each) and puts wall-art-style AI printables last among digital. Birthday printables were not scored. Default = follow F (the scored option) unless the owner already has birthday designs made → grill-me Q1.
|
||||
2. **Join Share & Save on day 1** (Shop Manager → Marketing → Share & Save; official page above). Every hub-site and opt-in-email link then uses `confettiprintco.etsy.com/...` and earns 4 % back. F did not mention it.
|
||||
3. **Demand method is a hard gate, not a nice-to-have** (F §3, ~3 h). Until it is run, Etsy's #1 ranking rests on an estimate; run it Mon–Tue, log results through C1.
|
||||
4. **Check the new-shop payment reserve** (Finances → Payment account) on day 1 — if Etsy holds a share of early sales, Etsy's cash-first advantage shrinks (**UNVERIFIED**).
|
||||
5. Pinterest: OUT by default (see §2.1) — F left it conditional.
|
||||
|
||||
**$0 first-customer path (unchanged from F):** 30 listings by day 21, each with exact-phrase title, 13 tags, AI-disclosure line, 6+ mockups, ≤ 5 files ≤ 20 MB, launch sale; Offsite Ads opted out. Cost this week: shop set-up fee (~$15 **UNVERIFIED**) + 10 × $0.20 = ~$17.
|
||||
**This week's checklist:** Mon: open shop, policies (digital = no returns), Offsite opt-out, Share & Save join, create "Printify" production partner (unused), payment-reserve check. Mon–Tue: demand method on 8 seed phrases → C1 form. Wed–Sun: 3 themes chosen, 10 listings live by Sunday (F day 8–21 pace pulled forward one week because Etsy leads the chain).
|
||||
**Weekly cadence (ongoing):** Mon 20 min Stats/Orders CSV → C4; Tue–Thu 10 new listings; Fri rewrite the bottom-10 titles from the digest; day-30 gate (F §4.5): ≥ 1 sale or ≥ 300 views → continue to 60.
|
||||
|
||||
### 3.2 KDP — J.M. Hartley (extends E §3.4 days 1–14)
|
||||
|
||||
**NEW / changed vs E:**
|
||||
1. **Resolve the title-A conflict.** business_projects 40 names "365 Questions for Couples" first; E ranks seniors large-print Q&A first (16/18) and couples third (14/18, winner-take-most). Recommendation: **title A = seniors large-print** — it also has Q4 gift demand ("adult children buying for parents", E §2.2) and must be live by ~Nov 1 to catch it; couples becomes title B (its "52 weeks × 7 questions" hook, E §2.2, replaces the bare "365 questions" framing). Owner's call → grill-me Q2.
|
||||
2. **Do the bank account + tax interview on day 1** (E §4.4 notes it as a payout prerequisite but the calendar omits it). It gates the first royalty, which gates the ads phase.
|
||||
3. **Publish title A by day 11 no matter what else slips** — KDP's 21–60-day clock is exogenous (indexing + zero-review visibility); it cannot be compressed with effort later, only started earlier.
|
||||
4. **Do not rely on a KDP "Request a Review" button**: search results (getbooksreviewed.com, 2026; iwrity.com, 2026) describe such a button for authors, but it is documented for Seller Central, not KDP — **UNVERIFIED**. The compliant asks remain the neutral back-matter line (E §1.7) only.
|
||||
5. Back-matter opt-in link → shared hub `/jmhartley` (C3), not a separate site.
|
||||
|
||||
**$0 first-customer path (unchanged from E):** exact-phrase title, 7 keywords, 3 categories, Read Sample, A+ content, $9.99 for 30 days, AI-generated disclosure ticked. Cost this week: $0 (Claude Pro share + Canva free).
|
||||
**This week's checklist:** Mon: KDP account, bank + tax interview, Author Central shell. Mon–Tue: browser BSR re-read of the 3 shortlist + 2 unscored niches (E §2.1) → C1 form; pick niche A. Wed–Sun: `claude -p` batches (300 Q&As), llama dedup/safety pass, 10 % owner fact-check (E day 3–7). Next week: interior + cover, publish by 2026-09-25 (day 11).
|
||||
**Weekly cadence:** Mon 10 min KDP Reports CSV + BSR/keyword-rank form; one title per 10–14 days until three are live; day-45 and day-90 checkpoints (E §6).
|
||||
|
||||
### 3.3 API — RapidAPI (extends D §E week 1; demoted to background this week)
|
||||
|
||||
**NEW / changed vs D:**
|
||||
1. **This is not a build week.** D's calendar starts the DocForge build in week 1; this strategy moves it to the week of 2026-10-05 (after Etsy reaches 30 listings and KDP title A is live), because API is last in the chain and its first sale is 60–120 days out regardless.
|
||||
2. **Give the existing Dividend Tracker listing a maintenance pass now** (D's checklist A applied to the listing that already exists, which D did not plan for): example request/response saved on every endpoint, one changelog line, hard BASIC limit, `X-Upgrade-Url`/429 body (D §C) — ~2 h. It keeps Service Level and "last updated" fresh at zero cost and is the only API asset that can produce a signal this month.
|
||||
3. **PayPal account + Uptime Kuma canary on the existing API** this week (both from D, pulled forward because they are cheap and prerequisite).
|
||||
4. **Formally record N8N `4fuzFJAclba2Syy7` as decommissioned** in project docs (already stated in the API hub handoff 2026-09-10; no N8N change made here — it stays paused/untouched, feedback_no_docker_restarts).
|
||||
5. Docs-site opt-in → shared hub `/docs` (C3), and the docs pages become the API's C2 drafts.
|
||||
|
||||
**$0 first-customer path (unchanged from D):** complete listing + free BASIC + docs site + example repo + ≤ 2 disclosed Q&A answers/week. Realistic: yes, slow. Cost this week: $0.
|
||||
**This week's checklist (≤ 2 h):** PayPal; Uptime Kuma monitor on the live endpoint; listing maintenance pass; Search Console property on the docs domain (domain ownership **UNVERIFIED**, D grill-me Q6).
|
||||
**Weekly cadence:** Mon 10 min analytics export; otherwise dormant until Oct 5; from then D's weeks 1–4.
|
||||
|
||||
## 4. Time-to-first-sale-at-$0 ranking, launch order, bootstrap chain
|
||||
|
||||
| Rank | Business | First sale at $0 (from this week) | Confidence | First *cash* available | Evidence |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | **Etsy digital** | 21–56 d after 30 listings (30 by ~Oct 5) → **~Oct 26 – Nov 30** | ~55 % (F) — provisional until the demand method is run | days–weeks after sale (Etsy Payments; reserve **UNVERIFIED**) | F §1, §5: $0.20/listing, 0 fulfilment, 80 % net; 1–8 wk first-sale range (secondary, UNVERIFIED) |
|
||||
| 2 | **KDP** | 21–60 d after title A (publish ~Sep 25) → **~Oct 16 – Nov 24** | LOW-MED (E) | ~60 d after month end → **late Dec 2026 / Jan 2027** | E §4.3: fresh indie Q&A titles at BSR #43k–#300k; zero-review visibility counter-evidence |
|
||||
| 3 | **API** | 60–120 d after DocForge listed (~Oct 19) → **~Dec 18 – Feb 16** | LOW-MED (D) | end of month after the charge month, PayPal | D §Viability: solo sellers 4–12 months; owner's 0/4 baseline |
|
||||
|
||||
KDP's *sale* window overlaps Etsy's (it may even sell first, since it publishes earlier than Etsy reaches 30 listings), but Etsy stays #1 because the chain is about **cash that can fund the next business's ads**, and KDP's royalty lag pushes its cash to December. This confirms the ranking parked on 2026-09-10 (Etsy → KDP → API) — with the caveat that grill-me Q1 (2026-09-10) is answered here as: **lock the order now, verify Etsy demand in parallel this week, and swap Etsy/KDP only if the demand method fails its own gate** (≥ 1 shortlist niche with p1 badges and a non-saturated result count).
|
||||
|
||||
**Funding chain (approval numbers, nothing spent now):** Etsy profit ≥ $50 → F's $42 Etsy Ads ranking experiment (F §4.3), then Etsy profit funds **KDP's** $90/mo Amazon Ads (E §3.2) from ~Nov instead of waiting for KDP's own December royalties; KDP + Etsy profit ≥ $50/mo net → D's $30/mo Google Search test for the API docs site. Each step needs an explicit owner approval when the trigger fires (C5 posts the trigger).
|
||||
|
||||
## 5. Automation + training-data layer
|
||||
|
||||
Rule: nothing touches a logged-in session; local model does scoring/QA/digests; Claude Pro (`claude -p`) does drafting only; every proposal is logged next to the human decision (feedback_always_include_training_loop). All N8N workflows below are additions to the existing self-hosted N8N — none built in this run.
|
||||
|
||||
| Automation | Trigger | N8N + local model | Needs Claude? | Cost | Training log (table · label) |
|
||||
|---|---|---|---|---|---|
|
||||
| Keyword intake (C1) | Form Trigger, weekly | embed + cluster + rubric score | no | $0 | `marketing_keywords` · outcome = later rank/sales of items using the cluster |
|
||||
| Listing/title/docs drafts (C2) | owner runs `claude -p` with the shared prompt; pastes output into a Form | llama QA checklist; variant scoring | yes (drafting, ~1–3 calls per item, well under $20/mo) | $0 | `marketing_content_drafts` · `owner_pick`, `edit_distance`, 30-day views |
|
||||
| Opt-in delivery + "new item" mail (C3) | webhook / new listing row | llama drafts mail; Brevo/Buttondown free tier sends after approval | no | $0 (free tiers, **UNVERIFIED** limits) | `marketing_optins` + `marketing_content_drafts kind='email'` · owner edits stored as diff |
|
||||
| Weekly digest (C4) | watched folder, Monday cron | parse CSVs; llama 10-line digest + rewrite memo → ntfy | no | $0 | `marketing_metrics`; memo suggestions logged with accept/reject |
|
||||
| Kill/scale evaluator (C5) | cron on each doc's dates | SQL + ntfy | no | $0 | `marketing_decisions` · rule, inputs, human decision, outcome_30d |
|
||||
| Uptime/Service-Level canary (API, D) | cron 5 min | HTTP call; ntfy on failure | no | $0 | `canary_runs` (keep as-is from D; it is telemetry, not a decision) |
|
||||
| Q&A candidate screening (API, D) | StackExchange RSS | llama relevance 0–1 | no | $0 | `marketing_decisions kind='qa_candidate'` · owner answered y/n |
|
||||
| KDP content QA (E) | after each `claude -p` batch | llama dedup/unsafe-claim flags; nomic-embed cross-title dedup | no (drafting already done) | $0 | `marketing_decisions kind='content_qa'` · human accept/reject |
|
||||
|
||||
Local-model fit check: llama3.1:8b on the 8 GB GPU is adequate for checklists, scoring and short digests; it is *not* used for customer-facing copy (drafting stays with Claude Pro so quality is not the variable under test). Long-term, `marketing_content_drafts` (Claude draft → owner edit) is exactly the dataset needed to fine-tune or few-shot the local model into the drafting role — the same replacement path already recorded for `lead_review_log` in the API hub.
|
||||
|
||||
## 6. Weekly metrics + kill/scale triggers (owner checks Monday, ≤ 30 min for all three)
|
||||
|
||||
| Business | The 2–3 weekly numbers | Kill (pause, keep listings up) | Scale (approve the next spend) |
|
||||
|---|---|---|---|
|
||||
| Etsy | listing views (30-d), favourites ÷ views, orders | F §7 unchanged: at day 90 with ≥ 30 listings, any two of: sales < 3, views < 300/30 d, fav/views < 1.5 %, no p1 rank; hard stop on an unresolved Creativity-Standards takedown | day-30 gate passed (≥ 1 sale or ≥ 300 views) → go to 60 listings; ≥ $50 profit + one badge → $42 ads test (F) |
|
||||
| KDP | BSR per title (manual read), paid copies (KDP Reports), keyword-rank check for the 7 phrases | E §6 unchanged: day 90 < 10 copies, or no title < #500k after day 45, or any policy removal, or < $3 royalty/owner-hour | ≥ 25 copies or any title < #150k → $90/mo Amazon Ads (funded by Etsy profit if earlier than KDP's own payout) |
|
||||
| API | BASIC subscribers (cumulative), requests/day, Service Level % | D §Kill unchanged: day 60 after DocForge listed, all of: < 25 BASIC, 0 paid, < 100 docs clicks/30 d, SL ≥ 99 % | any paid subscriber, or ≥ 50 BASIC with ≥ 30 % making > 20 calls → $30/mo Google Search test |
|
||||
| System (43) | opt-ins ÷ sales; `edit_distance` trend on drafts | if `edit_distance` is not falling after 20 drafts, the prompt (not the model) is the problem — revise prompt_version | when llama's QA rejects match the owner's ≥ 90 % over 30 items, let it gate without review |
|
||||
|
||||
## 7. Grill-me questions (≤ 6)
|
||||
|
||||
1. **Etsy product scope:** business_projects 39 says birthday-party printables; F's evidence says junk-journal/planner kits. Which one goes into the first 10 listings, and do you accept that birthday printables are unscored?
|
||||
2. **KDP title A:** seniors large-print (E's #1, Q4 gift window) or your "365 Questions for Couples" (E's #3)? If couples, do you accept the "52 weeks × 7" reframing and a Vol. 2 series?
|
||||
3. **Lock the chain now?** This strategy locks Etsy → KDP → API and only swaps Etsy/KDP if the demand method fails its own gate this week. Agree, or wait for the demand numbers first (the parked 2026-09-10 question)?
|
||||
4. **Pinterest is ruled OUT here** (needs a 2-week manual daily warm-up = a persona). Confirm, or opt in to the warm-up?
|
||||
5. **Cross-funding:** may Etsy profit fund KDP's first $90/mo ad month (~Nov) before KDP's own royalties arrive (~Dec), or must each business fund only itself?
|
||||
6. **Tables + hub site:** approve the five `marketing_*` tables in the `business_projects` DB and one static hub site (three paths) on the existing Traefik/cloudflared stack, built the weekend after Etsy's first 10 listings are live?
|
||||
|
||||
## 8. Sources
|
||||
|
||||
Fetched this run (2026-09-15):
|
||||
- Etsy Help — How to Save on Etsy Fees with the Share & Save Program: https://help.etsy.com/hc/en-us/articles/16981332744087 (4 % of eligible order total refunded; 30-day click window; `yourshopname.etsy.com` link format)
|
||||
- Etsy Seller Handbook — Introducing Share & Save: https://www.etsy.com/seller-handbook/article/1187231088945 (etsy.com 403'd in the 2026-09-09 run; not re-fetched)
|
||||
- Amazon review / attribution claims (secondary, **UNVERIFIED** for KDP): https://getbooksreviewed.com/how-authors-get-verified-reviews-for-print-books-on-amazon-and-what-changed-in-2026/ ; https://www.iwrity.com/how-to-get-reviews-on-amazon ; https://pubnook.com/article/amazon-book-review-policy-2026-what-authors-can--cant-do
|
||||
Inherited (all fetched 2026-09-09; full lists in the three evaluations):
|
||||
- RapidAPI docs/ToS (fees 25 %, PayPal, ranking metrics, provider may not solicit consumers) — D §Sources
|
||||
- KDP Content Guidelines, royalty/print cost, metadata pages — E §8
|
||||
- Etsy Help fee/policy pages (listing $0.20, 6.5 %, Offsite Ads, Etsy Ads, digital limits, AI disclosure) — F §9
|
||||
Local: `research/api-business-evaluation.md`, `research/kdp-business-evaluation.md`, `research/etsy-business-evaluation.md`, `research/github-aup-lead-pipeline.md`, the three business `.claude/context.md` hubs, business_projects rows 39/40/43–46 (read-only).
|
||||
|
||||
**UNVERIFIED list (this run):** Etsy new-shop payment reserve/hold; Etsy set-up fee amount (~$15); Brevo/Buttondown free-tier limits; KDP "Request a Review" button availability for KDP authors; Amazon Attribution / 10 % referral bonus for KDP paperbacks; KDP payout timing verbatim; 7×50-char KDP keyword slots; docs-site domain ownership; all Etsy demand/competition counts (inherited from F until the owner runs the browser method); all first-sale time ranges (secondary sources, inherited from D/E/F).
|
||||
|
||||
Update rule: revise §3 checklists and §4 dates when the owner answers §7; replace UNVERIFIED items as they are confirmed; when the `marketing_*` tables exist, point C1–C5 at the real workflow IDs.
|
||||
@@ -0,0 +1,577 @@
|
||||
# DocForge build gameplan — HTML→PDF + PDF→tables JSON on Express/TypeScript/Vercel
|
||||
|
||||
Written 2026-09-15 by the gameplan background agent (Fable 5.1). Executes the 2026-09-15 API decisions in
|
||||
`decisions/DECISIONS.md` over `research/api-business-evaluation.md` (agent D) and the memory playbook
|
||||
`project_api_creation_playbook.md` (phases 2–7). Nothing here redesigns. Tracks: business_projects 48 (DocForge),
|
||||
49 (support inbox), 43/44.
|
||||
|
||||
**Decisions honoured:** Express + TypeScript on Vercel, playbook phases 2–7 · headless Chromium via `@sparticuz/chromium`
|
||||
+ `puppeteer-core` · pure-JavaScript PDF parser · **no Upstash** · renderer network access blocked (SSRF) · upload
|
||||
size + page caps · Vercel function timeout raised to 60 s · pricing BASIC free 100 req/mo, PRO $12, ULTRA $39,
|
||||
MEGA $99, $0.005/req overage on paid tiers · day-one telemetry: subscriber + plan headers POSTed to an N8N webhook
|
||||
(cloudflared) → shared `marketing_*` weekly-metrics table; Vercel KV paying-customer counter, alert at 15 · public
|
||||
example repo in three languages · no manual Q&A answering · support inbox = company Gmail ("The Boring API Company")
|
||||
→ N8N Gmail API poll → Claude draft → code sample verified on the LIVE endpoint through the RapidAPI gateway on our
|
||||
own free key → weekly human approve/reject gate logged as training data → auto-send after 20 consecutive unedited
|
||||
passes · build slot ≈ 2026-10-05, may slip · weekday blocks only.
|
||||
|
||||
---
|
||||
|
||||
> **AMENDED 2026-09-15 17:05 (DECISIONS.md rows 22–23):** Vercel KV DROPPED — the paying-customer counter and 15-customer alert live in Postgres via the N8N telemetry webhook. Support-email disclosure = Option 1 (no line in the email; one sentence on docs/listing; prompt never denies being AI; add a short notice only when auto-send is enabled).
|
||||
|
||||
## 1. Playbook phase map
|
||||
|
||||
| Playbook phase | DocForge application | Deviation (decided) |
|
||||
|---|---|---|
|
||||
| 1 Idea selection | Done (agent D: Document processing 84/100; PPT twist = HTML→PDF **plus** tables-as-JSON in one listing) | — |
|
||||
| 2 Project setup | Repo layout §2; `npm install express cors express-rate-limit helmet multer puppeteer-core @sparticuz/chromium pdfjs-dist zod` ; dev deps per playbook | **No `@upstash/redis`**; `/health` reports `cache: 'none'` and `chromium: 'ok'|'unavailable'` instead of a Redis ping; no keepalive cron needed |
|
||||
| 3 Data source | None — pure compute. Only outbound call is the telemetry POST | — |
|
||||
| 4 Local testing | `npm run dev` + the test corpus §9 via `curl` single-line | Local dev uses full `puppeteer` (bundled Chromium) behind an env switch; prod uses `@sparticuz/chromium` |
|
||||
| 5 Vercel deploy | `vercel` then `vercel --prod` from inside the project dir; env vars §6 | `vercel.json` gets `maxDuration: 60` and memory per §6 |
|
||||
| 6 RapidAPI Studio listing | General / Endpoints / Monetize / Docs per playbook; base URL in General tab (bottom); health check `/health` | Plans §7 (not the playbook defaults) |
|
||||
| 7 Account details | Username `Monoxide3637`; listing URL `https://rapidapi.com/Monoxide3637/api/docforge`; **click Save Changes** on visibility | Support e-mail on the listing = the Boring API Company Gmail, not the personal address |
|
||||
| 8 Marketing | Out of scope here except: example repo IN, Q&A answering OUT (decision) | GitHub outreach pipeline stays retired |
|
||||
|
||||
---
|
||||
|
||||
## 2. Repo layout (`/opt/appdata/docker/Business/API Idea/docforge/`)
|
||||
|
||||
```
|
||||
docforge/
|
||||
├── api/index.ts # Vercel entry: export default app
|
||||
├── src/
|
||||
│ ├── index.ts # express app: helmet, cors, json(limit 2mb), rate-limit, routes, 404, error handler
|
||||
│ ├── routes/pdf.ts # POST /v1/pdf/from-html, POST /v1/pdf/extract
|
||||
│ ├── routes/health.ts # GET /health
|
||||
│ ├── services/render.ts # chromium launch (cached browser per instance), request interception, page cap
|
||||
│ ├── services/extract.ts # pdfjs-dist text + table heuristics
|
||||
│ ├── services/telemetry.ts # waitUntil(fetch(N8N_WEBHOOK)) — never blocks the response
|
||||
│ ├── services/counter.ts # KV paying-customer counter (§5.3)
|
||||
│ ├── middleware/gateway.ts # X-RapidAPI-Proxy-Secret check, plan/user header parsing
|
||||
│ ├── middleware/limits.ts # body/upload/page caps → 413
|
||||
│ ├── schemas/*.ts # zod request schemas (also feed the OpenAPI examples)
|
||||
│ └── errors.ts # ApiError(code, status, message); error body shape §3.4
|
||||
├── openapi.yaml # §8 (source of truth for the RapidAPI endpoint definitions + docs)
|
||||
├── test/
|
||||
│ ├── corpus/ # §9 files (kept small; big ones generated by script)
|
||||
│ └── smoke.sh # single-line curls against local and prod
|
||||
├── vercel.json # §6
|
||||
├── .env.example # N8N_WEBHOOK_URL, N8N_WEBHOOK_TOKEN, RAPIDAPI_PROXY_SECRET, KV_* , RENDER_MODE
|
||||
├── .gitignore # node_modules, dist, .env, .env.local, .vercel
|
||||
├── package.json / tsconfig.json # playbook scripts: dev / build / start / vercel-build
|
||||
└── README.md # quick-start (mirrors the RapidAPI Docs tab)
|
||||
```
|
||||
|
||||
Rules from the playbook that still apply: read env vars inside functions, never at module level; global error handler;
|
||||
404 handler; IP rate limit 100 req / 15 min as a backstop behind the gateway.
|
||||
|
||||
---
|
||||
|
||||
## 3. Endpoint contracts
|
||||
|
||||
Base path `/v1`. All requests arrive through the RapidAPI gateway; direct calls without a valid
|
||||
`X-RapidAPI-Proxy-Secret` get `401 gateway_required` (except `/health`).
|
||||
|
||||
### 3.1 `POST /v1/pdf/from-html` — render HTML to PDF
|
||||
Request `application/json`:
|
||||
```json
|
||||
{
|
||||
"html": "<html>…</html>", // required, ≤ 2,000,000 bytes (UTF-8)
|
||||
"options": {
|
||||
"format": "A4", // "A4" | "Letter" | "Legal" (default A4)
|
||||
"landscape": false,
|
||||
"margin": {"top":"20mm","right":"15mm","bottom":"20mm","left":"15mm"},
|
||||
"printBackground": true,
|
||||
"scale": 1.0, // 0.5–2.0
|
||||
"headerTemplate": "", "footerTemplate": "", // Chromium templates, ≤ 4 KB each
|
||||
"waitForJs": false // true = run scripts up to 5 s before printing
|
||||
},
|
||||
"output": "binary" // "binary" (default) | "base64"
|
||||
}
|
||||
```
|
||||
Response `200`: `application/pdf` bytes with headers `X-DocForge-Pages`, `X-DocForge-Render-Ms`, `X-DocForge-Bytes`;
|
||||
or, with `output: "base64"`, `application/json`
|
||||
`{"pdf_base64":"…","pages":3,"bytes":48211,"render_ms":812}`.
|
||||
Limits: input ≤ 2 MB · output ≤ **50 pages** (rendered PDF is page-counted; over cap → 413 and nothing returned) ·
|
||||
render budget **30 s** · all external network fetches blocked (see §4) — inline CSS, `data:` images and system fonts
|
||||
only; a `warnings: ["blocked: https://…"]` list is returned in the JSON form and in `X-DocForge-Warnings` (count) for
|
||||
binary.
|
||||
|
||||
### 3.2 `POST /v1/pdf/extract` — PDF to text + tables JSON
|
||||
Request: `multipart/form-data` with field `file` (≤ **10 MB**, `application/pdf`), **or** `application/json`
|
||||
`{"pdf_base64":"…"}`. No URL input (SSRF). Query: `mode=both|text|tables` (default both), `pages=1-5,8` (optional,
|
||||
1-based, ≤ 50 pages processed).
|
||||
Response `200 application/json`:
|
||||
```json
|
||||
{
|
||||
"pages": 4,
|
||||
"processed_pages": [1,2,3,4],
|
||||
"metadata": {"title":"…","author":"…","producer":"…","created":"2026-01-02T00:00:00Z"},
|
||||
"text": [{"page":1,"text":"…"}],
|
||||
"tables": [
|
||||
{"page":2,"index":0,"rows":[["Item","Qty","Price"],["Widget","2","9.99"]],
|
||||
"bbox":[72,140,540,320],"confidence":0.82}
|
||||
],
|
||||
"warnings": ["page 3: no text layer (scanned image?)"],
|
||||
"extract_ms": 640
|
||||
}
|
||||
```
|
||||
Limits: ≤ 10 MB, ≤ 50 pages processed, 30 s budget, encrypted PDFs rejected, image-only pages return empty text with a
|
||||
warning (no OCR at launch — say so in the listing).
|
||||
|
||||
### 3.3 `GET /health`
|
||||
`{"status":"ok","timestamp":"…","uptime":123,"chromium":"ok|unavailable","cache":"none","version":"1.0.0"}` —
|
||||
returns 503 when Chromium cannot launch (so the RapidAPI health check and the canary see it).
|
||||
|
||||
### 3.4 Error body (every non-2xx)
|
||||
`{"error":{"code":"page_limit_exceeded","message":"Rendered document has 61 pages; limit is 50.","request_id":"…"}}`
|
||||
|
||||
| HTTP | code | when |
|
||||
|---|---|---|
|
||||
| 400 | `invalid_json`, `missing_html`, `invalid_option`, `not_a_pdf`, `invalid_page_range` | validation |
|
||||
| 401 | `gateway_required` | missing/wrong proxy secret |
|
||||
| 413 | `payload_too_large`, `page_limit_exceeded` | 2 MB / 10 MB / 50 pages |
|
||||
| 415 | `unsupported_media_type` | wrong content type |
|
||||
| 422 | `render_failed`, `encrypted_pdf`, `parse_failed` | content problems |
|
||||
| 429 | `rate_limited` | gateway quota (RapidAPI) or IP backstop; body names the PRO tier + `X-Upgrade-Url` |
|
||||
| 500 | `internal_error` | never includes a stack |
|
||||
| 504 | `render_timeout`, `extract_timeout` | 30 s budget hit |
|
||||
|
||||
Every response carries `X-Request-Id`, and on BASIC `X-Upgrade-Url: https://rapidapi.com/Monoxide3637/api/docforge/pricing`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Security controls
|
||||
|
||||
| Threat | Control |
|
||||
|---|---|
|
||||
| SSRF via rendered HTML (`<img src="http://169.254.169.254/…">`, `<link>`, `fetch()`, `@import`) | `page.setRequestInterception(true)`; abort every request whose URL is not `about:blank` or `data:`; count them into `warnings`; no allowlist at launch. Also `--disable-features=NetworkService` is NOT used (breaks rendering); interception is the control. JS off by default (`waitForJs:false` → `page.setJavaScriptEnabled(false)`). |
|
||||
| SSRF via extract | No URL input at all; file or base64 only. |
|
||||
| Local file read (`file://`) | Blocked by the same interception (non-data URLs aborted); Chromium launched with `--disable-file-system`-equivalent flags from `@sparticuz/chromium` defaults; content set via `page.setContent`, never navigated to a file. |
|
||||
| Resource exhaustion (huge DOM, infinite JS, zip-bomb PDFs) | Body cap 2 MB / 10 MB (`express.json({limit})`, multer `limits.fileSize`), 30 s per-request budget with `page.setDefaultTimeout`, 50-page cap, `scale` bounded, one page per browser context and `context.close()` in `finally`. |
|
||||
| Cost abuse / direct calls bypassing the gateway | `X-RapidAPI-Proxy-Secret` compared with a constant-time check; IP rate limit backstop. |
|
||||
| Data retention | Nothing written to disk except Chromium's own tmp; no request bodies logged; telemetry carries no content (§5). |
|
||||
| Header/response hygiene | helmet; `X-Content-Type-Options: nosniff`; PDFs returned as `attachment` unless `inline=true`. |
|
||||
| Secrets | Vercel env vars only; N8N webhook token, proxy secret, KV token never in the repo; `.env` chmod 600 locally. |
|
||||
| Dependency hygiene | `npm audit` in the nightly canary Action; pin `@sparticuz/chromium` to the version matching `puppeteer-core` (the package README lists the pairing — check at build time). |
|
||||
|
||||
---
|
||||
|
||||
## 5. Telemetry (day one)
|
||||
|
||||
### 5.1 Gateway headers to read
|
||||
RapidAPI forwards subscriber identity to the origin as request headers — the ones the build reads are
|
||||
`X-RapidAPI-User` (subscriber username), `X-RapidAPI-Subscription` (plan name, e.g. `BASIC`/`PRO`) and
|
||||
`X-RapidAPI-Proxy-Secret` (origin verification). **UNVERIFIED this run** (rapidapi.com pages not fetched by rule);
|
||||
confirm the exact header names on the RapidAPI docs "Request headers" page in the first build block and adjust
|
||||
`middleware/gateway.ts` before anything else is built on them.
|
||||
|
||||
### 5.2 POST payload to the N8N webhook (fire-and-forget, `waitUntil`, 2 s timeout, never blocks the response)
|
||||
```json
|
||||
{
|
||||
"v": 1,
|
||||
"ts": "2026-10-20T14:03:11.412Z",
|
||||
"api": "docforge",
|
||||
"endpoint": "pdf.from_html", // pdf.from_html | pdf.extract | health
|
||||
"subscriber": "sha256:…first16", // hashed X-RapidAPI-User (raw username never leaves the function)
|
||||
"plan": "PRO", // X-RapidAPI-Subscription or "direct" when absent
|
||||
"status": 200,
|
||||
"error_code": null,
|
||||
"duration_ms": 812,
|
||||
"pages": 3,
|
||||
"input_bytes": 18233,
|
||||
"output_bytes": 48211,
|
||||
"warnings": 1,
|
||||
"region": "iad1",
|
||||
"cold_start": false,
|
||||
"request_id": "…"
|
||||
}
|
||||
```
|
||||
Auth: `Authorization: Bearer <N8N_WEBHOOK_TOKEN>` (masked; lives in Vercel env + Vault). N8N side (separate build,
|
||||
playbook_n8n_automations): validate token → insert one row per event into a raw `marketing_api_events` table (or the
|
||||
approved raw table of the `marketing_*` set) → a Monday cron aggregates into the shared **weekly-metrics** table:
|
||||
`week, api, plan, requests, unique_subscribers, error_rate, p50_ms, p95_ms, pages_rendered`. No content, no IPs.
|
||||
|
||||
### 5.3 Paying-customer counter (Vercel KV)
|
||||
- Key `docforge:paying:<subscriber_hash>` set on the first request whose plan ∈ {PRO, ULTRA, MEGA}; `docforge:paying:count`
|
||||
incremented once per new key; alert when count reaches **15** (ntfy via N8N; the 15 = 5-customer buffer before the
|
||||
~20 threshold in the framework's build queue).
|
||||
- Weekly reconcile: the Monday N8N job compares the KV count with distinct paid-plan subscribers seen in
|
||||
`marketing_api_events` (a subscriber who downgrades still exists in KV — the framework only needs an upper-bound alert).
|
||||
- **UNVERIFIED / verify at build:** "Vercel KV" is now provisioned through the Vercel Marketplace and is Upstash-backed
|
||||
(Vercel's own KV product was folded into Marketplace storage in late 2024). If that conflicts with "no Upstash", the
|
||||
fallback that keeps the decision's intent is Vercel Edge Config (read-mostly; counter updated by N8N through the
|
||||
Vercel API) or simply letting N8N own the counter in Postgres and send the 15-alert. Raise this at the first build
|
||||
block; do not silently pick.
|
||||
|
||||
---
|
||||
|
||||
## 6. Vercel configuration
|
||||
|
||||
`vercel.json`
|
||||
```json
|
||||
{
|
||||
"functions": { "api/index.ts": { "maxDuration": 60, "memory": 2048 } },
|
||||
"routes": [{ "src": "/(.*)", "dest": "/api/index.ts" }]
|
||||
}
|
||||
```
|
||||
- **Timeout 60 s** (decision). Facts read 2026-09-15 from search snippets, **UNVERIFIED**: with Fluid compute the
|
||||
Hobby plan allows up to 300 s and Pro up to 800 s (https://vercel.com/docs/functions/configuring-functions/duration ,
|
||||
https://vercel.com/changelog/higher-defaults-and-limits-for-vercel-functions-running-fluid-compute); default memory
|
||||
is 1 vCPU / 2 GB and Hobby cannot change it (https://vercel.com/docs/functions/configuring-functions/memory). So the
|
||||
`memory` key may be ignored on Hobby — harmless.
|
||||
- **Function bundle size:** snippets say Vercel raised the 250 MB function limit to 5 GB on 2026-06-30
|
||||
(https://opsily.com/blog/vercel-serverless-function-size-limit-exceeded ,
|
||||
https://www.danielolawoyin.com/blog/puppeteer-on-vercel-the-ultimate-guide-to-serverless-browser-automation-2026) —
|
||||
**UNVERIFIED**; `@sparticuz/chromium` ships a ~50 MB compressed binary that fits under the old limit anyway
|
||||
(https://gist.github.com/kettanaito/56861aff96e6debc575d522dd03e5725 ,
|
||||
https://www.stefanjudis.com/blog/how-to-use-headless-chrome-in-serverless-functions/). If the bundle is rejected,
|
||||
switch to `@sparticuz/chromium-min` with the binary hosted as a Vercel Blob (same package family, no new vendor).
|
||||
- **Cold start:** the decision says verify. Measure p50/p95 of the first request after 15 min idle with the canary
|
||||
(§10); expectation from the snippets is 2–5 s cold, < 1 s warm (UNVERIFIED). Keep the browser instance cached at
|
||||
module scope inside the function (allowed: it is not an env var) so warm calls reuse it.
|
||||
- Env vars (`vercel env add`, production; values masked): `RAPIDAPI_PROXY_SECRET`, `N8N_WEBHOOK_URL`,
|
||||
`N8N_WEBHOOK_TOKEN`, `KV_REST_API_URL`, `KV_REST_API_TOKEN` (or the fallback per §5.3), `RENDER_MODE=sparticuz`.
|
||||
Locally `RENDER_MODE=puppeteer`. Use `printf` not `echo` when piping values (playbook gotcha).
|
||||
- Region: `iad1` default (matches the playbook's N. Virginia note); no cron needed (no Redis to keep alive).
|
||||
|
||||
---
|
||||
|
||||
## 7. RapidAPI listing
|
||||
|
||||
### 7.1 Plans (Monetize tab; quota in Objects → Requests row)
|
||||
| Plan | Price | Rate limit | Monthly quota | Overage |
|
||||
|---|---|---|---|---|
|
||||
| BASIC | Free | 10 / min | 100 (**hard** limit) | — |
|
||||
| PRO | $12 / mo | 60 / min | 2,000 | $0.005 / req (soft) |
|
||||
| ULTRA | $39 / mo | 300 / min | 10,000 | $0.005 / req (soft) |
|
||||
| MEGA | $99 / mo | 1,000 / min | 50,000 | $0.005 / req (soft) |
|
||||
Price ladder from the framework applies later (10 paying + value add → PRO $14.99, etc.).
|
||||
|
||||
### 7.2 Short description (≤ 1 line)
|
||||
> Convert HTML to pixel-perfect PDF and turn any PDF into clean JSON — text and tables — with one fast, no-setup API.
|
||||
|
||||
### 7.3 Long description (Docs tab / General tab)
|
||||
> **DocForge** does the two document jobs every app eventually needs, without running a browser farm or a parsing
|
||||
> library yourself.
|
||||
>
|
||||
> **HTML → PDF.** Send HTML (with inline CSS) and get back a print-quality PDF rendered by headless Chromium: A4, Letter
|
||||
> or Legal, margins, landscape, headers and footers, background graphics. Invoices, reports, tickets, certificates,
|
||||
> statements.
|
||||
>
|
||||
> **PDF → JSON.** Upload a PDF and get its text per page, document metadata, and every detected table as rows of cells
|
||||
> with a confidence score. Feed it straight into your database, spreadsheet or LLM pipeline.
|
||||
>
|
||||
> Fast and predictable: sub-second warm renders for typical documents, hard caps (2 MB HTML in, 10 MB PDF in, 50 pages)
|
||||
> so you always know what a call costs, and clear error codes for everything else. Rendering is fully sandboxed — the
|
||||
> renderer cannot reach the network, so your documents never trigger outbound requests.
|
||||
>
|
||||
> Free BASIC plan: 100 requests a month, enough to build and demo. PRO from $12 a month. Overage on paid plans is
|
||||
> $0.005 per request, never a surprise bill.
|
||||
>
|
||||
> Runnable examples in Python, JavaScript and curl: github.com/<org>/docforge-examples. Support: the address on this
|
||||
> listing (replies within one business day).
|
||||
>
|
||||
> **Perfect For**
|
||||
> - SaaS apps that need PDF invoices, receipts, statements or certificates from an HTML template
|
||||
> - Internal tools that turn dashboards or reports into shareable PDFs
|
||||
> - Data pipelines that pull tables out of supplier PDFs, bank statements or price lists
|
||||
> - LLM / RAG applications that need clean per-page text instead of raw PDF bytes
|
||||
> - No-code builders (Make, n8n, Zapier) that need a single HTTP call for PDF generation or parsing
|
||||
>
|
||||
> **Not (yet) for:** scanned/image-only PDFs (no OCR at launch) and documents that must load remote fonts or images
|
||||
> at render time (inline them as base64).
|
||||
>
|
||||
> Changelog · 1.0.0 (2026-10) initial release: `POST /v1/pdf/from-html`, `POST /v1/pdf/extract`, `GET /health`.
|
||||
|
||||
### 7.4 Endpoint definitions (Definitions → Endpoints)
|
||||
Two REST endpoints from `openapi.yaml` (§8), each with a saved example request **and** a real sample response captured
|
||||
from the live deployment (playbook: test live before writing examples). Logo 500×500 PNG (navy circle, white "D",
|
||||
gold accent — playbook style). Category: Data (or Tools if Data is rejected). Health check: `/health`, 200.
|
||||
|
||||
---
|
||||
|
||||
## 8. OpenAPI 3.0 (`openapi.yaml`)
|
||||
|
||||
```yaml
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: DocForge
|
||||
version: 1.0.0
|
||||
description: HTML to PDF rendering and PDF to text/tables JSON extraction.
|
||||
contact: { name: The Boring API Company, email: <support-address-on-listing> }
|
||||
servers:
|
||||
- url: https://docforge.p.rapidapi.com
|
||||
description: RapidAPI gateway
|
||||
security:
|
||||
- RapidAPIKey: []
|
||||
RapidAPIHost: []
|
||||
paths:
|
||||
/v1/pdf/from-html:
|
||||
post:
|
||||
summary: Render HTML to PDF
|
||||
operationId: renderHtmlToPdf
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/RenderRequest' }
|
||||
example:
|
||||
html: "<h1>Invoice #1042</h1><p>Total: $120.00</p>"
|
||||
options: { format: A4, printBackground: true }
|
||||
output: base64
|
||||
responses:
|
||||
'200':
|
||||
description: PDF (binary) or JSON with base64 when output=base64
|
||||
headers:
|
||||
X-DocForge-Pages: { schema: { type: integer } }
|
||||
X-DocForge-Render-Ms: { schema: { type: integer } }
|
||||
X-Request-Id: { schema: { type: string } }
|
||||
content:
|
||||
application/pdf:
|
||||
schema: { type: string, format: binary }
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/RenderResponse' }
|
||||
'400': { $ref: '#/components/responses/Error' }
|
||||
'413': { $ref: '#/components/responses/Error' }
|
||||
'422': { $ref: '#/components/responses/Error' }
|
||||
'429': { $ref: '#/components/responses/Error' }
|
||||
'504': { $ref: '#/components/responses/Error' }
|
||||
/v1/pdf/extract:
|
||||
post:
|
||||
summary: Extract text and tables from a PDF
|
||||
operationId: extractPdf
|
||||
parameters:
|
||||
- in: query
|
||||
name: mode
|
||||
schema: { type: string, enum: [both, text, tables], default: both }
|
||||
- in: query
|
||||
name: pages
|
||||
schema: { type: string, example: "1-3,5" }
|
||||
description: 1-based page selection; at most 50 pages are processed.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
multipart/form-data:
|
||||
schema:
|
||||
type: object
|
||||
required: [file]
|
||||
properties:
|
||||
file: { type: string, format: binary, description: PDF up to 10 MB }
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [pdf_base64]
|
||||
properties:
|
||||
pdf_base64: { type: string }
|
||||
responses:
|
||||
'200':
|
||||
description: Extracted content
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/ExtractResponse' }
|
||||
'400': { $ref: '#/components/responses/Error' }
|
||||
'413': { $ref: '#/components/responses/Error' }
|
||||
'415': { $ref: '#/components/responses/Error' }
|
||||
'422': { $ref: '#/components/responses/Error' }
|
||||
'429': { $ref: '#/components/responses/Error' }
|
||||
'504': { $ref: '#/components/responses/Error' }
|
||||
/health:
|
||||
get:
|
||||
summary: Service health
|
||||
security: []
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status: { type: string }
|
||||
timestamp: { type: string, format: date-time }
|
||||
uptime: { type: number }
|
||||
chromium: { type: string, enum: [ok, unavailable] }
|
||||
cache: { type: string }
|
||||
version: { type: string }
|
||||
'503': { description: Chromium unavailable }
|
||||
components:
|
||||
securitySchemes:
|
||||
RapidAPIKey: { type: apiKey, in: header, name: X-RapidAPI-Key }
|
||||
RapidAPIHost: { type: apiKey, in: header, name: X-RapidAPI-Host }
|
||||
responses:
|
||||
Error:
|
||||
description: Error
|
||||
content:
|
||||
application/json:
|
||||
schema: { $ref: '#/components/schemas/ErrorBody' }
|
||||
schemas:
|
||||
RenderRequest:
|
||||
type: object
|
||||
required: [html]
|
||||
properties:
|
||||
html: { type: string, maxLength: 2000000 }
|
||||
options:
|
||||
type: object
|
||||
properties:
|
||||
format: { type: string, enum: [A4, Letter, Legal], default: A4 }
|
||||
landscape: { type: boolean, default: false }
|
||||
margin:
|
||||
type: object
|
||||
properties:
|
||||
top: { type: string, default: 20mm }
|
||||
right: { type: string, default: 15mm }
|
||||
bottom: { type: string, default: 20mm }
|
||||
left: { type: string, default: 15mm }
|
||||
printBackground: { type: boolean, default: true }
|
||||
scale: { type: number, minimum: 0.5, maximum: 2, default: 1 }
|
||||
headerTemplate: { type: string, maxLength: 4096 }
|
||||
footerTemplate: { type: string, maxLength: 4096 }
|
||||
waitForJs: { type: boolean, default: false }
|
||||
output: { type: string, enum: [binary, base64], default: binary }
|
||||
RenderResponse:
|
||||
type: object
|
||||
properties:
|
||||
pdf_base64: { type: string }
|
||||
pages: { type: integer }
|
||||
bytes: { type: integer }
|
||||
render_ms: { type: integer }
|
||||
warnings: { type: array, items: { type: string } }
|
||||
ExtractResponse:
|
||||
type: object
|
||||
properties:
|
||||
pages: { type: integer }
|
||||
processed_pages: { type: array, items: { type: integer } }
|
||||
metadata:
|
||||
type: object
|
||||
properties:
|
||||
title: { type: string, nullable: true }
|
||||
author: { type: string, nullable: true }
|
||||
producer: { type: string, nullable: true }
|
||||
created: { type: string, nullable: true }
|
||||
text:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
page: { type: integer }
|
||||
text: { type: string }
|
||||
tables:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
page: { type: integer }
|
||||
index: { type: integer }
|
||||
rows:
|
||||
type: array
|
||||
items: { type: array, items: { type: string } }
|
||||
bbox: { type: array, items: { type: number }, minItems: 4, maxItems: 4 }
|
||||
confidence: { type: number, minimum: 0, maximum: 1 }
|
||||
warnings: { type: array, items: { type: string } }
|
||||
extract_ms: { type: integer }
|
||||
ErrorBody:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: object
|
||||
required: [code, message]
|
||||
properties:
|
||||
code: { type: string }
|
||||
message: { type: string }
|
||||
request_id: { type: string }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Test corpus (`test/corpus/`, all synthetic or public-domain; nothing with personal data)
|
||||
|
||||
| # | File | Exercises |
|
||||
|---|---|---|
|
||||
| 1 | `invoice.html` — 1-page invoice, inline CSS, base64 logo | happy path, `printBackground`, headers/footers |
|
||||
| 2 | `report-12p.html` — 12 pages, page-break CSS, table spanning pages | multi-page, `X-DocForge-Pages` |
|
||||
| 3 | `landscape-letter.html` | `format`/`landscape` options |
|
||||
| 4 | `ssrf.html` — `<img src="http://169.254.169.254/latest/meta-data/">`, `<link href="https://…">`, `<script>fetch('http://10.0.0.1')</script>`, `@import url(...)`, `<iframe src="file:///etc/passwd">` | every request aborted; `warnings` count = 5; render still succeeds |
|
||||
| 5 | `js-heavy.html` — script that builds the DOM after 2 s; and a `while(true){}` variant | `waitForJs` true/false; timeout → 504 |
|
||||
| 6 | `oversize-2mb.html` (generated) and `pages-60.html` (generated) | 413 `payload_too_large`, 413 `page_limit_exceeded` |
|
||||
| 7 | `unicode-rtl.html` — Arabic, CJK, emoji, combining marks | font fallback with `@sparticuz/chromium` (known gap: CJK fonts may be missing — document or bundle a Noto subset) |
|
||||
| 8 | `tables-simple.pdf` (generated from #2) | extract: text + ruled table, confidence high |
|
||||
| 9 | `tables-borderless.pdf` — whitespace-aligned columns | table heuristic lower bound; confidence < 0.6 expected |
|
||||
| 10 | `multi-column-article.pdf` — two-column public-domain text | reading order of text per page |
|
||||
| 11 | `scanned-image-only.pdf` — one raster page | empty text + warning, no crash |
|
||||
| 12 | `encrypted.pdf` — owner password | 422 `encrypted_pdf` |
|
||||
| 13 | `not-a-pdf.bin`, `oversize-11mb.pdf` (generated), `pages-120.pdf` (generated) | 400 `not_a_pdf`, 413, page cap with `pages=` selection |
|
||||
| 14 | `metadata-rich.pdf` — title/author/created set | metadata block |
|
||||
| 15 | `pathological.pdf` — deeply nested object streams (generated fuzz) | parser time budget → 504 not hang |
|
||||
|
||||
`test/smoke.sh` runs 1–15 locally and (with the RapidAPI key from env, masked) against the gateway; the example repo's
|
||||
nightly Action reuses 1, 8 and `/health` as the canary.
|
||||
|
||||
---
|
||||
|
||||
## 10. Example repo (`docforge-examples`, public, MIT, non-promotional README)
|
||||
|
||||
```
|
||||
docforge-examples/
|
||||
├── README.md # what DocForge is, get a key on RapidAPI, run any example, support address
|
||||
├── python/
|
||||
│ ├── render_invoice.py # requests → from-html → invoice.pdf
|
||||
│ ├── extract_tables.py # multipart upload → tables → CSV
|
||||
│ └── requirements.txt
|
||||
├── node/
|
||||
│ ├── renderInvoice.mjs # fetch → binary → fs
|
||||
│ ├── extractTables.mjs # FormData upload → JSON
|
||||
│ └── package.json
|
||||
├── curl/
|
||||
│ ├── render.sh # single-line curl, base64 output → jq → file
|
||||
│ └── extract.sh
|
||||
├── samples/ # invoice.html, tables-simple.pdf (from the corpus)
|
||||
└── .github/workflows/canary.yml # nightly: run curl/*.sh against BASIC with a repo secret; fail → GitHub issue + ntfy via N8N
|
||||
```
|
||||
Support contact in the README = the Boring API Company Gmail. No "star us"/promotional language (GitHub AUP note from
|
||||
the retired outreach research). The canary doubles as the Service-Level watchdog (ranking input on RapidAPI).
|
||||
|
||||
---
|
||||
|
||||
## 11. Support inbox (build after launch; owned by the N8N project, listed here for sequencing)
|
||||
Gmail ("The Boring API Company") → N8N Gmail trigger (official API; the account/owner/brand are partner-meeting
|
||||
items) → classify (bug / how-to / billing / spam) → Claude drafts the reply from the README + OpenAPI + error table →
|
||||
if the draft contains a code sample, N8N runs it against the **live gateway with our own free key** and attaches the
|
||||
actual status/response; a failing sample blocks the draft → weekly human gate (approve / edit / reject; every verdict
|
||||
logged as training data with edit distance) → after 20 consecutive unedited approvals, auto-send is enabled per
|
||||
category. Billing questions always route to RapidAPI's own support link (we cannot see or change subscriptions).
|
||||
Disclosure line: see `research/ai-support-disclosure-research.md` (recommendation there; undecided until the user picks).
|
||||
|
||||
---
|
||||
|
||||
## 12. Day-by-day schedule (weekday business blocks; slot may slip behind the Etsy pipeline — shift whole weeks, keep order)
|
||||
|
||||
| Day | Date | Work | Done when |
|
||||
|---|---|---|---|
|
||||
| 1 | Mon 10-05 | Phase 2 scaffold (§2), `vercel.json`, `/health`, first deploy to a preview URL; **verify RapidAPI header names (§5.1) and KV/Marketplace status (§5.3)**; cold-start baseline with an empty render | `/health` 200 on Vercel; two UNVERIFIED items resolved and noted |
|
||||
| 2 | Tue 10-06 | `services/render.ts`: chromium launch, `setContent`, options mapping, interception (SSRF), page count, caps, timeouts | corpus 1–7 pass locally |
|
||||
| 3 | Wed 10-07 | Deploy render to prod; measure cold/warm p50/p95; fix bundle-size or font issues (`chromium-min` fallback if needed) | corpus 1–7 pass through the Vercel URL; numbers logged |
|
||||
| 4 | Thu 10-08 | `services/extract.ts` with pdfjs-dist: text per page, metadata, page selection, table heuristic v1 (x-cluster of text runs) | corpus 8, 10, 11–14 pass |
|
||||
| 5 | Fri 10-09 | Table heuristic v2 (borderless), confidence score, pathological/timeout handling | corpus 9, 15 pass; extract p95 < 5 s on a 20-page file |
|
||||
| 6 | Mon 10-12 | Gateway middleware, error bodies, `X-Upgrade-Url`/429 body, telemetry POST (§5.2) + N8N webhook receiver stub (N8N work per its playbook), KV counter | telemetry rows arrive in N8N for every corpus call |
|
||||
| 7 | Tue 10-13 | Phase 6: Studio project, General tab (base URL at the bottom!), endpoints from `openapi.yaml`, plans (§7.1), logo | listing private-complete |
|
||||
| 8 | Wed 10-14 | Docs tab (§7.3), saved example responses from live calls, health check, PayPal linked (if not already), visibility Public + **Save Changes** | listing public; own BASIC subscription + key created for the canary |
|
||||
| 9 | Thu 10-15 | Example repo (§10) + nightly canary Action; smoke through the gateway | canary green |
|
||||
| 10 | Fri 10-16 | Buffer: fix whatever the gateway changed (content-type, binary handling), update README/changelog, first weekly metrics row; write the handoff | day-60 kill clock starts at listing publish (≈ Wed 10-14 → day 60 ≈ Sun 12-13, read on Mon 12-14) |
|
||||
| 11–13 | 10-19 → 10-21 | Support inbox (§11) N8N build; disclosure line decision from the research doc; Uptime Kuma on `/health` | first real inbound handled through the gate |
|
||||
| weekly | Mon | 10 min: metrics table review, canary status, KV count, changelog line if anything shipped | — |
|
||||
|
||||
Kill / scale criteria unchanged from D (day 60 after listing: < 25 BASIC, 0 paid, < 100 docs clicks, SL ≥ 99 % → pause).
|
||||
EDGAR Events waits for the first paid subscriber or the day-60 kill.
|
||||
|
||||
---
|
||||
|
||||
## 13. UNVERIFIED list (this document)
|
||||
RapidAPI forwarded header names (§5.1) · Vercel KV = Marketplace/Upstash-backed (§5.3) · Vercel duration/memory
|
||||
limits and the 250 MB → 5 GB bundle change (§6, URLs given) · `@sparticuz/chromium` binary size and cold-start figures
|
||||
(§6) · CJK font coverage in the sparticuz build (§9 #7) · RapidAPI Data vs Tools category availability (§7.4).
|
||||
|
||||
## Update instructions
|
||||
Update §12 dates when the slot moves; replace each UNVERIFIED item with the verified fact + date on day 1; keep the
|
||||
contracts (§3) and `openapi.yaml` in lock-step — the YAML in this file is the seed, the repo file becomes the source of
|
||||
truth after day 7.
|
||||
@@ -0,0 +1,147 @@
|
||||
# ConfettiPrintCo (Etsy) — launch gameplan, 2026-09-16 → day-90 review 2026-12-15
|
||||
|
||||
- Written 2026-09-15 by a background agent (Fable 5.1) from the three 2026-09-15 DECISIONS.md entries,
|
||||
`research/etsy-business-evaluation.md` (F) and `research/digital-businesses-marketing-strategy.md` (J).
|
||||
Execution plan only — no design decisions are made here. Tracks: business_projects 39 (launch), 43 (Listing
|
||||
Engine), 46 (evaluation), 47 (demand method).
|
||||
- Day 0 = **Wed 2026-09-16** (developer-app registration). Day 30 = Fri 10-16. Day 60 = Sun 11-15 → reviewed Mon
|
||||
11-16. Day 90 = **Tue 2026-12-15**. Weekday blocks only: **biz 12:45–16:30**, **personal 16:30–22:00**. No
|
||||
weekend tasks anywhere in this plan.
|
||||
- Owners: **user** · **partner** (P1/P2, names TBD at the partner meeting) · **employee** (E, TBD) · **Claude**
|
||||
(interactive session or background agent) · **automation** (N8N / cron / scripts, unattended).
|
||||
- Anything not confirmed from a primary source is marked **UNVERIFIED**.
|
||||
|
||||
> **AMENDED 2026-09-15 17:05 (DECISIONS.md rows 22–24):** partner meeting = **Sunday 2026-09-20** (normal cadence), NOT Wed 09-30; the manual first batch of 3 listings moves from Fri 09-18 to **Mon 09-21**; weekly metrics are pulled via the Open API OAuth scopes once the app exists, not by a manual Stats-CSV export. Week-1/2 rows below are superseded where they conflict.
|
||||
|
||||
## 0. Ground rules baked into every task
|
||||
1. Products = code-generated typographic/geometric planner sticker sheets + journal dashboards (US Letter / A5 /
|
||||
Happy Planner Classic), PDF, ≤5 files ≤20 MB each. Birthday printables PARKED; junk-journal kits after listing 30.
|
||||
No AI-image slop; optional SDXL motifs are a later phase, not in this plan.
|
||||
2. Etsy only through the official **Open API v3** on our own shop (app key for scoring, OAuth for uploads). **Never
|
||||
fetch etsy.com** (DataDome 403), no browser automation, no scraping.
|
||||
3. Every listing description carries the AI-disclosure line. Offsite Ads opted out day 1. No review solicitation.
|
||||
4. Every agent proposal is stored next to the human verdict (`marketing_qa_verdicts`). Human gate = 15 min/week.
|
||||
5. POD = nothing until badge + $50 cumulative profit + a named customer-message owner (all three).
|
||||
6. Kill review at day 90 with ≥30 listings (F §7): kill if any two of — sales <3; views <300 trailing 30 d;
|
||||
favourites ÷ views <1.5 %; zero page-1 ranks. Pivot instead if views ≥300 and sales = 0. Hard stop on any
|
||||
Creativity-Standards takedown unresolved for 14 days.
|
||||
|
||||
## 1. Already built (2026-09-15, this agent)
|
||||
| Artifact | Path | State |
|
||||
|---|---|---|
|
||||
| Demand scorer | `/opt/appdata/docker/Business/etsy digital business idea/scripts/etsy_keyword_score.py` | dry-run tested; real run needs the keystring |
|
||||
| 20 seed phrases | `.../scripts/phrases.txt` | editable |
|
||||
| Sticker sheet prototype | `.../scripts/sticker_sheet_v0.py` → `out/sticker_sheet_v0.pdf` (5,024 bytes, 4×8 grid, 2 colours, cut margin) | rendered + visually checked |
|
||||
| Table DDL | `research/etsy-marketing-tables.sql` (5 `marketing_*` tables, idempotent) | NOT executed |
|
||||
|
||||
## 2. Week-by-week plan
|
||||
|
||||
### Week 1 — Wed 09-16 → Fri 09-18 (days 0–2): keys, tables, demand run, first manual batch
|
||||
| Day | Block | Task | Owner | Depends on |
|
||||
|---|---|---|---|---|
|
||||
| Wed 09-16 | biz | Register Etsy developer app on the user's own account (openapi.etsy.com → Developers → Create a New App; app name, "personal shop use", callback URL for later OAuth); keystring → Vault via the secrets path in `project_secrets_architecture.md`. Note the provisional-app daily call limit shown on the app page (**UNVERIFIED**; scoring needs <100 calls). | user | — |
|
||||
| Wed 09-16 | biz | Open the shop **ConfettiPrintCo** (set-up fee ~$15 **UNVERIFIED**), shop policies (digital = no returns), **opt out of Offsite Ads**, join **Share & Save**, create the unused "Printify" production partner, check Finances → payment account for a new-shop reserve (**UNVERIFIED**). | user | — |
|
||||
| Wed 09-16 | biz | Execute `etsy-marketing-tables.sql` in `business_projects` via the pg-query skill (dedup check first; one statement per `-c`). | Claude (interactive) | user approval in-session |
|
||||
| Wed 09-16 | personal | Review `phrases.txt` (20 phrases) — swap any the user knows are off-target. | user | — |
|
||||
| Thu 09-17 | biz | **Demand-scoring run**: `ETSY_API_KEY` from Vault (never printed) → `python3 etsy_keyword_score.py phrases.txt --out-dir out`. Read the markdown table; write rows into `marketing_keywords`. **Gate:** ≥2 strong phrases under 50,000 results → Etsy continues; <2 → Etsy fails, KDP launches first (KDP gameplan takes the biz block from Fri). | Claude (interactive) + user reads verdict | app key; tables |
|
||||
| Thu 09-17 | biz | If the gate passes: pick the 3 strongest phrases → 3 product briefs (e.g. functional sticker sheet, weekly dashboard A5, habit-tracker sheet Happy Planner). Log tag vocabulary from the top-20 tags. | user + Claude | demand run |
|
||||
| Thu 09-17 | personal | Generate v0 assets for the **one manual first batch** (3 listings): run `sticker_sheet_v0.py` variants (labels/palette per brief); Claude drafts a dashboard generator `dashboard_v0.py` (same reportlab pattern, A5 + Happy Planner page sizes). | Claude | briefs |
|
||||
| Fri 09-18 | biz | Manual first batch (starts the shop clock): 6 mockups per listing (Claude script: PDF → PNG at 150 dpi, placed on a plain 2-colour background; no stock photos), `claude -p --model claude-sonnet-5` listing copy with the AI-disclosure line, 13 tags from the vocabulary, price $2.49–3.49 stickers / $3.99 dashboards, 20 % launch sale. **User uploads the 3 listings by hand in Shop Manager** (this batch only; OAuth upload comes with the pipeline). Cost 3 × $0.20. | user (upload) + Claude (assets/copy) | shop open |
|
||||
| Fri 09-18 | biz | Send the **partner-meeting agenda** (section 4) to partners; propose a weekday slot in week 2 or 3. | user | — |
|
||||
| Fri 09-18 | personal | Record `marketing_listing_drafts` rows for the 3 manual listings (status `live`, `etsy_listing_id` from Shop Manager) so the pipeline's history starts at listing 1. | Claude | tables |
|
||||
|
||||
### Week 2 — Mon 09-21 → Fri 09-25 (days 5–9): pipeline build phase A (generation + assembly)
|
||||
KDP title A must also publish by 09-25 (separate gameplan); Etsy takes ~60 % of biz blocks this week.
|
||||
| Day | Block | Task | Owner | Depends on |
|
||||
|---|---|---|---|---|
|
||||
| Mon 09-21 | biz | Weekly metrics: export Etsy Stats + Orders CSV (or note "no data yet") → `marketing_weekly_metrics` (metrics: listings_live, views_30d, favourites_30d, orders). 20 min. | user (export) + automation (parse; Claude until N8N exists) | tables |
|
||||
| Mon 09-21 | biz | **A1 generator library**: refactor `sticker_sheet_v0.py` + `dashboard_v0.py` into `generators/` with a JSON brief input (labels, palette, grid, page size ∈ {letter, a5, hp_classic}) and deterministic output naming (`<slug>/<slug>_<size>.pdf`). | Claude (background agent, weekday block) | week-1 prototypes |
|
||||
| Tue 09-22 | biz | **A2 mockup assembler**: `assemble.py` — 6 PNGs per product (full sheet, 2 crops, size chart, "what you get" grid, cover with title text), all from the PDF via reportlab/pdftoppm; no external images. | Claude (background agent) | A1 |
|
||||
| Wed 09-23 | biz | **A3 copy generator**: `copy.py` runs `claude -p --model claude-sonnet-5` with the shared drafting prompt (keyword row + product facts + Etsy limits → title ≤140, 13 tags, description with mandatory disclosure line); writes `marketing_listing_drafts` status `copy_drafted`; `prompt_version` recorded. | Claude (background agent) | A1, tables |
|
||||
| Thu 09-24 | biz | **A4 QA judge**: `qa.py` — deterministic checklist (char limits, 13 tags, disclosure present, file ≤20 MB, price ≥ min, no IP words) + `claude -p` judge writing `agent_recommendation / agent_reason / agent_score` into `marketing_qa_verdicts`. | Claude (background agent) | A3 |
|
||||
| Fri 09-25 | biz | Run A1→A4 end-to-end on 10 keyword rows → 10 drafts in `qa_pending`. Fix breakages. | Claude | A1–A4 |
|
||||
| Fri 09-25 | personal | **First 15-min human gate**: user approves/rejects the 10 drafts; `human_verdict`, `override_reason` filled. | user | A4 |
|
||||
|
||||
### Week 3 — Mon 09-28 → Fri 10-02 (days 12–16): pipeline build phase B (OAuth upload + gate UI) + partner meeting
|
||||
| Day | Block | Task | Owner | Depends on |
|
||||
|---|---|---|---|---|
|
||||
| Mon 09-28 | biz | Weekly metrics (20 min). | user + automation | — |
|
||||
| Mon 09-28 | biz | **B1 OAuth**: one-time authorisation of the developer app against the shop (PKCE flow, scopes `listings_w listings_r shops_r transactions_r` — scope names **UNVERIFIED** until the app page is read); refresh token → Vault; `etsy_auth.py` refreshes on demand. | user (one browser consent click) + Claude | app key |
|
||||
| Tue 09-29 | biz | **B2 uploader**: `upload.py` — createDraftListing → uploadListingFile (PDF) → uploadListingImage ×6 → updateListing (tags, price, `who_made=i_did`, `is_digital`) → publish; writes `etsy_listing_id`, status `live`. Test on ONE listing first, in draft state, confirm in Shop Manager. | Claude (background agent) | B1, approved drafts |
|
||||
| Wed 09-30 | biz | **B3 gate runner**: N8N Form (or a one-page local HTML) listing `qa_pending` drafts with mockup + judge reason; approve/reject/revise buttons write `marketing_qa_verdicts`; approve triggers B2. ntfy on new gate batch. | Claude (background agent) | B2 |
|
||||
| Wed 09-30 | personal | **Partner meeting** (agenda in section 4). Record decisions in DECISIONS.md and `marketing_qa_verdicts.human_reviewer` convention. | user + partners | agenda sent 09-18 |
|
||||
| Thu 10-01 | biz | Upload the ~10 approved week-2 drafts via B2 (→ ~13 live). Cost 10 × $0.20. | automation (Claude supervises the first batch) | gate |
|
||||
| Fri 10-02 | biz | **B4 weekly metrics automation**: N8N watched folder for the Stats/Orders CSV → `marketing_weekly_metrics`; Monday digest via llama3.1 → ntfy; C5 kill/scale evaluator as SQL on days 30/60/90. | Claude (background agent) | tables |
|
||||
| Fri 10-02 | personal | Human gate #2 (15 min). | user (or gate assignee named at the meeting) | B3 |
|
||||
|
||||
### Week 4 — Mon 10-05 → Fri 10-09 (days 19–23): steady state begins; reach ~30 listings
|
||||
| Day | Block | Task | Owner |
|
||||
|---|---|---|---|
|
||||
| Mon 10-05 | biz | Metrics ingest (automated; user checks the digest, 10 min). DocForge (API) build may start this week if Etsy pipeline is done; else it slips — log in DECISIONS.md. | automation + user |
|
||||
| Tue 10-06 | biz | Pipeline run: 10 new keyword rows (rotate phrases 4–13 from the demand table) → drafts. | automation |
|
||||
| Wed 10-07 | biz | Add A5 + Happy Planner size variants of the 5 best-performing sheets (each size = its own listing). | automation |
|
||||
| Thu 10-08 | biz | Human gate #3 → upload → **~30 listings live** (target ≥30 by 10-09; F said 30 by day 21). | gate assignee + automation |
|
||||
| Fri 10-09 | biz | Rewrite the bottom-10 titles from the digest's search-term data (Claude drafts; gate approves). | Claude + gate assignee |
|
||||
|
||||
### Week 5 — Mon 10-12 → Fri 10-16 (days 26–30): **day-30 gate on Fri 10-16**
|
||||
| Day | Block | Task | Owner |
|
||||
|---|---|---|---|
|
||||
| Mon 10-12 | biz | Metrics; pipeline run (10 more drafts; junk-journal kit generator spec drafted only if stickers/dashboards ≥30 live). | automation + Claude |
|
||||
| Wed 10-14 | biz | Human gate #4 → upload. | gate assignee + automation |
|
||||
| Fri 10-16 | biz | **Day-30 gate** (C5 SQL → ntfy): ≥1 sale OR ≥300 views/30 d → continue to 60 listings; else hold at 30 and apply F §7 pivot logic. **Pinterest day-30 decision**: only if 30 listings exist and the meeting named an employee for the 10-min/day warm-up; otherwise stays OUT. Log in DECISIONS.md. | user (decision), automation (inputs) |
|
||||
|
||||
### Weeks 6–9 — Mon 10-19 → Fri 11-13 (days 33–58): scale to 60, watch for the first sale
|
||||
Weekly rhythm (all weekday, all under 1 h of human time per week):
|
||||
- **Mon biz** metrics digest (automation) + 10-min read (user). **Tue biz** pipeline batch of 10 (automation). **Thu biz** human gate 15 min (assignee) → upload (automation). **Fri biz** bottom-10 title rewrites (Claude → gate).
|
||||
- Week 6 (10-19): junk-journal kit generator (`kit_v0.py`, ephemera-style geometric labels/tags/pockets, PDF ≤20 MB) if ≥30 sticker/dashboard listings are live; else more size variants.
|
||||
- Week 7 (10-26): expected first-sale window opens (F: 21–56 d after 30 listings → ~11-06 to 12-11 with 30 live on 10-09 — **UNVERIFIED** secondary-source range). Any Creativity-Standards notice → respond within 14 days (owner named at the meeting).
|
||||
- Week 8 (11-02): 60 listings target. First `outcome_30d` back-fill into `marketing_qa_verdicts` for the week-2 drafts (automation).
|
||||
- Week 9 (11-09): if cumulative profit ≥$50 AND one listing shows a Bestseller/Popular badge → ask the user for F's $42 Etsy Ads ranking experiment (approval only, nothing auto-spent). Cross-funding to KDP ads is allowed only after this same gate; every transfer logged.
|
||||
|
||||
### Week 10 — Mon 11-16 → Fri 11-20: day-60 review (day 60 = Sun 11-15 → reviewed Mon 11-16)
|
||||
- Mon 11-16 biz: C5 runs the day-60 inputs (listings live, views 30 d, fav/views, orders, page-1 count). User reads; no kill at day 60 — this is the trend check. If `edit_distance` in the gate is not falling after 20 drafts, revise `prompt_version` (J §6). If judge/human agreement ≥90 % over 30 items, the gate may run every second week.
|
||||
- Wed 11-18 biz: review the opt-in page (`/confetti` bonus pack link on the last PDF page) — hub site status is a partner-meeting item; if no hub exists yet, the last page carries the shop link only.
|
||||
|
||||
### Weeks 11–13 — Mon 11-23 → Fri 12-11: hold the rhythm, prepare the kill review
|
||||
- Thu 11-26 is US Thanksgiving — skip that day's gate; move it to Wed 11-25.
|
||||
- Week 12 (11-30): freeze new product types; only variants/rewrites, so the day-90 numbers reflect the launch set.
|
||||
- Week 13 (12-07): Claude drafts the day-90 review memo from `marketing_weekly_metrics` (automation) for user reading on Fri 12-11.
|
||||
|
||||
### Week 14 — Mon 12-14 → Tue 12-15: **day-90 kill review**
|
||||
- Tue 12-15 biz: apply F §7 with ≥30 listings: any two of {sales <3, views <300 trailing 30 d, fav/views <1.5 %, zero page-1 ranks} → kill (pause Etsy, keep listings up, chain moves on). Views ≥300 and sales = 0 → pivot 2 weeks (price/mockups) before re-applying. Otherwise scale: 60→100 listings, junk-journal kits, POD gate re-checked. Decision → DECISIONS.md + `marketing_weekly_metrics` row `kill_review`.
|
||||
|
||||
## 3. Pipeline build phases (summary)
|
||||
| Phase | Days | Components | Done when |
|
||||
|---|---|---|---|
|
||||
| A generation + assembly | 09-21 → 09-25 | A1 generators (brief JSON → PDF, 3 sizes), A2 mockups ×6, A3 `claude -p` copy w/ disclosure, A4 QA judge | 10 drafts reach `qa_pending` end-to-end |
|
||||
| B upload + gate | 09-28 → 10-02 | B1 OAuth + token refresh, B2 Open API v3 uploader, B3 gate UI + ntfy, B4 metrics automation + C5 evaluator | one draft goes gate → live without manual Shop Manager work |
|
||||
| C evolution | from 10-19 | kit generator, size variants, prompt_version tuning from edit_distance, judge-only gating after ≥90 % agreement | gate time trends toward 0 |
|
||||
|
||||
## 4. Partner-meeting agenda (Wed 09-30 personal block; agenda sent Fri 09-18)
|
||||
1. Etsy account owner of record + Etsy Payments / tax identity (must be one named person).
|
||||
2. **Customer-message owner** (also POD gate condition 3) and the takedown-response owner (14-day clock).
|
||||
3. Weekly 15-min QA-gate assignee (partner's mother was floated) + backup; how override_reason must be written.
|
||||
4. Demand-method re-runs: who triggers the monthly re-score (or automation only).
|
||||
5. Pinterest day-30 opt-in: is an employee available for 10 min/day × 2 weeks from 10-16? If not, Pinterest stays OUT.
|
||||
6. Hub site domain/brand: `*.reverseproxyserver.net` vs buying a ~$12/yr domain; owner of `/confetti` opt-in page.
|
||||
7. `marketing_*` table ownership + who reads the Monday digest when the user is away.
|
||||
8. Cross-funding rule read-back: Etsy profit → KDP ads only after Etsy's own gate; every transfer logged.
|
||||
9. Company support inbox / umbrella brand (shared with the API and KDP items on the same agenda).
|
||||
|
||||
## 5. Metrics that feed the gates (all in `marketing_weekly_metrics`, business = 'etsy')
|
||||
| Metric | Source | Used by |
|
||||
|---|---|---|
|
||||
| listings_live | Open API v3 `getListingsByShop` (after OAuth) or Shop Manager count | day-30/90 precondition (≥30) |
|
||||
| views_30d, favourites_30d | Etsy Stats CSV (weekly export) or Open API shop stats (**UNVERIFIED** whether v3 exposes per-listing views) | kill rules 2 + 3, day-30 gate |
|
||||
| orders, revenue_cents, profit_cents | Orders CSV / `getShopReceipts` (transactions_r) | kill rule 1, $50 profit gate, cross-funding ledger |
|
||||
| page1_listings | `findAllListingsActive` for each listing's primary phrase, position of our listing_id in the top 48 (API, not browser) | kill rule 4 |
|
||||
| badge_listings | Shop Manager (badges are not in the public API — **UNVERIFIED**) | POD gate, ads-test gate |
|
||||
| fav_per_view | derived | kill rule 3 |
|
||||
| gate_agreement, edit_distance | `marketing_qa_verdicts` | evolution loop (J §6) |
|
||||
|
||||
## 6. UNVERIFIED list
|
||||
Provisional-app daily rate limit; OAuth scope names; whether v3 exposes per-listing views/badges; set-up fee amount;
|
||||
new-shop payment reserve; first-sale time range (secondary sources); Thanksgiving handling assumes partners observe it.
|
||||
|
||||
Update rule: revise dates when the demand gate result lands (09-17) and after the partner meeting (09-30); replace
|
||||
UNVERIFIED items as the app page / API responses confirm them.
|
||||
@@ -0,0 +1,182 @@
|
||||
-- ============================================================================
|
||||
-- marketing_* tables for the business_projects Postgres DB
|
||||
-- Approved 2026-09-15 (DECISIONS.md, strategy grill-me Q6). DDL ONLY — this
|
||||
-- file was written by a background agent and has NOT been executed. Run it
|
||||
-- via the pg-query skill (one statement per -c) after a dedup check.
|
||||
--
|
||||
-- Shared by Etsy (ConfettiPrintCo), KDP (J.M. Hartley) and API (DocForge);
|
||||
-- the `business` column ('etsy' | 'kdp' | 'api') separates them. Every table
|
||||
-- that stores an agent proposal also stores the human verdict next to it
|
||||
-- (training-data rule: feedback_always_include_training_loop).
|
||||
--
|
||||
-- 1. marketing_keywords keyword intake + demand scores (pipeline stage 1)
|
||||
-- 2. marketing_listing_drafts generated product + copy awaiting QA / upload
|
||||
-- 3. marketing_qa_verdicts Claude-judge QA + weekly human gate (training data)
|
||||
-- 4. marketing_optins product-embedded opt-in subscribers
|
||||
-- 5. marketing_weekly_metrics Monday metrics feeding the kill/scale rules
|
||||
-- ============================================================================
|
||||
|
||||
-- 1. Keyword intake ---------------------------------------------------------
|
||||
-- One row per (business, phrase, scored_on). Etsy rows are written by
|
||||
-- scripts/etsy_keyword_score.py (Open API v3 findAllListingsActive).
|
||||
-- business which shop/channel the phrase belongs to
|
||||
-- phrase exact search phrase scored
|
||||
-- scored_on date the demand run happened
|
||||
-- source 'etsy_open_api_v3' | 'manual' | 'search_console' | ...
|
||||
-- supply_count result count (Etsy `count`) = competition
|
||||
-- demand_score numeric demand proxy (Etsy: top-20 favourites median)
|
||||
-- demand_json raw detail (fav sum/max, distinct shops, shop_ids, ...)
|
||||
-- vocabulary top tags of the leaders, for our own 13-tag sets
|
||||
-- is_strong demand gate flag (Etsy: <50k results AND strong favourites)
|
||||
-- cluster_id optional embedding cluster (nomic-embed) for dedup
|
||||
-- status 'candidate' | 'chosen' | 'rejected' | 'used'
|
||||
-- notes free text
|
||||
CREATE TABLE IF NOT EXISTS marketing_keywords (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
business TEXT NOT NULL CHECK (business IN ('etsy','kdp','api')),
|
||||
phrase TEXT NOT NULL,
|
||||
scored_on DATE NOT NULL DEFAULT CURRENT_DATE,
|
||||
source TEXT NOT NULL DEFAULT 'etsy_open_api_v3',
|
||||
supply_count INTEGER,
|
||||
demand_score NUMERIC(12,2),
|
||||
demand_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
vocabulary TEXT[] NOT NULL DEFAULT '{}',
|
||||
is_strong BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
cluster_id INTEGER,
|
||||
status TEXT NOT NULL DEFAULT 'candidate',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (business, phrase, scored_on)
|
||||
);
|
||||
|
||||
-- 2. Listing drafts ----------------------------------------------------------
|
||||
-- One row per generated product (the pipeline's unit of work).
|
||||
-- keyword_id FK to the phrase that spawned it
|
||||
-- slug stable product id, also the asset folder name
|
||||
-- product_type 'sticker_sheet' | 'dashboard' | 'junk_journal_kit' | 'book' | 'endpoint_page'
|
||||
-- size_variant 'us_letter' | 'a5' | 'happy_planner_classic' | ...
|
||||
-- generator script + version that produced the PDF (e.g. sticker_sheet_v0)
|
||||
-- generator_params template inputs (labels, palette, grid) for reproducibility
|
||||
-- asset_paths PDF(s) + 6 mockup PNGs on disk
|
||||
-- title listing title (<=140 chars on Etsy)
|
||||
-- tags 13 Etsy tags / 7 KDP keywords
|
||||
-- description listing body; MUST contain the AI-disclosure line
|
||||
-- ai_disclosure_ok generator/QA confirms the disclosure line is present
|
||||
-- copy_model claude -p model used for copy (e.g. claude-sonnet-5)
|
||||
-- prompt_version copy prompt revision, for the edit-distance trend
|
||||
-- price_cents launch price
|
||||
-- status 'generated' | 'copy_drafted' | 'qa_pending' | 'approved'
|
||||
-- | 'rejected' | 'uploaded' | 'live' | 'expired'
|
||||
-- etsy_listing_id set after upload via Open API v3 OAuth
|
||||
-- published_at when it went live
|
||||
CREATE TABLE IF NOT EXISTS marketing_listing_drafts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
business TEXT NOT NULL CHECK (business IN ('etsy','kdp','api')),
|
||||
keyword_id BIGINT REFERENCES marketing_keywords(id),
|
||||
slug TEXT NOT NULL UNIQUE,
|
||||
product_type TEXT NOT NULL,
|
||||
size_variant TEXT,
|
||||
generator TEXT,
|
||||
generator_params JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
asset_paths TEXT[] NOT NULL DEFAULT '{}',
|
||||
title TEXT,
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
description TEXT,
|
||||
ai_disclosure_ok BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
copy_model TEXT,
|
||||
prompt_version TEXT,
|
||||
price_cents INTEGER,
|
||||
status TEXT NOT NULL DEFAULT 'generated',
|
||||
etsy_listing_id BIGINT,
|
||||
published_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
-- 3. QA verdicts (TRAINING DATA) ---------------------------------------------
|
||||
-- One row per (draft, review round). The Claude judge writes the first three
|
||||
-- agent_* columns; the weekly 15-minute human gate fills human_* columns.
|
||||
-- The (agent_recommendation, human_verdict, override_reason) triple is the
|
||||
-- dataset that eventually lets the judge gate unattended (rule: after 20
|
||||
-- consecutive unedited passes / >=90% agreement over 30 items).
|
||||
-- draft_id FK to marketing_listing_drafts
|
||||
-- round 1, 2, ... (a rejected draft can be regenerated)
|
||||
-- checklist_json deterministic checks: char limits, 13 tags, disclosure,
|
||||
-- file <=20 MB, price >= min, banned words
|
||||
-- agent_model judge model (claude-sonnet-5 / local llama for checklists)
|
||||
-- agent_recommendation 'approve' | 'reject' | 'revise'
|
||||
-- agent_reason judge's one-paragraph rationale
|
||||
-- agent_score 0-100 confidence
|
||||
-- human_verdict 'approve' | 'reject' | 'revise' | NULL until reviewed
|
||||
-- human_reviewer who sat the gate (user / partner / employee name)
|
||||
-- override_reason REQUIRED when human_verdict != agent_recommendation
|
||||
-- edit_distance chars changed by the human in title+description
|
||||
-- reviewed_at when the human verdict landed
|
||||
-- outcome_30d later back-fill: views/favs/sales 30 d after publish
|
||||
CREATE TABLE IF NOT EXISTS marketing_qa_verdicts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
business TEXT NOT NULL CHECK (business IN ('etsy','kdp','api')),
|
||||
draft_id BIGINT NOT NULL REFERENCES marketing_listing_drafts(id),
|
||||
round INTEGER NOT NULL DEFAULT 1,
|
||||
checklist_json JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
agent_model TEXT,
|
||||
agent_recommendation TEXT NOT NULL CHECK (agent_recommendation IN ('approve','reject','revise')),
|
||||
agent_reason TEXT,
|
||||
agent_score SMALLINT,
|
||||
human_verdict TEXT CHECK (human_verdict IN ('approve','reject','revise')),
|
||||
human_reviewer TEXT,
|
||||
override_reason TEXT,
|
||||
edit_distance INTEGER,
|
||||
reviewed_at TIMESTAMPTZ,
|
||||
outcome_30d JSONB,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (draft_id, round),
|
||||
CHECK (human_verdict IS NULL
|
||||
OR human_verdict = agent_recommendation
|
||||
OR override_reason IS NOT NULL)
|
||||
);
|
||||
|
||||
-- 4. Opt-ins -----------------------------------------------------------------
|
||||
-- Product-embedded opt-in only (last PDF page -> hub /confetti). No cold email.
|
||||
-- email subscriber address (the only PII we hold; never export)
|
||||
-- source_slug which product's bonus page they came from
|
||||
-- consent_ts double-opt-in confirmation time (NULL = unconfirmed)
|
||||
-- sequence_step welcome-sequence progress (0 = none sent)
|
||||
-- provider 'brevo' | 'buttondown' | ... (free tier, UNVERIFIED limits)
|
||||
-- unsubscribed_at set on unsubscribe; row kept for suppression
|
||||
CREATE TABLE IF NOT EXISTS marketing_optins (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
business TEXT NOT NULL CHECK (business IN ('etsy','kdp','api')),
|
||||
email TEXT NOT NULL,
|
||||
source_slug TEXT,
|
||||
consent_ts TIMESTAMPTZ,
|
||||
sequence_step SMALLINT NOT NULL DEFAULT 0,
|
||||
provider TEXT,
|
||||
unsubscribed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (business, email)
|
||||
);
|
||||
|
||||
-- 5. Weekly metrics ------------------------------------------------------------
|
||||
-- Long format: one row per (business, week, metric). Fed Mondays from the
|
||||
-- Etsy Stats/Orders CSV export (or Open API v3 shop stats once OAuth exists),
|
||||
-- KDP Reports, and DocForge telemetry. The day-30 gate and day-90 kill rules
|
||||
-- are SQL over this table.
|
||||
-- week_start Monday of the ISO week the value covers
|
||||
-- metric 'listings_live' | 'views_30d' | 'favourites_30d' | 'orders'
|
||||
-- | 'revenue_cents' | 'profit_cents' | 'page1_listings'
|
||||
-- | 'badge_listings' | 'fav_per_view' | 'optins' | ...
|
||||
-- value numeric value
|
||||
-- source_file CSV path / API call that produced it (provenance)
|
||||
-- entered_by 'automation' | user / partner / employee name
|
||||
CREATE TABLE IF NOT EXISTS marketing_weekly_metrics (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
business TEXT NOT NULL CHECK (business IN ('etsy','kdp','api')),
|
||||
week_start DATE NOT NULL,
|
||||
metric TEXT NOT NULL,
|
||||
value NUMERIC(14,2) NOT NULL,
|
||||
source_file TEXT,
|
||||
entered_by TEXT NOT NULL DEFAULT 'automation',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (business, week_start, metric)
|
||||
);
|
||||
@@ -0,0 +1,307 @@
|
||||
# KDP launch gameplan — titles A, B, then C (2026-09-16 → 2026-12-25)
|
||||
|
||||
Written 2026-09-15 by the gameplan background agent (Fable 5.1). Executes the decisions in
|
||||
`decisions/DECISIONS.md` (three 2026-09-15 entries) on top of `research/kdp-business-evaluation.md` (agent E).
|
||||
Nothing here redesigns; where a number depends on the final page count it is marked "recompute".
|
||||
Tracks: business_projects 40 (first 3 titles), 45 (evaluation). Pen name: J.M. Hartley.
|
||||
|
||||
**Decisions honoured (do not reopen):** "100,000 Whys" brand dropped, per-title series names · A = seniors
|
||||
large-print Q&A, 6×9, black ink, white paper, 16 pt, ~140 pp, ~300 Q&A, $12.99, live by **2026-09-25** · B = couples
|
||||
"52 weeks × 7 questions", 6×9, black ink, cream paper, 12 pt, ~120 pp, $9.99 · C = kids single-theme "why" vs
|
||||
pet-behaviour "why" (§5 decides) · AI-generated text disclosure ticked on every title · generation automated
|
||||
(`claude -p --model claude-sonnet-5`), KDP upload manual ~1 h/title, never automate the logged-in Amazon account ·
|
||||
no amazon.com fetches (search snippets only; every rank UNVERIFIED + dated) · kill = 10 copies / 90 d / BSR #500k,
|
||||
royalty/title < $10 at day 90 = weak-demand signal, policy removal = hard stop · A+B evaluation ≈ 2026-12-25 ·
|
||||
weekday blocks only.
|
||||
|
||||
---
|
||||
|
||||
## 0. Numbers to carry (recompute at final page count)
|
||||
|
||||
| Title | Pages | Print cost (1.00 + 0.012/pp) | List | Royalty (60 % × list − print) | Spine (white 0.002252"/pp, cream 0.0025"/pp — KDP formula, UNVERIFIED this run) |
|
||||
|---|---|---|---|---|---|
|
||||
| A seniors | 140 | $2.68 | $12.99 | **$5.11** | 0.315" |
|
||||
| B couples | 120 | $2.44 | $9.99 | **$3.55** | 0.300" |
|
||||
| C (either) | ~120 | $2.44 | $9.99 | $3.55 | 0.270" (white) |
|
||||
|
||||
Kindle edition $4.99 at 70 % after the paperback is live (decision). Weak-demand line: < $10 royalty per title at
|
||||
day 90 = fewer than 2 copies of A or 3 copies of B.
|
||||
|
||||
---
|
||||
|
||||
## 1. Generation pipeline (same for every title; built once during the A week)
|
||||
|
||||
Location: `/opt/appdata/docker/Business/amazon kdp business idea/pipeline/` (to be created in the build block; this
|
||||
document does not create it). All steps are scripts the owner runs from a weekday block; nothing touches Amazon.
|
||||
|
||||
### 1.1 Prompting structure (`claude -p --model claude-sonnet-5`)
|
||||
- **One title spec file per title** (`titles/<slug>.yaml`): audience, reading level, format (Q / A / fun-fact), length
|
||||
caps (A: answer ≤ 20 words, fun fact ≤ 25 words; B: 7 questions per week, no answers, one "talk about it" prompt per
|
||||
week), banned content (medical/legal/financial advice, politics, religion, brand slogans, anything sexual for A/C),
|
||||
topic quotas, decade window for nostalgia (A: 1950–1985 US/UK popular culture).
|
||||
- **Batches of 30 items, 10–11 batches per title** (A = 300 Q&A, B = 364 questions = 52 × 7, C ≈ 200–250). Each batch
|
||||
call gets: the spec, the topic quota for that batch, and the *questions already generated* (titles only, not
|
||||
answers) so the model does not repeat itself. Output is strict JSON (`{"items":[{"q":"","a":"","fact":"","topic":"",
|
||||
"confidence":0-1}]}`); a parse failure re-runs the batch once, then stops for a human.
|
||||
- **Second pass, same model, "verifier" prompt:** every item is re-checked in a fresh call with no memory of writing
|
||||
it ("Is the answer correct? Is the fun fact true? Reply per item: OK / FIX <text> / DROP <reason>"). Items marked
|
||||
FIX get the replacement text; DROP is removed. This is the cheap fact-check; it does not replace the owner sample.
|
||||
- **Cost:** ~22 calls per title inside the Pro pool; run batches in the business block, not overnight (Pro limits).
|
||||
|
||||
### 1.2 QA pass (local, free)
|
||||
1. **Dedup:** nomic-embed-text over question text; cosine > 0.85 against (a) the same title and (b) every previous
|
||||
title = reject (no self-plagiarism across the series).
|
||||
2. **Safety/claims:** llama3.1:8b rubric — flags medical, legal, financial advice, real-person defamation risk,
|
||||
dated "current" facts (e.g. "the current president"), and reading level (A: grade 6–7; C: grade 3–4).
|
||||
3. **Owner sample:** 10 % random (30 items for A) fact-checked by a human in a browser; > 2 wrong in 30 → re-run the
|
||||
verifier pass over the whole title and sample again. Log the sample and the verdicts.
|
||||
4. **Training log:** every generated item, verifier verdict, llama flag, owner verdict → `kdp_decisions(kind='content_qa')`
|
||||
(schema in evaluation §5; table creation is a separate approved DB step, not part of this plan).
|
||||
5. Top-up: generate one extra batch so the drop rate does not push A below 300 Q&A (the subtitle count must be true —
|
||||
evaluation §1.2 "misleading content").
|
||||
|
||||
### 1.3 Interior build (script → print-ready PDF)
|
||||
- JSON → HTML template → PDF via headless Chromium (same `@sparticuz/chromium`/puppeteer skill DocForge uses; locally
|
||||
plain `puppeteer`). Page: 6 × 9 in, **bleed off** (text-only), margins: inside 0.75", outside 0.5", top/bottom 0.75"
|
||||
(KDP minimums for 24–150 pages are 0.375" inside / 0.25" outside — UNVERIFIED this run, the print previewer enforces
|
||||
them anyway). Fonts embedded (Atkinson Hyperlegible or Georgia for A at 16 pt / 1.35 line height; B at 12 pt).
|
||||
- A layout: 2–3 Q&A per page, answer under the question (no "answers at the back" — the audience does not want to flip),
|
||||
fun fact in italics; chapters by topic (Music & Movies, History, Science & Nature, Geography, Words & Numbers,
|
||||
Everyday Life). B layout: one week per spread (7 numbered questions + a "this week we talked about…" line).
|
||||
- Front matter (title page, copyright page **with the AI transparency note**, how to use), back matter (neutral review
|
||||
line per evaluation §1.7 + opt-in line "50 bonus questions → hub `/jmhartley`", other titles in the series).
|
||||
- Page count is measured from the PDF, then §0 is recomputed and the spine width entered into the cover template.
|
||||
- Cover: Canva (free) from stock/vector elements → no AI-image disclosure needed; if any Canva AI tool is used, tick the
|
||||
images disclosure too. Export PDF with 0.125" bleed; spine text only if ≥ 79 pages (KDP rule, UNVERIFIED verbatim).
|
||||
|
||||
### 1.4 The manual upload hour (owner or the partner named at the meeting; per title)
|
||||
| Min | Step |
|
||||
|---|---|
|
||||
| 0–5 | KDP Bookshelf → Create paperback. Language, title, subtitle, series name (create series on title A), edition 1, author J.M. Hartley. |
|
||||
| 5–15 | Description (§3), **AI-generated content: Yes → text, "some/most/all sections" per the honest answer (all)**; images: as applicable; public domain: No. |
|
||||
| 15–25 | 7 keywords, 3 categories (§3), "low-content book" **unticked**, large-print flag ticked for A (KDP has a Large Print checkbox — UNVERIFIED this run; tick if shown), adult content No. |
|
||||
| 25–40 | Content tab: free KDP ISBN, print options (6×9, black & white, white/cream per title, matte cover), upload interior PDF, upload cover PDF, run Print Previewer, fix margin errors, approve. |
|
||||
| 40–50 | Pricing: US list $12.99/$9.99, 60 % plan, Expanded Distribution ON, other marketplaces auto-converted; check minimum list ≥ print-cost floor. |
|
||||
| 50–60 | Publish. Screenshot every tab into the project folder. Log the publish timestamp (day 0 for the kill clock). |
|
||||
| after | Review takes up to 72 h. When live: Author Central claim, order one author copy (cost = print + shipping) for a physical QA pass, then set up the Kindle edition. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Week-by-week plan (weekdays only; business block 12:45–16:30 unless noted)
|
||||
|
||||
Day 0 (kill clock) = the publish click. Title A day 0 target = Fri 2026-09-25 → day 45 = Mon 2026-11-09, day 90 =
|
||||
Thu 2026-12-24. Title B day 0 target = Fri 2026-10-09 → day 45 = Mon 2026-11-23, day 90 = Thu 2027-01-07.
|
||||
|
||||
| Week (Mon) | Title A | Title B | C / other |
|
||||
|---|---|---|---|
|
||||
| **W1 Wed 09-16 → Fri 09-18** | Wed: KDP account check, bank + tax interview, Author Central shell (the payout gate). Thu: write `titles/seniors-trivia.yaml`, run batches 1–6. Fri: batches 7–11 + verifier pass. | — | Wed: Etsy dev-app registration is the same afternoon (other project) — keep KDP to 90 min Wed. |
|
||||
| **W2 09-21 → 09-25** | Mon: llama/dedup pass, owner 30-item sample, top-up batch. Tue: interior script + PDF, measure pages, recompute §0. Wed: cover in Canva, spine from final count; metadata final (§3). Thu: **upload hour**; publish. Fri: buffer — if the previewer rejects margins, fix and republish the same day. **A must be published by Fri 09-25.** | Mon 30 min: write `titles/couples-52x7.yaml`. | — |
|
||||
| **W3 09-28 → 10-02** | When live (≤ 72 h): claim on Author Central, order author copy, create Kindle edition ($4.99). Start weekly Mon read: BSR + keyword rank (manual browser read → N8N form). | Tue–Thu: batches 1–13 (364 questions + 52 weekly prompts), verifier, dedup vs A, owner sample. | Fri 30 min: log the §5 rank read as "baseline, 2026-09-15 snippets". |
|
||||
| **W4 10-05 → 10-09** | Mon read #1. Author copy arrives → physical QA (type size, margins, cream vs white). | Mon–Tue: interior (12 pt, cream), cover, metadata. Wed: **upload hour**, publish (target day 0 = Fri 10-09 at the latest). | DocForge build slot starts this week (other project) — KDP takes Mon + one upload hour only. |
|
||||
| **W5 10-12 → 10-16** | Mon read #2. If any Print Previewer / content flag e-mail arrived: respond within 24 h (policy removal = hard stop → escalate to the user). | Live; Kindle edition; Author Central. | — |
|
||||
| **W6 10-19 → 10-23** | Mon read #3. First-sale window opens (21 d after publish = 10-16). | Mon read #1. | — |
|
||||
| **W7 10-26 → 10-30** | Mon read #4 + first KDP Reports CSV drop (sales by day). | Mon read #2. | Fri 30 min: **C rank re-read** (search snippets only) → update §5 scores with a new date. |
|
||||
| **W8 11-02 → 11-06** | Mon read #5. Gift-window check: A should be indexed for "trivia for seniors large print" by now (search the phrase, note page). | Mon read #3. | — |
|
||||
| **W9 11-09 → 11-13** | **Mon 11-09 = A day-45 check** (§4). | Mon read #4. | If A day-45 = no policy flag and BSR ever < #1M: write `titles/<C>.yaml` (theme per §5), no generation yet. |
|
||||
| **W10 11-16 → 11-20** | Mon read #6. | Mon read #5. | Tue–Thu: C batches + verifier + QA (only if W9 gate passed). |
|
||||
| **W11 11-23 → 11-27** (Thu 11-26 Thanksgiving) | Mon read #7. | **Mon 11-23 = B day-45 check** (§4). | Mon–Tue: C interior + cover; **Wed 11-25 upload hour** if the user confirms C at the B day-45 check (a Dec 1 publish still catches late gift traffic). Otherwise C waits for the 12-23 evaluation. |
|
||||
| **W12 11-30 → 12-04** | Mon read #8. First royalty statement for September appears; cash ≈ end of November for a September sale (evaluation §4.4). | Mon read #6. | C live (if published); Kindle edition. |
|
||||
| **W13 12-07 → 12-11** | Mon read #9. | Mon read #7. | Cross-funded ad month decision (Etsy gate) belongs to the marketing ledger, not this plan; nothing to do here unless the user approves $90/mo. |
|
||||
| **W14 12-14 → 12-18** | Mon read #10. Prepare the evaluation sheet (§4.3). | Mon read #8. | — |
|
||||
| **W15 12-21 → 12-25** (Fri 12-25 Christmas) | **Wed 12-23 = A day-88 / A+B evaluation** (§4.3; A's formal day 90 is Thu 12-24, use the 12-23 read). | B at day 75 on 12-23; formal day 90 = 2027-01-07 — record both. | C decision: continue / kill per §4.3 outcome; C's own day-45 ≈ 2027-01-15 if published 12-01. |
|
||||
|
||||
Standing weekly cadence (Mon, 10 min): KDP Reports CSV into the watched folder; BSR + 7-keyword rank per title into
|
||||
the N8N form; note any Amazon e-mail. No other KDP work on non-listed days.
|
||||
|
||||
---
|
||||
|
||||
## 3. Title A — complete KDP metadata
|
||||
|
||||
**Title:** Large Print Trivia for Seniors
|
||||
**Subtitle:** 300 Easy-to-Read Questions and Answers with a Fun Fact for Every One — a Memory-Friendly Quiz Book for
|
||||
Adults 60+, Grandparents and Family Game Nights
|
||||
**Series name:** Easy-Read Trivia Library (Book 1). Planned Book 2: *Large Print Music & Movies Trivia for Seniors*;
|
||||
Book 3: *Large Print Trivia for Seniors: The 1950s, 60s and 70s*.
|
||||
**Author:** J.M. Hartley · **Edition:** 1 · **Language:** English · **Print:** 6 × 9 in, black & white interior, white
|
||||
paper, matte cover, 140 pp (recompute) · **Large-print flag:** yes · **Low-content:** no · **ISBN:** free KDP ISBN ·
|
||||
**AI-generated content:** Yes — text, all sections; images: no (unless a Canva AI tool touched the cover).
|
||||
|
||||
**7 backend keywords** (2–3-word buyer phrases; none repeats a title/subtitle word; no banned terms):
|
||||
1. `brain games elderly`
|
||||
2. `nostalgia activities`
|
||||
3. `gift for grandma`
|
||||
4. `gift for grandpa`
|
||||
5. `retirement home activity`
|
||||
6. `conversation starters older`
|
||||
7. `general knowledge older adults`
|
||||
|
||||
**3 category suggestions** (pick the closest nodes offered in the upload dialog; names below are the BISAC-style labels
|
||||
KDP uses — UNVERIFIED node paths this run):
|
||||
1. Games & Activities › Trivia
|
||||
2. Games & Activities › Quizzes
|
||||
3. Health, Fitness & Dieting › Aging (audience-based; fall back to Self-Help › Aging if the node is not offered)
|
||||
|
||||
**Description** (≈ 170 words; HTML allowed — bold the first line):
|
||||
|
||||
> **Three hundred questions, big clear type, and a smile on every page.**
|
||||
>
|
||||
> Made for readers who love a good quiz but not squinting at tiny print, this book puts each question, its answer and
|
||||
> a one-line fun fact together on the same page — no flipping to the back. Topics cover the things we all grew up
|
||||
> with: music and movies, history, science and nature, geography, words and numbers, and everyday life.
|
||||
>
|
||||
> Use it on your own, at the kitchen table with the grandchildren, or as a ready-made activity for a seniors' group,
|
||||
> care home or family game night. Every question is answerable from general knowledge, so nobody is left out.
|
||||
>
|
||||
> Inside: 300 questions and answers · a fun fact with every answer · 16-point type on bright white paper · six
|
||||
> themed chapters · a 50-question bonus pack you can download from the last page.
|
||||
>
|
||||
> Book 1 of the Easy-Read Trivia Library.
|
||||
>
|
||||
> Transparency note: the questions, answers and fun facts in this book were written with the help of AI and checked
|
||||
> by a human editor before publication. If you spot a mistake, the last page tells you how to let us know.
|
||||
|
||||
Back-matter lines (in the book, not the description): neutral review line ("If this book gave you a good evening,
|
||||
a short review on Amazon helps other readers find it") and the opt-in ("50 bonus questions: hub `/jmhartley`").
|
||||
|
||||
---
|
||||
|
||||
## 4. Checks
|
||||
|
||||
### 4.1 Day-45 check (A: Mon 2026-11-09 · B: Mon 2026-11-23)
|
||||
Read and log: paid copies to date (KDP Reports), best BSR seen on any weekly read, keyword page for the 7 phrases,
|
||||
review count, any Amazon e-mail. Signals: (a) **policy e-mail / block / removal → hard stop, escalate the same day**;
|
||||
(b) BSR never < #500k → "not yet on the radar" — check title/keyword indexing, do not rewrite content; (c) any BSR
|
||||
< #500k → on track; (d) ≥ 1 copy → note the day of first sale for the bootstrap ledger.
|
||||
|
||||
### 4.2 Day-90 check (A: Thu 2026-12-24 · B: Thu 2027-01-07)
|
||||
Per title: copies, royalty to date, best BSR, reviews. Weak-demand signal = royalty < $10. Kill test for the business
|
||||
(all titles together) = < 10 paid copies AND no title < #500k after day 45 → pause KDP, move the slot; policy removal
|
||||
on any title = stop regardless.
|
||||
|
||||
### 4.3 A+B evaluation sheet (Wed 2026-12-23)
|
||||
| Field | A | B | C (if live) |
|
||||
|---|---|---|---|
|
||||
| Publish date / days live | 09-25 / 89 | 10-09 / 75 | 12-01 / 22 |
|
||||
| Paid copies · royalty $ | | | |
|
||||
| Best BSR · latest BSR | | | |
|
||||
| Keyword page (7 phrases) | | | |
|
||||
| Reviews · policy e-mails | | | |
|
||||
| Verdict: continue / re-scan niche / kill | | | |
|
||||
Outcome routing: ≥ 25 copies across A+B or any title < #150k → continue + approve the $90/mo ads test (funding per the
|
||||
ledger rule); 10–24 copies → re-scan niches, keep publishing cadence; < 10 → kill test. Record the sheet in
|
||||
`kdp_decisions(kind='evaluation')` and DECISIONS.md.
|
||||
|
||||
---
|
||||
|
||||
## 5. Title C — UNVERIFIED rank read (search snippets, 2026-09-15) and recommendation
|
||||
|
||||
Method: WebSearch snippets of amazon.com product pages only; no page was fetched. Ranks are point-in-time in *Books*,
|
||||
drift daily, and must be re-read (W7, W9) before C is generated. Rubric = evaluation §2 (demand · beatability ·
|
||||
freshness · black-ink fit · owner fit · series; 0–3 each, max 18; go ≥ 12 and one indie < #300k and #1 indie < 500 reviews).
|
||||
|
||||
**Evidence (all UNVERIFIED, read 2026-09-15):**
|
||||
- Kids "why", single theme: *The Big Book of Why: Space — 60 Questions* (N. Brennan, independently published
|
||||
2026-06-17) **#753,665**; *Space Exploration for Kids 2026* (E. Kelley, 2026-04-29) #3,332,425; *The Human Body for
|
||||
Kids: A Fun Visual Guide* (Anand Learning Press, 2026-05-26) #3,376,664. Generic multi-theme "whys" for context:
|
||||
*100,000 Whys for Kids — The Only Why Book* (O. Everwood, 2026-05-30) **#300**; *100000 Whys Book for Kids* full colour
|
||||
(D. Matthews, 2026-04-06) #204,890; *100,000 Whys for Kids 2026* (C.D. Still) #578,725 (was #203,637 on 09-09 —
|
||||
shows how fast these drift); *The Big Book of Why Questions* (Inspo Paper, 2026-06-20) #1,357,431. Sources:
|
||||
https://www.amazon.com/Big-Book-Why-Amazing-Questions/dp/B0H5T11N4Y , https://www.amazon.com/100-000-Whys-Kids-Questions/dp/B0H3H1KGCJ ,
|
||||
https://www.amazon.com/100000-Whys-Book-Kids-Questions/dp/B0GWDHT8WY , https://www.amazon.com/Human-Body-Kids-Visual-Guide/dp/B0H33CM8PL
|
||||
(snippet-only).
|
||||
- Pet behaviour "why does my dog/cat": no independently published 2025–2026 title surfaced in two searches. Incumbents
|
||||
are traditionally published and vet/behaviourist-authored: *Why Does My Dog…?* (John Fisher, Souvenir Press, BSR not
|
||||
in snippet), *The Cat Behavior Answer Book, 2nd ed.* (Arden Moore, Storey, BSR not in snippet), *The 125 Most-Asked
|
||||
Questions About Cats* (J. Malone, 1992) **#11,667,807**. Sources: https://www.amazon.com/Why-Does-My-Dog/dp/028563481X ,
|
||||
https://www.amazon.com/Cat-Behavior-Answer-Book-Understanding/dp/1635864496 ,
|
||||
https://www.amazon.com/Most-Asked-Questions-About-Cats-Answers/dp/0688105521 (snippet-only).
|
||||
|
||||
**Scores (2026-09-15, UNVERIFIED):**
|
||||
|
||||
| Niche | Demand | Beatable | Fresh | B/W fit | Owner fit | Series | Total |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| Kids "why", single theme, black ink | 1 (only direct comp is #753k at 3 months; generic demand is huge but colour) | 2 (indie comps have few reviews, snippet-only) | 3 (all 2026) | 2 (comps are colour; B/W needs line art to look intentional) | 2 (kids' category = most AI clones + 2026 enforcement; child-safety review load) | 3 | **13** |
|
||||
| Pet behaviour "why does my dog/cat" | 1 (no fresh indie signal; only decades-old trad titles) | 2 (incumbents are credentialed authors — a pen name with an AI note is weaker on authority) | 1 (no 2025–26 entrants seen) | 3 | 1 (behaviour answers shade into veterinary/health advice = the "unsafe claims" class the QA pass rejects; disappointing-content risk) | 3 | **11** |
|
||||
|
||||
Both are below the report's earlier 15/18 for kids (that score assumed demand 2; the only single-theme indie found is
|
||||
at #753k). Neither meets the "one indie < #300k" go condition on this read.
|
||||
|
||||
**Recommendation:** Title C = **kids single-theme "why", black ink with line art**, theme = **animals** (the one theme
|
||||
with no indie "why" comp in the snippets; space is already taken by Brennan at #753k, human body by two > #3M titles).
|
||||
Working title *Why Do Cats Purr? 200 Animal Whys for Curious Kids 6–10*, own series name (e.g. "Curious Kids Whys").
|
||||
Pet-behaviour is **parked** (score 11; health-advice risk conflicts with the AI-disclosed pen-name model). Gate: C is
|
||||
generated only if (1) the W9 re-read shows at least one single-theme kids' "why" indie < #500k or the generic-whys
|
||||
top-3 are still < #1k (demand proof), and (2) A's day-45 shows no policy flag. If (1) fails at W9, C falls back to
|
||||
**Easy-Read Trivia Library Book 2** (seniors Music & Movies edition) — the one series with an in-house data point.
|
||||
|
||||
---
|
||||
|
||||
## 6. Title A — 40-question sample in the final format
|
||||
|
||||
Format per item: **Q** · **A** · *Fun fact* (one line). Drawn from the six chapters; verifier pass not yet run on these
|
||||
— they are hand-written to show the target quality, not generated output.
|
||||
|
||||
**Music & Movies**
|
||||
1. **Q** Which singer was known as "The King of Rock and Roll"? **A** Elvis Presley. *He never performed a concert outside North America.*
|
||||
2. **Q** Which Beatles song begins "Yesterday, all my troubles seemed so far away"? **A** "Yesterday". *Paul McCartney said the melody came to him in a dream.*
|
||||
3. **Q** Which actor played Rick in *Casablanca*? **A** Humphrey Bogart. *The line "Play it again, Sam" is never actually said in the film.*
|
||||
4. **Q** Which TV show featured Lucy and Ricky Ricardo? **A** *I Love Lucy*. *It was one of the first sitcoms filmed with three cameras in front of a live audience.*
|
||||
5. **Q** Which crooner was nicknamed "Ol' Blue Eyes"? **A** Frank Sinatra. *He won an Academy Award for acting in* From Here to Eternity *(1953).*
|
||||
6. **Q** Which 1939 film features the line "There's no place like home"? **A** *The Wizard of Oz*. *Dorothy's slippers were silver in the book; they became ruby red to show off Technicolor.*
|
||||
7. **Q** What was Walt Disney's first feature-length animated film? **A** *Snow White and the Seven Dwarfs* (1937). *Critics called it "Disney's Folly" before it became a huge hit.*
|
||||
8. **Q** How many keys does a standard piano have? **A** 88. *52 are white and 36 are black.*
|
||||
9. **Q** How many strings does a standard guitar have? **A** Six. *A bass guitar usually has four.*
|
||||
10. **Q** Which instrument has 47 strings and seven pedals? **A** The concert harp. *The pedals change the pitch so the harp can play in any key.*
|
||||
|
||||
**History**
|
||||
11. **Q** Who was the first person to walk on the Moon? **A** Neil Armstrong, on July 20, 1969. *Around 600 million people watched it on television.*
|
||||
12. **Q** In which year did the Second World War end? **A** 1945. *Germany surrendered in May and Japan in September of that year.*
|
||||
13. **Q** In which year did the Titanic sink? **A** 1912. *The wreck was not found until 1985, about 12,500 feet down.*
|
||||
14. **Q** Which ship carried the Pilgrims to America in 1620? **A** The Mayflower. *The crossing took about 66 days.*
|
||||
15. **Q** Which country gave the Statue of Liberty to the United States? **A** France. *Its copper skin was brown when it was dedicated in 1886 and weathered to green.*
|
||||
16. **Q** Who was the first woman to fly solo across the Atlantic? **A** Amelia Earhart, in 1932. *Her flight took just under 15 hours.*
|
||||
17. **Q** Who appears on the U.S. $100 bill? **A** Benjamin Franklin. *He was never president — he and Alexander Hamilton are the two non-presidents on today's bills.*
|
||||
|
||||
**Science & Nature**
|
||||
18. **Q** Which planet is known as the Red Planet? **A** Mars. *It has the tallest volcano in the solar system, Olympus Mons, about three times the height of Everest.*
|
||||
19. **Q** What is the largest animal that has ever lived? **A** The blue whale. *Its heart is about the size of a small car.*
|
||||
20. **Q** What is the hardest natural substance? **A** Diamond. *It is made of the same element as pencil "lead" — carbon.*
|
||||
21. **Q** What do bees collect from flowers to make honey? **A** Nectar. *One bee makes about a twelfth of a teaspoon of honey in its lifetime.*
|
||||
22. **Q** What gas do plants take in from the air? **A** Carbon dioxide. *They give back oxygen, which is why forests are called the planet's lungs.*
|
||||
23. **Q** How many bones are in the adult human body? **A** 206. *Babies are born with about 270, and many fuse together as they grow.*
|
||||
24. **Q** What is the chemical symbol for gold? **A** Au. *It comes from the Latin word for gold, "aurum".*
|
||||
25. **Q** What is a group of lions called? **A** A pride. *Lions are the only big cats that live in family groups.*
|
||||
26. **Q** What does a barometer measure? **A** Air pressure. *Falling pressure usually means rain or a storm is on the way.*
|
||||
|
||||
**Geography**
|
||||
27. **Q** What is the largest ocean on Earth? **A** The Pacific. *It covers more area than all the world's land put together.*
|
||||
28. **Q** What is the capital of Canada? **A** Ottawa. *Many people guess Toronto, which is the biggest city but not the capital.*
|
||||
29. **Q** What is the longest river in South America? **A** The Amazon. *It carries more water than any other river on Earth.*
|
||||
30. **Q** What is the smallest U.S. state by area? **A** Rhode Island. *Its official name was "Rhode Island and Providence Plantations" until voters shortened it in 2020.*
|
||||
31. **Q** Which city is nicknamed "The Windy City"? **A** Chicago. *The nickname may refer to long-winded politicians rather than the weather — historians still argue about it.*
|
||||
32. **Q** What is the tallest mountain above sea level? **A** Mount Everest. *It grows a few millimetres taller every year as the plates beneath it push together.*
|
||||
|
||||
**Words & Numbers**
|
||||
33. **Q** How many sides does a stop sign have? **A** Eight. *The octagon was chosen so drivers could recognise it even from the back.*
|
||||
34. **Q** How many minutes are in a full day? **A** 1,440. *That is 86,400 seconds.*
|
||||
35. **Q** Who wrote *Pride and Prejudice*? **A** Jane Austen. *The 1813 first edition named her only as "the author of* Sense and Sensibility*".*
|
||||
36. **Q** Which sport uses the words "love" and "deuce"? **A** Tennis. *"Love" for zero may come from the French "l'oeuf" — an egg, for its shape.*
|
||||
37. **Q** What is the name of the fairy in *Peter Pan*? **A** Tinker Bell. *In the original stage play she was just a darting light and the sound of bells.*
|
||||
|
||||
**Everyday Life**
|
||||
38. **Q** Which board game has the properties Boardwalk and Park Place? **A** Monopoly. *The street names come from Atlantic City, New Jersey.*
|
||||
39. **Q** What is the main ingredient in guacamole? **A** Avocado. *Botanically, an avocado is a berry with one big seed.*
|
||||
40. **Q** Who painted the *Mona Lisa*? **A** Leonardo da Vinci. *It is smaller than most visitors expect — about 30 by 21 inches.*
|
||||
|
||||
Quality bar for the generated batches: every item must read like these — one clear question, an answer a reader can
|
||||
confirm from general knowledge, and a fun fact that adds something rather than restating the answer.
|
||||
|
||||
---
|
||||
|
||||
## 7. UNVERIFIED list (this document)
|
||||
Every BSR in §5 (snippet-only, 2026-09-15) · KDP margin minimums and spine formula (§1.3, §0) · Large-print checkbox
|
||||
and category node names (§3) · 72-h review window and "spine text ≥ 79 pages" (§1.4) · payout timing (evaluation §4.4).
|
||||
|
||||
## Update instructions
|
||||
Update this file at each Mon read that changes a date, at the day-45/day-90 checks, and after the W7/W9 rank re-reads
|
||||
(replace §5 scores, keep the old ones in a dated line). Record every verdict in DECISIONS.md and `kdp_decisions`.
|
||||
@@ -0,0 +1,159 @@
|
||||
# NetBird Self-Hosted Buildplan + Bootstrap Decision Aid
|
||||
|
||||
> **Preflight (agent K, 2026-09-15, Opus 4.8). Read-only research — nothing deployed.**
|
||||
> Role (locked): **NetBird = the OWNER's own device mesh** (primary host, server-01, laptop, phone) +
|
||||
> pulling the owner's admin surfaces off cloudflared. Partners never join this mesh (that is Twingate).
|
||||
> **server-01 = TEST-ONLY sandbox**: NetBird's stack is *validated* there as throwaway; the
|
||||
> **production control plane deploys on the PRIMARY server.**
|
||||
> Resolves the 3 UNVERIFIED items from `netbird-vs-tailscale-mesh-vpn.md`. Claims pinned to URL + date
|
||||
> **2026-09-15**; unproven items still marked **UNVERIFIED**.
|
||||
|
||||
---
|
||||
|
||||
## The 3 open items — RESOLVED
|
||||
|
||||
### (a) Management DB engine — SQLite default; CAN use the homelab Postgres ✅
|
||||
Source: https://docs.netbird.io/selfhosted (postgres-store / configuration) (2026-09-15)
|
||||
- **Default = SQLite** ("As of version 0.26.0, NetBird used SQLite as its default database store").
|
||||
- **PostgreSQL is fully supported and *recommended for production*** ("supports concurrent access,
|
||||
enabling multiple management instances for high availability").
|
||||
- **Point it at the existing homelab Postgres** (`postgres-lggkk0kcgwko440kk04wowgk`) for unified
|
||||
backup:
|
||||
- **New combined `config.yaml`:** `server.store.engine: postgres` + `server.store.dsn: <DSN>`; env
|
||||
`NB_STORE_ENGINE_POSTGRES_DSN`.
|
||||
- **Older multi-container `management.json`:** `"StoreConfig": { "Engine": "postgres" }`; env
|
||||
`NETBIRD_STORE_ENGINE_POSTGRES_DSN`; set `NETBIRD_STORE_CONFIG_ENGINE=postgres` in `setup.env`.
|
||||
- MySQL is **not** a supported alternative.
|
||||
- **Recommendation:** SQLite for the **server-01 sandbox** (zero setup, throwaway); **Postgres on the
|
||||
homelab instance for production** (unified restic backup, per `playbook_postgres_databases` — create a
|
||||
dedicated `netbird` DB + user, store the DSN in Vault). **UNVERIFIED:** exact DSN format / SSL params
|
||||
against this Postgres — confirm from the current config-files reference at build time.
|
||||
|
||||
### (b) Authelia via NetBird generic-OIDC — WORKS; no need to fall back to Authentik ✅ (one caveat)
|
||||
Sources: https://docs.netbird.io/selfhosted/identity-providers ;
|
||||
https://docs.netbird.io/selfhosted/identity-providers/generic-oidc ;
|
||||
**https://www.authelia.com/integration/openid-connect/clients/netbird/** (Authelia ships an official
|
||||
NetBird client integration guide) ; authelia/authelia Discussion #7185 ; netbird issue #4679 (2026-09-15)
|
||||
- NetBird has an **"OIDC (Generic)" connector** for "any OIDC-compliant provider." Only 3 fields
|
||||
required: **Client ID, Client Secret, Issuer URL.**
|
||||
- **Authelia is NOT on NetBird's tested list**, BUT **Authelia publishes an official NetBird
|
||||
integration** (redirect URIs, PKCE, public/private client, `authorization_code` + `refresh_token`
|
||||
grants, token-endpoint auth). Community reports confirm it working.
|
||||
- **→ Respect P4: use Authelia via the generic-OIDC connector. Do NOT stand up Authentik** for this.
|
||||
- **CAVEAT (UNVERIFIED — test on server-01):** the NetBird **CLI / mobile headless login** may use the
|
||||
**OAuth 2.0 Device Authorization Grant**; confirm the running Authelia version enables the device-code
|
||||
flow (it is a relatively recent Authelia feature) — if not, the dashboard/browser `authorization_code`
|
||||
+ PKCE path still works and CLI enrollment can use **setup keys** instead. Fallback to Authentik only
|
||||
if Authelia's device flow proves unworkable AND setup keys are insufficient.
|
||||
|
||||
### (c) TURN/relay through cloudflared — HTTP components proxy fine; coturn TURN **needs a UDP port** ⚠️
|
||||
Sources: https://docs.netbird.io/selfhosted (quickstart/ports) (2026-09-15)
|
||||
- Required exposure: **TCP 80/443** (dashboard + management gRPC + signal gRPC, all HTTP/2 over TLS) and
|
||||
**UDP 3478** (coturn STUN/TURN).
|
||||
- **TCP 80/443 → proxyable** through Traefik and/or **cloudflared** (HTTP/HTTPS + gRPC-over-HTTP2).
|
||||
- **UDP 3478 (coturn) → NOT carried by a cloudflared HTTP tunnel.** Legacy TURN needs a **directly
|
||||
reachable public UDP port**, which brushes **P5** (don't internet-expose what the mesh can reach).
|
||||
- **Answer: `needs-udp-port` for classic coturn.** Two ways to satisfy P5:
|
||||
1. **NetBird's newer Relay** component uses a **relay-over-TLS (WSS/443)** transport, which **can go
|
||||
through a TCP reverse proxy / possibly cloudflared** — potentially removing the need for coturn's
|
||||
UDP port. **UNVERIFIED** that cloudflared cleanly carries NetBird's WSS relay end-to-end for
|
||||
roaming peers; **must test on server-01**. If it works, coturn UDP 3478 may be omittable.
|
||||
2. Otherwise, open **UDP 3478 (+ the coturn relay port range)** directly on the primary server —
|
||||
accept it as a scoped exception (WireGuard is E2E-encrypted; coturn only relays opaque encrypted
|
||||
packets), documented for owner sign-off.
|
||||
- **Deciding test:** stand up the stack on server-01, force a roaming peer onto relay (block P2P), and
|
||||
see whether the WSS relay alone connects through cloudflared. That single test settles P5 posture.
|
||||
|
||||
---
|
||||
|
||||
## Bootstrap vs straight-self-host — decision aid (owner decides before Phase 2)
|
||||
|
||||
| Path | Time to "mesh works" | Risk | What it costs | Ends at |
|
||||
|---|---|---|---|---|
|
||||
| **A. Bootstrap on NetBird Cloud free tier, then migrate** | **<1 hr** (join 4 nodes) | **Low** — vendor runs the plane; nothing to break | €0; a temporary cloud tenant (5 users/100 machines); only the mesh runs over it, **no prod data** | Prove P2P/posture/policy fast, then rebuild self-hosted calmly and cut over |
|
||||
| **B. Straight to self-hosted** | **0.5–1.5 days** (5 containers + Postgres + Authelia OIDC + TURN/relay + Traefik/cloudflared + resolve item (c)) | **Medium** — solo-operator burden is the one weak cell (score 2/5 in the decision doc); OIDC + TURN-through-cloudflared are the fiddly bits | €0 software + owner time; must get item (c) right before roaming works | Owned control plane immediately (principle #1), but competes with the **09-20 Twingate deadline** for time |
|
||||
|
||||
**What "bootstrap then migrate" actually entails:** (1) create a free NetBird Cloud account; (2) install
|
||||
the agent on all 4 nodes with a setup key; (3) validate mesh + a routing peer + a posture check; (4)
|
||||
later stand up the self-hosted stack on the primary server, create a **new** setup key, **re-enroll the
|
||||
same 4 nodes** against the self-hosted management (peers are cheap to re-add), migrate policies by hand
|
||||
(small ruleset); (5) decommission the cloud tenant. Migration cost is low because there is little state
|
||||
— policies are few and peers re-enroll.
|
||||
|
||||
**Recommendation: PATH A (bootstrap), deciding factor = the 09-20 Twingate deadline.**
|
||||
This week's scarce resource is owner/agent time, and it belongs to Twingate + the partner swap.
|
||||
Bootstrapping NetBird Cloud proves the mesh in under an hour at zero risk and **zero prod data**, then
|
||||
the self-hosted build (Path B target) happens on server-01 sandbox first, without deadline pressure,
|
||||
resolving item (c) before it touches the primary server. This does **not** compromise principle #1 —
|
||||
self-hosted remains the destination; the cloud tenant is a throwaway proving ground, retired at cutover.
|
||||
(Flip to straight-self-host only if the owner explicitly wants no third-party plane to ever touch the
|
||||
mesh, even temporarily.)
|
||||
|
||||
---
|
||||
|
||||
## server-01 SANDBOX validation runbook (throwaway; never prod data)
|
||||
1. On **server-01**, deploy the NetBird self-hosted stack via the official `docker-compose` +
|
||||
`setup.env` (getting-started-with-self-hosting / `netbird-installer`). Use **SQLite** (throwaway).
|
||||
2. Wire **OIDC to Authelia** via the generic-OIDC connector (Client ID/Secret/Issuer). Confirm
|
||||
dashboard login (auth-code + PKCE). **Test CLI/device-flow enrollment** → resolves item (b) caveat.
|
||||
3. Enroll a **throwaway peer**; verify: policy (a deny/allow rule), **posture check** (OS/version),
|
||||
and a **routing peer** advertising a test subnet.
|
||||
4. **Force relay fallback** (block direct P2P) and test whether the **WSS relay reaches through
|
||||
cloudflared without UDP 3478** → resolves item (c). Record the result.
|
||||
5. Confirm **Postgres DSN** connectivity from a management container against a scratch DB → dry-run
|
||||
item (a) for production.
|
||||
6. **Tear it all down** — server-01 keeps only Ollama/GPU + Obsidian (`feedback_sandbox_isolation`).
|
||||
|
||||
## PRIMARY-server production deploy shape (target)
|
||||
Sources: prior `netbird-vs-tailscale-mesh-vpn.md` §"Recommended deployment shape" + docs (2026-09-15)
|
||||
- **Control plane (bridged docker-compose, official `netbirdio/*` images — no linuxserver variant):**
|
||||
`management` + `signal` + `relay` + `coturn` + `dashboard`.
|
||||
- **Front:** Traefik internally; **cloudflared** publishes dashboard/management/signal (TCP 443, gRPC)
|
||||
to roaming devices — no ports opened for those. **TURN/relay** per item (c): prefer WSS relay via
|
||||
cloudflared if the sandbox test passes; else open UDP 3478 directly (scoped exception).
|
||||
- **DB:** external **Postgres** (dedicated `netbird` DB + user on `postgres-lggkk0kcgwko440kk04wowgk`);
|
||||
DSN in Vault.
|
||||
- **Agents** on each of the 4 nodes (`netbirdio/netbird`): **documented host-mode exception** —
|
||||
`network_mode: host` + `cap_add: NET_ADMIN` + `/dev/net/tun`.
|
||||
- **Routing peer** on the primary host advertises the host LAN + Docker `172.16.16.x` so the mesh
|
||||
reaches existing Traefik-fronted/host-bound services without per-service exposure (P5/P9).
|
||||
- **cloudflared coexistence:** public/business SaaS **stays** on cloudflared; NetBird **replaces
|
||||
cloudflared for the owner's OWN admin access** (SSH, dashboards, Vault UI, server-01).
|
||||
- **Vault paths to CREATE** (confirmed none exist today; propose KV v2 under `secret/`):
|
||||
- `secret/netbird/management` — mgmt admin/API token, datastore encryption key
|
||||
- `secret/netbird/oidc` — Authelia client-id/secret + issuer URL
|
||||
- `secret/netbird/turn` — coturn shared secret / relay auth
|
||||
- `secret/netbird/setup-key` — enrollment keys (short TTL; rotate on device churn)
|
||||
- `secret/netbird/db` — Postgres DSN (new — for the external-DB decision)
|
||||
- **Backup:** the Postgres `netbird` DB (restic, standard schedule) + `/var/lib/netbird` config;
|
||||
management is stateless enough to redeploy from Vault + backup. Existing peer tunnels keep forwarding
|
||||
if the plane is down (softens the solo-operator risk).
|
||||
|
||||
---
|
||||
|
||||
## Answers at a glance (for the wrap-up JSON)
|
||||
- **netbird_db_engine:** SQLite default; **Postgres supported & recommended → can use homelab Postgres**
|
||||
via `NB_STORE_ENGINE_POSTGRES_DSN` / `StoreConfig.Engine=postgres`.
|
||||
- **authelia_oidc:** **works** via generic-OIDC (Authelia ships an official NetBird guide); device-flow
|
||||
for CLI is the only UNVERIFIED sub-point → test on server-01; setup keys are the fallback.
|
||||
- **turn_via_cloudflared:** **needs-udp-port** for classic coturn; NetBird's **WSS relay on 443 may
|
||||
avoid it** (UNVERIFIED — test the relay through cloudflared on server-01).
|
||||
- **bootstrap_recommendation:** **bootstrap** on NetBird Cloud free tier first — deciding factor is the
|
||||
09-20 Twingate deadline owning this week's time; self-hosted stays the destination.
|
||||
|
||||
## Sources (all accessed 2026-09-15)
|
||||
- NetBird self-hosted guide/quickstart/ports — https://docs.netbird.io/selfhosted
|
||||
- Postgres store / config — https://docs.netbird.io/selfhosted (postgres-store, configuration-files)
|
||||
- Identity providers + generic OIDC — https://docs.netbird.io/selfhosted/identity-providers ;
|
||||
https://docs.netbird.io/selfhosted/identity-providers/generic-oidc ;
|
||||
https://docs.netbird.io/selfhosted/identity-providers/authentik
|
||||
- Authelia↔NetBird official integration — https://www.authelia.com/integration/openid-connect/clients/netbird/
|
||||
- Community: authelia/authelia Discussion #7185 ; netbirdio/netbird issue #4679
|
||||
- Pricing (free tier) — https://netbird.io/pricing
|
||||
- Local: `netbird-vs-tailscale-mesh-vpn.md`, `netbird-twingate-gameplan.md`, `security_principles.md`,
|
||||
`project_docker_network_proxy.md`, `playbook_postgres_databases.md`
|
||||
|
||||
## Update instructions
|
||||
Fill in the server-01 test results for items (b) device-flow and (c) WSS-relay-through-cloudflared, then
|
||||
mark them RESOLVED and update `netbird-twingate-gameplan.md` Phase 2. Spin Phase 2 steps into
|
||||
`playbook_netbird_phases.md` when the build starts.
|
||||
@@ -0,0 +1,96 @@
|
||||
# NetBird + Twingate Deployment Gameplan
|
||||
|
||||
> Owner-decided plan (2026-09-15). Supersedes the "retire Twingate" line in
|
||||
> `research/netbird-vs-tailscale-mesh-vpn.md`: the owner is keeping BOTH tools, with **distinct
|
||||
> roles** (below). Mesh-tool-vs-Tailscale decision itself stands — NetBird self-hosted wins.
|
||||
> Driver deadline: **partner meeting Sun 2026-09-20** (Etsy) must work over Tyler's CGNAT.
|
||||
|
||||
---
|
||||
|
||||
## Decisions locked (2026-09-15)
|
||||
|
||||
1. **Role split (the thing that makes "both tools" coherent):**
|
||||
- **Twingate = access for OTHER PEOPLE (partners) to a SPECIFIC resource (Nextcloud).** Partners
|
||||
don't join the owner's mesh; they get scoped, zero-trust access to Nextcloud (Talk + files)
|
||||
regardless of CGNAT. This is the actual fix for Tyler's CGNAT and the fastest path to
|
||||
"partner meetings work."
|
||||
- **NetBird = the OWNER's own device mesh** (primary host, server-01, laptop, phone) + pulling the
|
||||
owner's admin surfaces (SSH, dashboards, Vault UI) off cloudflared. Bigger, self-hosted, later.
|
||||
2. **server-01 is TEST-ONLY** (`feedback_sandbox_isolation`). NetBird's self-hosted stack gets
|
||||
**validated on server-01 as a throwaway sandbox** (the 3 UNVERIFIED items). The **production**
|
||||
NetBird control plane and the **Twingate connector both deploy on the PRIMARY server.** Nothing
|
||||
real lives on server-01 (exceptions remain Ollama/GPU + Obsidian).
|
||||
3. **Sequencing:** **Twingate first** (deadline-driven), NetBird self-hosted after.
|
||||
4. **Partner change is IN SCOPE:** Austin has left the business → **remove Austin's access
|
||||
everywhere; add Hailee** (new 33% partner). Audited in preflight, executed in main work.
|
||||
5. **NetBird bootstrap = DECIDED (2026-09-15): bootstrap on NetBird Cloud free tier, then migrate to
|
||||
self-hosted.** Decouples "does the mesh work for my nodes" from "can I self-host the control plane
|
||||
cleanly." Preflight still resolves the 3 unverified facts (DB engine, TURN-through-cloudflared,
|
||||
Authelia-OIDC) to de-risk the self-hosted cutover.
|
||||
|
||||
---
|
||||
|
||||
## The problem (why this exists)
|
||||
|
||||
- Tyler is behind **CGNAT** at work/on mobile → WebRTC P2P for Nextcloud **Talk** fails → he can't
|
||||
reliably join partner meetings. Current band-aid = the public **Open Relay Project** TURN
|
||||
(`staticauth.openrelay.metered.ca`), a third-party relay the owner doesn't control and which has
|
||||
been flaky. (`project_nextcloud_talk_coturn`.)
|
||||
- Twingate routes the partner's Nextcloud traffic through a connector on the owner's network →
|
||||
**CGNAT becomes irrelevant** and no port-forwarding is needed (respects P5).
|
||||
|
||||
---
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 0 — PREFLIGHT (tonight, background agent K, read-only/research)
|
||||
No deployments, no mutations. Produces the runbooks + audit that make tomorrow's work mechanical.
|
||||
1. **Partner access audit (Austin → Hailee):** read-only enumeration of every place Austin has access
|
||||
(Nextcloud users/groups/shares, `/Partner Meetings` folder, Talk rooms, any app/service logins,
|
||||
Vault/Bitwarden entries) + every place Hailee must be added. Output = offboard/onboard checklist.
|
||||
**No revocations tonight** — access changes happen with the owner present.
|
||||
2. **Twingate deploy runbook:** current free-tier limits, connector container shape (image, network
|
||||
mode, on PRIMARY server), Resource + Remote-Network model to expose Nextcloud to a partner, how a
|
||||
partner authenticates/connects, Vault secret paths to create. Output = mechanical deploy runbook.
|
||||
3. **NetBird self-host buildplan:** resolve the 3 UNVERIFIED items from current docs — (a) mgmt DB
|
||||
engine (SQLite default? can it point at homelab Postgres?), (b) Authelia via generic-OIDC vs
|
||||
fall back to Authentik, (c) TURN/relay reachable through cloudflared without opening a UDP port —
|
||||
plus a crisp **bootstrap-vs-self-hosted recommendation with the info the owner needs to decide**,
|
||||
the server-01 validation runbook, and the primary-server production deploy shape.
|
||||
|
||||
### Phase 1 — TWINGATE + PARTNER SWAP (next infra block, agent/owner, MUTATING — main work L)
|
||||
Deadline: complete before **Sun 09-20**.
|
||||
1. Create Twingate network + deploy the connector on the **primary server** (Vault-backed secrets).
|
||||
2. Define Nextcloud as a Twingate **Resource**; scope access to partners.
|
||||
3. **Offboard Austin** per the audit checklist (Nextcloud shares/groups, `/Partner Meetings`, Talk,
|
||||
any service logins, Vault/Bitwarden) + note external items (Discord) for the owner to do manually.
|
||||
4. **Onboard Hailee** to Nextcloud + Twingate; add to `/Partner Meetings`, Talk, relevant groups.
|
||||
5. **Verify:** Tyler (or a CGNAT-simulated test) joins a Talk call through Twingate end-to-end;
|
||||
Hailee can reach Nextcloud; Austin can reach nothing.
|
||||
6. Once Twingate relay is proven, plan to **remove Open Relay Project** from Talk TURN settings
|
||||
(only after confirming Talk still connects via Twingate).
|
||||
|
||||
### Phase 2 — NETBIRD SELF-HOSTED (later this week, after bootstrap decision)
|
||||
Own phase-prompt set (`playbook_netbird_phases.md`), written once Phase 0's buildplan lands and the
|
||||
bootstrap call is made. High level (from the research report):
|
||||
1. Validate the 3 items on the **server-01 sandbox** (throwaway peer, policy, posture, routing peer).
|
||||
2. Wire OIDC (Authelia if verified, else Authentik); create Vault paths `secret/netbird/*`.
|
||||
3. Deploy production control plane on the **primary server** (management + signal + relay + coturn +
|
||||
dashboard) behind Traefik/cloudflared.
|
||||
4. Enroll the 4 real nodes; confirm P2P + relay fallback from a roaming network.
|
||||
5. Cut owner admin access (SSH/dashboards/Vault UI) off cloudflared onto the mesh.
|
||||
6. Backup/restore of `/var/lib/netbird` + upgrade/rollback runbook (new playbook).
|
||||
7. Decommission any bootstrap cloud tenant; retire the old plan from the roadmap.
|
||||
|
||||
---
|
||||
|
||||
## Week priority context (owner's question: is anything more important?)
|
||||
Infra is the right focus. Recommended sequence within it:
|
||||
**Twingate + partner swap (deadline)** → **secrets-proxy #174 + agent-sudo #176** (these retire
|
||||
sudo-bridge and unlock more autonomous agent work — highest leverage on "get more done") →
|
||||
**NetBird self-hosted** (biggest) → **voice system** (last). Wed personal block = media_pipeline as
|
||||
planned. See the session summary for the fuller argument.
|
||||
|
||||
## Update instructions
|
||||
Update phase status inline as each completes. When Phase 2 starts, spin its steps into
|
||||
`playbook_netbird_phases.md` and index it in `MEMORY.md`.
|
||||
@@ -0,0 +1,265 @@
|
||||
# NetBird vs Tailscale — Mesh-VPN / Zero-Trust Access Decision
|
||||
|
||||
> Research-only. No installs, no config changes, no secrets written. Every pricing/feature claim is
|
||||
> pinned to a source + access date below; anything unproven is marked **UNVERIFIED**.
|
||||
> Author: background research agent (Opus 4.8) · Date: **2026-09-15**
|
||||
|
||||
---
|
||||
|
||||
## Verdict
|
||||
|
||||
**Pick: NetBird.** For *this* owner the deciding axis is not raw polish — it is the #1 stated
|
||||
principle, *"everything self-hosted on-prem except the Claude model itself; eliminate vendor lock-in
|
||||
on tooling."* NetBird ships its **entire coordination stack — management, signal, relay/TURN,
|
||||
dashboard — as the same open-source code it runs in its cloud**, self-hostable via docker-compose,
|
||||
with no per-node license and native OIDC/SSO, policy ACLs, and posture checks. Tailscale's
|
||||
coordination server is **proprietary SaaS that is officially not self-hostable**; owning that control
|
||||
plane means **Headscale**, a third-party re-implementation with real feature gaps (CLI-first, weaker
|
||||
peer-relay reachability, community-built UI). Choosing Tailscale therefore means either accepting
|
||||
vendor lock-in on the control plane (a direct strike against principle #1) or running a *different
|
||||
project's* partial clone of it. NetBird lets mesh **and** zero-trust access consolidate into **one
|
||||
tool the owner actually owns**, which is exactly the "consolidate into ONE self-hostable tool" thesis
|
||||
that made NetBird the newer candidate. The prior unconfirmed lean (Tailscale + Twingate, host-mode)
|
||||
does not survive the self-host-first weighting: it is two proprietary control planes where NetBird is
|
||||
one open one. **The honest cost of the pick is solo-operator maintenance** (5-ish containers + an IdP
|
||||
vs. a hosted plane or a single Headscale binary) — that is the one thing that would flip it (below).
|
||||
|
||||
---
|
||||
|
||||
## Owner criteria & weights (what I optimized for)
|
||||
|
||||
Drawn from `project_ai_infrastructure_vision.md`, `security_principles.md` (P1–P9),
|
||||
`project_docker_network_proxy.md`, and the task constraints — **not** a generic "best VPN" list.
|
||||
|
||||
| # | Criterion | Weight | Why this weight (owner's documented stance) |
|
||||
|---|-----------|:---:|---|
|
||||
| 1 | **Self-hostability of the control plane / anti-vendor-lock-in** | **30%** | The #1 core-vision principle. A control plane you cannot own is an explicit strike. |
|
||||
| 2 | **$ cost — current + at scale** | 15% | $20/mo Claude Pro hard cap; "control costs as businesses scale"; watches every recurring cost. |
|
||||
| 3 | **Zero-trust / SSO-OIDC-MFA + secrets-into-Vault fit** | 15% | P1–P9; Authelia present (P4 prefers Authelia over Authentik); Vault is system of record for secrets. |
|
||||
| 4 | **Solo-operator maintenance burden (upgrades, HA, DB backup, blast radius)** | 15% | Owner runs everything alone; a self-hostable tool that is painful solo may lose on paper. |
|
||||
| 5 | **Docker/compose deployment fit** | 8% | Docker-first; canonical template = bridged + host-LAN-bound; documented host-mode exception for tunnel agents. |
|
||||
| 6 | **Interop with Traefik + cloudflared + 172.16.16.x; can it replace cloudflared for owner's own access** | 7% | Must not fight the existing proxy stack; business SaaS stays on cloudflared/Traefik. |
|
||||
| 7 | **NAT traversal / relay self-hosting** | 5% | Self-host-first extends to relays; roaming laptop/phone need reliable traversal. |
|
||||
| 8 | **Maturity / community** | 3% | Solo operator needs docs + a community to lean on. |
|
||||
| 9 | **Migration effort from the prior Tailscale+Twingate idea** | 2% | Prior lean is context to test, not a default; nothing is deployed yet, so switching cost is low. |
|
||||
|
||||
Topology being connected: primary Docker host (Debian 13, kernel 6.12.107) + **server-01 =
|
||||
TEST-ONLY sandbox (never prod data)** + roaming laptop + phone. ~4 nodes today; must scale cleanly.
|
||||
|
||||
---
|
||||
|
||||
## Tailscale profile
|
||||
|
||||
**Control plane.** Proprietary, vendor-operated coordination server (runs on AWS, EU/Frankfurt by
|
||||
default), **officially not self-hostable**. WireGuard data-plane is end-to-end encrypted and never
|
||||
crosses the coordinator either way. [tailscale pricing/docs; DEV/meetrix comparisons — 2026-09-15]
|
||||
|
||||
**Self-host reality = Headscale (third-party).** Open-source re-implementation of the coordination
|
||||
server. Gaps vs. official as of 2026: **CLI-first** (web UI only via the community **Headplane**
|
||||
project), **peer-relay reachability is weaker** (Headscale devices need direct line-of-sight to the
|
||||
relay node; Tailscale's hosted service relays even when endpoints can't reach the relay directly),
|
||||
requires a **publicly reachable server + TLS cert + ongoing maintenance**, and bills conceptually
|
||||
**per-node** (self-managed) rather than per-user. HA/upgrade/backup of Headscale's own DB is the
|
||||
operator's problem. [Headscale vs Tailscale comparisons; itprotutorials; homelabaddiction — 2026-09-15]
|
||||
|
||||
**Pricing (tailscale.com/pricing, 2026-09-15).**
|
||||
- **Personal (Free):** **$0**, **up to 6 users**, **unlimited user devices**, 50 tagged resources, 1,000 ephemeral-resource minutes/mo.
|
||||
- **Standard:** **$8 / user / mo**, unlimited users + devices.
|
||||
- **Premium:** **$18 / user / mo**, up to 300 ACL groups, 10,000 ephemeral min/mo.
|
||||
- **Enterprise:** custom / invoice.
|
||||
- *For this 4-node, 1-user homelab the free tier is $0 and sufficient today.*
|
||||
|
||||
**ACL / policy.** Mature tag-based ACL model (`tag:`, groups, grant rules) in HuJSON policy file — widely considered the reference-quality implementation. Tailscale SSH + ACL check modes.
|
||||
|
||||
**SSO / MFA.** Cloud tier ties into Google/Microsoft/GitHub/Okta/OIDC as the user directory; MFA delegated to the IdP. **Headscale supports OIDC login** (incl. self-hosted IdPs) but the SSO UX and device-approval polish is thinner.
|
||||
|
||||
**Subnet router + exit node.** First-class: a node advertises `--advertise-routes` to reach a LAN/Docker subnet; exit node routes public egress. Kernel networking + IP forwarding recommended for exit-node/router roles.
|
||||
|
||||
**DERP relays.** **Self-hostable** via the open-source `derper` binary/Docker image — but the derper version must match host `tailscaled`, and `OmitDefaultRegions:true` removes the hosted fallback (self-relay becomes a single point of failure). [dev.to/lucifer1004; sitepoint — 2026-09-15]
|
||||
|
||||
**Container shape.** Agent runs as `tailscale/tailscale` container needing **`/dev/net/tun` + `NET_ADMIN`** (and typically host networking for subnet-router/exit-node use) — i.e. it forces the **documented host-mode template exception**. Self-hosting the plane = additionally running Headscale (single Go binary/container) + Headplane + a public TLS endpoint.
|
||||
|
||||
---
|
||||
|
||||
## NetBird profile
|
||||
|
||||
**Control plane.** **Fully open-source and first-class self-hostable** — the self-hosted stack is the
|
||||
same code as NetBird Cloud. Components (docker-compose): **management** (API + business logic + policy
|
||||
engine), **signal** (peer signaling), **relay/TURN + coturn** (fallback relay/STUN-TURN),
|
||||
**dashboard** (web UI). WireGuard data-plane is end-to-end encrypted; the management server never sees
|
||||
traffic. [docs.netbird.io/selfhosted — 2026-09-15]
|
||||
|
||||
**Management DB.** Self-hosted management persists under `/var/lib/netbird/` in the management
|
||||
container; backup = `docker compose cp` of config + the management store. Default store is
|
||||
file/SQLite-style with a Postgres option in newer releases. **UNVERIFIED**: exact default DB engine in
|
||||
the current release and whether it can point at the existing homelab Postgres — confirm against the
|
||||
current self-hosted guide before building. Upgrades: read release notes for breaking changes; very old
|
||||
versions need staged upgrades. [docs.netbird.io/selfhosted/selfhosted-guide — 2026-09-15]
|
||||
|
||||
**Pricing (netbird.io/pricing, 2026-09-15; page shows EUR only).**
|
||||
- **Free (Cloud):** **€0**, **up to 5 users, 100 machines**, P2P + encryption + access controls + private DNS + community support.
|
||||
- **Team:** **€6 / user / mo** (adds SSO with enterprise IdPs, audit logging); 100 machines + 10/extra user; extra machines €0.50/mo.
|
||||
- **Business:** **€12 / user / mo** (adds device approvals, MDM/EDR, **posture checks**, traffic-event logging).
|
||||
- **Enterprise:** custom.
|
||||
- **Self-hosted:** **$0 software, unlimited nodes/features** — you pay only your own compute. This is the option that satisfies principle #1.
|
||||
- *EUR→USD conversion is approximate and not shown on the page (**UNVERIFIED** exact USD).*
|
||||
|
||||
**ACL / policy + posture.** Policy-based access rules (groups, ports, protocols, bidirectional/oneway),
|
||||
network routes, DNS, and **posture checks** (OS version, NetBird version, geo, peer network range) —
|
||||
posture is available in self-hosted and Business cloud. Meets the P9 zero-trust-internal goal.
|
||||
|
||||
**SSO / OIDC / MFA.** OIDC is **core even self-hosted** — an embedded local IdP for basic use, or
|
||||
external OIDC. Officially documented/tested: **Zitadel, Keycloak, Authentik, PocketID** (self-hosted)
|
||||
+ Entra/Google/Okta/Auth0/JumpCloud (managed), plus a **generic OIDC connector for "any OIDC IdP."**
|
||||
**Authentik** has a full step-by-step guide. **Authelia is NOT on the tested list** — generic-OIDC
|
||||
integration is *likely* but **UNVERIFIED**; note P4 prefers Authelia, so this is a real open item
|
||||
(either verify Authelia-as-OIDC works with NetBird, or accept running Authentik alongside Authelia for
|
||||
this one integration). MFA is delegated to the IdP. [docs.netbird.io/selfhosted/identity-providers — 2026-09-15]
|
||||
|
||||
**Relay / signal self-hosting.** Both signal and relay/TURN are self-hosted components of the stack —
|
||||
NAT traversal fallback stays entirely on owned infrastructure. No dependency on a vendor relay.
|
||||
|
||||
**Subnet routing / host services.** NetBird **routing peers** (network routes) expose the host LAN /
|
||||
Docker `172.16.16.x` subnet to the mesh, and exit-node routing is supported — same role Tailscale's
|
||||
subnet router fills.
|
||||
|
||||
**Container shape.** Agent (`netbirdio/netbird`) needs **`/dev/net/tun` + `NET_ADMIN`** and host
|
||||
networking to place the tunnel on the host — the **same documented host-mode template exception** as
|
||||
Tailscale. The self-hosted control plane is a normal bridged docker-compose stack behind
|
||||
Traefik/cloudflared (no host-mode needed for the plane itself).
|
||||
|
||||
---
|
||||
|
||||
## Weighted scoring table
|
||||
|
||||
Scores 1–5 (5 = best fit for *this* owner). Weighted = score × weight.
|
||||
|
||||
| Criterion | Wt | Tailscale (cloud) | Tailscale self-host (Headscale) | **NetBird self-host** | NetBird Cloud (free) |
|
||||
|---|:--:|:--:|:--:|:--:|:--:|
|
||||
| 1 Self-host control plane / anti-lock-in | .30 | 1 | 3 (3rd-party, partial) | **5** | 2 |
|
||||
| 2 $ cost | .15 | 4 ($0 free tier) | 4 ($0 + compute) | 4 ($0 + compute) | 4 ($0 free tier) |
|
||||
| 3 Zero-trust / SSO-OIDC / Vault fit | .15 | 4 | 3 | **4** | 4 |
|
||||
| 4 Solo-operator burden | .15 | 5 | 3 | **2** | 5 |
|
||||
| 5 Docker/compose fit | .08 | 4 | 4 | **4** | 4 |
|
||||
| 6 Interop (Traefik/cloudflared/subnet) | .07 | 4 | 4 | **4** | 4 |
|
||||
| 7 NAT traversal / relay self-host | .05 | 3 (DERP self-host, caveats) | 3 | **5** | 3 |
|
||||
| 8 Maturity / community | .03 | 5 | 4 | **4** | 4 |
|
||||
| 9 Migration from prior TS+Twingate idea | .02 | 3 | 3 | **4** | 4 |
|
||||
| **Weighted total** | | **3.19** | **3.30** | **3.98** | **3.44** |
|
||||
|
||||
**Read:** NetBird self-hosted wins on the weighting that reflects the owner's principles (**3.98**).
|
||||
Its only sub-par cell is #4 solo-operator burden (2) — the honest, load-bearing trade. If criterion
|
||||
#1 were dropped to a generic weight, Tailscale-cloud's polish would close much of the gap — which is
|
||||
precisely why the weighting matters and why the prior lean looked reasonable before self-host-first
|
||||
was applied.
|
||||
|
||||
---
|
||||
|
||||
## Recommended deployment shape (what I'd actually run)
|
||||
|
||||
**Tool: NetBird. Control plane: self-hosted** (target state), with **NetBird Cloud free tier as the
|
||||
zero-risk bootstrap** while the self-hosted stack is stood up and validated on server-01.
|
||||
|
||||
1. **Bootstrap (day 1, optional):** join all 4 nodes to **NetBird Cloud free tier** (5 users / 100
|
||||
machines, $0) to prove mesh + posture + policies fast. This is a temporary convenience, not the
|
||||
destination — no prod data flows over it beyond the mesh itself.
|
||||
2. **Target — self-hosted control plane** on the primary Docker host as a bridged docker-compose stack:
|
||||
`management` + `signal` + `relay` + `coturn` + `dashboard`, fronted by **Traefik** internally and
|
||||
published externally through **cloudflared** (the dashboard/management/signal endpoints need to be
|
||||
reachable by roaming devices — cloudflared already does this without opening ports; verify TURN/UDP
|
||||
relay reachability, which may need a directly exposed port rather than the HTTP tunnel — **UNVERIFIED**,
|
||||
test). Image-selection order per policy: official `netbirdio/*` images (no linuxserver variant exists).
|
||||
3. **Agents** on each node (`netbirdio/netbird`) run under the **documented host-mode template
|
||||
exception**: `network_mode: host` + `cap_add: NET_ADMIN` + `/dev/net/tun`. Everything else stays on
|
||||
the standard bridged + host-LAN-bound template.
|
||||
4. **Reach host-bound services** via a **NetBird routing peer** on the primary host advertising the
|
||||
host LAN IP + the Docker `172.16.16.x` subnet — the mesh then reaches existing Traefik-fronted and
|
||||
host-bound services without per-service exposure (satisfies P5/P9: LAN-only services stop needing a
|
||||
cloudflared route).
|
||||
5. **IdP / SSO:** connect NetBird to an OIDC IdP. **Preferred:** verify **Authelia** works via
|
||||
NetBird's generic-OIDC connector (respects P4). **Fallback:** run **Authentik** for this one
|
||||
integration (fully documented) — flag the P4 tension for owner sign-off. MFA delegated to the IdP.
|
||||
6. **Secrets → Vault (paths to CREATE, do not populate in this research task):**
|
||||
- `secret/netbird/management` — OIDC client secret, management API/admin token, datastore encryption key.
|
||||
- `secret/netbird/oidc` — IdP client-id/secret + issuer URL.
|
||||
- `secret/netbird/turn` — coturn shared secret / auth credentials.
|
||||
- `secret/netbird/setup-key` — enrollment/setup keys (short TTL, rotate on device churn).
|
||||
Delivery follows the existing SecretSpec+Vault container pattern; nothing hardcoded in compose (P2).
|
||||
7. **cloudflared coexistence:** cloudflared/Traefik **stay** as the public HTTPS front for
|
||||
**business SaaS** and public services. NetBird **replaces cloudflared for the owner's OWN remote
|
||||
access** to homelab/admin surfaces (SSH, dashboards, Vault UI, server-01) — those routes get pulled
|
||||
off cloudflared (P5: don't internet-expose what LAN/mesh can reach). Two lanes: public = cloudflared;
|
||||
private/admin = NetBird mesh.
|
||||
8. **Blast radius:** WireGuard tunnels are peer-to-peer; if the self-hosted management plane is **down,
|
||||
existing peer connections keep forwarding** — new enrollments/policy changes pause until it's back.
|
||||
That materially softens the criterion-#4 risk. Back up `/var/lib/netbird` + configs on the standard
|
||||
schedule; management is stateless enough to redeploy from Vault + backup.
|
||||
|
||||
---
|
||||
|
||||
## Honest case for the loser + flip condition
|
||||
|
||||
**The case for Tailscale (the loser):** It is the more polished, lower-burden product. The **free
|
||||
tier ($0, 6 users, unlimited devices)** already covers this homelab at zero cost and zero maintenance;
|
||||
its **ACL/tag model is the reference implementation**; **DERP is self-hostable**; and a solo operator
|
||||
gets a battle-tested UI and the largest community. If the owner's real constraint turns out to be
|
||||
*time*, not *ownership*, Tailscale-cloud is the rational pick — you offload the entire control-plane
|
||||
upkeep to a vendor whose only visibility is coordination metadata, never traffic. The prior
|
||||
Tailscale+Twingate lean was a reasonable pre-principle answer.
|
||||
|
||||
**Flip condition (single, crisp):** **The pick flips to Tailscale (cloud) if the owner decides that,
|
||||
for the mesh-VPN control plane specifically, solo-operator maintenance time outweighs the self-host
|
||||
principle — i.e. consciously accepts vendor lock-in on this one tool.** Secondary flip: if a hands-on
|
||||
trial shows NetBird's self-hosted TURN/relay can't be made reliably reachable through the existing
|
||||
cloudflared/Cloudflare-free-plan setup without extra port exposure that violates P5, and Tailscale's
|
||||
DERP self-host proves cleaner, prefer Tailscale-cloud + self-hosted DERP as the pragmatic middle.
|
||||
|
||||
---
|
||||
|
||||
## Migration / rollout steps (high level — no execution)
|
||||
|
||||
1. **Verify the 3 open items first** (below) — especially Authelia-OIDC and TURN-through-cloudflared.
|
||||
2. Stand up **NetBird self-hosted stack on server-01 (sandbox)** — never prod data — and enroll a
|
||||
throwaway peer; validate policy, posture, routing peer to a test subnet.
|
||||
3. Wire **OIDC** (Authelia if verified, else Authentik); create the **Vault paths** and load secrets
|
||||
via the SecretSpec pattern.
|
||||
4. Enroll the **4 real nodes** (primary host routing peer, server-01, laptop, phone); confirm P2P +
|
||||
relay-fallback works from a roaming network.
|
||||
5. **Cut owner's admin access** (SSH/dashboards/Vault UI/server-01) from cloudflared routes onto the
|
||||
mesh; leave business/public SaaS on cloudflared/Traefik.
|
||||
6. Add **backup/restore** of `/var/lib/netbird` + configs to the standard schedule; document
|
||||
upgrade/rollback runbook (a new playbook per `feedback_playbook_accessibility`).
|
||||
7. Decommission the bootstrap NetBird-Cloud tenant once self-hosted is proven; retire the
|
||||
Tailscale+Twingate plan from the roadmap.
|
||||
|
||||
---
|
||||
|
||||
## Open questions / grill-me (≤6)
|
||||
|
||||
1. **Authelia vs Authentik for NetBird OIDC (P4 tension):** confirm Authelia works via NetBird's
|
||||
generic-OIDC connector, or accept Authentik for this one integration? (**UNVERIFIED** — must test.)
|
||||
2. **TURN/relay through cloudflared:** can the self-hosted coturn/relay be reached for roaming peers
|
||||
without opening a direct UDP port (which brushes P5)? (**UNVERIFIED** — Cloudflare free plan + UDP.)
|
||||
3. **Management DB engine:** does the current self-hosted release default to SQLite, and can it point
|
||||
at the existing homelab Postgres for unified backup? (**UNVERIFIED**.)
|
||||
4. **Ownership vs. time — the real decision:** is control-plane self-hosting worth the solo-operator
|
||||
upkeep here, or is this the one place to accept a SaaS plane? (This is the flip condition.)
|
||||
5. **Bootstrap on cloud free tier or go straight to self-hosted?** Speed vs. purity.
|
||||
6. **Exact USD pricing** if the free tier is ever outgrown (page is EUR-only). (**UNVERIFIED** USD.)
|
||||
|
||||
---
|
||||
|
||||
## Sources (URL + access date)
|
||||
|
||||
All accessed **2026-09-15**:
|
||||
- Tailscale pricing — https://tailscale.com/pricing
|
||||
- NetBird pricing — https://netbird.io/pricing
|
||||
- NetBird self-hosted guide — https://docs.netbird.io/selfhosted/selfhosted-guide
|
||||
- NetBird identity providers (OIDC/Authentik) — https://docs.netbird.io/selfhosted/identity-providers ; https://docs.netbird.io/selfhosted/identity-providers/authentik
|
||||
- Headscale vs Tailscale (feature gaps, 2026) — https://dev.to/selfhostingsh/headscale-vs-tailscale-self-hosted-control-plane-1h1f ; https://meetrix.io/blogs/headscale-vs-tailscale/ ; https://itprotutorials.com/headscale-setup-guide-2026/ ; https://homelabaddiction.com/headscale-vs-tailscale-for-homelabs-in-2026-when-self-hosting-the-control-plane-actually-makes-sense/
|
||||
- Tailscale DERP self-host + subnet/exit — https://dev.to/lucifer1004/custom-tailscale-derp-server-n73 ; https://www.sitepoint.com/tailscale-peer-relays-nat-traversal-derp/
|
||||
- Local context (no URL): `project_ai_infrastructure_vision.md`, `security_principles.md`, `project_docker_network_proxy.md`, prior `project_tailscale_twingate_deploy` (memory)
|
||||
|
||||
**Training-loop note (per `feedback_always_include_training_loop`):** log the eventual owner decision
|
||||
(NetBird vs. flip-to-Tailscale) + the flip-condition outcome as a decision record so the local model
|
||||
learns this infra-tradeoff pattern.
|
||||
@@ -0,0 +1,288 @@
|
||||
# Omarchy AI Skills — Deep Dive (crash auto-investigation focus + portability)
|
||||
|
||||
Research date: 2026-09-13. Author: background research agent (Opus 4.8).
|
||||
Scope: fact-check the AI skills that ship with Omarchy Linux, with a focus on the alleged
|
||||
"crash → investigate → fix" skill, and assess whether any of them can be lifted and reused on our
|
||||
own (Debian/LMDE) machines. **This agent installed nothing, changed no config, needed no credential.**
|
||||
Builds on `research/omarchy-os-evaluation.md` (read first; not re-derived here).
|
||||
|
||||
**No adoption decision is made here.** This is a fact-gathering + portability assessment + a spec
|
||||
blueprint. **The main session decides** whether to spec/build anything.
|
||||
|
||||
Repo state examined: `omacom/omarchy`, branch **`quattro`** (Omarchy 4 line), via the public GitHub
|
||||
API tree + raw file contents (accessed 2026-09-13).
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict (preliminary, hedged)
|
||||
|
||||
**The crash-investigation skill is REAL and shipped** — but the user's recollection is half right and
|
||||
half wrong, and the wrong half is the important one. Omarchy 4 ships a `diagnose-crash` skill, wired to
|
||||
a `systemd-coredump` journal watcher, that on a crash pops a "Process crashed" notification; **clicking
|
||||
it** hands the crash to your default agent with the skill. The skill does exactly investigate → correlate
|
||||
→ report. But it **explicitly refuses to fix anything** — its own words: *"Leave the system as you found
|
||||
it. Diagnosis reads; it does not fix, tidy, or reconfigure."* It is also **human-click-triggered, not
|
||||
unattended.** So the "and then fixes the issue" part the user saw in a video is **not** what the shipped
|
||||
skill does — either the demo showed the agent going off-script, or it's a conflation with a separate
|
||||
auto-fix skill (a community "modify→verify→revert" loop exists, but is **not** Omarchy's). **Worth
|
||||
porting? Lean-yes for the diagnosis+report method** — it maps cleanly onto our `diagnose` skill and, by
|
||||
design, already fails read-only, which is exactly our constrained-autonomy posture. The wiring is
|
||||
Omarchy/Hyprland-specific and must be rewritten for Debian. **Main session decides.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Skill inventory
|
||||
|
||||
Omarchy ships **two** agent skills by default (under `default/agents/skills/`, symlinked into
|
||||
`~/.claude/skills` and the other harness skill dirs on an installed system). Note: the AI manual page
|
||||
still describes "one default skill" (the system-tailoring one) — the `diagnose-crash` skill was added in
|
||||
the v4 "quattro" line and the prose lags the repo. A separate set of `.md` files at repo-root
|
||||
`agents/skills/` are **Omarchy's own source-development skills (NOT shipped to end users)** and are
|
||||
listed at the bottom for completeness.
|
||||
|
||||
| Skill name | Repo path (branch `quattro`) | Purpose (one line) | Symlink target on installed system | Source URL |
|
||||
|---|---|---|---|---|
|
||||
| **omarchy** (system tailoring) | `default/agents/skills/omarchy/SKILL.md` (+ `capture.md`, `contributing.md`, `hooks.md`, `hyprland.md`, `plugins.md`, `theming.md`) | Safely customize Hyprland / Omarchy shell / terminals / themes via `omarchy` CLI + `~/.config/` edits | `~/.claude/skills/` (also `~/.codex/skills`, `~/.pi/agent/skills`, `~/.gemini/config/skills`, `~/.hermes/skills`, `~/.agents/skills`) | https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/omarchy/SKILL.md |
|
||||
| **diagnose-crash** | `default/agents/skills/diagnose-crash/SKILL.md` (+ `reporting.md`) | Diagnose why a process crashed from a `systemd-coredump` core dump; optionally file a verified Omarchy bug upstream | same skill dirs as above | https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/diagnose-crash/SKILL.md |
|
||||
|
||||
**Crash wiring (not skills, but the machinery that invokes `diagnose-crash`):**
|
||||
|
||||
| File | Repo path | Role |
|
||||
|---|---|---|
|
||||
| `omarchy-crash-watch` | `bin/omarchy-crash-watch` | journald watcher on the coredump `MESSAGE_ID`; emits the "Process crashed" notification |
|
||||
| `omarchy-agent-crash` | `bin/omarchy-agent-crash` | click handler / manual entry (`omarchy agent crash <pid>`); gathers facts, invokes default agent with the skill |
|
||||
| `omarchy-crash-mute` | `bin/omarchy-crash-mute` | silence notifications for one program (the only state change diagnosis may make) |
|
||||
| `omarchy-toggle-crash-capture` | `bin/omarchy-toggle-crash-capture` | global on/off for crash capture |
|
||||
| `omarchy-crash-watch.service` | `default/systemd/user/omarchy-crash-watch.service` | **user** systemd unit that runs the watcher, gated on `graphical-session.target` + `WAYLAND_DISPLAY` |
|
||||
|
||||
**NOT shipped to users — Omarchy's own dev skills** (repo-root `agents/skills/*.md`): `acceptance-tests`,
|
||||
`command-metadata`, `icon-font`, `install-scripts`, `migrations`, `shell-dev`, `visual-verification`.
|
||||
These are for developing Omarchy itself; ignore for porting.
|
||||
|
||||
---
|
||||
|
||||
## 3. Crash auto-investigation skill — existence finding + full mechanism
|
||||
|
||||
### Existence: (a) SHIPPED Omarchy skill — confirmed.
|
||||
It is a real skill in the distro repo (`default/agents/skills/diagnose-crash/`), not a demo or a
|
||||
third-party add-on. The AI manual describes the behavior in prose. (accessed 2026-09-13:
|
||||
https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/diagnose-crash/SKILL.md ;
|
||||
https://omarchy.org/manual/ai/)
|
||||
|
||||
**Important truth-check on the "and then fixes it" claim: FALSE for the shipped skill.** The skill is
|
||||
diagnosis + report only, by explicit design. See guardrail quote below. Any "it fixed the problem" demo
|
||||
was the agent acting outside the skill's instructions, or a conflation with an unrelated community
|
||||
auto-fix loop (e.g. the "auto-detect → fix ONE thing → commit → verify → keep/revert" pattern found on
|
||||
skillsplayground/community repos — **that is not Omarchy's skill**).
|
||||
|
||||
### Trigger wiring (quoted)
|
||||
A **user** systemd service runs a journald follower keyed to the systemd-coredump message id:
|
||||
|
||||
- `omarchy-crash-watch.service`: `ExecStart=/usr/bin/omarchy-crash-watch`, `After=graphical-session.target`,
|
||||
`ConditionEnvironment=WAYLAND_DISPLAY`, disabled via a toggle flag file
|
||||
(`ConditionPathExists=!%h/.local/state/omarchy/toggles/crash-capture-off`).
|
||||
- `omarchy-crash-watch` tails the journal: `journalctl -f -n 0 -o json "MESSAGE_ID=$COREDUMP_MESSAGE_ID"`
|
||||
(`COREDUMP_MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1`), filters to **this user's** crashes only
|
||||
(`((uid == UID))` — *"a daemon dumping core is a sysadmin's problem"*), de-dupes crash loops (60 s
|
||||
window), then emits a **critical** notification: title `"Process crashed: $comm"`, body
|
||||
`"Click to diagnose with AI"`, with `--exec omarchy-agent-crash "$pid" "$comm" "$exe" "$signal"`.
|
||||
- **The human clicks the notification** (or runs `omarchy agent crash <pid>` by hand). It is
|
||||
**NOT** unattended: *"The toast only offers a diagnosis."*
|
||||
- `omarchy-agent-crash` looks up the crash time from `coredumpctl`, builds a prompt with the recorded
|
||||
facts, and `exec omarchy-agent --prompt "$prompt"` — i.e. hands it to whatever the default agent is
|
||||
(Claude Code, if that's the default). The prompt says: *"Use the diagnose-crash skill… If your harness
|
||||
has no skill mechanism, read the skill files directly and follow them instead."*
|
||||
|
||||
### What the SKILL.md instructs (structure + key excerpts)
|
||||
Frontmatter `name: diagnose-crash`, triggers listed (crash, segfault, SIGSEGV, SIGABRT, core dump,
|
||||
coredumpctl, "why did X crash", backtrace symbolization). Body sections:
|
||||
|
||||
1. **Establish the facts** — `coredumpctl info <pid>` (backtrace + the command line the process ran);
|
||||
`coredumpctl list` to see one-off vs pattern.
|
||||
2. **Rule out the boring causes first** — `free -h` + journal for OOM kills; *"A process killed by the
|
||||
OOM killer is not a bug in that process."*
|
||||
3. **Correlate against the timeline** — crash timestamp vs filesystem mtimes, the journal around that
|
||||
moment, and **recent package updates** (*"A crash that starts right after an update points at the
|
||||
update."*).
|
||||
4. **Read the whole core, not just frame 0** — other thread stacks show in-flight work; flag in-process
|
||||
third-party code but don't blame without evidence.
|
||||
5. **Symbolize when you can** — extract the core to a fresh `mktemp` path, symbolize via
|
||||
`DEBUGINFOD_URLS="https://debuginfod.archlinux.org" gdb … -ex 'bt'`, then delete the core (privacy:
|
||||
*"A core is a verbatim copy of the process's memory and can hold passwords, tokens, and private
|
||||
documents."*). *"never invent function names to fill the gap."*
|
||||
6. **Report** — (1) what crashed + what it was doing, (2) most likely mechanism separating **proves** vs
|
||||
**inferring**, (3) whether user data was lost + where to recover, (4) recurrence + what would fix it.
|
||||
7. **Offer to stop the notifications** — the only allowed mutation: `omarchy-crash-mute '<program>'`
|
||||
(and how to lift it), only if the user asks.
|
||||
8. **If it is an Omarchy bug** → `reporting.md`.
|
||||
|
||||
### Tools + permissions
|
||||
`coredumpctl`, `gdb` + debuginfod, `journalctl`, `free`, filesystem stat, and (only on explicit user
|
||||
consent) `omarchy-crash-mute`. Reads the user's own core dumps. **No root/sudo required** for the
|
||||
diagnosis path itself (coredumpctl reads the user's own dumps; the watcher runs as the user).
|
||||
`reporting.md` uses `gh` and explicitly **refuses to install or authenticate `gh`** — if it's missing,
|
||||
it hands the user the text to file themselves, and **never files unprompted** ("The user has explicitly
|
||||
agreed… wait for a yes.").
|
||||
|
||||
### Auto-fix behavior + guardrails
|
||||
**It does not auto-fix. Verbatim guardrail (SKILL.md):**
|
||||
> *"**Leave the system as you found it.** Diagnosis reads; it does not fix, tidy, or reconfigure. The one
|
||||
> thing to clean up is your own: delete the core you extracted above… The single change a diagnosis may
|
||||
> make is the mute below, and only when the user asks for it."*
|
||||
|
||||
The one permitted state change (a per-program notification mute) is quoted, quoting-safety-checked
|
||||
(warns that muting `python3.13` silences all Python), and reversible. Upstream bug-filing is gated on
|
||||
three conditions: verified Omarchy-sphere bug, explicit user yes, and a working `gh` — else hand off text.
|
||||
|
||||
**This is, by luck, almost exactly our posture: fail read-only, human-in-the-loop for any mutation.**
|
||||
|
||||
---
|
||||
|
||||
## 4. Portability assessment
|
||||
|
||||
**Mixed — the method is portable, the wiring is not.** These are plain Claude Code skills (Markdown
|
||||
`SKILL.md` files) that run on any distro under a Pro OAuth login (same skill mechanism as ours; Omarchy
|
||||
adds no model and no Anthropic-API dependency — confirmed in the prior report §3). The **diagnosis
|
||||
method** in `diagnose-crash/SKILL.md` is 90% distro-agnostic. The **trigger machinery** is
|
||||
Omarchy/Hyprland-specific and must be rewritten.
|
||||
|
||||
**Works as-is on Debian/LMDE (standard systemd userland):**
|
||||
- `coredumpctl info/list/dump`, `gdb`, `journalctl`, `free -h` — all present on Debian with systemd +
|
||||
`systemd-coredump` installed. (Debian: `systemd-coredump` is a separate package and **not installed by
|
||||
default** — must be `apt install systemd-coredump`; otherwise cores go to the kernel `core_pattern`.)
|
||||
- The whole "establish facts → rule out OOM → correlate timeline → symbolize → report" reasoning.
|
||||
- The privacy discipline (fresh mktemp core, delete after) and the "don't invent symbols" honesty rules.
|
||||
- The read-only stance and the "reads, does not fix" guardrail.
|
||||
|
||||
**Needs rewrite / would NOT be present on Debian/LMDE:**
|
||||
| Omarchy dependency | Why it breaks on Debian | Debian substitute |
|
||||
|---|---|---|
|
||||
| `debuginfod.archlinux.org` URL | Arch-only symbol server | `https://debuginfod.debian.net` (Debian's debuginfod) |
|
||||
| Package-update correlation via `pacman` log | No pacman on Debian | `/var/log/apt/history.log` / `dpkg.log` |
|
||||
| `omarchy-crash-watch.service` (user unit, `graphical-session.target` + `WAYLAND_DISPLAY`) | Assumes Hyprland Wayland graphical session; our servers are headless | Rewrite as a system-level `journalctl MESSAGE_ID` watcher (no graphical gate) feeding NTFY/Hermes |
|
||||
| `omarchy-notification-send` / `-wait` (Quickshell/Hyprland toast + click-to-exec) | Quickshell/Hyprland-only; no desktop on server-01 | Our **NTFY** alert + Hermes handoff instead of a clickable toast |
|
||||
| `omarchy-agent` / `omarchy-agent-crash` / `omarchy-default-agent` | Omarchy helper scripts | Our own launcher into Claude Code / Hermes |
|
||||
| `omarchy-crash-mute` / `omarchy-toggle-crash-capture` | Omarchy state-flag scheme | Our own suppress-list, or skip |
|
||||
| `omarchy version` / `omarchy debug` (in `reporting.md`) | Omarchy CLI | N/A — we file into our own tracker, not omacom/omarchy |
|
||||
| Upstream target `omacom/omarchy` in `reporting.md` | Wrong repo for us | Our own issue tracker / handoff, or drop entirely |
|
||||
|
||||
**Net:** lift the `diagnose-crash/SKILL.md` reasoning nearly verbatim; swap the Arch debuginfod + pacman
|
||||
references for Debian equivalents; replace the entire Hyprland-desktop trigger/notify chain with a
|
||||
headless journald watcher → NTFY/Hermes path; drop the omarchy-specific mute + upstream-reporting glue.
|
||||
|
||||
---
|
||||
|
||||
## 5. Blueprint: a Claude-Code-native version for us (SPEC ONLY — do not build here)
|
||||
|
||||
Map Omarchy's `diagnose-crash` onto our existing **`diagnose` skill** (six-phase disciplined debugging)
|
||||
and the **constrained-autonomy / Hermes / NTFY / sudo-bridge** design. The good news: Omarchy's skill is
|
||||
already read-only, so most of the safety work is aligning triggers and the fix-gate, not rewriting the
|
||||
reasoning.
|
||||
|
||||
**Trigger source (Debian, headless):**
|
||||
- Require `systemd-coredump` installed. Watch the journal for the coredump message-id (same technique as
|
||||
`omarchy-crash-watch`): `journalctl -f -o json "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1"`, **or**
|
||||
more broadly `coredumpctl` + unit-failure signals (`systemctl --failed`, `OnFailure=` units,
|
||||
container health-check failures for our Docker stack — extend beyond raw coredumps).
|
||||
- No graphical gate; runs as a system/Hermes-owned watcher. On our servers, **fail closed**: on a
|
||||
detected crash/failure, Hermes attempts diagnose (read-only) automatically — this is where the Omarchy
|
||||
"human clicks a toast" step becomes "Hermes auto-invokes diagnose, no click needed."
|
||||
|
||||
**Diagnose → report phases (merge Omarchy's steps into our `diagnose` skill):**
|
||||
1. Build the feedback loop / gather facts — `coredumpctl info`, unit status, container logs.
|
||||
2. Rule out the boring causes — OOM (`free -h`, journal OOM kills), disk full, restart loops.
|
||||
3. Correlate the timeline — crash time vs `apt/dpkg` history, config mtimes, recent deploys/Coolify-vs-Jenkins
|
||||
changes, neighbouring-service journal warnings.
|
||||
4. Symbolize where possible — Debian debuginfod; core to fresh mktemp, **delete after** (carry over the
|
||||
privacy rule verbatim).
|
||||
5. Report artifact (below), **proves vs infers** separated, honest about evidence limits.
|
||||
6. **STOP. No mutation.** (Omarchy's "leave the system as you found it" is our default state.)
|
||||
|
||||
**WHERE the sudo-bridge gate sits:** the diagnose+report phases are **entirely read-only and need no
|
||||
approval** — Hermes runs them autonomously. **Any proposed fix that mutates state (restart a service,
|
||||
edit a config, roll back a package, `docker` action) is NOT executed by the skill.** Instead the report
|
||||
ends with a *proposed* remediation, and:
|
||||
- If Hermes can fix within its read-only/allowlisted envelope → it does nothing privileged; it only
|
||||
reports.
|
||||
- If the fix is privileged/mutating → it is **packaged as a sudo-bridge request → human phone approval**.
|
||||
Nothing destructive runs unattended. If Hermes cannot even safely diagnose or the fix needs a human →
|
||||
**high-priority NTFY alert** (this replaces Omarchy's clickable desktop toast).
|
||||
- Never restart containers autonomously (our standing rule); a restart is a sudo-bridge-gated proposal.
|
||||
|
||||
**Report artifact:** a Markdown crash/failure report written to a known path (e.g. an incidents dir or
|
||||
the relevant Machine/project `.claude/context.md` handoff), containing: what failed + what it was doing,
|
||||
mechanism (proves vs infers), OOM/resource ruled-out, timeline correlation (esp. recent apt/deploy),
|
||||
symbolized stack or an honest "unsymbolized, shape only," data-loss assessment, recurrence likelihood,
|
||||
and a **proposed** fix flagged as read-only-safe vs requires-sudo-bridge-approval.
|
||||
|
||||
**Read-only vs may-mutate:** read-only = all fact-gathering, symbolization (to a temp core it deletes),
|
||||
timeline correlation, report writing, NTFY alert. May-mutate (ALL gated behind sudo-bridge human
|
||||
approval) = service restart, config edit, package rollback, container action, any suppress/mute flag.
|
||||
|
||||
**This is a spec. This agent does not build it.** Main session decides whether to spec/build.
|
||||
|
||||
---
|
||||
|
||||
## 6. What to steal vs. leave
|
||||
|
||||
**Steal:**
|
||||
- The `diagnose-crash/SKILL.md` **diagnosis method** almost verbatim — it is a genuinely good, evidence-
|
||||
first crash-triage checklist that already fails read-only. Fold it into our `diagnose` skill.
|
||||
- The **journald `MESSAGE_ID` coredump watcher pattern** (`omarchy-crash-watch`) as the Debian trigger
|
||||
concept — but headless, feeding Hermes/NTFY not a Wayland toast.
|
||||
- The **privacy rule** (core dumps hold secrets → fresh mktemp, delete after) and the **"proves vs
|
||||
infers / never invent symbols"** honesty discipline — both align with our principles.
|
||||
- The **guardrail wording** itself ("Diagnosis reads; it does not fix") — near-perfect fit for our
|
||||
constrained-autonomy fail-closed posture; lift it as our fix-gate boundary statement.
|
||||
- From the **`omarchy` (system-tailoring) skill**: worth a look for a homelab "config-tailoring" skill —
|
||||
its **"never edit the package-owned dir, always edit user config, back up first, confirm before
|
||||
reset"** decision framework is a reusable safe-edit pattern (see §7 other-reusable).
|
||||
|
||||
**Leave:**
|
||||
- The entire Hyprland/Quickshell/Wayland notification + desktop trigger chain (`omarchy-notification-*`,
|
||||
graphical-session gating) — irrelevant to headless servers.
|
||||
- `omarchy-agent*`, `omarchy-crash-mute`, `omarchy-toggle-crash-capture`, `omarchy version/debug`,
|
||||
and the upstream-reporting flow targeting `omacom/omarchy`.
|
||||
- Arch specifics (pacman correlation, archlinux debuginfod URL) — swap for Debian equivalents.
|
||||
- Omarchy's repo-root dev skills (`migrations`, `install-scripts`, etc.) — for building Omarchy itself.
|
||||
|
||||
---
|
||||
|
||||
## 7. Risks / open questions for the human (main session)
|
||||
|
||||
1. **The "it fixes the crash" belief is wrong for the shipped skill.** Confirm the user is OK that we'd
|
||||
port a **diagnose-and-report** skill, and that auto-fix stays behind the sudo-bridge gate (our rule
|
||||
anyway). Do NOT replicate an unattended auto-fix loop.
|
||||
2. **`systemd-coredump` is not installed by default on Debian.** A ported crash-watch would need it
|
||||
(`apt install systemd-coredump`) or a different failure signal. Decide scope: raw coredumps only, or
|
||||
also unit failures + container health (broader, more useful for our stack).
|
||||
3. **Debuginfod for Debian:** `debuginfod.debian.net` exists but symbol coverage differs from Arch; many
|
||||
stripped packages won't symbolize. Set expectations.
|
||||
4. **Where does the report artifact live**, and does Hermes writing incident reports conflict with the
|
||||
"no unprompted DB writes / one feature at a time" rules? Scope it.
|
||||
5. **Other reusable skill flagged:** the **`omarchy` system-tailoring skill** — its safe-edit framework
|
||||
(edit `~/.config`, never the package dir; back up first; confirm before reset) is a decent template
|
||||
if we ever want a Hermes "config-change" skill. One line: worth reading, not urgent.
|
||||
6. **Version drift:** repo is rolling (`quattro` branch); the manual prose still says "one skill" while
|
||||
the repo ships two. Re-verify skill list before building.
|
||||
7. **This is a laptop-only consideration for Omarchy itself** (server decision remains plain Debian per
|
||||
prior report). The *skill port* is distro-independent and could serve Debian servers regardless of
|
||||
whether Omarchy ever gets installed anywhere.
|
||||
|
||||
---
|
||||
|
||||
## 8. Sources (URL + access date 2026-09-13)
|
||||
|
||||
- Repo tree (skill + wiring paths) — public GitHub API `repos/omacom/omarchy/git/trees/quattro?recursive=1` — enumerated `default/agents/skills/{omarchy,diagnose-crash}` + `bin/omarchy-crash-*` + `default/systemd/user/omarchy-crash-watch.service`.
|
||||
- `diagnose-crash` skill (verbatim excerpts, incl. "Leave the system as you found it") — https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/diagnose-crash/SKILL.md (raw: raw.githubusercontent.com/omacom/omarchy/quattro/…).
|
||||
- `diagnose-crash/reporting.md` (upstream-report gating, gh non-install rule) — https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/diagnose-crash/reporting.md
|
||||
- `omarchy-crash-watch` (journald MESSAGE_ID watcher, notification wiring) — https://raw.githubusercontent.com/omacom/omarchy/quattro/bin/omarchy-crash-watch
|
||||
- `omarchy-agent-crash` (click handler → default agent + skill) — https://raw.githubusercontent.com/omacom/omarchy/quattro/bin/omarchy-agent-crash
|
||||
- `omarchy-crash-watch.service` (user unit, graphical gate) — https://raw.githubusercontent.com/omacom/omarchy/quattro/default/systemd/user/omarchy-crash-watch.service
|
||||
- `omarchy` (system-tailoring) skill — https://github.com/omacom/omarchy/blob/quattro/default/agents/skills/omarchy/SKILL.md
|
||||
- AI manual (prose: watches systemd-coredump, "Process crashed" notification, diagnose-crash skill; symlink locations; "one default skill") — https://omarchy.org/manual/ai/ and https://github.com/omacom/omarchy/blob/quattro/manual/17-ai.md
|
||||
- Prior report (grounding; not re-derived) — `research/omarchy-os-evaluation.md`.
|
||||
|
||||
**UNVERIFIED / caveats flagged in-text:** exact Debian debuginfod symbol coverage; whether the video the
|
||||
user saw was the shipped skill going off-script or a different community auto-fix skill (the shipped skill
|
||||
does not fix — verified); manual prose vs repo skill-count drift (repo ships two; manual says one).
|
||||
@@ -0,0 +1,118 @@
|
||||
# Omarchy OS Evaluation
|
||||
|
||||
Research date: 2026-09-13. Author: background research agent (Opus 4.8).
|
||||
Scope: fact-gathering + preliminary hedged leans for two use cases. **No final adoption decision made here** — the main session must confirm against actual infra and future plans.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict (preliminary, hedged)
|
||||
|
||||
**UC1 — Laptop daily-driver desktop OS: lean-yes / depends-on-laptop-GPU. Confidence: MEDIUM.**
|
||||
Omarchy is a real, actively developed, opinionated *desktop* distro (Arch + Hyprland) built exactly for a keyboard-driven developer daily driver, and its AI story genuinely fits the user's Claude Code + Pro-subscription workflow. The lean flips to *depends* if the laptop has NVIDIA or hybrid NVIDIA graphics: Hyprland-on-Wayland-on-NVIDIA still has open, recent breakage reports (see §4). Because it supports dual-boot and a live/VM trial, the downside of *trying* it is low. **The main session must confirm the laptop's actual GPU, disk free space, and partition layout before any install** — those are unknown to this agent.
|
||||
|
||||
**UC2 — Server OS for server-01: lean-no. Confidence: HIGH.**
|
||||
Omarchy is an opinionated *desktop* distro whose entire reason to exist is a pre-themed Hyprland GUI. It is not designed, documented, or marketed as a server OS; running it headless discards ~all of its value and leaves you with plain rolling-release Arch plus desktop cruft. For an always-on box running real workloads, rolling-release + frequent-update exposure is a reliability liability versus Debian's stability guarantees. The planned plain-Debian migration is the stronger server choice on the reliability axis. **The main session must confirm against server-01's actual workload/uptime needs**, but nothing found here supports Omarchy as a server OS.
|
||||
|
||||
---
|
||||
|
||||
## 2. What Omarchy is
|
||||
|
||||
- **Maker:** David Heinemeier Hansson (DHH), creator of Ruby on Rails / Basecamp. Announced mid-2025 ("Omarchy is out", world.hey.com/dhh). Org/repo: `omacom/omarchy` (formerly `basecamp/omarchy`) on GitHub.
|
||||
- **Base distro:** Arch Linux. It is *not* a from-scratch distro — it is an opinionated installer + configuration layer on top of Arch, now shipping as its own ISO. (omarchy.org, accessed 2026-09-13)
|
||||
- **Release model:** Rolling release. Uses `pacman` + AUR; "always current." (deepwiki.com/basecamp/omarchy, omarchy-official.org, accessed 2026-09-13)
|
||||
- **Window manager / desktop:** Hyprland (tiling Wayland compositor), Tokyo Night theming out of the box, curated app set (Neovim, Chromium, Alacritty, LibreOffice, etc.). (omarchy.org, accessed 2026-09-13)
|
||||
- **Target audience:** Developers / "AI-agent-first" users who want a keyboard-driven, pre-configured Arch+Hyprland desktop without the dotfile archaeology. (omarchy.org)
|
||||
- **Current version:** **4.0.3**, current as of the site on 2026-09-13 (rolling, so this is a moving target). (omarchy.org, accessed 2026-09-13)
|
||||
|
||||
---
|
||||
|
||||
## 3. AI-features truth-check ("Claude skills")
|
||||
|
||||
**Finding: The "AI features like Claude skills" the user heard about are a conflation. Omarchy does not ship its own AI model or a suite of Claude skills. What it actually does:**
|
||||
|
||||
1. **Pre-wires coding-agent CLIs as lazy stubs.** "Every major coding-agent CLI comes pre-wired as a lazy-loaded launcher. The launchers are tiny [mise]-managed stubs in `~/.local/bin/`, so nothing is downloaded until the first time you actually run one." ~13 agents are listed (Claude Code, OpenAI Codex, OpenCode, GitHub Copilot CLI, Google Antigravity CLI, others). (github.com/omacom/omarchy manual/17-ai.md @ quattro, accessed 2026-09-13)
|
||||
2. **Claude Code is just the normal Claude Code CLI**, launchable via `claude`, the agent launcher shortcut, or a `c`/auto-approving-mode shortcut. This means it uses the user's **own Anthropic authentication** — i.e., the standard Claude Code Pro/Max **OAuth subscription** works exactly as it does anywhere else. Omarchy adds no separate model or billing. (omarchy.org/manual/ai/, accessed 2026-09-13)
|
||||
3. **"Claude skills" = ONE experimental skill.** Omarchy "ships with a default skill for tailoring the system," symlinked into Claude Code's skills directory (`~/.claude/skills`). The manual explicitly says: *"you should treat this skill as experimental"* and that "different models will use it to different effect." (omarchy.org/manual/ai/ and manual/17-ai.md, accessed 2026-09-13)
|
||||
|
||||
**API-key implications for this user:** Neutral-to-positive, *not* a downgrade. Claude Code in Omarchy uses whatever auth the user already has — a Claude Pro OAuth login is fully supported, no paid Anthropic API key is required for Claude Code specifically. (The *other* pre-wired agents, e.g. OpenAI Codex/Copilot, would each need their own accounts/keys, but those are optional and irrelevant to the user's Claude-Pro workflow.) Note DHH's own X post also mentions `Super+A = ChatGPT` / `Super+Shift+A = Grok` launchers — those are web-app shortcuts, not bundled models.
|
||||
|
||||
**Bottom line:** "AI features like Claude skills" overstates it. Reality = convenience launchers for coding-agent CLIs (Claude Code among them, works on Pro OAuth) plus one experimental system-tailoring skill. This is a nice-to-have for a laptop dev box; it is **not** a reason to put Omarchy on a server, and it provides nothing the user can't already get by installing Claude Code on any distro.
|
||||
|
||||
---
|
||||
|
||||
## 4. UC1 — Desktop suitability (laptop daily driver)
|
||||
|
||||
- **Positioning:** Purpose-built as a developer daily-driver desktop. Fast install ("~35s to <2min"), full-disk *or* dual-boot options, live-USB via ISO, and VM trial images for Mac/Windows before installing. (omarchy.org, accessed 2026-09-13)
|
||||
- **App availability:** Full Arch ecosystem — `pacman` + AUR — so app availability is excellent (one of Arch's biggest strengths). XWayland covers X11-only apps (e.g., DaVinci Resolve needs XWayland workarounds). (travis.media, makeuseof, accessed 2026-09-13)
|
||||
- **Community/docs:** Active. Official manual (omarchy.org/manual, learn.omacom.io), busy GitHub issue tracker, third-party blogs and guides. DHH's profile drives a sizeable community. Documentation is good for a young distro.
|
||||
- **NVIDIA / Wayland risk (CALL-OUT):** This is the main daily-driver risk. Recent (last ~90 days) open issues on `omacom/omarchy`:
|
||||
- Hybrid laptop bug: `LIBVA_DRIVER_NAME=nvidia` breaks *all* hardware video decoding when the compositor renders on the iGPU (black video, VA-API hangs). (github.com/omacom/omarchy#11527, accessed 2026-09-13)
|
||||
- SDDM/Hyprland greeter SIGSEGV crashes recorded 2026-09-11. (github.com/omacom/omarchy#11376, accessed 2026-09-13)
|
||||
- Choppy mouse / inconsistent cursor on hybrid AMD+NVIDIA setups (third-party fix writeups exist). (qainsights.com, akitaonrails.com, accessed 2026-09-13)
|
||||
- General: NVIDIA-on-Wayland-on-Hyprland is "not as broken as everyone says" but still not fully stable. (makeuseof.com, accessed 2026-09-13)
|
||||
- **The stack has an RTX 2060 Super (NVIDIA) 8GB in play. If the target laptop has NVIDIA or hybrid NVIDIA graphics, treat daily-driver readiness as conditional and trial via live-USB/dual-boot first.** Laptop GPU is UNVERIFIED by this agent — main session must check.
|
||||
- **Update stability:** Rolling — see §6/§7. Omarchy mitigates with Btrfs + Snapper snapshots (with a caveat, see §6).
|
||||
|
||||
---
|
||||
|
||||
## 5. UC2 — Server suitability (server-01), blunt
|
||||
|
||||
**Blunt read: Omarchy is not a server OS. Do not treat it as a Debian-server alternative.**
|
||||
|
||||
- **Designed as a desktop, not a server.** No server use is mentioned anywhere in Omarchy's own docs; it is positioned purely as a desktop OS. Its entire value proposition is a pre-themed Hyprland GUI + curated desktop apps. (omarchy.org, accessed 2026-09-13)
|
||||
- **Headless makes no sense.** Strip Hyprland/Quickshell/desktop apps and you're left with plain rolling-release Arch plus dead desktop weight. At that point you'd just run Arch, and Arch itself is a questionable server base for a reliability-first box.
|
||||
- **Rolling-release stability risk for an always-on server:** Arch pushes new package versions continuously with no LTS/point-release freeze. That means more frequent, potentially breaking updates and manual interventions (see §7) — the opposite of what you want for unattended, always-on self-hosted workloads. Debian's whole model (frozen stable, security-only backports, multi-year support) is engineered for exactly this reliability. This is the decisive axis.
|
||||
- **Security-update cadence:** Arch delivers security fixes fast *by rolling the whole package forward* (you must update the live system, sometimes with breakage), whereas Debian backports security fixes into a frozen version (update without version churn). For a server, Debian's model is lower-risk.
|
||||
- **vs incumbent (LMDE 7 / Debian 13 base) and the planned plain-Debian migration:** On server reliability, plain Debian wins clearly. LMDE/Debian give stability guarantees Omarchy explicitly does not target. The "best server distro" framing the user heard about Debian is directionally reasonable for a reliability-first server; Omarchy does not compete in that category.
|
||||
- **The AI angle does not rescue the server case.** Claude Code/skills (§3) are laptop-dev conveniences and can be installed on Debian anyway; they are not a server capability and give no reason to accept rolling-release risk on server-01.
|
||||
|
||||
---
|
||||
|
||||
## 6. Migration & disk footprint
|
||||
|
||||
- **Dual-boot:** Supported. The installer offers full-disk *or* dual-boot alongside an existing Linux/OS install. (omarchy.org, accessed 2026-09-13)
|
||||
- **Live-USB / trial without installing:** Yes — distributed as an ISO (bootable), plus VM trial images for Mac/Windows to evaluate before touching hardware. So a non-destructive trial is possible. (omarchy.org, accessed 2026-09-13)
|
||||
- **Installer disk handling:** Offers both whole-disk and dual-boot; it does not *require* the whole disk. Exact partitioning behavior in dual-boot mode is UNVERIFIED here — the main session should confirm on the live installer and check free space first. **Do not assume; verify laptop disk/partition layout before installing.**
|
||||
- **Reversibility:** A dual-boot or VM trial is easily reversible (remove the partition / delete the VM). A full-disk install over LMDE is not reversible without a backup — trial via dual-boot or live-USB first.
|
||||
- **Disk footprint:** No official number published. Omarchy claims it runs on very low-spec hardware ("even a 2011 ThinkPad X220 with 2GB RAM"), implying a modest base. A realistic curated Arch+Hyprland desktop install with the bundled apps is on the order of **~15–30 GB** including apps and snapshot overhead — this figure is an **estimate / UNVERIFIED**; confirm against the actual ISO/installer.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rolling-release operator burden
|
||||
|
||||
Running Arch/rolling (which is what Omarchy is under the theming) demands more of the operator than Debian stable:
|
||||
|
||||
- **Manual intervention frequency:** Higher. Updates arrive continuously; occasionally an update needs manual steps (e.g., `pacman` `.pacnew` merges, keyring updates, intervention notices posted on the Arch news feed you're expected to read before big updates).
|
||||
- **Breakage classes:** version-bump regressions, config-format changes, kernel/driver (esp. NVIDIA) mismatches after an update, and partial-upgrade breakage if you update piecemeal.
|
||||
- **Snapshot/rollback story:** Omarchy ships **Btrfs + Snapper + Limine** — every `pacman` transaction snapshots, and you can boot a prior snapshot from the bootloader if an update breaks boot. It also does disk-space pre-flight checks and concurrency locking on updates. **Caveat (recent):** an open issue reports `btrfs-overlayfs` is enabled by default and is mutually exclusive with `limine-snapper-sync`, which can make snapshot rollback impossible on a *stock* install until reconfigured. (github.com/omacom/omarchy#8047; deepwiki boot-management page; musabase.com; accessed 2026-09-13) — verify current state before relying on rollback.
|
||||
- **Implication per use case:** On a *laptop* an attentive user can absorb this and the snapshot safety net helps. On an *always-on server*, this operator burden and breakage exposure is exactly the wrong trade vs Debian — reinforcing the UC2 lean-no.
|
||||
|
||||
---
|
||||
|
||||
## 8. Risks / open questions for the human (main session)
|
||||
|
||||
1. **Laptop GPU is unknown.** If NVIDIA/hybrid, daily-driver readiness is conditional (§4) — trial first.
|
||||
2. **Laptop disk free space & partition layout are unknown.** Required before any dual-boot install. Verify.
|
||||
3. **Snapshot rollback caveat** (#8047): confirm whether stock Omarchy 4.0.3 can actually roll back before trusting it as a safety net.
|
||||
4. **Server decision is really Debian-vs-Debian.** The genuine server choice is plain Debian vs staying on LMDE 7 — Omarchy is out of category for server-01.
|
||||
5. **AI features add nothing server-side and nothing you can't already get on Debian** — Claude Code + the one experimental skill are laptop conveniences on your existing Pro OAuth.
|
||||
6. **Rolling-release maintenance appetite:** does the user want to babysit rolling updates on a daily driver? Fine for a dev laptop, not for the server.
|
||||
7. **Ubuntu constraint respected** — nothing here recommends Ubuntu. Debian (non-Ubuntu) remains the server recommendation direction.
|
||||
|
||||
---
|
||||
|
||||
## 9. Sources
|
||||
|
||||
- Omarchy official site — https://omarchy.org/ (accessed 2026-09-13) — identity, base, WM, version 4.0.3, install/dual-boot/live-USB/VM, hardware claims.
|
||||
- Omarchy AI manual — https://omarchy.org/manual/ai/ (accessed 2026-09-13) — agent launchers, Claude Code, experimental skill.
|
||||
- Omarchy manual source (GitHub) — https://github.com/omacom/omarchy/blob/quattro/manual/17-ai.md (accessed 2026-09-13) — verbatim "lazy-loaded launcher" / "default skill … treat as experimental" quotes.
|
||||
- DHH "Omarchy is out" — https://world.hey.com/dhh/omarchy-is-out-4666dd31 (accessed 2026-09-13) — maker, origin.
|
||||
- DHH X post on AI integration — https://x.com/dhh/status/1952769372600049925 (accessed 2026-09-13) — ChatGPT/Grok/Claude Code shortcuts.
|
||||
- Rolling release / snapshots — https://deepwiki.com/basecamp/omarchy/2.2-boot-management-and-snapshots and .../6.4-update-system (accessed 2026-09-13).
|
||||
- Snapshot rollback caveat — https://github.com/omacom/omarchy/issues/8047 (accessed 2026-09-13).
|
||||
- NVIDIA/Wayland issues — https://github.com/omacom/omarchy/issues/11527 ; https://github.com/omacom/omarchy/issues/11376 (accessed 2026-09-13).
|
||||
- NVIDIA/Hyprland context — https://www.makeuseof.com/hyprland-on-nvidia-isnt-as-broken-as-everyone-says/ ; https://qainsights.com/fixing-choppy-mouse-movement-in-omarchy-on-a-hybrid-amd-nvidia-laptop/ ; https://akitaonrails.com/en/2026/01/21/omarchy-3-dual-gpu-setup-with-amd-and-nvidia/ (accessed 2026-09-13).
|
||||
- XWayland/app compat — https://travis.media/blog/install-davinci-resolve-omarchy-nvidia/ (accessed 2026-09-13).
|
||||
- Getting-started / footprint context — https://omarchy-official.org/blog/getting-started-omarchy-install/ (accessed 2026-09-13).
|
||||
|
||||
**UNVERIFIED items flagged in-text:** exact disk footprint number; dual-boot installer partition specifics; laptop GPU/disk/partition (out of this agent's knowledge — main session to check).
|
||||
@@ -0,0 +1,114 @@
|
||||
# Partner Access Audit — Austin offboard / Hailee onboard
|
||||
|
||||
> **READ-ONLY preflight (agent K, 2026-09-15, Opus 4.8).** This audit made **zero changes**. It
|
||||
> enumerates every place the departed partner **Austin** currently has access and every place the new
|
||||
> 33% partner **Hailee** must be added. Execution happens in the mutating follow-up (prompt L) with the
|
||||
> owner present. Secrets seen during the audit are **masked** here.
|
||||
> Partner set after change: **Tyler (CEO)**, **Pasture2408 (owner/admin)**, **Hailee (new 33%)**.
|
||||
> NOT partners — do NOT grant access: employees **Grim** and **Josh** (via Hailee). Austin's old
|
||||
> Marketing/People role is **unassigned** — do not assume Hailee inherits it.
|
||||
|
||||
---
|
||||
|
||||
## Method (read-only sources actually queried)
|
||||
- **Nextcloud** (`nextcloud-nextcloud-1`, linuxserver.io image — occ runs via the `occ` wrapper, NOT
|
||||
`-u www-data`): `user:list`, `user:info`, `group:list`, `share:list`, `circles:manage:list`,
|
||||
`talk:turn:list`, `talk:stun:list`, `talk:signaling:list`, `talk:bot:list`.
|
||||
- **Vault** (AppRole, read-only, token revoked after use): listed all `secret/` paths and scanned
|
||||
path names + field names + values for the string `austin` (case-insensitive).
|
||||
- **Bitwarden bridge**: container `bitwarden-bridge` is **EXITED (128), 8 days** — could not be queried
|
||||
read-only without a restart (restart is forbidden). Flagged as a **manual owner check**.
|
||||
|
||||
---
|
||||
|
||||
## Findings — where Austin currently has access
|
||||
|
||||
### Nextcloud (authoritative, from occ)
|
||||
| # | Object | Detail | Action |
|
||||
|---|--------|--------|--------|
|
||||
| N1 | **User account** | `Austin_Mktg` (display "Austin"), backend Database, **enabled: true**, no email set, last_seen 2026-05-31, home `/data/Austin_Mktg` (~62 MB used) | disable, then delete/transfer |
|
||||
| N2 | **Group memberships** | **NONE** — Austin is in no groups (only group on the instance is `admin` = `Pasture2408`) | nothing to remove |
|
||||
| N3 | **Share id=2** | `/Partner Meetings` (source `/Pasture2408/files/Partner Meetings`, file id 3650) shared **user→Austin_Mktg** by Pasture2408 | **remove share id 2** |
|
||||
|
||||
**`/Partner Meetings` folder full share set (share:list --owner=Pasture2408):**
|
||||
- id 1 → **Tyler_CEO** (keep)
|
||||
- id 2 → **Austin_Mktg** (**REMOVE**)
|
||||
- id 3 → **recording-bot** (keep — service account)
|
||||
- (unrelated public link shares id 6/7 = business-proposals-july-2026.html/.md — no recipient, not partner-scoped)
|
||||
|
||||
### Nextcloud Talk
|
||||
- `talk:bot:list` empty; no Teams/Circles (`circles:manage:list` empty).
|
||||
- occ has **no `talk:room:list`** command, so Talk **room participant lists cannot be enumerated by
|
||||
occ**. Austin's Talk access is tied to his **user account** — disabling/deleting `Austin_Mktg`
|
||||
(N1) removes him from all Talk conversations. **Manual owner verification** recommended in the Talk
|
||||
UI (or `talk:user:remove Austin_Mktg`, a mutating command, during execution) to confirm no room
|
||||
ownership needs transfer.
|
||||
- TURN currently = Open Relay band-aid (`staticauth.openrelay.metered.ca`, secret masked); standalone
|
||||
signaling server configured (secret masked). Not Austin-specific — noted for the Twingate/Talk work,
|
||||
not this offboard.
|
||||
|
||||
### Vault
|
||||
- **No Austin references anywhere.** Scanned all `secret/` path names, field names, and values — **0
|
||||
matches** for `austin`. Nothing to remove in Vault.
|
||||
|
||||
### Bitwarden
|
||||
- **UNVERIFIED / manual** — `bitwarden-bridge` container is stopped; not queried (no restart allowed).
|
||||
Owner must check the vault for any item referencing Austin (shared logins, Discord creds, etc.).
|
||||
|
||||
### Other homelab services
|
||||
- No evidence of an Austin login in any other service: Vault holds credentials for gitea, n8n, grafana,
|
||||
jellyfin, ntfy bots, etc., and **none reference Austin**. Partners are scoped to Nextcloud only per
|
||||
the role model, so no Gitea/ntfy/Grafana partner account is expected. **Manual owner spot-check** of
|
||||
Gitea user list still advised (belt-and-suspenders).
|
||||
|
||||
### External (cannot inspect — MANUAL OWNER ACTIONS)
|
||||
- **Discord** — remove Austin from the business server / revoke roles.
|
||||
- **Personal email / any shared inbox or calendar invites** — remove Austin.
|
||||
- **Any external SaaS** (payment/banking split, shared docs outside Nextcloud) — remove Austin;
|
||||
reflect the ownership change (Austin 0%, Hailee 33%).
|
||||
|
||||
---
|
||||
|
||||
## OFFBOARD CHECKLIST — Austin (execute in prompt L, owner present)
|
||||
- [ ] Nextcloud: **remove share id 2** (`/Partner Meetings` → Austin_Mktg) — `occ share:delete 2`
|
||||
- [ ] Nextcloud: **disable** `Austin_Mktg` (`occ user:disable Austin_Mktg`) — reversible first step
|
||||
- [ ] Nextcloud: confirm/transfer any Talk room ownership, then **remove from all rooms**
|
||||
(`occ talk:user:remove Austin_Mktg`) — verify in Talk UI first
|
||||
- [ ] Nextcloud: **delete or transfer-ownership** of `Austin_Mktg` (decide: `files:transfer-ownership
|
||||
Austin_Mktg Pasture2408` if his ~62 MB home has anything worth keeping, else `user:delete`)
|
||||
- [ ] Twingate: ensure Austin is **NOT** in the partner Group that has the Nextcloud Resource (he
|
||||
should never be added; verify during Twingate setup)
|
||||
- [ ] Bitwarden: **manual** — check for/remove any item referencing Austin (bridge is down; owner does
|
||||
this in the vault UI)
|
||||
- [ ] Gitea/other services: **manual spot-check** for an Austin account (none expected)
|
||||
- [ ] Discord: **manual** — remove Austin, revoke roles
|
||||
- [ ] Email/calendar/external SaaS: **manual** — remove Austin; record ownership change
|
||||
|
||||
---
|
||||
|
||||
## ONBOARD CHECKLIST — Hailee (new 33% partner; NOT the Marketing/People role)
|
||||
- [ ] Nextcloud: **create user** for Hailee (e.g. `Hailee_Partner` — match the `<Name>_<Role>` naming
|
||||
convention; do NOT reuse `_Mktg`, role is unassigned). Generate a password per
|
||||
`feedback_password_generation` (`bw generate -ulns --length 20`).
|
||||
- [ ] Nextcloud: set Hailee's **email** (owner supplies) so sharing notifications work
|
||||
- [ ] Nextcloud: **share `/Partner Meetings`** to Hailee's user (mirror share id 1's model — user
|
||||
share, same permissions as Tyler)
|
||||
- [ ] Nextcloud Talk: **add Hailee to the partner Talk room(s)** Tyler is in (via UI or
|
||||
`talk:room:add`)
|
||||
- [ ] Twingate: **add Hailee to the partner Group** that is assigned the Nextcloud Resource, and
|
||||
**invite her as a Twingate user** (counts against the 5-user free-tier cap — see runbook)
|
||||
- [ ] Twingate: Hailee **installs the Twingate client** and authenticates (no clientless option)
|
||||
- [ ] Discord: **manual** — invite Hailee, assign partner role
|
||||
- [ ] Ownership/records: **manual** — record Hailee at 33%; Austin at 0%
|
||||
- [ ] **Do NOT** grant Grim or Josh (Hailee's employees) any Nextcloud/Twingate/partner access
|
||||
|
||||
---
|
||||
|
||||
## Free-tier headcount note (feeds the Twingate runbook)
|
||||
After the swap the human partners needing Twingate access = **Tyler + Hailee** (Pasture2408 = owner/
|
||||
admin). That is **2–3 users**, well within Twingate free tier's **5 users / 1 admin**. Removing Austin
|
||||
also frees a seat. No paid upgrade needed for the partner set.
|
||||
|
||||
## Update instructions
|
||||
As each item is executed in prompt L, check it off here and note the date. If Talk room ownership must
|
||||
transfer, record which rooms. Re-run the Bitwarden check once the bridge is back up.
|
||||
@@ -0,0 +1,160 @@
|
||||
# Twingate Deploy Runbook — partner access to Nextcloud over CGNAT
|
||||
|
||||
> **Preflight runbook (agent K, 2026-09-15, Opus 4.8). Read-only research — nothing deployed.**
|
||||
> Role (locked): **Twingate = scoped zero-trust access for PARTNERS to Nextcloud only.** Partners do
|
||||
> NOT join the owner's mesh (that is NetBird). The connector deploys on the **PRIMARY server**.
|
||||
> Driver: Tyler is behind CGNAT; this makes CGNAT irrelevant because the connector is **outbound-only**
|
||||
> (no port-forward, respects P5). Deadline: partner meeting **Sun 2026-09-20**.
|
||||
> All vendor claims pinned to URL + access date **2026-09-15**; anything unproven = **UNVERIFIED**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Free-tier limits (Starter/Free)
|
||||
Source: https://www.twingate.com/pricing (2026-09-15)
|
||||
- **Users:** up to **5**
|
||||
- **Remote Networks:** **10**
|
||||
- **Resources:** **50**
|
||||
- **Connectors:** (bundled; deploy 2 per Remote Network for HA — **UNVERIFIED** exact free-tier
|
||||
connector cap, but 2 connectors/network is the documented HA norm)
|
||||
- **Admin users:** **1**
|
||||
- **Devices:** **5 per user**
|
||||
- **Global Relay locations:** 12
|
||||
- Next tier = **Home $15/mo (up to 7 users)**.
|
||||
|
||||
**Fit:** partner set after the swap = Tyler + Hailee (+ owner) = **2–3 users, 1 Remote Network, 1
|
||||
Resource** → comfortably inside the free tier. No upgrade needed.
|
||||
|
||||
---
|
||||
|
||||
## 2. How it works (the model the executor must build)
|
||||
Source: https://www.twingate.com/docs (quick-start, resources, connectors) (2026-09-15)
|
||||
|
||||
```
|
||||
Partner device (Twingate client) ──► Twingate SaaS Controller (policy/broker)
|
||||
│ QUIC P2P, Relay(TURN-like) fallback │
|
||||
└──────────────► Connector (on PRIMARY server, OUTBOUND only) ──► Nextcloud (internal)
|
||||
```
|
||||
|
||||
- **Remote Network** = logical grouping that holds Resources (pick a cloud/location label; it is just a
|
||||
label — the connector runs on the owner's primary server regardless).
|
||||
- **Connector** = container behind the firewall that reaches the Resources. **Makes only outbound
|
||||
connections** → **no ports opened, no port-forwarding, CGNAT-proof** (P5-clean).
|
||||
- **Resource** = the internal service exposed (Nextcloud). Defined by internal IP/hostname. **A Resource
|
||||
is only reachable once added to a Group.**
|
||||
- **Group** = the access unit. Partners in the Group get the Resource automatically.
|
||||
- **Client:** partners **must install the Twingate client app** (macOS/iOS/Windows/Android/Linux from
|
||||
get.twingate.com). **There is no clientless/browser-only path** to a private Resource
|
||||
(help.twingate.com, 2026-09-15). Plan for Tyler + Hailee to install the client.
|
||||
|
||||
### CGNAT behaviour (the whole reason for this)
|
||||
- Connector is outbound-only → Tyler's CGNAT on his side and the owner's side are both irrelevant.
|
||||
- Client↔Connector tries **QUIC P2P**, falls back to a **Twingate Relay (works like a TURN server)**
|
||||
when direct traversal is blocked → connectivity succeeds even when both ends are NAT'd
|
||||
(https://www.twingate.com/docs/peer-to-peer-communication-in-twingate, 2026-09-15). **This is the
|
||||
clean CGNAT fix.**
|
||||
- **CAVEAT (UNVERIFIED — must test before relying on it for the 09-20 meeting):** Twingate routes
|
||||
Resource traffic over a **100.96.0.0/12 CGNAT range via the loopback/virtual interface**. Chrome &
|
||||
Firefox **Local Network Access (LNA)** can prompt/block Resources treated as "local"
|
||||
(help.twingate.com LNA article, 2026-09-15) — the partner may need to allow LNA in the browser.
|
||||
Also **UNVERIFIED**: whether Nextcloud **Talk (WebRTC media)** works fully end-to-end through the
|
||||
Twingate tunnel, or whether Talk still needs its own TURN/HPB path. **Test a Talk call over Twingate
|
||||
before removing the Open Relay TURN band-aid.**
|
||||
|
||||
---
|
||||
|
||||
## 3. Connector container shape (deploy on PRIMARY server)
|
||||
Sources: https://www.twingate.com/docs/deploy-connector-with-docker-compose ;
|
||||
https://hub.docker.com/r/twingate/connector ;
|
||||
https://github.com/Twingate-Solutions/twingate-custom-connector-container (2026-09-15)
|
||||
|
||||
- **Image:** `twingate/connector:latest` (official). **Pin to a digest or a `:1`-major tag** per
|
||||
`feedback_verify_image_tag_before_deploy` / image-selection policy — do NOT ship floating `:latest`
|
||||
to prod; resolve the current RepoDigest at deploy time.
|
||||
- **Required env:** `TWINGATE_NETWORK` (tenant name), `TWINGATE_ACCESS_TOKEN`, `TWINGATE_REFRESH_TOKEN`
|
||||
(both generated per-connector in the Admin Console at deploy time — treat as secrets).
|
||||
- **Optional env:** `TWINGATE_LOG_ANALYTICS=v3`, `TWINGATE_LOG_LEVEL=3`, `TWINGATE_DNS=<resolver>`.
|
||||
- **Network:** docs use **`network_mode: host`** (enables local P2P; default would be bridge). This is
|
||||
the **documented host-mode template exception** for tunnel agents (same class as the NetBird/Tailscale
|
||||
agent exception) — acceptable here. **UNVERIFIED but likely:** a **bridged** container also works if
|
||||
it can route to Nextcloud's internal address; if the standard bridged+host-LAN template is preferred,
|
||||
test bridged first and fall back to host-mode only if P2P/reachability fails.
|
||||
- **sysctls:** `net.ipv4.ping_group_range: "0 2147483647"`.
|
||||
- **restart:** `always` (or match the stack's standard restart policy).
|
||||
|
||||
**Compose template (secrets via SecretSpec/Vault, NOT inline — P2):**
|
||||
```yaml
|
||||
services:
|
||||
twingate-connector:
|
||||
image: twingate/connector:latest # pin to live RepoDigest before deploy
|
||||
restart: always
|
||||
environment:
|
||||
- TWINGATE_NETWORK=${TWINGATE_NETWORK}
|
||||
- TWINGATE_ACCESS_TOKEN=${TWINGATE_ACCESS_TOKEN}
|
||||
- TWINGATE_REFRESH_TOKEN=${TWINGATE_REFRESH_TOKEN}
|
||||
- TWINGATE_LOG_ANALYTICS=v3
|
||||
network_mode: host # documented tunnel-agent exception; test bridged first if preferred
|
||||
sysctls:
|
||||
net.ipv4.ping_group_range: "0 2147483647"
|
||||
```
|
||||
For **HA**, deploy a **second connector** in the same Remote Network (docs recommend 2). Optional but
|
||||
low effort on the free tier.
|
||||
|
||||
---
|
||||
|
||||
## 4. Vault paths to CREATE (do not populate in preflight — none exist today)
|
||||
Confirmed via Vault scan: **no `twingate/*` path exists yet.** Propose (KV v2 under `secret/`):
|
||||
- `secret/twingate/connector` — `TWINGATE_NETWORK`, `TWINGATE_ACCESS_TOKEN`, `TWINGATE_REFRESH_TOKEN`
|
||||
(for a 2nd HA connector, add `secret/twingate/connector-2` with its own token pair — tokens are
|
||||
per-connector).
|
||||
- `secret/twingate/admin` (optional) — Twingate Admin Console / API credentials if automating.
|
||||
Delivery = existing **SecretSpec + Vault** pattern (`playbook_secretspec_env_resolution`); nothing
|
||||
hardcoded in compose. Rotate access/refresh tokens on connector re-provision.
|
||||
|
||||
---
|
||||
|
||||
## 5. Step-by-step deploy (mechanical — for prompt L)
|
||||
1. **Sign up / log in** to Twingate (free tier); create the tenant if new. Record `TWINGATE_NETWORK`.
|
||||
2. **Create a Remote Network** (Network → Add) — name it e.g. `primary-homelab`; location label only.
|
||||
3. **Deploy Connector** → choose **Docker** → the console generates `TWINGATE_ACCESS_TOKEN` +
|
||||
`TWINGATE_REFRESH_TOKEN`. **Write both to `secret/twingate/connector` in Vault immediately.**
|
||||
4. **Deploy the connector container** on the **PRIMARY server** using the compose in §3 (secrets via
|
||||
SecretSpec/Vault). Confirm it shows **Connected/green** in the Admin Console.
|
||||
5. **Add a Resource** = Nextcloud. Enter its internal address (the host/IP the connector can reach —
|
||||
e.g. the Traefik-fronted internal hostname or the container/host IP:port; verify what resolves from
|
||||
the connector's network namespace). Give it a clear name (`Nextcloud`).
|
||||
6. **Create a Group** `Partners`; **assign the Nextcloud Resource** to it (Resource is unreachable until
|
||||
in a Group).
|
||||
7. **Invite partner users:** Tyler + Hailee (NOT Austin, NOT Grim/Josh). Add them to the `Partners`
|
||||
group. (≤5 users total → free tier.)
|
||||
8. **Partners install the Twingate client**, authenticate, and open Nextcloud — reachable regardless of
|
||||
CGNAT.
|
||||
9. **VERIFY:** Tyler (or a CGNAT-simulated test) reaches Nextcloud web + files through Twingate;
|
||||
**run a Talk call over Twingate** (see §2 caveat). Hailee reaches Nextcloud; Austin reaches nothing.
|
||||
10. **Only after** a Talk call is confirmed working via Twingate, plan to remove the Open Relay Project
|
||||
TURN entry from Talk (separate step; do not remove it pre-emptively).
|
||||
11. (Optional) Deploy a **2nd connector** for HA (`secret/twingate/connector-2`).
|
||||
|
||||
---
|
||||
|
||||
## 6. Decisions already made (executor does not re-decide)
|
||||
- Connector on **primary server**, not server-01. Partners get **Nextcloud only** (one Resource, one
|
||||
Group). Client-based (no clientless option exists). Secrets in **Vault** via SecretSpec. Free tier.
|
||||
|
||||
## Open / UNVERIFIED (carry into execution)
|
||||
1. Free-tier **connector count** cap (2/network assumed for HA).
|
||||
2. **Bridged vs host-mode** connector — test bridged first; host-mode is the documented fallback.
|
||||
3. **Browser LNA** prompts (Chrome/Firefox) may need a partner-side allow.
|
||||
4. **Nextcloud Talk WebRTC fully over Twingate** — must be tested before dropping Open Relay TURN.
|
||||
|
||||
## Sources (all accessed 2026-09-15)
|
||||
- Pricing/free tier — https://www.twingate.com/pricing
|
||||
- Quick-start / model — https://www.twingate.com/docs
|
||||
- Connectors — https://www.twingate.com/docs/connectors
|
||||
- Docker Compose deploy — https://www.twingate.com/docs/deploy-connector-with-docker-compose
|
||||
- Image — https://hub.docker.com/r/twingate/connector
|
||||
- P2P/Relay — https://www.twingate.com/docs/peer-to-peer-communication-in-twingate
|
||||
- CGNAT / LNA gotchas — https://help.twingate.com (CGNAT IP Conflicts; Browser LNA articles)
|
||||
|
||||
## Update instructions
|
||||
As prompt L executes, record actual `TWINGATE_NETWORK`, the Resource's resolved internal address, the
|
||||
Talk-over-Twingate test result, and whether bridged worked (so host-mode can be dropped). Check off §5.
|
||||
Reference in New Issue
Block a user