refactor(core): compose feature requests into shared host capabilities - #3
Merged
Conversation
Why:\nThe core repo needed a single published configuration contract instead of flat per-service variables and wizard output that still emitted the removed restic contribution model. Leaving the builder and scaffold on the old shape would have kept the repo internally inconsistent even if the runtime playbooks had been refactored.\n\nWhat:\n- updated the tracked inventory example to the nested features.* and host.* model\n- rewired bootstrap to consume host.ufw baseline settings from the new contract\n- changed the repo-local wizard builder and inventory template to emit the nested host and feature mappings directly\n- removed flat backup output keys from the builder context and replaced them with host.restic jobs plus nested feature/host settings\n- tightened the wizard profile default so public HTTPS exposure is derived instead of hard-coded into the default firewall baseline\n\nValidation:\n- ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml\n- bash tests/smoke/test_builder_obsidian_setup_uri_generation.sh\n- bash tests/smoke/test_builder_restic_target_keypair_consistency.sh\n- bash tests/smoke/test_builder_restic_target_name_normalization.sh
…dings Why:\nFeature roles and shared host capability roles needed an explicit site-owned integration layer. Without a dedicated composition step, the repo would either fall back to runtime self-registration or force shared-service ownership back into feature roles.\n\nWhat:\n- added the platform_bindings role as the site-local composition layer\n- composed platform_ingress_routes from enabled features that require shared ingress\n- composed platform_backup_datasets from host foundation state, workspaces, Obsidian CouchDB data, and Traefik ACME state\n- composed platform_ufw_requests from public HTTPS ingress requirements\n- produced effective platform_obsidian_livesync, platform_host_traefik, platform_host_restic, and platform_host_ufw mappings for downstream role consumption\n\nValidation:\n- bash tests/smoke/test_platform_bindings_public_https.sh\n- bash tests/smoke/test_platform_bindings_private_mesh.sh
Why:\nThe site playbooks still targeted the old single-stack flow even after the role contracts moved to host_traefik, host_restic, and host_ufw. The repo needed explicit orchestration that composes site bindings first, runs feature-local work, and then reconciles shared host capabilities.\n\nWhat:\n- rewired site.yml to run platform_bindings before obsidian_livesync, host_traefik, and host_ufw\n- rewired backup.yml to run platform_bindings before host_restic\n- updated the dedicated Obsidian feature playbook to consume the composed obsidian role contract\n\nValidation:\n- ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml\n- ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml
Why:\nThe old preflight role validated removed flat variables and restic contribution wiring, which meant it was no longer checking the real contract the repo now executes. Preflight needed to move up to the nested host/features model and the site-composed platform outputs.\n\nWhat:\n- loaded inventory variables before running platform_bindings in playbooks/preflight.yml\n- rewrote preflight validation around nested features.* and host.* contracts\n- added validation for removed legacy variables, public HTTPS ingress requirements, private mesh planning inputs, host.restic job selection, and unsupported feature_owned_jobs\n- validated composed platform_ingress_routes, platform_backup_datasets, and platform_ufw_requests instead of the deleted contribution model\n- kept the aggregated report, warnings, and remote connectivity probe behavior intact\n\nValidation:\n- ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml\n- bash tests/smoke/test_preflight_placeholder_scan.sh\n- bash tests/smoke/test_preflight_restic_cache_valid_time_validation.sh\n- bash tests/smoke/test_preflight_restic_target_name_validation.sh\n- bash tests/smoke/test_preflight_tailscale_tag_validation.sh\n- bash tests/smoke/test_preflight_feature_owned_jobs_rejected.sh\n- bash tests/smoke/test_preflight_restic_broad_path_warning.sh
…ooks Why:\nThe repo documentation still described the deleted flat variable surface and the removed restic contribution model. That would have left operators following the wrong ownership boundaries even though the code had already moved on.\n\nWhat:\n- rewrote the top-level README and architecture docs around nested features.* and host.* configuration plus site-local composition\n- updated preflight, quickstart, restore, and implementation status docs to describe host capability ownership and composed backup datasets\n- removed references to restic_backup_contributions and other deleted flat inventory keys from the operator docs\n- refreshed the config wizard spec examples so the documented target paths match the nested contract\n\nValidation:\n- rg -n restic_backup_contributions
Why: The previous smoke suite still asserted the removed flat backup contribution model and did not exercise the new site-local composition layer. Without updating the tests and CI, the repo would have looked refactored while silently losing the coverage that makes the new ownership boundaries safe to extend. What: - updated the builder smoke tests to assert nested host.* and features.* output plus removal of flat backup keys - updated the preflight smoke tests to load platform_bindings and validate the nested contract messages - added composition smoke coverage for platform_bindings in public_https and private_mesh modes - added preflight coverage for unsupported host.restic.feature_owned_jobs - expanded CI to run the new builder, composition, and preflight smoke suite Validation: - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/lockdown.yml - bash tests/smoke/test_builder_obsidian_setup_uri_generation.sh - bash tests/smoke/test_builder_restic_target_keypair_consistency.sh - bash tests/smoke/test_builder_restic_target_name_normalization.sh - bash tests/smoke/test_platform_bindings_public_https.sh - bash tests/smoke/test_platform_bindings_private_mesh.sh - bash tests/smoke/test_preflight_placeholder_scan.sh - bash tests/smoke/test_preflight_restic_broad_path_warning.sh - bash tests/smoke/test_preflight_restic_cache_valid_time_validation.sh - bash tests/smoke/test_preflight_restic_target_name_validation.sh - bash tests/smoke/test_preflight_tailscale_tag_validation.sh - bash tests/smoke/test_preflight_feature_owned_jobs_rejected.sh
Why The platform composition layer crashed on missing obsidian ingress.route_name before preflight could report a contract error. The builder also still leaked public HTTPS into the host UFW baseline, which violated the refactor's ownership split. What Make platform_bindings tolerate a missing route_name without undefined-variable failures. Add explicit preflight validation for features.obsidian_livesync.ingress.route_name. Stop the builder from auto-injecting 443 into host.ufw.baseline_tcp_public. Add smoke coverage and CI wiring for the new contract and ownership checks. Validation bash tests/smoke/test_preflight_obsidian_route_name_validation.sh bash tests/smoke/test_builder_restic_target_name_normalization.sh for t in tests/smoke/*.sh; do bash "" || exit 1; done ./scripts/init-local-config.sh >/dev/null umask 177 && printf 'ci-test-only\n' > .vault_pass ./scripts/install-collections.sh >/dev/null ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/lockdown.yml ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/features/obsidian.yml
Why\nThe core PR depends on new host capability roles that only exist on matching provider branches until the services and base refactors merge. CI was installing provider collections from their default remote branches, so syntax checks on the core PR could not resolve the new roles. The collection installer also inherited a brittle user-level Ansible temp path, which made local validation less reliable.\n\nWhat\n- stage branch-matched provider repo checkouts during pull_request validation when the same branch exists in crownops-deploy-base and crownops-deploy-services\n- point collection installation at those staged checkouts only when they are present, while preserving the default source fallback for normal validation\n- harden scripts/install-collections.sh to create and use a repo-local ANSIBLE_LOCAL_TEMP by default\n\nValidation\n- python3 -m pip install "ansible-core==2.17.*"\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ./scripts/install-collections.sh\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_placeholder_scan.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_tailscale_tag_validation.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_platform_bindings_public_https.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_platform_bindings_private_mesh.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_builder_restic_target_name_normalization.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_builder_obsidian_setup_uri_generation.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_builder_restic_target_keypair_consistency.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_restic_target_name_validation.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_restic_cache_valid_time_validation.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_restic_broad_path_warning.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_feature_owned_jobs_rejected.sh\n- TMPDIR=/dev/shm/crownops-core-tmp bash tests/smoke/test_preflight_obsidian_route_name_validation.sh\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml\n- TMPDIR=/dev/shm/crownops-core-tmp ANSIBLE_LOCAL_TEMP=/dev/shm/crownops-core-tmp/ansible ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/lockdown.yml
Why - platform_bindings was overwriting host-owned Traefik routes and restic datasets instead of composing additive effective contracts, which blocked extension and would have forced follow-up refactors. - preflight could crash with undefined-variable errors when nested host/features roots were missing, so operators would not get the aggregated contract validation the refactor promised. - the smoke harness depended on ambient tempdir and umask behavior, which made the new validation surface brittle outside the GitHub runner defaults. What - normalize host and feature roots inside platform_bindings, preserve host-owned Traefik routes/restic datasets/UFW requests, and avoid crashing when optional roots are absent during validation. - harden preflight validation with safe nested-contract aliases, additive contract collision checks, missing root/path checks, and effective-dataset validation against the fully composed restic contract. - add smoke coverage for missing nested contract roots and additive host extension merges, switch temp fixture helpers to a controlled helper, and wire the new smoke tests into CI. - document that host-owned routes, datasets, and requests remain additive extension points after site composition. Validation - ./scripts/install-collections.sh - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/lockdown.yml - bash tests/smoke/test_*.sh
Why - the refactor published host.traefik as an additive extension point, but preflight was still validating only part of that public contract before remote execution - that gap would let incomplete custom Traefik host capability configs survive preflight and fail later during deployment, which is the wrong failure boundary for an operator-facing repo What - validate the required host.traefik contract fields whenever Traefik is enabled, even when Obsidian is not the ingress source - validate effective Traefik route definitions against the same contract shape the shared role expects, while still allowing implicit entrypoint defaults from the services collection - add a smoke test that exercises host-only Traefik preflight validation and wire it into CI Validation - ./scripts/install-collections.sh with CROWNOPS_BASE_COLLECTION_SOURCE=/home/user/Code/vps/crownops-deploy-base and CROWNOPS_SERVICES_COLLECTION_SOURCE=/home/user/Code/vps/crownops-deploy-services - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/preflight.yml - bash tests/smoke/test_preflight_placeholder_scan.sh - bash tests/smoke/test_preflight_missing_contract_roots.sh - bash tests/smoke/test_preflight_tailscale_tag_validation.sh - bash tests/smoke/test_platform_bindings_public_https.sh - bash tests/smoke/test_platform_bindings_private_mesh.sh - bash tests/smoke/test_platform_bindings_host_extensions.sh - bash tests/smoke/test_preflight_host_traefik_contract_validation.sh - bash tests/smoke/test_builder_restic_target_name_normalization.sh - bash tests/smoke/test_builder_obsidian_setup_uri_generation.sh - bash tests/smoke/test_builder_restic_target_keypair_consistency.sh - bash tests/smoke/test_preflight_restic_target_name_validation.sh - bash tests/smoke/test_preflight_restic_cache_valid_time_validation.sh - bash tests/smoke/test_preflight_restic_broad_path_warning.sh - bash tests/smoke/test_preflight_feature_owned_jobs_rejected.sh - bash tests/smoke/test_preflight_obsidian_route_name_validation.sh - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/bootstrap.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/site.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/backup.yml - ansible-playbook --syntax-check -i inventories/prod/hosts.yml playbooks/lockdown.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
host.*andfeatures.*configuration contractsWhy
This repo is the environment-facing composition layer. It should describe desired host capabilities and feature contracts, not duplicate shared service or firewall implementation details. The refactor makes that boundary explicit and keeps validation close to the site-specific inputs that operators actually edit.
Validation
validateworkflow is passing