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

10 KiB

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):

# --- 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:

# 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

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

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)

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)

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

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

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

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).