NextMyDay/docs/specs/spec-001-git-bootstrap.md

279 lines
10 KiB
Markdown

# SPEC-001 — Git connection bootstrap (remote `origin` → Gitea)
| Field | Value |
|---|---|
| **Status** | Ready for implementation |
| **Created** | 2026-10-13 |
| **Author** | OpenArchitect |
| **Target milestone** | **Prep** (first slice) — see `PROJECT_PLAN.md` §11 |
| **Governing doctrine** | §12 / §12.2 · **D-010** (secrets never committed) · **D-036** (layout) |
| **Related spec** | *(none)* |
---
## 1. Goal
Make `/root/myday` a git repository whose `main` branch is **connected to and verified against**
the Gitea remote `NextMyDay`, with a repeatable, secret-safe push/pull path.
**Definition of done:** a clean `git push -u origin main` succeeds and the commit is visible in
Gitea, with **no secret present in `.git/config`, in the commit, or in `git remote -v`.**
### 1.1 Non-goals (explicitly deferred)
These are **later Prep slices** and must NOT be built here:
- `AGENT.md`, `README.md`, `config.example.yaml`
- `docs/` restructuring, `docs/specs/` index
- CI (build + test on push)
- Feature-branch workflow automation
- The Go project skeleton (`cmd/myday/`, `internal/core/`, `internal/modules/`)
---
## 2. Environment facts (discovery, verified 2026-10-13)
| Fact | Evidence |
|---|---|
| `/root/myday` is **not** a git repo | no `.git/`; directory holds only `PROJECT_PLAN.md` |
| Remote host is reachable and is **Gitea 1.22.0** | `GET /api/v1/version` → `9.0.2+gitea-1.22.0` |
| Remote repo is **empty** (no commits) | confirmed by operator |
| **No git identity configured** | `/root/.gitconfig` does not exist |
| No `.opencode/` directory exists in the project yet | `ls /root/myday` |
---
## 3. Architectural decisions introduced by this spec
| ID | Decision | Rationale |
|---|---|---|
| **A-01** | Canonical remote is `https://git.lohmar.co.uk/cclohmar/NextMyDay.git`. The truncated `GIT_URL` originally supplied (`…/cclohmar/`) is **discarded** — an org root is not a valid remote. | Single source of truth for the remote; prevents a silent misconfiguration. |
| **A-02** | The token lives in a **credential store outside the repo** (`~/.git-credentials`, mode `600`). The remote URL is **clean**. | The token must never enter `.git/config`, `git remote -v`, shell history, commit objects, or error output. Embedding it in the URL (rejected) leaks through all five. |
| **A-03** | `AUTO_STASH="true"` is realised as git-native config: `pull.rebase=true` + **`rebase.autoStash=true`**. No custom script. | `rebase.autoStash` *is* the platform feature for this requirement. Prefer native over bespoke. |
| **A-04** | **Two-tier configuration.** Git-native settings (identity, remote, branch, pull behaviour) → `.git/config`. Agent-workflow variables (`COMMIT_PREFIX`, `AUTO_STASH`, …) → `.opencode/git.env` (**gitignored**) with a committed `.opencode/git.env.example`. | Git is authoritative for what git owns; automation knobs are not git's business. Keeps git-native state discoverable via `git config --list`. |
| **A-05** | `user.name` / `user.email` are set **repo-local only**, never `--global`. | A sandbox-hosted agent must not mutate machine-wide identity. |
> **Open decision for the plan (raise as O-12):** A-04 introduces `.opencode/git.env` as a
> convention. `PROJECT_PLAN.md` does not yet define where agent-workflow variables live.
> This spec assumes `.opencode/`. Confirm before M0 so the convention is documented once.
---
## 4. Configuration contract
The canonical variable contract. **Committed** column states whether the *value* may ever be
committed.
| Variable | Value | Materialises as | Committed? |
|---|---|---|---|
| `GIT_URL` | `https://git.lohmar.co.uk/cclohmar/NextMyDay.git` | `remote.origin.url` (clean, no token) | ✅ (URL only) |
| `GIT_TOKEN` | *(secret)* | `~/.git-credentials` via `credential.helper=store` | ❌ **never** |
| `GIT_USER_NAME` | `Claus Lohmar` ⚠️ *assumed — confirm* | `.git/config` `user.name` | ✅ |
| `GIT_USER_EMAIL` | `claus.lohmar@lohmar.co.uk` | `.git/config` `user.email` | ✅ |
| `REMOTE_ORIGIN` | `origin` | `remote.<name>` | ✅ |
| `DEFAULT_BRANCH` | `main` | `HEAD` / `init.defaultBranch` | ✅ |
| `COMMIT_PREFIX` | `chore:` | commit-message convention (Conventional Commits, §12.2) | ✅ |
| `AUTO_STASH` | `true` | `pull.rebase=true` + `rebase.autoStash=true` | ✅ |
---
## 5. Structural changes
### 5.1 Files to CREATE — inside the repo (committed)
| Path | Purpose |
|---|---|
| `.gitignore` | **D-010 gate.** Must exist **before the first `git add`**. Excludes `config.yaml`, credentials, DB files, build output. |
| `.opencode/git.env.example` | Documents the agent-workflow variable contract with placeholder secrets. |
| `docs/specs/spec-001-git-bootstrap.md` | This spec (already written). |
**`.gitignore` — initial content (minimal, expand later):**
```gitignore
# --- secrets: never committed (D-010) ---
config.yaml
.env
.env.*
!.env.example
.opencode/git.env
*.token
*.pem
*.key
# --- credential stores ---
.git-credentials
# --- local runtime state ---
*.db
*.db-wal
*.db-shm
data/
# --- Go build output ---
/bin/
myday
*.exe
# --- editor / OS ---
.DS_Store
.idea/
.vscode/
*.swp
```
**`.opencode/git.env.example` — content:**
```dotenv
# Agent git-workflow contract. Copy to .opencode/git.env (gitignored) and fill secrets.
GIT_URL=https://git.lohmar.co.uk/cclohmar/NextMyDay.git
GIT_TOKEN= # NEVER commit a real value
GIT_USER_NAME=Claus Lohmar
GIT_USER_EMAIL=claus.lohmar@lohmar.co.uk
REMOTE_ORIGIN=origin
DEFAULT_BRANCH=main
COMMIT_PREFIX=chore:
AUTO_STASH=true
```
### 5.2 Files to CREATE — outside the repo (never committed)
| Path | Mode | Purpose |
|---|---|---|
| `/root/.git-credentials` | **`600`** | Holds `https://<user>:<token>@git.lohmar.co.uk`. |
| `/root/myday/.opencode/git.env` | **`600`** | Local copy of the contract with the real token. |
### 5.3 Files MODIFIED via `git config` (not hand-edited)
`.git/config` gains: `user.name`, `user.email`, `push.default`, `pull.rebase`,
`rebase.autoStash`, `remote.origin.*`, `branch.main.*`.
`/root/.gitconfig` gains **only** `credential.helper=store` (the sole global write, justified).
### 5.4 Untouched
`PROJECT_PLAN.md` — **not** modified by this spec. (Any plan change, e.g. logging O-12, is a
separate `docs:` commit.)
---
## 6. Developer implementation spec
---
### 🏗️ DEVELOPER IMPLEMENTATION SPEC
**Target Module:** `/root/myday` (repository root)
**Pattern:** Bootstrap / idempotent provisioning script
**Structural Changes:**
- Create `.gitignore` **before any staging** (D-010 gate)
- Create `.opencode/git.env.example` (committed) and `.opencode/git.env` (`600`, gitignored)
- Create `/root/.git-credentials` (`600`, outside repo)
- Initialise repo, set repo-local identity, add clean remote, first commit, push `-u`
**Contract/API:** the variable table in §4.
**Instructions for the builder:**
1. Run steps 6.1 → 6.6 in order. **Stop and report** on any failed assertion.
2. Do **not** set `user.name`/`user.email` globally. Do **not** put the token in the remote URL.
3. Do **not** force-push. Do **not** touch `PROJECT_PLAN.md`.
---
#### 6.1 Create the guard files FIRST
```bash
cd /root/myday
mkdir -p docs/specs .opencode
# write .gitignore and .opencode/git.env.example exactly as specified in §5.1
chmod 600 .opencode/git.env # after writing the real values
```
#### 6.2 Initialise the repository
```bash
cd /root/myday
git init -b main # git >= 2.28
# fallback if `-b` is unsupported:
# git init && git symbolic-ref HEAD refs/heads/main
git config --local user.name "Claus Lohmar"
git config --local user.email "claus.lohmar@lohmar.co.uk"
git config --local push.default simple
git config --local pull.rebase true
git config --local rebase.autoStash true # A-03: this is AUTO_STASH="true"
```
#### 6.3 Credential store (token never touches the repo)
```bash
umask 077
printf 'https://%s:%s@git.lohmar.co.uk\n' "cclohmar" "$GIT_TOKEN" > /root/.git-credentials
chmod 600 /root/.git-credentials
git config --global credential.helper store
```
#### 6.4 Remote (clean URL)
```bash
cd /root/myday
git remote add origin https://git.lohmar.co.uk/cclohmar/NextMyDay.git
git remote -v
# ASSERT: output contains no "@" character
```
#### 6.5 First commit — asserted secret-free
```bash
cd /root/myday
git add -A
git status --porcelain
# ASSERT: no config.yaml, no .env, no .opencode/git.env, no *.db, no .git-credentials
git check-ignore -v config.yaml .opencode/git.env # ASSERT: both are ignored
git commit -m "chore: bootstrap MyDay repository"
```
#### 6.6 Push and verify
```bash
cd /root/myday
git push -u origin main
git ls-remote --heads origin main # ASSERT: main present on the remote
git branch -vv # ASSERT: main tracks origin/main
git log --oneline -1
```
**Acceptance criteria**
| # | Criterion |
|---|---|
| AC-1 | `git remote get-url origin` → `https://git.lohmar.co.uk/cclohmar/NextMyDay.git` (**no** credentials) |
| AC-2 | `git push -u origin main` succeeded; `main` visible in Gitea |
| AC-3 | `git log --oneline -1` → subject starting `chore:` (honours `COMMIT_PREFIX`) |
| AC-4 | `git ls-files` contains `.gitignore`, `.opencode/git.env.example`, `docs/specs/spec-001-git-bootstrap.md`, `PROJECT_PLAN.md` |
| AC-5 | `git ls-files` contains **no** `config.yaml`, `.env`, `.opencode/git.env`, `*.db` |
| AC-6 | `git config --local pull.rebase` → `true`; `rebase.autoStash` → `true` |
| AC-7 | `stat -c '%a' /root/.git-credentials` → `600` |
**Rollback**
```bash
cd /root/myday
git remote remove origin
rm -rf /root/myday/.git
rm -f /root/.git-credentials /root/myday/.opencode/git.env
git config --global --unset credential.helper
```
---
## 7. Security notes
- **The supplied token was transmitted in plaintext and is therefore considered
compromised.** Rotate it in Gitea (`/user/settings/applications`) once the connection is
verified, then update `/root/.git-credentials`. Prefer a **scoped** token (repo write only).
- `.git-credentials` stores the token in **plaintext** on disk. Acceptable here (single-tenant,
root-owned, `600`). A future hardening step is a keyring/secret-store helper — out of scope.
- The `600` mode is the only protection; verify it (AC-7).
- Consider replacing token-over-HTTPS with an **SSH deploy key** before CI writes to this repo.
## 8. Follow-ups (not in this spec)
1. Log **A-04 / O-12** (where agent-workflow variables live) into `PROJECT_PLAN.md` — separate `docs:` commit.
2. Confirm `GIT_USER_NAME` (currently assumed `Claus Lohmar`).
3. Remaining Prep artefacts: `AGENT.md`, `README.md`, `config.example.yaml`, `docs/` layout.
4. Rotate the exposed token (§7).