Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,23 @@ See [`docs/advanced.md`](docs/advanced.md) — none of it is required for a work

This is a single-server Drupal installer and operator kit. It is not a managed hosting platform, multi-tenant SaaS, or compliance certification. The advanced features are useful, but the community claim is intentionally narrow: install and operate Drupal on one VPS, calmly.

## Deployment profiles

Actools supports deployment profiles via the `--profile` flag on `init`.

The default `community` profile is suitable for all standard installs. A `community-plus` profile — designed for schools, universities, and regulated organisations that need evidence generation and governance gates — arrives in later phases.

```bash
# Default — community profile (no flag needed)
sudo ./actools.sh init --domain example.com --email admin@example.com

# Community-plus (arriving in later phases)
sudo ./actools.sh init --domain example.com --email admin@example.com \
--profile community-plus
```

The active profile is shown in `actools doctor`. Once set at init time the profile is pinned in `actools.env` for the lifetime of the deployment.

## Security

To report a security vulnerability, email **hello@feesix.com**. We aim to respond within 48 hours. Do not open a public GitHub issue for security issues.
Expand Down
6 changes: 6 additions & 0 deletions actools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ LOG_FILE="$INSTALL_DIR/actools-install.log"
LOG_DIR="$INSTALL_DIR/logs/install"
PKG_DONE_FLAG="/var/lib/actools/.packages_done"

# D.0: Source dispatch.sh early — provides resolver functions for all modes.
# Sourced here (after INSTALL_DIR is known) so init/preflight/handoff modes
# all have access. init.sh also sources it internally for independence.
# shellcheck source=/dev/null
source "${INSTALL_DIR}/installer/dispatch.sh" 2>/dev/null || true

R='\033[0;31m'; G='\033[0;32m'; Y='\033[1;33m'; C='\033[0;36m'; NC='\033[0m'

mkdir -p "$LOG_DIR" 2>/dev/null || true
Expand Down
68 changes: 68 additions & 0 deletions cli/actools
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,74 @@
INSTALL_DIR="/home/actools"
cd "$INSTALL_DIR" 2>/dev/null || { echo "Actools not found at $INSTALL_DIR"; exit 1; }

# =============================================================================
# D.0 — Profile resolution
#
# Strip --profile / --profile=VALUE from $@ before the main case statement.
# Read ACTOOLS_PROFILE from actools.env if present, resolve via dispatch.sh.
#
# Note on sibling-files discipline (brief Section 2, D.0-3):
# cli/actools is a static installed file. There is no setup_cli.sh heredoc
# generator in this codebase — the H.7.1-7 risk was audited and does not
# apply here. This comment is the explicit record of that audit.
# =============================================================================

_actools_cli_profile=""
_actools_cli_remaining_args=()

# Strip --profile=VALUE or --profile VALUE from $@
while [[ $# -gt 0 ]]; do
case "$1" in
--profile=*)
_actools_cli_profile="${1#*=}"
shift
;;
--profile)
_actools_cli_profile="${2:-}"
shift 2
;;
*)
_actools_cli_remaining_args+=("$1")
shift
;;
esac
done

# Reconstruct positional args without --profile
set -- "${_actools_cli_remaining_args[@]+"${_actools_cli_remaining_args[@]}"}"

# Read ACTOOLS_PROFILE from actools.env if present.
_actools_env_profile=""
_actools_profile_source="default"
if [[ -f "${INSTALL_DIR}/actools.env" ]]; then
# shellcheck disable=SC1091
_actools_env_profile="$(
set -a
source "${INSTALL_DIR}/actools.env" 2>/dev/null
echo "${ACTOOLS_PROFILE:-}"
)"
fi

# Source dispatch.sh (provides resolver functions and actools::cli::resolve_profile).
# shellcheck source=/dev/null
source "${INSTALL_DIR}/installer/dispatch.sh" 2>/dev/null || true

# Resolve profile with fail-closed conflict handling (Decision 2).
if declare -f actools::cli::resolve_profile >/dev/null 2>&1; then
if ! ACTOOLS_PROFILE="$(actools::cli::resolve_profile "$_actools_cli_profile" "$_actools_env_profile")"; then
exit $?
fi
export ACTOOLS_PROFILE

# Record how the profile was determined for doctor reporting.
if [[ -n "$_actools_cli_profile" ]]; then
_actools_profile_source="cli"
elif [[ -n "$_actools_env_profile" ]]; then
_actools_profile_source="env-file"
fi
export _actools_profile_source
fi

php_svc() { echo "php_${1:-prod}"; }

case "${1:-help}" in
Expand Down
9 changes: 9 additions & 0 deletions cli/commands/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,17 @@ run_doctor() {
set +a
fi

# D.0: Source dispatch.sh after env file has made ACTOOLS_PROFILE available.
# Resolvers are available for D.1+ doctor-check dispatch; not called in D.0.
# shellcheck source=/dev/null
source "${INSTALL_DIR}/installer/dispatch.sh" 2>/dev/null || true

print_title "ACTOOLS DOCTOR"

# Report active profile — doctrine claim: active profile is always operator-visible.
echo "Active profile: ${ACTOOLS_PROFILE:-community} (source: ${_actools_profile_source:-default})"
echo

local fails=0 warns=0

# ── 1. Site URL reachable ─────────────────────────────────────────────
Expand Down
22 changes: 22 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## D.0 — Community Seam Hardening (Resolver Dispatch Foundation)

**New files:**
- `installer/dispatch.sh` — resolver function family (`resolve_feature_handler`, `resolve_preflight_check`, `resolve_doctor_check`, `resolve_handoff_section`, `profile_is_valid`, `actools::cli::resolve_profile`). Single dispatch surface for all profile-aware operations.
- `tests/fixtures/profiles/test/` — test fixture profile (4 files). Exercises resolver dispatch against a non-production profile without requiring D.1+ modules to exist.
- `tests/test_d0_dispatch.bats` — 33 bats tests covering all resolver paths, CLI profile resolution, fixture activation, sibling-scope audit, and community-install regression.
- `docs/briefs/PHASE_D0_README.md` — historical record of D.0 scope and closure.

**Modified files:**
- `installer/init.sh` — added `--profile` flag; validates against allowed list; writes `ACTOOLS_PROFILE` to `actools.env`.
- `installer/preflight.sh` — sources `dispatch.sh` after `profile.sh` sets `ACTOOLS_PROFILE`.
- `installer/handoff.sh` — sources `dispatch.sh` after `profile.sh` sets `ACTOOLS_PROFILE`.
- `cli/commands/doctor.sh` — sources `dispatch.sh`; adds "Active profile" line to output.
- `cli/actools` — global `--profile` flag parsing; fail-closed profile conflict detection; exports `ACTOOLS_PROFILE`.
- `actools.sh` — sources `dispatch.sh` after `INSTALL_DIR` is set.
- `README.md` — added deployment profiles section.

**Baseline test count after D.0:** 43 existing + 33 new = 76 total.

**D.0 defining property:** Community installs see zero behaviour change. Adding `--profile community-plus` does nothing observable (no `plus_*` modules exist yet).


All notable changes to Actools are documented here.

---
Expand Down
104 changes: 104 additions & 0 deletions docs/briefs/METHODOLOGY_NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# METHODOLOGY_NOTES.md — Actools Drupal Community-Plus Arc

**Project:** actoolsDrupal — community-only Drupal 11 install platform with community-plus profile extensions
**Arc origin:** D.0 — Community Seam Hardening (resolver dispatch foundation)
**Document character:** Continuous process. Methodology entries accrete by observation, not by template. Empty sections are honest; they fill as phases close.

---

## The partnership

Three members, locked, permanent regardless of future contributor scale:

- **Orchestrator** — names scope, locks briefs, makes strategic decisions, routes external review consolidated reports to Sir Opus
- **Sir Opus** — brief author, reviewer gate, empirical PoC verifier; verdicts to Sonnet only through orchestrator
- **Sonnet** — implementer; works in own window with locked brief as handoff package; the best engineer this partnership knows

The orchestrator may rotate external review instances (typically ChatGPT for Roles A/B/C, Gemini for Role D when prompted properly). External rotations are capacity-protection work; the founding three never expand.

---

## The methodology orbit

The discipline framework travels from WPGovern. The lessons themselves are observation-earned within this arc. WPGovern's `METHODOLOGY_NOTES.md` (and Sonnet's `CODING_AGENT_REFERENCE.md`) are reference context — useful, not authoritative for this project's claims.

Specific lessons formalize in this document only after observation within the Drupal arc. The held-candidate model travels: single observation → hold; second observation across distinct phases → eligible for formalization.

---

## Lessons (Drupal arc)

*Empty at D.0 start. Lessons accrete by observation as phases close.*

The first candidates to watch (potentially observable in D.0):

- **Universal lessons that almost certainly travel from WPGovern:**
- Integration tests at every wiring layer (WPGovern Lesson 1)
- Same-window vs fresh-window external review (WPGovern Lesson 3)
- Brief authorship ceremony non-negotiable (WPGovern Lesson 4)
- Settled decisions don't get relitigated (WPGovern Lesson 6)
- Glob patterns must filter sidecar files (WPGovern Lesson 7)
- Perimeter discipline — calibration earned through demonstrated work (WPGovern Lesson 8)
- Pattern-match assumption / discipline-travel between sibling modules (WPGovern Lesson 2 eighth refinement)
- Doctrine-vs-implementation audit (WPGovern Lesson 11)
- Four-role layered review architecture for fresh-surface phases (WPGovern Lesson 9 second refinement)

These will register as Drupal-arc lessons after their first observation in this project's surfaces.

---

## Held candidates (single observation; awaiting second to formalize)

| Candidate | Origin | What it claims | Observation site to watch |
|---|---|---|---|
| Internal-verification-scope-must-enumerate-sibling-files | WPGovern H.6.2 + H.7 latent fix | When a brief names files for a fix, the brief author must also enumerate sibling files that share the defect class, not rely on review to surface them | D.0's sibling-scope meta-test; D.0 verification round |
| Sonnet's bonus-discipline within scope | WPGovern H.6.1 / H.6.2 / H.7 / H.7.1 — four data points | Sonnet contributes structural improvements within scope that compound methodology (test consolidation, contract-level checks, byte-preserving patterns); structurally different from orchestrator's or reviewers' contributions because it's about the implementer's role | D.0 implementation round; closure recognition |

---

## Refinements (Drupal arc)

*Empty at D.0 start. Refinements register when an existing lesson develops a more precise edge case worth naming.*

---

## Closed phases

*Empty at D.0 start.*

When a phase closes:
- Phase ID + closure type (internal verdict / external review)
- Methodology candidates observed
- Methodology refinements registered
- Cross-references to relevant artifacts (briefs, review reports, PoC results)

---

## Active phase

**D.0 — Community Seam Hardening (Resolver Dispatch Foundation)**

Status: Brief LOCKED. Implementation pending. See `docs/briefs/d0_phase_brief.md`.

---

## Cross-project references

| Project | Methodology document | Status |
|---|---|---|
| WPGovern | `wpgovern/METHODOLOGY_NOTES.md` | 11 lessons + 8 refinements + 8 milestones at v1 close. Source for the discipline framework. |
| Drupal (this) | `docs/briefs/METHODOLOGY_NOTES.md` (this file) | Empty at D.0 start; accretes by observation. |
| Sonnet's coding agent reference | `CODING_AGENT_REFERENCE.md` (from WPGovern, applicable here) | First-person-from-inside implementer methodology — referenced by name from the D.0 brief; assumed internalized by Sonnet. |

---

## Operating principles (carried from the partnership)

- **No exit options.** Never selling, never flipping, perpetual maintenance posture.
- **Operational features only.** Feature requests are filtered to "what is required and what makes the system better in operation"; aspirational requests are politely declined or ignored.
- **Quarterly informational cadence externally.** Public posture is informational, not solicitous. Maybe one informative post on a new feature every three to four months. The work speaks for itself.
- **Community-only platform; content products monetize separately.** Drupal-pure positioning. No cross-references to other governance projects in this arc's design canon.

---

*Document accretes from D.0 onward. Last update: D.0 brief lock.*
90 changes: 90 additions & 0 deletions docs/briefs/PHASE_D0_README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# D.0 — Community Seam Hardening (Resolver Dispatch Foundation)

**Phase status:** Delivered
**Foundation version:** v14.1.0
**Brief:** `docs/briefs/d0_phase_brief.md` (authored by Sir Opus)

---

## What D.0 delivered

A single canonical dispatch surface through which every operation entry point
asks "given the active profile, what is the handler for operation X?" and
receives a deterministic answer.

**New files:**

| File | Purpose |
|---|---|
| `installer/dispatch.sh` | Resolver function family + profile validation + `actools::cli::resolve_profile` |
| `tests/fixtures/profiles/test/manifest.sh` | Test fixture profile activator + handler stubs |
| `tests/fixtures/profiles/test/plus_preflight_check.sh` | Preflight stub |
| `tests/fixtures/profiles/test/plus_doctor_check.sh` | Doctor stub |
| `tests/fixtures/profiles/test/plus_handoff_section.sh` | Handoff stub |
| `tests/test_d0_dispatch.bats` | D.0 verification suite (33 tests) |
| `docs/briefs/PHASE_D0_README.md` | This file |

**Modified files:**

| File | What changed |
|---|---|
| `installer/init.sh` | `--profile` flag parsing; dispatch.sh sourcing; profile validation; writes `ACTOOLS_PROFILE` to `actools.env` |
| `installer/preflight.sh` | Sources `dispatch.sh` after `profile.sh` sets `ACTOOLS_PROFILE` |
| `installer/handoff.sh` | Sources `dispatch.sh` after `profile.sh` sets `ACTOOLS_PROFILE` |
| `cli/commands/doctor.sh` | Sources `dispatch.sh`; adds "Active profile" line |
| `cli/actools` | Global `--profile` flag parsing; `ACTOOLS_PROFILE` resolution via `actools::cli::resolve_profile`; exports profile |
| `actools.sh` | Sources `dispatch.sh` after `INSTALL_DIR` is set (covers all modes) |
| `README.md` | Added `--profile` paragraph |
| `CHANGELOG.md` | D.0 entry |

---

## What D.0 deliberately did NOT deliver

1. **No `plus_*` modules.** The seam is in place; modules arrive in D.1+.
2. **No resolver call sites.** `dispatch.sh` is sourced and available; it is
not called from any entry point. Calling resolvers is D.1+ work.
3. **No install-stage refactor.** Stage dispatcher lands in D.1.
4. **No `actools.env` migration for existing installs.** Default-by-absence is
the contract. Existing installs without `ACTOOLS_PROFILE` resolve to community.
5. **No `actools doctor --deep` changes.** Deep mode placeholder unchanged.

---

## D.0 defining property (verified)

Adding `--profile community-plus` to any `actools` command on a D.0 install
does nothing observable — because no `plus_*` modules exist. Community
installs produce byte-identical journey output to v14.1 (modulo the new
"Active profile" line in `actools doctor`).

---

## Where to find the dispatch surface

```bash
# The resolver function family:
installer/dispatch.sh

# The allowed profiles list (single source of truth):
grep "_ACTOOLS_ALLOWED_PROFILES" installer/dispatch.sh

# D.0 bats tests:
tests/test_d0_dispatch.bats

# Fixture profile:
tests/fixtures/profiles/test/

# Sibling-scope audit:
# All files reading ACTOOLS_PROFILE either source dispatch.sh or carry
# a DISPATCH_EXEMPT comment. The meta-test in test_d0_dispatch.bats enforces this.
```

---

## D.1 entry point

D.1 (install-stage dispatcher) hangs `plus_*` hardening modules off the
`PROFILE_INSTALL_STAGES` pattern. The first D.1 change is adding
`run_install_stage` and iterating `profile_install_stages` through it.
D.0's `actools::dispatch::resolve_feature_handler` is the D.1 entry point.
Loading