diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0669699 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,18 @@ + +# OpenSpec Instructions + +These instructions are for AI assistants working in this project. + +Always open `@/openspec/AGENTS.md` when the request: +- Mentions planning or proposals (words like proposal, spec, change, plan) +- Introduces new capabilities, breaking changes, architecture shifts, or big performance/security work +- Sounds ambiguous and you need the authoritative spec before coding + +Use `@/openspec/AGENTS.md` to learn: +- How to create and apply change proposals +- Spec format and conventions +- Project structure and guidelines + +Keep this managed block so 'openspec update' can refresh the instructions. + + \ No newline at end of file diff --git a/openspec/AGENTS.md b/openspec/AGENTS.md new file mode 100644 index 0000000..96ab0bb --- /dev/null +++ b/openspec/AGENTS.md @@ -0,0 +1,456 @@ +# OpenSpec Instructions + +Instructions for AI coding assistants using OpenSpec for spec-driven development. + +## TL;DR Quick Checklist + +- Search existing work: `openspec spec list --long`, `openspec list` (use `rg` only for full-text search) +- Decide scope: new capability vs modify existing capability +- Pick a unique `change-id`: kebab-case, verb-led (`add-`, `update-`, `remove-`, `refactor-`) +- Scaffold: `proposal.md`, `tasks.md`, `design.md` (only if needed), and delta specs per affected capability +- Write deltas: use `## ADDED|MODIFIED|REMOVED|RENAMED Requirements`; include at least one `#### Scenario:` per requirement +- Validate: `openspec validate [change-id] --strict` and fix issues +- Request approval: Do not start implementation until proposal is approved + +## Three-Stage Workflow + +### Stage 1: Creating Changes +Create proposal when you need to: +- Add features or functionality +- Make breaking changes (API, schema) +- Change architecture or patterns +- Optimize performance (changes behavior) +- Update security patterns + +Triggers (examples): +- "Help me create a change proposal" +- "Help me plan a change" +- "Help me create a proposal" +- "I want to create a spec proposal" +- "I want to create a spec" + +Loose matching guidance: +- Contains one of: `proposal`, `change`, `spec` +- With one of: `create`, `plan`, `make`, `start`, `help` + +Skip proposal for: +- Bug fixes (restore intended behavior) +- Typos, formatting, comments +- Dependency updates (non-breaking) +- Configuration changes +- Tests for existing behavior + +**Workflow** +1. Review `openspec/project.md`, `openspec list`, and `openspec list --specs` to understand current context. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, optional `design.md`, and spec deltas under `openspec/changes//`. +3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement. +4. Run `openspec validate --strict` and resolve any issues before sharing the proposal. + +### Stage 2: Implementing Changes +Track these steps as TODOs and complete them one by one. +1. **Read proposal.md** - Understand what's being built +2. **Read design.md** (if exists) - Review technical decisions +3. **Read tasks.md** - Get implementation checklist +4. **Implement tasks sequentially** - Complete in order +5. **Confirm completion** - Ensure every item in `tasks.md` is finished before updating statuses +6. **Update checklist** - After all work is done, set every task to `- [x]` so the list reflects reality +7. **Approval gate** - Do not start implementation until the proposal is reviewed and approved + +### Stage 3: Archiving Changes +After deployment, create separate PR to: +- Move `changes/[name]/` → `changes/archive/YYYY-MM-DD-[name]/` +- Update `specs/` if capabilities changed +- Use `openspec archive --skip-specs --yes` for tooling-only changes (always pass the change ID explicitly) +- Run `openspec validate --strict` to confirm the archived change passes checks + +## Before Any Task + +**Context Checklist:** +- [ ] Read relevant specs in `specs/[capability]/spec.md` +- [ ] Check pending changes in `changes/` for conflicts +- [ ] Read `openspec/project.md` for conventions +- [ ] Run `openspec list` to see active changes +- [ ] Run `openspec list --specs` to see existing capabilities + +**Before Creating Specs:** +- Always check if capability already exists +- Prefer modifying existing specs over creating duplicates +- Use `openspec show [spec]` to review current state +- If request is ambiguous, ask 1–2 clarifying questions before scaffolding + +### Search Guidance +- Enumerate specs: `openspec spec list --long` (or `--json` for scripts) +- Enumerate changes: `openspec list` (or `openspec change list --json` - deprecated but available) +- Show details: + - Spec: `openspec show --type spec` (use `--json` for filters) + - Change: `openspec show --json --deltas-only` +- Full-text search (use ripgrep): `rg -n "Requirement:|Scenario:" openspec/specs` + +## Quick Start + +### CLI Commands + +```bash +# Essential commands +openspec list # List active changes +openspec list --specs # List specifications +openspec show [item] # Display change or spec +openspec validate [item] # Validate changes or specs +openspec archive [--yes|-y] # Archive after deployment (add --yes for non-interactive runs) + +# Project management +openspec init [path] # Initialize OpenSpec +openspec update [path] # Update instruction files + +# Interactive mode +openspec show # Prompts for selection +openspec validate # Bulk validation mode + +# Debugging +openspec show [change] --json --deltas-only +openspec validate [change] --strict +``` + +### Command Flags + +- `--json` - Machine-readable output +- `--type change|spec` - Disambiguate items +- `--strict` - Comprehensive validation +- `--no-interactive` - Disable prompts +- `--skip-specs` - Archive without spec updates +- `--yes`/`-y` - Skip confirmation prompts (non-interactive archive) + +## Directory Structure + +``` +openspec/ +├── project.md # Project conventions +├── specs/ # Current truth - what IS built +│ └── [capability]/ # Single focused capability +│ ├── spec.md # Requirements and scenarios +│ └── design.md # Technical patterns +├── changes/ # Proposals - what SHOULD change +│ ├── [change-name]/ +│ │ ├── proposal.md # Why, what, impact +│ │ ├── tasks.md # Implementation checklist +│ │ ├── design.md # Technical decisions (optional; see criteria) +│ │ └── specs/ # Delta changes +│ │ └── [capability]/ +│ │ └── spec.md # ADDED/MODIFIED/REMOVED +│ └── archive/ # Completed changes +``` + +## Creating Change Proposals + +### Decision Tree + +``` +New request? +├─ Bug fix restoring spec behavior? → Fix directly +├─ Typo/format/comment? → Fix directly +├─ New feature/capability? → Create proposal +├─ Breaking change? → Create proposal +├─ Architecture change? → Create proposal +└─ Unclear? → Create proposal (safer) +``` + +### Proposal Structure + +1. **Create directory:** `changes/[change-id]/` (kebab-case, verb-led, unique) + +2. **Write proposal.md:** +```markdown +# Change: [Brief description of change] + +## Why +[1-2 sentences on problem/opportunity] + +## What Changes +- [Bullet list of changes] +- [Mark breaking changes with **BREAKING**] + +## Impact +- Affected specs: [list capabilities] +- Affected code: [key files/systems] +``` + +3. **Create spec deltas:** `specs/[capability]/spec.md` +```markdown +## ADDED Requirements +### Requirement: New Feature +The system SHALL provide... + +#### Scenario: Success case +- **WHEN** user performs action +- **THEN** expected result + +## MODIFIED Requirements +### Requirement: Existing Feature +[Complete modified requirement] + +## REMOVED Requirements +### Requirement: Old Feature +**Reason**: [Why removing] +**Migration**: [How to handle] +``` +If multiple capabilities are affected, create multiple delta files under `changes/[change-id]/specs//spec.md`—one per capability. + +4. **Create tasks.md:** +```markdown +## 1. Implementation +- [ ] 1.1 Create database schema +- [ ] 1.2 Implement API endpoint +- [ ] 1.3 Add frontend component +- [ ] 1.4 Write tests +``` + +5. **Create design.md when needed:** +Create `design.md` if any of the following apply; otherwise omit it: +- Cross-cutting change (multiple services/modules) or a new architectural pattern +- New external dependency or significant data model changes +- Security, performance, or migration complexity +- Ambiguity that benefits from technical decisions before coding + +Minimal `design.md` skeleton: +```markdown +## Context +[Background, constraints, stakeholders] + +## Goals / Non-Goals +- Goals: [...] +- Non-Goals: [...] + +## Decisions +- Decision: [What and why] +- Alternatives considered: [Options + rationale] + +## Risks / Trade-offs +- [Risk] → Mitigation + +## Migration Plan +[Steps, rollback] + +## Open Questions +- [...] +``` + +## Spec File Format + +### Critical: Scenario Formatting + +**CORRECT** (use #### headers): +```markdown +#### Scenario: User login success +- **WHEN** valid credentials provided +- **THEN** return JWT token +``` + +**WRONG** (don't use bullets or bold): +```markdown +- **Scenario: User login** ❌ +**Scenario**: User login ❌ +### Scenario: User login ❌ +``` + +Every requirement MUST have at least one scenario. + +### Requirement Wording +- Use SHALL/MUST for normative requirements (avoid should/may unless intentionally non-normative) + +### Delta Operations + +- `## ADDED Requirements` - New capabilities +- `## MODIFIED Requirements` - Changed behavior +- `## REMOVED Requirements` - Deprecated features +- `## RENAMED Requirements` - Name changes + +Headers matched with `trim(header)` - whitespace ignored. + +#### When to use ADDED vs MODIFIED +- ADDED: Introduces a new capability or sub-capability that can stand alone as a requirement. Prefer ADDED when the change is orthogonal (e.g., adding "Slash Command Configuration") rather than altering the semantics of an existing requirement. +- MODIFIED: Changes the behavior, scope, or acceptance criteria of an existing requirement. Always paste the full, updated requirement content (header + all scenarios). The archiver will replace the entire requirement with what you provide here; partial deltas will drop previous details. +- RENAMED: Use when only the name changes. If you also change behavior, use RENAMED (name) plus MODIFIED (content) referencing the new name. + +Common pitfall: Using MODIFIED to add a new concern without including the previous text. This causes loss of detail at archive time. If you aren’t explicitly changing the existing requirement, add a new requirement under ADDED instead. + +Authoring a MODIFIED requirement correctly: +1) Locate the existing requirement in `openspec/specs//spec.md`. +2) Copy the entire requirement block (from `### Requirement: ...` through its scenarios). +3) Paste it under `## MODIFIED Requirements` and edit to reflect the new behavior. +4) Ensure the header text matches exactly (whitespace-insensitive) and keep at least one `#### Scenario:`. + +Example for RENAMED: +```markdown +## RENAMED Requirements +- FROM: `### Requirement: Login` +- TO: `### Requirement: User Authentication` +``` + +## Troubleshooting + +### Common Errors + +**"Change must have at least one delta"** +- Check `changes/[name]/specs/` exists with .md files +- Verify files have operation prefixes (## ADDED Requirements) + +**"Requirement must have at least one scenario"** +- Check scenarios use `#### Scenario:` format (4 hashtags) +- Don't use bullet points or bold for scenario headers + +**Silent scenario parsing failures** +- Exact format required: `#### Scenario: Name` +- Debug with: `openspec show [change] --json --deltas-only` + +### Validation Tips + +```bash +# Always use strict mode for comprehensive checks +openspec validate [change] --strict + +# Debug delta parsing +openspec show [change] --json | jq '.deltas' + +# Check specific requirement +openspec show [spec] --json -r 1 +``` + +## Happy Path Script + +```bash +# 1) Explore current state +openspec spec list --long +openspec list +# Optional full-text search: +# rg -n "Requirement:|Scenario:" openspec/specs +# rg -n "^#|Requirement:" openspec/changes + +# 2) Choose change id and scaffold +CHANGE=add-two-factor-auth +mkdir -p openspec/changes/$CHANGE/{specs/auth} +printf "## Why\n...\n\n## What Changes\n- ...\n\n## Impact\n- ...\n" > openspec/changes/$CHANGE/proposal.md +printf "## 1. Implementation\n- [ ] 1.1 ...\n" > openspec/changes/$CHANGE/tasks.md + +# 3) Add deltas (example) +cat > openspec/changes/$CHANGE/specs/auth/spec.md << 'EOF' +## ADDED Requirements +### Requirement: Two-Factor Authentication +Users MUST provide a second factor during login. + +#### Scenario: OTP required +- **WHEN** valid credentials are provided +- **THEN** an OTP challenge is required +EOF + +# 4) Validate +openspec validate $CHANGE --strict +``` + +## Multi-Capability Example + +``` +openspec/changes/add-2fa-notify/ +├── proposal.md +├── tasks.md +└── specs/ + ├── auth/ + │ └── spec.md # ADDED: Two-Factor Authentication + └── notifications/ + └── spec.md # ADDED: OTP email notification +``` + +auth/spec.md +```markdown +## ADDED Requirements +### Requirement: Two-Factor Authentication +... +``` + +notifications/spec.md +```markdown +## ADDED Requirements +### Requirement: OTP Email Notification +... +``` + +## Best Practices + +### Simplicity First +- Default to <100 lines of new code +- Single-file implementations until proven insufficient +- Avoid frameworks without clear justification +- Choose boring, proven patterns + +### Complexity Triggers +Only add complexity with: +- Performance data showing current solution too slow +- Concrete scale requirements (>1000 users, >100MB data) +- Multiple proven use cases requiring abstraction + +### Clear References +- Use `file.ts:42` format for code locations +- Reference specs as `specs/auth/spec.md` +- Link related changes and PRs + +### Capability Naming +- Use verb-noun: `user-auth`, `payment-capture` +- Single purpose per capability +- 10-minute understandability rule +- Split if description needs "AND" + +### Change ID Naming +- Use kebab-case, short and descriptive: `add-two-factor-auth` +- Prefer verb-led prefixes: `add-`, `update-`, `remove-`, `refactor-` +- Ensure uniqueness; if taken, append `-2`, `-3`, etc. + +## Tool Selection Guide + +| Task | Tool | Why | +|------|------|-----| +| Find files by pattern | Glob | Fast pattern matching | +| Search code content | Grep | Optimized regex search | +| Read specific files | Read | Direct file access | +| Explore unknown scope | Task | Multi-step investigation | + +## Error Recovery + +### Change Conflicts +1. Run `openspec list` to see active changes +2. Check for overlapping specs +3. Coordinate with change owners +4. Consider combining proposals + +### Validation Failures +1. Run with `--strict` flag +2. Check JSON output for details +3. Verify spec file format +4. Ensure scenarios properly formatted + +### Missing Context +1. Read project.md first +2. Check related specs +3. Review recent archives +4. Ask for clarification + +## Quick Reference + +### Stage Indicators +- `changes/` - Proposed, not yet built +- `specs/` - Built and deployed +- `archive/` - Completed changes + +### File Purposes +- `proposal.md` - Why and what +- `tasks.md` - Implementation steps +- `design.md` - Technical decisions +- `spec.md` - Requirements and behavior + +### CLI Essentials +```bash +openspec list # What's in progress? +openspec show [item] # View details +openspec validate --strict # Is it correct? +openspec archive [--yes|-y] # Mark complete (add --yes for automation) +``` + +Remember: Specs are truth. Changes are proposals. Keep them in sync. diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/design.md b/openspec/changes/add-elixir-bazel-k8s-deployment/design.md new file mode 100644 index 0000000..afaf90c --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/design.md @@ -0,0 +1,76 @@ +## Context +`codeline` is currently a small C daemon using manual process management and local file persistence. The requested direction introduces a modern runtime and delivery platform: OTP-based service behavior, Bazel-driven container publishing, and hardened Kubernetes deployment patterns. + +## Goals / Non-Goals +- Goals: + - Provide a functionally equivalent codeline service on Elixir/OTP. + - Use `GenServer` and supervision for robust process lifecycle management. + - Use ETS as the operational datastore for MOTD and posted lines. + - Keep runtime data ephemeral for phase 1 (no persistent volume-backed state). + - Build and publish container images through Bazel to `ghcr.io/carverauto/codeline`. + - Standardize Kubernetes deployment structure with Kustomize base/production overlay and hardening controls. + - Expose the service at `codeline.slowburnin.net` using `external-dns` with MetalLB-backed public addresses. + - Support stable IPv4 service exposure from `k3s-pool`. +- Non-Goals: + - Re-creating legacy implementation details that are unsafe or irrelevant in containerized production. + - Introducing external managed databases as part of this rewrite. + - Adding persistent volume-backed state management in this phase. + +## Decisions +- Decision: Implement client/session workflow using OTP processes (`GenServer`) under a supervision tree. + - Alternatives considered: direct process spawning with raw sockets, or retaining C server with wrappers. + - Rationale: OTP supervision and message-passing improves fault isolation and maintainability. + +- Decision: Use ETS tables as primary service datastore. + - Alternatives considered: local files, Mnesia, external DB. + - Rationale: ETS is lightweight, in-process, fast, and aligns with requested architecture. + +- Decision: Do not implement PVC-backed persistence in this phase. + - Alternatives considered: ETS snapshot to disk on PVC, DETS on PVC. + - Rationale: current scope prioritizes runtime rewrite and platform delivery; persistence can be added as a follow-on change. + +- Decision: Use Bazel as the canonical build and image publishing path. + - Alternatives considered: Dockerfile-only pipeline or mix-only release pipeline. + - Rationale: Bazel provides reproducible builds and consistent CI/CD integration. + +- Decision: Use Kustomize with `k8s/base` and `k8s/prod` overlays. + - Alternatives considered: flat manifests or Helm chart. + - Rationale: Kustomize overlays satisfy the requested environment layering while keeping manifests transparent. + +- Decision: Publish service using Kubernetes `LoadBalancer` + MetalLB + `external-dns`. + - Alternatives considered: Ingress-only exposure or manual DNS management. + - Rationale: MetalLB provides deterministic on-prem/public IP allocation, and `external-dns` automates DNS record management for `codeline.slowburnin.net`. + +- Decision: Configure IPv4-only service semantics for this phase. + - Alternatives considered: dual-stack service. + - Rationale: current environment has unstable IPv6 reachability; IPv4-only is required for reliable public access. + +## Risks / Trade-offs +- ETS is in-memory and ephemeral by default. + - Mitigation: document non-durable behavior explicitly and test restart semantics. + +- Rewriting runtime behavior may introduce command compatibility gaps. + - Mitigation: define compatibility-focused tests and acceptance scenarios for key command flows. + +- Security hardening controls may conflict with runtime assumptions (filesystem writes, privileged operations). + - Mitigation: design container/runtime to run non-root with read-only filesystem and explicit writable paths only if required. + +- Public exposure through MetalLB and DNS automation can create unintended reachability if policies are too broad. + - Mitigation: enforce explicit NetworkPolicy allowlists, constrained Service ports, and clearly scoped `external-dns` ownership/annotations. + +## Migration Plan +1. Implement Elixir service with feature parity for core commands. +2. Validate local runtime behavior and tests. +3. Build/publish container via Bazel into GHCR. +4. Deploy into Kubernetes `codeline` namespace via `k8s/prod` overlay. +5. Verify public IPv4 allocation through MetalLB and DNS propagation for `codeline.slowburnin.net`. +6. Perform smoke verification and monitor logs/health probes. + +## Rollback Plan +1. Stop rollout of new deployment and scale down Elixir pods. +2. Re-deploy previous stable runtime path. +3. Restore service endpoint routing to prior version. +4. Document incident notes and parity gaps before retry. + +## Open Questions +- Exact authentication/authorization mechanism for GHCR push in CI environment. diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/proposal.md b/openspec/changes/add-elixir-bazel-k8s-deployment/proposal.md new file mode 100644 index 0000000..8ac466b --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/proposal.md @@ -0,0 +1,28 @@ +# Change: Rewrite codeline in Elixir with Bazel container delivery and hardened Kubernetes deployment + +## Why +The current C daemon is difficult to evolve safely and has limited operational controls for modern deployment environments. +Migrating to Elixir with OTP supervision and codifying build/deploy standards enables maintainable runtime behavior, reproducible container delivery, and secure Kubernetes operations. + +## What Changes +- Replace the C runtime with an Elixir/OTP implementation using `GenServer` processes for command/session behavior. +- Use ETS tables for in-memory datastore responsibilities currently handled through flat files. +- Keep storage explicitly ephemeral in this phase (no PVC-backed persistence yet). +- Add Bazel-based container build and image publish flow targeting `ghcr.io/carverauto/codeline`. +- Add Kubernetes manifests organized as Kustomize base/overlay structure (`k8s/base`, `k8s/prod`). +- Apply Kubernetes hardening defaults, including namespace scoping, security context restrictions, and network policies. +- Expose the service publicly through MetalLB with IPv4 addressing from `k3s-pool`. +- Configure `external-dns` integration so `codeline.slowburnin.net` resolves to the MetalLB service address(es). +- Expose inbound telnet traffic on TCP/31337 through the Kubernetes `LoadBalancer` Service to the Elixir application. +- **BREAKING**: Runtime storage and process model change from file-backed C daemon to OTP + ETS service. + +## Impact +- Affected specs: + - `serve-codeline` + - `publish-codeline-image` + - `deploy-codeline-kubernetes` +- Affected code: + - New Elixir application modules and supervision tree + - New Bazel build/publish targets for container images + - New Kubernetes/Kustomize manifests under `k8s/` + - Legacy `codeline.c` runtime path retired from primary deployment path diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/specs/deploy-codeline-kubernetes/spec.md b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/deploy-codeline-kubernetes/spec.md new file mode 100644 index 0000000..e52e7b6 --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/deploy-codeline-kubernetes/spec.md @@ -0,0 +1,85 @@ +## ADDED Requirements +### Requirement: Kustomize Directory Structure +The repository MUST provide Kubernetes manifests with Kustomize structure at `k8s/base` and `k8s/prod`. + +#### Scenario: Base and production render +- **WHEN** operators run a Kustomize build for `k8s/base` or `k8s/prod` +- **THEN** manifests SHALL render successfully +- **AND** the production overlay SHALL compose from the base + +### Requirement: Namespace-Scoped Deployment +The Kubernetes deployment MUST run in the `codeline` namespace. + +#### Scenario: Applied resources +- **WHEN** manifests are applied to a cluster +- **THEN** workload resources SHALL be created in the `codeline` namespace + +### Requirement: Hardened Pod Security Context +The deployment MUST apply hardened container and pod security settings. + +#### Scenario: Pod security posture +- **WHEN** pods are inspected in the cluster +- **THEN** containers SHALL run as non-root +- **AND** privilege escalation SHALL be disabled +- **AND** all Linux capabilities SHALL be dropped unless explicitly required +- **AND** seccomp SHALL use `RuntimeDefault` +- **AND** root filesystem SHALL be read-only unless an explicit writable mount is defined + +### Requirement: Network Access Controls +The deployment MUST define NetworkPolicy resources that restrict ingress and egress to required traffic only. + +#### Scenario: Deny-by-default policy +- **WHEN** network policies are applied +- **THEN** unsolicited traffic SHALL be denied by default +- **AND** only explicitly allowed service traffic SHALL be permitted + +#### Scenario: Telnet ingress allowance +- **WHEN** codeline is exposed publicly +- **THEN** ingress on TCP port 31337 to codeline pods SHALL be explicitly allowed by NetworkPolicy +- **AND** non-required ports SHALL remain blocked + +### Requirement: MetalLB Public Service Exposure +The deployment MUST expose codeline using a Kubernetes `LoadBalancer` Service integrated with MetalLB. + +#### Scenario: IPv4 address allocation from k3s pool +- **WHEN** the Service is applied in the cluster +- **THEN** MetalLB SHALL allocate a public IPv4 address from the `k3s-pool` address pool +- **AND** the Service status SHALL report the assigned IPv4 load balancer address + +#### Scenario: Telnet service port +- **WHEN** the Service definition is rendered and applied +- **THEN** the Service SHALL expose TCP port 31337 for inbound telnet traffic +- **AND** the Service target port SHALL route to the Elixir application listener + +### Requirement: IPv4 Service Addressing +The deployment MUST use IPv4-only service exposure for this phase. + +#### Scenario: IPv4-only load balancer addressing +- **WHEN** the Service is created in the cluster +- **THEN** the Service SHALL be configured with IPv4 `SingleStack` IP family policy +- **AND** the Service SHALL receive an IPv4 load balancer address + +### Requirement: External DNS Record Management +The deployment MUST integrate with `external-dns` to manage DNS for `codeline.slowburnin.net`. + +#### Scenario: DNS record publication +- **WHEN** the production service is deployed with required `external-dns` annotations +- **THEN** DNS records for `codeline.slowburnin.net` SHALL be created or updated to point at the Service load balancer address(es) +- **AND** DNS management SHALL be automated without manual record edits + +### Requirement: Production Reliability Controls +The production overlay MUST include health and availability controls for stable operation. + +#### Scenario: Health and disruption handling +- **WHEN** pods are deployed in production +- **THEN** readiness and liveness probes SHALL be configured +- **AND** resource requests and limits SHALL be set +- **AND** a PodDisruptionBudget SHALL be present for controlled disruptions + +### Requirement: Phase 1 Storage Scope +The phase 1 Kubernetes manifests MUST not introduce PVC/PV resources for codeline runtime state. + +#### Scenario: Manifest inspection +- **WHEN** operators render `k8s/base` and `k8s/prod` +- **THEN** no PersistentVolumeClaim or PersistentVolume resources SHALL be present for codeline state +- **AND** runtime data behavior SHALL remain ETS-only and ephemeral diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/specs/publish-codeline-image/spec.md b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/publish-codeline-image/spec.md new file mode 100644 index 0000000..57ec350 --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/publish-codeline-image/spec.md @@ -0,0 +1,22 @@ +## ADDED Requirements +### Requirement: Bazel Container Build +The project MUST provide Bazel targets to build a container image for the Elixir codeline service. + +#### Scenario: Local or CI image build +- **WHEN** the Bazel container build target is executed +- **THEN** a runnable OCI image SHALL be produced for the codeline service + +### Requirement: Bazel GHCR Publish +The project MUST provide a Bazel-driven publish path for `ghcr.io/carverauto/codeline`. + +#### Scenario: Authenticated image push +- **WHEN** valid GHCR credentials are present and publish target is executed +- **THEN** the image SHALL be pushed to `ghcr.io/carverauto/codeline` with the configured tag + +### Requirement: Image Tagging Convention +The project MUST define and document image tagging rules for release and non-release builds. + +#### Scenario: Release tagging +- **WHEN** a production release is prepared +- **THEN** the published image SHALL include an immutable version tag +- **AND** the deployment overlay SHALL reference that immutable tag diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/specs/serve-codeline/spec.md b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/serve-codeline/spec.md new file mode 100644 index 0000000..6464076 --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/specs/serve-codeline/spec.md @@ -0,0 +1,37 @@ +## ADDED Requirements +### Requirement: Elixir OTP Runtime +The codeline service MUST run as an Elixir OTP application with supervised processes for lifecycle management. + +#### Scenario: Service boot +- **WHEN** the application starts +- **THEN** a supervision tree SHALL start all required runtime components +- **AND** failures in child processes SHALL be handled by supervision restart strategy + +### Requirement: GenServer-Based Session Handling +The system MUST implement client command/session behavior using one or more `GenServer` processes. + +#### Scenario: Client command execution +- **WHEN** a client issues a supported command +- **THEN** the responsible `GenServer` SHALL validate and execute the command +- **AND** the response SHALL be returned over the active session + +### Requirement: ETS Datastore for Runtime State +The system MUST store MOTD and posted code-line entries in ETS tables. + +#### Scenario: Post and list flow +- **WHEN** a client posts a code-line entry +- **THEN** the entry SHALL be persisted in ETS +- **AND** a subsequent list operation SHALL return the entry from ETS + +#### Scenario: Pod restart behavior +- **WHEN** the application pod restarts +- **THEN** ETS-backed runtime data SHALL be treated as non-durable +- **AND** prior in-memory entries SHALL not be assumed to persist across restart + +### Requirement: Runtime Configuration +The service MUST support runtime configuration for listen port and admin credentials without source edits. + +#### Scenario: Environment-specific config +- **WHEN** deployment provides environment-specific settings +- **THEN** the service SHALL start with those settings applied +- **AND** no code changes SHALL be required for configuration updates diff --git a/openspec/changes/add-elixir-bazel-k8s-deployment/tasks.md b/openspec/changes/add-elixir-bazel-k8s-deployment/tasks.md new file mode 100644 index 0000000..9d3a1a9 --- /dev/null +++ b/openspec/changes/add-elixir-bazel-k8s-deployment/tasks.md @@ -0,0 +1,27 @@ +## 1. Runtime Rewrite (Elixir) +- [x] 1.1 Create Elixir OTP application skeleton and supervision tree for codeline service. +- [x] 1.2 Implement session/menu handling with `GenServer` processes for client command flows. +- [x] 1.3 Implement ETS-backed storage for MOTD and posted code lines. +- [x] 1.4 Ensure ETS data lifecycle is explicitly ephemeral across pod restarts. +- [x] 1.5 Add runtime configuration for listen port and admin credentials. +- [x] 1.6 Add tests for command parsing, admin auth flow, and ETS read/write behavior. + +## 2. Bazel Build and Image Publish +- [x] 2.1 Add Bazel targets to build the Elixir release artifact and OCI image. +- [x] 2.2 Add Bazel target/script to push image to `ghcr.io/carverauto/codeline`. +- [x] 2.3 Document required registry authentication and tagging conventions. + +## 3. Kubernetes Deployment (Kustomize) +- [x] 3.1 Create `k8s/base` with Namespace, Deployment, Service, ConfigMap/Secret references, and baseline labels. +- [x] 3.2 Create `k8s/prod` overlay with production-specific replicas, image tags, and environment values. +- [x] 3.3 Add hardening manifests and settings: NetworkPolicy, PodDisruptionBudget, resource requests/limits, probes, and strict pod/container security contexts. +- [x] 3.4 Ensure deployment targets the `codeline` namespace and applies successfully via Kustomize. +- [x] 3.5 Configure Service + MetalLB annotations/policies to allocate a public IPv4 from `k3s-pool`. +- [x] 3.6 Configure `external-dns` annotations/records for `codeline.slowburnin.net` pointing to the service load balancer address(es). +- [x] 3.7 Configure the `LoadBalancer` Service to expose telnet on TCP/31337 to the Elixir app and allow required ingress in NetworkPolicy. +- [x] 3.8 Do not include PVC/PV resources for codeline runtime data in this phase. + +## 4. Migration and Cutover +- [x] 4.1 Define migration notes from file-backed storage behavior to ETS-backed runtime behavior. +- [x] 4.2 Define rollback procedure to prior runtime path if production issues are detected. +- [x] 4.3 Update README/runbook with local run, container build/push, and Kubernetes deploy instructions. diff --git a/openspec/project.md b/openspec/project.md new file mode 100644 index 0000000..5fac4e7 --- /dev/null +++ b/openspec/project.md @@ -0,0 +1,59 @@ +# Project Context + +## Purpose +`codeline` preserves and modernizes a vintage 1994 "BoW Code-Line for internet" program so it builds and runs on modern UNIX-like systems. +The project goal is to keep the original lightweight telnet-style experience (post/list code lines, simple admin controls) while making targeted safety and compatibility improvements. + +## Tech Stack +- C (single-source program: `codeline.c`) +- POSIX APIs: sockets, `fork`, signals, file I/O, `dup2`, `kill`, `execl` +- Build tooling: `make` + `gcc` +- Runtime files: `codeline.codes` (posted lines), `codeline.motd` (login message) + +## Project Conventions + +### Code Style +- Keep the codebase simple and C89/C90-friendly, with minimal dependencies. +- Use uppercase macros for configuration/constants (`PORT`, `PROMPT`, `MOTD`, `CODES`). +- Prefer bounded string functions (`vsnprintf`, careful fixed-size buffers) over unsafe variants. +- Check return codes for network and system calls where practical, especially on security-sensitive paths. +- Follow existing function-oriented layout with explicit handlers (`post_handler`, `list_handler`, etc.). + +### Architecture Patterns +- Single daemon-style TCP server process using double-fork startup. +- Listener accepts connections and forks per client session. +- Text command interpreter with table-driven dispatch arrays for main/admin menus. +- Flat-file persistence in the process working directory (no database). +- Compile-time configuration via preprocessor macros (port, prompts, password mode). + +### Testing Strategy +- No automated test suite currently; validation is primarily manual. +- Core verification flow: + - Build with `make` + - Start `./codeline` + - Connect with a TCP client (for example `telnet`/`nc`) and exercise `POST`, `LIST`, `ADMIN`, and `QUIT` +- For troubleshooting crashes/runtime issues, use `strace -p ` as documented in `README.md`. +- For changes touching input handling, prioritize regression checks around buffer sizes, newline handling, and file/socket error paths. + +### Git Workflow +- Repository history shows a simple trunk workflow with occasional pull-request merges. +- Keep commits small and focused; use concise imperative commit messages. +- Existing commit style often uses short prefixes/icons to indicate intent (docs/fix/security), which is acceptable but optional. +- Prefer security hardening and compatibility fixes that preserve the original behavior. + +## Domain Context +This project is a retro BBS-style "code line" service from the 1990s warez/IRC scene, maintained mainly for preservation and hobby use. +Users connect over TCP, view an MOTD, then interact with a minimal command prompt. +Admin operations are protected by a shared code (`CODE` or optional encrypted `CR_CODE`) and can clear state or terminate the server. + +## Important Constraints +- UNIX-like environments only (depends on POSIX process/network APIs). +- Single-source, low-complexity implementation is preferred over introducing frameworks. +- Persistence is local plaintext files only; no authentication system beyond shared admin code. +- Network protocol is plaintext and not hardened for hostile public exposure (no TLS, no rate limiting, minimal access controls). +- Configuration is compile-time constants in `codeline.c`; runtime configurability is intentionally limited. + +## External Dependencies +- System C toolchain (`gcc`, `make`) and standard/POSIX C libraries. +- Optional `crypt()` support when `CR_CODE` mode is enabled (platform-dependent link/runtime requirements). +- No external SaaS, database, or third-party API dependencies.