From 78c37081d792970190622af9a4f7c529b54d3bcb Mon Sep 17 00:00:00 2001 From: Danilo Reyes Date: Wed, 1 Apr 2026 23:59:58 -0600 Subject: [PATCH] constitution firewall --- .specify/memory/constitution.md | 121 +++++++++++++++++++-------- .specify/templates/plan-template.md | 7 +- .specify/templates/spec-template.md | 6 +- .specify/templates/tasks-template.md | 42 +++++----- docs/constitution.md | 3 + docs/reference/index.md | 2 + specs/001-ai-docs/research.md | 5 ++ 7 files changed, 129 insertions(+), 57 deletions(-) diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md index a4670ff..1366ea2 100644 --- a/.specify/memory/constitution.md +++ b/.specify/memory/constitution.md @@ -1,50 +1,101 @@ -# [PROJECT_NAME] Constitution - + +# NixOS Repository Constitution ## Core Principles -### [PRINCIPLE_1_NAME] - -[PRINCIPLE_1_DESCRIPTION] - +### I. Constitution Authority +This constitution is the source of truth for Speckit-driven planning and +implementation work in this repository. Plans, specs, tasks, and runtime +guidance MUST align with it. When another document conflicts with this +constitution, the conflicting document MUST be updated in the same change set +or the conflict MUST be recorded in `specs/001-ai-docs/research.md`. -### [PRINCIPLE_2_NAME] - -[PRINCIPLE_2_DESCRIPTION] - +### II. Module and Host Boundaries +Code MUST live in the directory that owns the behavior. Shared feature logic +belongs under `modules/`; host-specific assembly belongs under `hosts//`; +factory helpers belong under `modules/factories/`; repo-wide shared options +belong under `modules/modules.nix` or the relevant shared module. New behavior +MUST NOT be placed in an unrelated host or module file for convenience. -### [PRINCIPLE_3_NAME] - -[PRINCIPLE_3_DESCRIPTION] - +### III. Host-Local Firewall Ownership +Any host that contains firewall rules MUST keep firewall-related logic in +`hosts//firewall.nix`. Host `configuration.nix` files MAY import that +file, but MUST NOT become the long-term home for firewall rule definitions, +NAT rules, nftables tables, forward-port rules, or other firewall-specific +logic. Firewall changes in specs, plans, and task lists MUST reference the +host-local `firewall.nix` path explicitly. -### [PRINCIPLE_4_NAME] - -[PRINCIPLE_4_DESCRIPTION] - +### IV. Nix Structure and Ordering +Nix code MUST preserve grouped parents when they have multiple children and +MUST flatten only single-child chains. Siblings that share a parent MUST live +under one parent block instead of being split across disconnected assignments. +Within module bodies, `options` MUST appear before `config`. Within attribute +sets, `inherit` statements MUST come first, then boolean leaves, then other +leaf assignments, then nested attribute sets. -### [PRINCIPLE_5_NAME] - -[PRINCIPLE_5_DESCRIPTION] - +### V. Secure Host and Secrets Discipline +Secret-dependent behavior MUST stay behind `config.my.secureHost`. Secrets MUST +live in the appropriate `secrets/*.yaml` file by purpose, and hosts marked +non-secure MUST avoid secret-dependent services and secret loading. Proxy-only +or network-only changes MUST still respect secret ownership, secure-host +gating, and host-local boundaries. -## [SECTION_2_NAME] - +## Repository Constraints -[SECTION_2_CONTENT] - +- Host definitions live in `hosts//configuration.nix` with optional + imports such as `hosts//firewall.nix` and `hosts//toggles.nix`. +- Module categories remain `apps`, `dev`, `scripts`, `servers`, `services`, + `shell`, `websites`, `network`, `users`, and `nix`, with feature directories + preferred over new flat modules. +- Service ports intrinsic to a server module SHOULD live with that module; + miscellaneous shared ports SHOULD live in `my.ports`. +- Firewall rules, NAT, nftables tables, and forward-port declarations for a + host MUST be reviewed as one unit inside that host's `firewall.nix`. -## [SECTION_3_NAME] - +## Delivery Workflow -[SECTION_3_CONTENT] - +- Every plan MUST include a constitution check that validates module ownership, + host ownership, secure-host impact, and whether firewall work belongs in + `hosts//firewall.nix`. +- Every spec that changes networking or exposure MUST state which host owns the + change and which firewall file is affected. +- Every task list that includes firewall work MUST name the concrete + `hosts//firewall.nix` path. +- Runtime guidance docs that describe repository structure MUST be updated when + host boundary rules change. ## Governance - -[GOVERNANCE_RULES] - +This constitution supersedes conflicting Speckit planning assumptions for this +repository. Amendments MUST be made in the same change set as any dependent +template or runtime guidance updates. Versioning follows semantic versioning: +MAJOR for incompatible governance changes or principle removals, MINOR for new +principles or materially expanded rules, PATCH for clarifications that do not +change required behavior. Compliance review is mandatory for every plan, spec, +and tasks artifact that claims alignment with this constitution. -**Version**: [CONSTITUTION_VERSION] | **Ratified**: [RATIFICATION_DATE] | **Last Amended**: [LAST_AMENDED_DATE] - +**Version**: 1.0.0 | **Ratified**: 2026-04-01 | **Last Amended**: 2026-04-01 diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md index 6a8bfc6..d1800af 100644 --- a/.specify/templates/plan-template.md +++ b/.specify/templates/plan-template.md @@ -31,7 +31,12 @@ *GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* -[Gates determined based on constitution file] +- Confirm each change lives in the directory that owns the behavior. +- Confirm shared logic stays in `modules/` and host-specific assembly stays in + `hosts//`. +- Confirm any firewall, NAT, nftables, or port-forwarding work is scoped to + `hosts//firewall.nix` for the affected host. +- Confirm any secret-dependent behavior respects `config.my.secureHost`. ## Project Structure diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md index c67d914..9bb2bbb 100644 --- a/.specify/templates/spec-template.md +++ b/.specify/templates/spec-template.md @@ -89,11 +89,13 @@ - **FR-003**: Users MUST be able to [key interaction, e.g., "reset their password"] - **FR-004**: System MUST [data requirement, e.g., "persist user preferences"] - **FR-005**: System MUST [behavior, e.g., "log all security events"] +- **FR-006**: If the feature changes host firewall behavior, the spec MUST name + the affected `hosts//firewall.nix` file explicitly. *Example of marking unclear requirements:* -- **FR-006**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] -- **FR-007**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] +- **FR-007**: System MUST authenticate users via [NEEDS CLARIFICATION: auth method not specified - email/password, SSO, OAuth?] +- **FR-008**: System MUST retain user data for [NEEDS CLARIFICATION: retention period not specified] ### Key Entities *(include if feature involves data)* diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md index 60f9be4..9173931 100644 --- a/.specify/templates/tasks-template.md +++ b/.specify/templates/tasks-template.md @@ -17,6 +17,8 @@ description: "Task list template for feature implementation" - **[P]**: Can run in parallel (different files, no dependencies) - **[Story]**: Which user story this task belongs to (e.g., US1, US2, US3) - Include exact file paths in descriptions +- If firewall behavior changes, tasks MUST reference `hosts//firewall.nix` + instead of only `hosts//configuration.nix` ## Path Conventions @@ -68,6 +70,8 @@ Examples of foundational tasks (adjust based on your project): - [ ] T007 Create base models/entities that all stories depend on - [ ] T008 Configure error handling and logging infrastructure - [ ] T009 Setup environment configuration management +- [ ] T010 If networking changes, update the affected `hosts//firewall.nix` + and import wiring in `hosts//configuration.nix` **Checkpoint**: Foundation ready - user story implementation can now begin in parallel @@ -83,17 +87,17 @@ Examples of foundational tasks (adjust based on your project): > **NOTE: Write these tests FIRST, ensure they FAIL before implementation** -- [ ] T010 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T011 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py +- [ ] T011 [P] [US1] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T012 [P] [US1] Integration test for [user journey] in tests/integration/test_[name].py ### Implementation for User Story 1 -- [ ] T012 [P] [US1] Create [Entity1] model in src/models/[entity1].py -- [ ] T013 [P] [US1] Create [Entity2] model in src/models/[entity2].py -- [ ] T014 [US1] Implement [Service] in src/services/[service].py (depends on T012, T013) -- [ ] T015 [US1] Implement [endpoint/feature] in src/[location]/[file].py -- [ ] T016 [US1] Add validation and error handling -- [ ] T017 [US1] Add logging for user story 1 operations +- [ ] T013 [P] [US1] Create [Entity1] model in src/models/[entity1].py +- [ ] T014 [P] [US1] Create [Entity2] model in src/models/[entity2].py +- [ ] T015 [US1] Implement [Service] in src/services/[service].py (depends on T013, T014) +- [ ] T016 [US1] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T017 [US1] Add validation and error handling +- [ ] T018 [US1] Add logging for user story 1 operations **Checkpoint**: At this point, User Story 1 should be fully functional and testable independently @@ -107,15 +111,15 @@ Examples of foundational tasks (adjust based on your project): ### Tests for User Story 2 (OPTIONAL - only if tests requested) ⚠️ -- [ ] T018 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T019 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py +- [ ] T019 [P] [US2] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T020 [P] [US2] Integration test for [user journey] in tests/integration/test_[name].py ### Implementation for User Story 2 -- [ ] T020 [P] [US2] Create [Entity] model in src/models/[entity].py -- [ ] T021 [US2] Implement [Service] in src/services/[service].py -- [ ] T022 [US2] Implement [endpoint/feature] in src/[location]/[file].py -- [ ] T023 [US2] Integrate with User Story 1 components (if needed) +- [ ] T021 [P] [US2] Create [Entity] model in src/models/[entity].py +- [ ] T022 [US2] Implement [Service] in src/services/[service].py +- [ ] T023 [US2] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T024 [US2] Integrate with User Story 1 components (if needed) **Checkpoint**: At this point, User Stories 1 AND 2 should both work independently @@ -129,14 +133,14 @@ Examples of foundational tasks (adjust based on your project): ### Tests for User Story 3 (OPTIONAL - only if tests requested) ⚠️ -- [ ] T024 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py -- [ ] T025 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py +- [ ] T025 [P] [US3] Contract test for [endpoint] in tests/contract/test_[name].py +- [ ] T026 [P] [US3] Integration test for [user journey] in tests/integration/test_[name].py ### Implementation for User Story 3 -- [ ] T026 [P] [US3] Create [Entity] model in src/models/[entity].py -- [ ] T027 [US3] Implement [Service] in src/services/[service].py -- [ ] T028 [US3] Implement [endpoint/feature] in src/[location]/[file].py +- [ ] T027 [P] [US3] Create [Entity] model in src/models/[entity].py +- [ ] T028 [US3] Implement [Service] in src/services/[service].py +- [ ] T029 [US3] Implement [endpoint/feature] in src/[location]/[file].py **Checkpoint**: All user stories should now be independently functional diff --git a/docs/constitution.md b/docs/constitution.md index c2ab4d6..51eb2a6 100644 --- a/docs/constitution.md +++ b/docs/constitution.md @@ -11,6 +11,7 @@ - Module auto-import: `modules/modules.nix` auto-imports legacy flat modules under `modules/apps`, `modules/dev`, `modules/scripts`, `modules/servers`, `modules/services`, `modules/shell`, `modules/websites`, and `modules/network`, excluding `librewolf.nix`, and also discovers nested `nixos.nix` files under those trees. `config/base.nix` registers `modules/home-manager.nix` as a Home Manager shared module, which discovers nested `home.nix` files under `modules/` for every Home Manager user. Factories live in `modules/factories/` (`mkserver`, `mkscript`), and shared options are in `modules/nix` and `modules/users`. - Home Manager helper layer: Common Home Manager wrapper logic belongs in `parts/core.nix` helpers under `inputs.self.lib` when it is repeated across multiple `home.nix` modules. Current helpers include split-loader support plus `hmModule`, `hmShellType`, and `hmOnlyUser` for shared enablement and shell-selection patterns. - Hosts and toggles: Host definitions live in `hosts//configuration.nix` with host-specific toggles in `hosts//toggles.nix`. The `my` namespace carries toggles for apps/dev/scripts/services/shell, feature flags like `enableProxy` and `enableContainers`, and per-host `interfaces` and `ips` maps. +- Host-local firewall ownership: Hosts that define firewall rules MUST keep firewall-related logic in `hosts//firewall.nix`, imported from `hosts//configuration.nix` as needed. Host `configuration.nix` files are the assembly point, not the long-term home for firewall rule definitions. - Standalone Home Manager hosts: Home-only hosts may live under `hosts//home.nix` with `hosts//toggles.nix`, and should only enable modules that have a `home.nix` surface or are otherwise known to be Home Manager-compatible on that platform. - Port assignment: Service ports should live with the service module when the port is intrinsic to a server definition under `modules/servers/`. Miscellaneous or host-specific ports that do not belong to a server module should be centralized in `my.ports` in `modules/modules.nix` and referenced via `config.my.ports.*` (use `toString config.my.ports.*` where a string is required). - Main server and proxies: `my.mainServer` selects the host that should serve traffic by default (default `vps`). Reverse proxies use helpers in `parts/core.nix` (`proxy`, `proxyReverse`, `proxyReverseFix`, `proxyReversePrivate`) and pick IPs from `my.ips` plus the hostName/ip set by `mkserver` options. Nginx defaults to `proxyReverse` for any server with `enableProxy = true` unless `useDefaultProxy = false` or the server is listed in the Fix/Private proxy lists. @@ -21,6 +22,7 @@ - Minimize comments; prefer clear naming and shared helpers (`modules/factories/mkserver.nix`, `modules/factories/mkscript.nix`) to avoid duplication. - Use business-level, technology-agnostic language in AI docs; reserve implementation detail for module code. - Nix structure: flatten single-child attribute sets into their full path; keep multi-child sets nested for readability; merge siblings under a shared parent; flatten the shallowest subtree first to reduce indentation without losing clarity. +- Parent ownership rule: when a parent has multiple children, keep them grouped under that parent instead of splitting them across scattered assignments. Flatten only single-child chains. - Nix attribute ordering: prefer `options` before `config` in module bodies; inside attribute sets keep `inherit` statements first, then boolean leaf assignments, then other leaf assignments, then nested attribute sets; when simple leaves and nested children share a parent, place the simple leaves first. ```nix config.services.jellyfin.enable = true; # preferred single-leaf form @@ -78,6 +80,7 @@ config.services = { ## Maintenance Triggers and Update Process - Triggers: New factory/helper, new module category, new host, new toggle set, new proxy rule, new secret category/file, change to `my.mainServer` or `my.ips`, stylix scheme changes, or new auto-import filters/import trees. +- Triggers: New factory/helper, new module category, new host, new toggle set, new proxy rule, new host firewall ownership rule or `hosts//firewall.nix` layout change, new secret category/file, change to `my.mainServer` or `my.ips`, stylix scheme changes, or new auto-import filters/import trees. - Update flow: (1) Amend the relevant module or toggle files; (2) Update `docs/constitution.md` for rules/terminology changes; (3) Update playbooks under `docs/playbooks/` affected by the change; (4) Update `docs/reference/index.md` for navigation paths; (5) Note the decision in `specs/001-ai-docs/research.md` and refresh `quickstart.md` if discoverability shifts. - Validation: Confirm discoverability within two clicks (constitution → reference map/playbook), secrets map completeness, and alignment with success criteria SC-001–SC-004. diff --git a/docs/reference/index.md b/docs/reference/index.md index 8689b65..ed148b7 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -39,6 +39,7 @@ ## Hosts and Roles - NixOS configs: `hosts//configuration.nix` with toggles in `hosts//toggles.nix`. +- Firewall-bearing hosts: keep firewall logic in `hosts//firewall.nix` and import it from `hosts//configuration.nix`. - Standalone Home Manager configs: `hosts//home.nix` with optional toggles in `hosts//toggles.nix`. - Active NixOS hosts: `workstation`, `server`, `miniserver`, `galaxy`, `emacs`, `vps`. - Active Home Manager hosts: `mac`. @@ -64,6 +65,7 @@ - Default proxying: any server with `enableProxy = true` gets a `proxyReverse` vhost unless `useDefaultProxy = false` or it is listed in `proxyReverseFix` / `proxyReversePrivate`. - Main server selection: `my.mainServer` chooses where services live by default (default `vps`); `mkserver` sets `isLocal` based on this and picks IPs from `my.ips`. - Firewall generation: `inputs.self.lib.generateFirewallPorts` combines static ports, additional ports, and service ports from `my.servers` (excluding native firewall services). Use `my.network.firewall` settings and `getServicesWithNativeFirewall` to derive open ports. +- Host firewall placement: host-specific firewall rules, NAT, nftables tables, and forward-port definitions belong in `hosts//firewall.nix`. ## Secrets Map - Files and purposes: diff --git a/specs/001-ai-docs/research.md b/specs/001-ai-docs/research.md index 3ccd95f..1974f49 100644 --- a/specs/001-ai-docs/research.md +++ b/specs/001-ai-docs/research.md @@ -54,3 +54,8 @@ - **Decision**: Standardize Nix structure so single-child attribute sets are flattened into dotted attrpaths, siblings that share a parent are merged into one nested attribute set, simple leaf assignments appear before nested attribute sets, `inherit` statements appear first within a set, boolean leaves appear before other leaves, and module bodies place `options` before `config`. - **Rationale**: This keeps modules scan-friendly, reduces unnecessary indentation, and makes the high-signal contract (`options`) appear before implementation (`config`) consistently across the repo. - **Alternatives considered**: (a) Leave structure to formatter defaults only (rejected: formatters do not enforce these semantic grouping rules); (b) prefer fully flattened attrpaths everywhere (rejected: harms readability once a parent has multiple children); (c) keep `config` before `options` when it was written first (rejected: makes module interfaces harder to scan). + +## Decision 12 (2026-04-01): Host-local firewall files +- **Decision**: Any host that owns firewall rules MUST keep firewall-related logic in `hosts//firewall.nix`, with `hosts//configuration.nix` importing that file rather than accumulating the firewall logic inline. +- **Rationale**: Firewall behavior is a distinct host concern that becomes hard to review and maintain when mixed into general host assembly. A dedicated `firewall.nix` preserves ownership boundaries and makes networking changes easier to audit. +- **Alternatives considered**: (a) Keep firewall rules inline in `configuration.nix` (rejected: mixes host assembly with a dense, security-sensitive subsystem); (b) centralize all firewall logic under `modules/network/` (rejected: hides host-specific rule ownership and deployment context).