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
2,285 changes: 0 additions & 2,285 deletions .ai-context/full.txt

This file was deleted.

6 changes: 3 additions & 3 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ jobs:
shellcheck --exclude=SC2034,SC2015,SC2164,SC2043,SC2012,SC1090,SC1091 cli/commands/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC2012 installer/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC1091 modules/audit/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC1090 modules/dr/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164 modules/observability/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC1090 experimental/dr/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164 experimental/observability/*.sh
shellcheck --exclude=SC2034,SC2164 modules/drupal/*.sh
shellcheck --severity=warning --exclude=SC2034,SC2015,SC2164,SC1090,SC1091 modules/audit/lib/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC2119,SC2120 modules/preview/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164,SC2119,SC2120 experimental/preview/*.sh
shellcheck --exclude=SC2034,SC2015,SC2164 modules/stack/*.sh
trivy:
runs-on: ubuntu-latest
Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Current state: `docs/hardening.md` describes MariaDB TLS as not enabled by defau

**What exists:**
- `CADDY_CLOUDFLARE_TOKEN` placeholder in `actools.env.example`
- Documentation in `modules/network/cloudflare-setup.md` describing DNS-01 and Origin-Cert options
- Documentation in `experimental/network/cloudflare-setup.md` describing DNS-01 and Origin-Cert options

**What's missing:**
- The Caddy image does not include the `caddy-dns/cloudflare` provider plugin
Expand Down Expand Up @@ -111,7 +111,7 @@ Current state: `docs/operations.md` describes the update flow and names manual r
**Status:** Deployed in disaster-recovery installations only, not in standard install

**What exists:**
- Audit wrapper at `modules/security/actools-audit`
- Audit wrapper at `experimental/security/actools-audit`
- The disaster-recovery resurrection path installs the wrapper chain (`actools` symlink → `actools-audit` → `actools-real`)

**What's missing:**
Expand Down
2 changes: 1 addition & 1 deletion actools.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ DRUPAL_ADMIN_EMAIL=admin@example.com
# Required when running behind a Cloudflare Tunnel with ports 80/443 closed.
# Token needs Zone:DNS:Edit permission for your domain.
# Leave blank for standard HTTP-01 challenge (open ports).
# See modules/network/cloudflare-setup.md for tunnel TLS posture options.
# See experimental/network/cloudflare-setup.md for tunnel TLS posture options.
CADDY_CLOUDFLARE_TOKEN=

# -- Site Identity ------------------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions docs/advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ How it works (planned): `db-full-backup.sh` runs daily at 02:00, dumping with `-

## DNA resurrection

> **Experimental — not wired.** `actools immortalize` and `actools resurrect` are **not** registered commands. The scripts live in `modules/dr/`, are unvalidated against the current stack, and `resurrect.sh` would install a separate `actools-real` binary — do **not** run it on a live server. This section is design reference only.
> **Experimental — not wired.** `actools immortalize` and `actools resurrect` are **not** registered commands. The scripts live in `experimental/dr/`, are unvalidated against the current stack, and `resurrect.sh` would install a separate `actools-real` binary — do **not** run it on a live server. This section is design reference only.

The design: `immortalize` captures a complete server blueprint — OS, Docker versions, container manifests, modules, binlog position, redacted env keys — into an age-encrypted JSON snapshot. `resurrect` would replay it on a fresh server in 11 steps (install dependencies → create user → clone repo → restore secrets → start stack → restore database → install CLI + cron + RBAC → health check).

Expand All @@ -67,7 +67,7 @@ A password manager or encrypted vault is fine. **Do not commit these to git.**

## GDPR compliance

> **Experimental — not wired.** `actools gdpr …` is **not** a registered command. The code lives in `modules/compliance/gdpr.sh` and is not validated against the current Drupal version. Design reference only.
> **Experimental — not wired.** `actools gdpr …` is **not** a registered command. The code lives in `experimental/compliance/gdpr.sh` and is not validated against the current Drupal version. Design reference only.

Planned surface:

Expand All @@ -85,7 +85,7 @@ Planned export format: JSON in `backups/gdpr-exports/` with profile, roles, cont

## Preview environments

> **Experimental — not wired.** `actools branch …` is **not** a registered command. The code lives in `modules/preview/branch.sh`. Design reference only.
> **Experimental — not wired.** `actools branch …` is **not** a registered command. The code lives in `experimental/preview/branch.sh`. Design reference only.

The design: per-branch isolated Drupal environments for PR previews, design reviews, and risky migrations.

Expand Down Expand Up @@ -119,7 +119,7 @@ Would generate three workflows from templates — **test** (PR: CodeSniffer, PHP

## AI assistant

> **Experimental — not wired.** `actools ai …` is **not** a registered command. The code lives in `modules/ai/assistant.sh`. Design reference only.
> **Experimental — not wired.** `actools ai …` is **not** a registered command. The code lives in `experimental/ai/assistant.sh`. Design reference only.

The design: a small local model with codebase context for "how does this script work" questions.

Expand All @@ -145,9 +145,9 @@ actools tunnel restart
actools tunnel logs
```

The tunnel runs as a systemd service (`cloudflared.service`). Configuration template in `modules/network/cloudflared-config.yml.example`. Once active, you can remove the UFW rules for 80/443 — only SSH remains inbound.
The tunnel runs as a systemd service (`cloudflared.service`). Configuration template in `experimental/network/cloudflared-config.yml.example`. Once active, you can remove the UFW rules for 80/443 — only SSH remains inbound.

See `modules/network/cloudflare-setup.md` for the one-time setup.
See `experimental/network/cloudflare-setup.md` for the one-time setup.

---

Expand Down
105 changes: 54 additions & 51 deletions docs/architecture/runtime-authority-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,58 +93,61 @@ A row may carry a compound status (e.g. `current (monolithic) → target via dis
- **Doc contradictions (P0-B/P0-J).** `docs/architecture.md`: "v11.2.0+" (`:3`), "the CLI … never contains business logic" (`:9`), "21 bats tests" (`:49`), `"version":"11.2.0"` + `phases_complete` state machine (`:84-87`), `actools-real` (`:119`) — all false vs code. `docs/CHANGELOG.md:113` Dockerfile claim false. `cli/actools:12-15` false self-comment.
- **Design-canon home (build-trigger #2)** was **absent** before P0-A (`design/` did not exist; LOCKED/brief/arch not committed under `docs/` or `design/`). P0-A creates `design/` with the three canon files.

## Standalone modules (C1 inventory — live vs orphan)

> **Recorded at phase C1** (doc + guard only; **no code deletion, no
> behaviour change**). This section is the human-readable mirror of
> `tests/guards/orphan_inventory_guard_test.bats`, which pins the live-module
> set. **Any phase that changes the live-module set must update BOTH** this
> table and that guard's `EXPECTED_LIVE_MODULES` list. Baseline `82ba206`.

`modules/` holds **13** directories. **6 are LIVE** — reached by the live
install path (sourced from `actools.sh`, or referenced by the installer /
operator CLI). **7 are orphan** — no live reference anywhere in `actools.sh`,
`installer/`, or `cli/` (verified by per-module grep, recorded in
`PHASE0_LEDGER` Entry 021). The 12 original orphans split into **dead-twins** (duplicated
live inline/module logic) and **4.5-seeds** (committed 4.5 design,
quarantined into `experimental/` in C3, not deleted). **C2 removed the 5
dead-twins** and reclassified `ai` + `preview` (previously dead-twin) **as
4.5-seeds** — their dirs stay in place for C3 — so the **7 that remain are
all 4.5-seeds**. C1 had acted on none of them — it only classified and
guarded, which is what made C2 safe.

**Totals: 6 live · 7 orphan (all 4.5-seed, C3-quarantine-bound) · 13 total.**
5 dead-twins (`health, migrate, preflight, storage, worker`) removed in C2;
`ai` + `preview` reclassified as 4.5-seeds (C3 quarantine). (At C1 the figure
was **12 of 18** orphan — correcting the plan-of-record §2's "12 of 19"
off-by-one; C2 then removed 5, leaving **7 of 13**.)

| module | status | evidence | disposition |
## Standalone modules (live vs quarantined)

> **Recorded at C1** (doc + guard only). **Amended C2** (dead-twin removal) and
> **C3** (4.5-seed quarantine into `experimental/`). Human-readable mirror of
> `tests/guards/orphan_inventory_guard_test.bats`, which pins the live-module set
> **and** that nothing under `experimental/` is ever sourced. **Any phase that
> changes the live-module set must update BOTH** this table and that guard's
> `EXPECTED_LIVE_MODULES`. Baseline `8c1897c`.

After C3, `modules/` holds **exactly the 6 LIVE modules** — those reached by the
live install path (sourced from `actools.sh`, or referenced by the installer /
operator CLI). The **7 4.5-design seeds** that previously sat in `modules/` as
orphans (no live reference) were **moved to `experimental/`** in C3 — `git mv`,
content byte-identical, history preserved. (The 5 dead-twins — `health, migrate,
preflight, storage, worker` — were deleted in C2.)

**This is a surface quarantine, not physical removal.** The install is in-place
(`INSTALL_DIR` = the repo dir, `actools.sh:94`) and `chown -R`'s the whole tree
(`actools.sh:405`), so the `experimental/` files still reside on the box — but
they are off the live surface, and the guard fails CI if any `experimental/…`
path is ever reached by the live closure or wired into an entry point. The
"unwired design reference" framing the operator docs already used (see
`enterprise.md`) is unchanged; only the paths moved to `experimental/`.

**Totals: 6 live (`modules/`) · 7 quarantined 4.5-seed (`experimental/`).**
(Lineage: 18 module dirs at C1 → 12 orphan / 6 live; C2 deleted 5 dead-twins and
reclassified `ai`+`preview` as seeds → 13 dirs, 7 orphan; C3 moved the 7 seeds
to `experimental/` → `modules/` = 6, `experimental/` = 7.)

| module | location | status | evidence |
|---|---|---|---|
| `audit` | LIVE | referenced on the live path — `cli/actools:313,317` (operator-CLI surface, installed by copy) | — |
| `backup` | LIVE | sourced on the live path — `actools.sh:516` (`modules/backup/cron.sh`) | — |
| `db` | LIVE | sourced on the live path — `actools.sh:457` (`modules/db/core.sh`) | — |
| `drupal` | LIVE | sourced on the live path — `actools.sh:181` (`modules/drupal/provision.sh`) | — |
| `host` | LIVE | sourced on the live path — `actools.sh:193` loop over `modules/host/*.sh` | — |
| `stack` | LIVE | sourced on the live path — `actools.sh:204` loop over `modules/stack/*.sh` | — |
| `ai` | orphan · 4.5-seed (reclassified C2) | no live reference | C3: quarantine → `experimental/` |
| `preview` | orphan · 4.5-seed (reclassified C2) | no live reference | C3: quarantine → `experimental/` |
| `compliance` | orphan · 4.5-seed | no live reference | C3: quarantine → `experimental/` |
| `dr` | orphan · 4.5-seed | no live reference | C3: quarantine → `experimental/` |
| `network` | orphan · 4.5-seed | no live reference | C3: quarantine → `experimental/` |
| `observability` | orphan · 4.5-seed | no live reference | C3: quarantine → `experimental/` |
| `security` | orphan · 4.5-seed | no live reference | C3: quarantine → `experimental/` |

The LIVE rows above are exactly the guard's `EXPECTED_LIVE_MODULES`
(`audit backup db drupal host stack`). The guard **derives** the actual set
from the tree — the source-closure of `actools.sh` (the `CLOSURE` engine from
`live_closure.bash`) **unioned** with `modules/<name>` references in
`actools.sh` / `installer/` / `cli/actools` — and fails CI if it diverges from
this list in **either** direction (an undocumented new live module, or a
documented-live module that stops being sourced). `audit` is the one live
module reached without an `${INSTALL_DIR}` source line from `actools.sh`: it is
invoked from `cli/actools` (the copied operator-CLI surface), which is why the union with
the entry-point grep, not the closure alone, is required.
| `audit` | `modules/` | LIVE | live path — `cli/actools:313,317` (operator-CLI surface, installed by copy) |
| `backup` | `modules/` | LIVE | live path — `actools.sh:516` (`modules/backup/cron.sh`) |
| `db` | `modules/` | LIVE | live path — `actools.sh:457` (`modules/db/core.sh`) |
| `drupal` | `modules/` | LIVE | live path — `actools.sh:181` (`modules/drupal/provision.sh`) |
| `host` | `modules/` | LIVE | live path — `actools.sh:193` loop over `modules/host/*.sh` |
| `stack` | `modules/` | LIVE | live path — `actools.sh:204` loop over `modules/stack/*.sh` |
| `ai` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `compliance` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `dr` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `network` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `observability` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `preview` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |
| `security` | `experimental/` | quarantined 4.5-seed | no live reference (C3 move) |

The LIVE rows are exactly the guard's `EXPECTED_LIVE_MODULES`
(`audit backup db drupal host stack`). The guard **derives** the actual set from
the tree — the source-closure of `actools.sh` (the `CLOSURE` engine from
`live_closure.bash`) **unioned** with `modules/<name>` references in `actools.sh`
/ `installer/` / `cli/actools` — and fails CI if it diverges in either
direction. A separate arm fails if any `experimental/…` path enters the live
closure. `audit` is the one live module reached without an `${INSTALL_DIR}`
source line from `actools.sh`: it is invoked from `cli/actools` (the copied
operator-CLI surface), which is why the union with the entry-point grep, not the
closure alone, is required.

## Update rule

Expand Down
8 changes: 4 additions & 4 deletions docs/enterprise.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Enterprise Hardening (planned / experimental)

> **Status: design reference — NOT operational today.** This document describes **planned** enterprise and disaster-recovery features. The standard installer does **not** deploy them, and the commands shown throughout — `actools immortalize`, `actools resurrect`, `actools gdpr …`, `actools migrate --point-in-time …`, `actools backup status` — are **not registered** in the `actools` CLI: running them returns *unknown command*. The supporting scripts exist under `modules/backup/`, `modules/dr/`, and `modules/compliance/` but are unwired and unvalidated against the current stack. **Do not rely on the runbooks below in a real incident.** See [`../ROADMAP.md`](../ROADMAP.md) for status.
> **Status: design reference — NOT operational today.** This document describes **planned** enterprise and disaster-recovery features. The standard installer does **not** deploy them, and the commands shown throughout — `actools immortalize`, `actools resurrect`, `actools gdpr …`, `actools migrate --point-in-time …`, `actools backup status` — are **not registered** in the `actools` CLI: running them returns *unknown command*. The supporting scripts exist under `modules/backup/`, `experimental/dr/`, and `experimental/compliance/` but are unwired and unvalidated against the current stack. **Do not rely on the runbooks below in a real incident.** See [`../ROADMAP.md`](../ROADMAP.md) for status.

The intent of this page: capture the target design for a future production-grade tier (~1 hour RPO, <15 minute RTO). Nothing here is part of the community installer today.

Expand Down Expand Up @@ -33,7 +33,7 @@ The PITR scripts exist in `modules/backup/` but are not invoked by the standard

## DNA Resurrection *(planned — not wired; do not run)*

The design: `immortalize` would capture a complete server blueprint into an age-encrypted JSON snapshot, and `resurrect` would replay it on a fresh server. **Neither is a registered command.** The `modules/dr/resurrect.sh` script is unvalidated and would install a separate `actools-real` binary — **do not run it on a real server.**
The design: `immortalize` would capture a complete server blueprint into an age-encrypted JSON snapshot, and `resurrect` would replay it on a fresh server. **Neither is a registered command.** The `experimental/dr/resurrect.sh` script is unvalidated and would install a separate `actools-real` binary — **do not run it on a real server.**

```bash
# (planned — not available)
Expand All @@ -60,7 +60,7 @@ Never commit these to git. Store in a password manager or encrypted vault.

## GDPR Compliance *(planned — not wired)*

`actools gdpr …` is **not** a registered command. The code lives in `modules/compliance/gdpr.sh` and is unvalidated against the current Drupal version.
`actools gdpr …` is **not** a registered command. The code lives in `experimental/compliance/gdpr.sh` and is unvalidated against the current Drupal version.

```bash
# (planned — not available)
Expand Down Expand Up @@ -89,7 +89,7 @@ actools <pitr-restore> "2026-03-26 13:45:00"
### Server is dead — rebuild from scratch *(planned)*

```bash
# (planned — modules/dr/resurrect.sh is unwired/unvalidated; do not run on a real server)
# (planned — experimental/dr/resurrect.sh is unwired/unvalidated; do not run on a real server)
```

### Handle a GDPR erasure request *(planned)*
Expand Down
6 changes: 3 additions & 3 deletions docs/hardening.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ sudo -u actools actools drush prod cache:rebuild

### Sudoers rules

Rules are in `modules/security/sudoers-roles`. Deploy with:
Rules are in `experimental/security/sudoers-roles`. Deploy with:

```bash
sudo cp modules/security/sudoers-roles /etc/sudoers.d/actools-roles
sudo cp experimental/security/sudoers-roles /etc/sudoers.d/actools-roles
sudo chmod 440 /etc/sudoers.d/actools-roles
sudo visudo -c -f /etc/sudoers.d/actools-roles # validate before use
```
Expand Down Expand Up @@ -214,7 +214,7 @@ When Cloudflare Tunnel is configured, ports 2–4 will be removed — the server
actools drush prod pm:security
```

Weekly automated scan via cron (installs via `modules/security/`):
Weekly automated scan via cron (installs via `experimental/security/`):

```bash
# /etc/cron.weekly/actools-security
Expand Down
4 changes: 2 additions & 2 deletions docs/observability.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ node_filesystem_size_bytes > 0.85

## Alerting

Prometheus alerting rules would live in `modules/observability/alerts.yml`. *(Note: this file is not present in the repo yet.)* To add an alert:
Prometheus alerting rules would live in `experimental/observability/alerts.yml`. *(Note: this file is not present in the repo yet.)* To add an alert:

```yaml
# modules/observability/alerts.yml
# experimental/observability/alerts.yml
groups:
- name: actools
rules:
Expand Down
2 changes: 1 addition & 1 deletion docs/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## AI Assistant *(experimental — not shipped)*

> The `actools ai` command is **not** registered in the CLI today; the code under `modules/ai/` is not wired in. The properties below describe the **intended** design for if/when it ships.
> The `actools ai` command is **not** registered in the CLI today; the code under `experimental/ai/` is not wired in. The properties below describe the **intended** design for if/when it ships.

The planned Actools AI assistant (`actools ai`) is designed to run entirely on your server.

Expand Down
Loading