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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- Generate native `wp-config.php` directly in the WordPress core parent (`public/` for a `public/wp/` layout), eliminating the default root configuration and forwarding loader. WP-CLI, section editing, setup drift and production diagnostics follow the same target.
- Recover literal salts from previous managed native root configurations without executing PHP; retain the root file for rollback. Customized sections, dynamic salt providers, location-dependent salt code and root symlinks require explicit migration. Literal salt edits, legacy profile layouts and alternate configuration targets remain supported.

## 1.2.4 — 2026-10-05

- Keep compiled environment caches and immutable dumps in OPcache on reads. Invalidate only a mutable cache actually rewritten by this process; unchanged writes do not waste shared memory.
Expand Down
1 change: 1 addition & 0 deletions docs/adr/0005-compatibility-and-differences.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The feature union stays available. Differences below are scoped behaviors with s
| ID | Source behavior / conflict | Proposed decision and preserved capability |
| --- | --- | --- |
| D01 | R/D Config validates lazily and often falls back after invalid input; malformed JSON is silently ignored. | Validate every run before writes, with source paths and option errors. Keep Result and extension validators; a compatibility diagnostic identifies formerly ignored invalid settings. |
| D31 | D and earlier native builds use a root configuration plus a core-parent proxy. | Native writes the main configuration directly in the core parent. Both legacy profile layouts and explicitly supplied targets retain their contracts. Stock previous native root files migrate without PHP execution or salt rotation; edited sections and dynamic salts require explicit migration. ADR 0029 and `WpConfigLocationTest` cover the three layouts, migration and refusal before writes. |
| D02 | R content-dev default is symlink, D is auto; null/empty collection defaults differ. | Legacy release profile keeps R defaults; native uses D/work-order defaults. Migration emits explicit values where omitting them would change behavior. |
| D03 | Documentation says opt-in wins, but R/D factory filters skip-config and custom steps first. CLI rejects list plus opt-in names. | Native implements requested opt-in precedence and list combinations; compatibility profile preserves upstream conflict behavior. Cover each combination, not just flag parsing. |
| D04 | VCS position differs between source declaration, effective factory order and the brief. | Native uses the work-order sequence; legacy D runs wpcliconfig then VCS then wpcli. Legacy R keeps its eleven-step automatic sequence and offers the additional VCS capability by explicit selection. See ADR 0001. |
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0006-runtime-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ Symfony Dotenv code carries its own MIT attribution and license. If any WP Start

Stable generation preserves managed salts, normalizes paths deliberately, retains section edits according to the tested regeneration policy and only writes changed bytes. Test two completely separate runner processes: an in-process cached Salter is not evidence of idempotence. Random generation is injected only in tests and never replaced with predictable production salts.

Native root wp-config plus webroot proxy follows D while compatibility can maintain R placement. Custom templates may opt out of the native bootstrap but must receive explicit diagnostics about guarantees they replace. Generated Site Health information uses a fixed safe field allowlist for paths, environment and cache metadata, never arbitrary environment values.
Native configuration placement is superseded by [ADR 0029](0029-native-core-parent-configuration.md): the main file lives in the WordPress core parent without a default proxy. The `upstream-dev` compatibility profile retains the D root/proxy layout. Custom templates may opt out of the native bootstrap but must receive explicit diagnostics about guarantees they replace. Generated Site Health information uses a fixed safe field allowlist for paths, environment and cache metadata, never arbitrary environment values.
2 changes: 1 addition & 1 deletion docs/adr/0010-managed-config-regeneration-and-dumps.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Status: implementation decision, 2026-09-29; full differential acceptance pendin

## Generated configuration

Native configuration lives at the project root and a webroot proxy points to it when needed. Release compatibility keeps the webroot location. Every target is checked before publication; `--force` can override file protection but cannot replace directories or symlinks. PHP syntax is checked for both main and proxy content before either is published. Files are replaced atomically with mode 0600, and identical content is not rewritten. Deployment must run PHP with read access to these private files.
Native configuration lives in the WordPress core parent without a default proxy, as specified in [ADR 0029](0029-native-core-parent-configuration.md). Release compatibility keeps that location; development compatibility and alternate targets retain their forwarding loader. Every target is checked before publication; `--force` can override file protection but cannot replace directories or symlinks. PHP syntax is checked for both main and any proxy content before either is published. Files are replaced atomically with mode 0600, and identical content is not rewritten. Deployment must run PHP with read access to these private files.

The main file retains all 19 section names from the source union. A comment stores hashes of the generated section bodies. On regeneration, a section whose current body differs from the previous generated hash is retained and reported. Untouched sections receive template/settings updates. This protects edits through the section API; arbitrary edits outside sections are not a supported persistence mechanism. A custom template receives a diagnostic that it owns the bootstrap guarantees it replaces, as required by ADR 0006.

Expand Down
48 changes: 48 additions & 0 deletions docs/adr/0029-native-core-parent-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ADR 0029: Native core-parent configuration

Status: implementation decision requested by the project owner on 2026-10-07.

## Decision

The native main configuration defaults to `Paths::wpParent('wp-config.php')`.
For Core in `public/wp`, this is `public/wp-config.php`. WordPress finds this
file through its standard parent-directory lookup; a fresh native project has
one generated configuration and no forwarding loader. The parser payload and
dotenv files remain outside the document root.

`release-3.0.1` retains its core-parent location. `upstream-dev` retains its root
configuration and proxy. Explicitly supplied invocation-context targets still
win and receive a proxy when the target differs from the core parent. WP-CLI
configuration, section editing, setup drift and production diagnostics resolve
the same profile-dependent target. This supersedes the native location in ADRs
0006 and 0010 and records the intentional difference D31.

## Existing projects

When the new target is absent or contains Runtime's old generated proxy, a
managed root configuration supplies the previous literal salts. Runtime reads
and tokenizes it without executing PHP, publishes the new main configuration
with rebased generated paths, and retains the original root file for rollback.
It never silently removes a user file. Operators archive the retained root file
after verifying the new configuration.

Edited managed sections, unmarked root files, symlinks and dynamic salt providers
require explicit migration before generation writes a replacement. An edited
section may depend on `__DIR__`; preserving its bytes while changing its file
location would change behavior. Back up the old files, move the main file to
the core parent, review relative paths, and regenerate its managed sections.
Ordinary regeneration at a fixed location continues to preserve section edits
and dynamic salt sections. Literal salt edits remain safe to migrate, while
`__DIR__` or `__FILE__` in the retained salt section requires explicit migration.
Both valid and dangling root configuration symlinks are refused before writes.
Server configuration denies direct HTTP access to
the generated configuration; default file permissions remain 0600.

## Verification

`WpConfigLocationTest` checks native, release and development layouts,
managed-root salt preservation without PHP execution, and refusal before
replacing edited configurations. `WpConfigTest` checks ordinary boot,
separate-process idempotence and section edits.
The WP-CLI target, production checks, real WordPress bootstrap and payload
rollback tests cover the consumers of the generated configuration.
2 changes: 2 additions & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Architecture decisions

- [0029: Native core-parent configuration](0029-native-core-parent-configuration.md)

- [0021: Native MU-plugin loading](0021-native-mu-plugin-loading.md)
- [0022: Tagged releases and Composer recovery compatibility](0022-release-and-composer-recovery-policy.md)
- [0023: Private environment writes and production payloads](0023-env-production.md)
Expand Down
8 changes: 8 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ The command reads inline `extra.wpstarter`, referenced JSON and root `wpstarter.

## Choose a behavior profile

Native configuration now lives directly in the WordPress core parent, normally
`public/wp-config.php`, without a root file or forwarding loader. Existing stock
native root configurations migrate with their literal salts preserved; Runtime
retains the original root file. Customized sections and dynamic salt providers
require the explicit migration described in [Generated wp-config](wp-config.md).
The `release-3.0.1` core-parent layout and `upstream-dev` root/proxy layout retain
their compatibility contracts.

Throughout Runtime 1.x, outbound WordPress HTTP blocking is explicit opt-in:
set `WP_HTTP_BLOCK_EXTERNAL=true` and review `WP_ACCESSIBLE_HOSTS` only when the
project requires that restriction. Production/staging and `composer-managed`
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,5 @@ Steps can read `composer-updated-packages`, `is-composer-install`,
`is-composer-update`, `is-runtime-command`, `is-runtime-selected-command`
and the derived `wp-config-php-path`. These are invocation context, not writable
project settings. Older aliases are listed in [Compatibility](compatibility.md).
The native configuration target is the WordPress core parent, such as
`public/wp-config.php`; `upstream-dev` retains its root target and forwarding loader.
2 changes: 1 addition & 1 deletion docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Deploy these artifacts together with the reviewed lockfile/application release:

| Artifact | Requirement |
| --- | --- |
| Generated `wp-config.php` and core-parent proxy | Match the selected profile, path layout and payload |
| Generated core-parent `wp-config.php` | Match the selected profile, path layout and payload; a proxy is only needed for alternate configuration targets |
| `var/runtime/<fingerprint>/` | Include every payload referenced by an active or rollback configuration |
| Generated `index.php`, MU loader and optional kernel bootstrap | Keep consistent with installed core/content packages |
| `.env.dump.php` when using build dumps | Private per-environment input; PHP-readable; contains secrets |
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ my-site/
├── composer.json
├── composer.lock
├── .env # private, created in the next step
├── wp-config.php # generated
├── wp-cli.yml # generated
├── vendor/
├── var/runtime/ # required generated PHP payload
└── public/ # web document root
├── index.php # generated
├── wp-config.php # generated main configuration; no forwarding loader
├── wp/ # Composer-managed WordPress core
└── wp-content/
├── mu-plugins/
Expand Down
8 changes: 4 additions & 4 deletions docs/maintainers/parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The package name is `sympress/runtime`. Test IDs below are PHPUnit groups or nam
| is | R/D Config/Result::is | Strict comparison; none is(null) true, errored is(null) false. | Config/Result | `PAR-RES-004` | verified | Parity |
| not | R/D Config/Result::not | Negation of is including error case. | Config/Result | `PAR-RES-005` | verified | Parity |
| either | R/D Config/Result::either | Strict membership across variadic alternatives; errors false. | Config/Result | `PAR-RES-006` | verified | Parity |
| Derived config path | D Requirements::determineWpConfigPath; R WpConfigStep | wp-config-php-path is computed, not a documented user default: root in D unless an existing webroot config contains WordPressEnvBridge; R writes webroot. | Config/RunContext | `PAR-CFG-012` | verified | D13 |
| Derived config path | D Requirements::determineWpConfigPath; R WpConfigStep | wp-config-php-path is computed, not a documented user default: native and R write the WordPress parent; D retains its root configuration and parent proxy. Explicit invocation context still overrides the target. | Config/RunContext; WpConfigLocationTest | `PAR-CFG-012` | verified | D13, D31 |

## Every configuration option

Expand Down Expand Up @@ -112,7 +112,7 @@ Native default order is the twelve-step sequence in the work order. Actual upstr
| Feature | Upstream reference | Behavior and edge cases | Implementation responsibility | Test ID | Status | Difference |
| --- | --- | --- | --- | --- | --- | --- |
| checkpaths | R/D Step/CheckPathStep | Blocking; create content and missing themes/plugins when appropriate; realpath core wp-settings and vendor autoload; warn env in webroot. | Steps/CheckPathsStep | `PAR-STEP-001` | verified | Parity |
| wpconfig | R/D Step/WpConfigStep | Blocking file-creation; relative paths, all salts/placeholders, root or legacy webroot target and proxy; every generated target guarded. | Steps/WpConfigStep | `PAR-STEP-002` | verified | D07, D13, D20 |
| wpconfig | R/D Step/WpConfigStep | Blocking file-creation; relative paths, all salts/placeholders; native writes one WordPress-parent configuration, D retains the root target and proxy. Managed native root configurations migrate without executing PHP or rotating salts; edited sections require explicit migration. Every generated target is guarded. | Steps/WpConfigStep; WpConfigLocationTest | `PAR-STEP-002` | verified | D07, D13, D20, D31 |
| index | R/D Step/IndexStep | Blocking file-creation; webroot index requires core index via shortest relative path, including same-root installation. | Steps/IndexStep | `PAR-STEP-003` | verified | Parity |
| flushenvcache | R/D Step/FlushEnvCacheStep | Only when configured env-dir/.env.cached.php exists; remove cache, return failure on removal error; absent file skips. | Steps/FlushEnvCacheStep | `PAR-STEP-004` | verified | Parity |
| muloader | R/D Step/MuLoaderStep; Util/MuPluginList | Only when MU entries exist; relative plugin list in generated loader under mu-plugins; overwrites guarded. | Steps/MuLoaderStep | `PAR-STEP-005` | verified | Parity |
Expand Down Expand Up @@ -191,11 +191,11 @@ Native default order is the twelve-step sequence in the work order. Actual upstr
| Salts across reruns | W idempotence; R/D WpConfigStep | Separate processes preserve managed fallback salts and user-provided secrets; missing keys alone generated; no force-triggered rotation. | Generation/SaltStore | `PAR-WP-014` | verified | D07 |
| Health info | R/D template DEBUG_INFO | debug_information priority 30 adds dedicated info section; allowlist paths/env/cache/bootstrap/SSL/theme flags only; no DB credentials, salt or arbitrary values. | WordPress/HealthInfo | `PAR-WP-015` | verified | Parity |
| Cleanup and bootstrap | R/D template CLEAN_UP, final require | Unset temporary bridge/debug variables; retain global table_prefix; require wp-settings once after sections. | Generation/WpConfig | `PAR-WP-016` | verified | Parity |
| D root proxy and CLI | D wp-config-loader.php; WP_CLI_HACK; wp-cli.yml | Proxy loads root configuration; WP-CLI parses config differently; WP_STARTER_WP_CONFIG_PATH guard prevents duplicate settings bootstrap; no copied lyric/comment hacks. | Generation/WpConfigProxy | `PAR-WP-017` | verified | D13 |
| D root proxy and CLI | D wp-config-loader.php; WP_CLI_HACK; wp-cli.yml | D retains a proxy loading the root configuration; native needs no proxy. WP-CLI parses config differently; WP_STARTER_WP_CONFIG_PATH guard prevents duplicate settings bootstrap for proxy layouts; no copied lyric/comment hacks. | Generation/WpConfigProxy | `PAR-WP-017` | verified | D13, D31 |
| Section extraction | R/D WpConfigSectionEditor::sectionContent | NAME : { ... } #@@/NAME with flexible whitespace; query uppercases/trims name, returns trimmed content lines; missing returns empty. | Generation/WpConfigSectionEditor | `PAR-SEC-001` | verified | Parity |
| Append and prepend | R/D WpConfigSectionEditor::append/prepend | Insert inside markers, preserve other sections; marker digest includes whitespace-stripped content + caller file/line; repeat same call no duplicate. | Generation/WpConfigSectionEditor | `PAR-SEC-002` | verified | D15 |
| Replace and delete | R/D WpConfigSectionEditor::replace/delete | Replace only content, preserve labels/closing markers; delete leaves empty section; escaped dollars/backslashes literal; nonexistent section no-op. | Generation/WpConfigSectionEditor | `PAR-SEC-003` | verified | D15 |
| Section error paths | R/D WpConfigSectionEditor | Missing/empty config and write failures throw; root versus legacy webroot resolved consistently; custom template section availability diagnosed. | Generation/WpConfigSectionEditor | `PAR-SEC-004` | verified | D15 |
| Section error paths | R/D WpConfigSectionEditor | Missing/empty config and write failures throw; native WordPress-parent and legacy targets resolved consistently; custom template section availability diagnosed. | Generation/WpConfigSectionEditor | `PAR-SEC-004` | verified | D15, D31 |

## Database and WP-CLI

Expand Down
21 changes: 19 additions & 2 deletions docs/wp-config.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
# Generated wp-config and sections

The `wpconfig` step generates `wp-config.php` at the project root. Run it with:
The native `wpconfig` step generates the main `wp-config.php` in the WordPress
core parent: `public/wp-config.php` when Core is installed in `public/wp/`.
This is the file WordPress discovers directly; native projects need no root
configuration or forwarding loader. Run it with:

```sh
vendor/bin/runtime --no-interaction wpconfig
```

Older compatibility profiles can use a different location; see [Compatibility](compatibility.md). A proxy in the core parent is generated when required. Both files are parsed before publication. Native unmarked files require overwrite approval; protected paths, symlinks and directories retain their documented guards. Existing salts and edited managed sections are preserved during regeneration.
The `release-3.0.1` profile also uses the core parent. `upstream-dev` keeps its
root configuration and core-parent proxy; an explicitly supplied alternate
target still receives a proxy when needed. See [Compatibility](compatibility.md).
Every generated file is parsed before publication. Native unmarked files require
overwrite approval; protected paths, symlinks and directories retain their guards.
Existing salts and edited managed sections are preserved during regeneration.

When upgrading an older native root layout, Runtime replaces its generated proxy
with the main configuration and recovers literal salts without executing the old
PHP. It retains the old root file for rollback; archive it after verifying the
new configuration. A customized root configuration or dynamic salt provider
requires an explicit migration: back up both files, move the main configuration
to the core parent, review paths relative to `__DIR__`, and regenerate the managed
sections. Keep `.env` and `var/runtime/` outside the public document root. Deny
direct HTTP requests to `wp-config.php` in the web-server configuration.

## What gets loaded

Expand Down
Loading
Loading