Phase 5: scaffold Crucible (sow-tools) — dispatcher + builder shims, container, PR-first CI, fail-closed (D11, D19).

This commit is contained in:
2026-06-11 20:36:13 +02:00
parent ff264f0d7c
commit 97f8427c4b
154 changed files with 948 additions and 76902 deletions
@@ -1,153 +0,0 @@
# Generated Wiki Stale Purge Design
## Goal
Add an explicit bulk purge path for generated NodeBB wiki pages that have become
stale after generator output changes, such as topdata visibility rules hiding
previously generated pages.
## Context
The topdata wiki deployer already owns generated page state through
`.cache/wiki/.wiki_deploy_manifest.json`. It currently recognizes stale
generated pages that remain in the deploy manifest but no longer exist in the
current generated wiki output. Existing stale policies can either report those
pages or archive them by rewriting their managed content.
That is not sufficient when the stale generated wiki topics should be removed
from NodeBB entirely. The Westgate Wiki plugin already treats a normal wiki topic
delete as an irreversible purge, so the deployer should use NodeBB's explicit
topic purge API when the operator asks for destructive generated-page cleanup.
## Scope
### In Scope
- Add `purge` as an explicit stale policy for topdata wiki deployment.
- Plan purge candidates only from generated page entries already tracked in the
wiki deploy manifest.
- Purge the exact tracked NodeBB topic for each stale generated page.
- Report purge counts in dry-run and live deploy output.
- Remove successfully purged page entries from the deploy manifest so later
deploys do not repeatedly target removed topics.
- Allow release automation to opt into purge through the existing stale-policy
environment variable path.
- Update toolkit and module documentation for the destructive cleanup mode.
### Out Of Scope
- Purging manually-authored NodeBB wiki pages.
- Discovering purge candidates by namespace browsing, title, slug, or wiki
search.
- Adding a soft-delete or restore workflow for stale generated wiki pages.
- Changing the Westgate Wiki plugin delete behavior.
- Adding a separate stale cleanup command outside `deploy-wiki`.
## Operator Interface
The existing `deploy-wiki` stale-policy surface gains a third explicit policy:
```bash
./deploy-wiki.sh --dry-run --stale-policy purge
./deploy-wiki.sh --stale-policy purge
```
Release automation may use the same policy through:
```bash
SOW_MODULE_WIKI_DEPLOY_STALE_POLICY=purge
```
`purge` remains opt-in. Existing `report` and `archive` behavior remain
unchanged.
## Purge Eligibility
A page may be purged only when all of these are true:
1. The page exists in `.cache/wiki/.wiki_deploy_manifest.json`.
2. The page is absent from the current generated wiki page set in the deploy
scope.
3. The deploy manifest entry has the tracked NodeBB topic id required to call
the topic purge API.
The deployer must not search NodeBB for candidate pages when purging stale
generated output. It must not purge pages that are currently generated. It must
not purge pages that only exist as manually-authored NodeBB wiki content.
If a stale manifest entry has no topic id, purge planning fails clearly instead
of guessing a remote target.
## Deploy Flow
Stale planning stays inside the existing `deploy-wiki` deploy plan:
- `report` increments the stale count only.
- `archive` produces the existing archive post-update action.
- `purge` produces a destructive topic-purge action for the manifest entry's
tracked topic id.
Dry runs calculate the same stale and purge counts as live runs but do not call
NodeBB and do not mutate the deploy manifest.
Live purge execution calls NodeBB's topic purge API for each planned stale
generated page. NodeBB auth, permission, missing-topic, or transport failures
abort the deploy with the failing generated page id and remote topic context.
## Manifest State
Archive retains stale entries in the manifest because the archived post remains
managed remotely.
Purge removes each successfully purged page entry from the next deploy manifest.
This records that the generated page no longer has a managed remote NodeBB topic
and prevents later deploys from attempting to purge an already removed topic.
If a purge deployment fails partway through, only state written after the
existing deploy execution succeeds should reach the manifest. A later rerun may
retry the stale manifest entries still present, matching the deployer's current
all-or-error manifest persistence behavior.
## Reporting And Validation
Toolkit deploy output gains a `purged` count alongside the existing `stale` and
`archived` counts. Dry-run summaries must make the destructive plan visible
before a live purge run.
`purge` must be accepted wherever stale policy is validated:
- project config stale-page settings
- `deploy-wiki --stale-policy`
- module release wrapper validation for
`SOW_MODULE_WIKI_DEPLOY_STALE_POLICY`
Unsupported stale policy values must keep failing early with clear error text.
## Testing
Toolkit tests should cover:
- stale policy validation accepts `purge`
- unsupported stale policy values still fail
- dry-run purge reports stale and purged counts without calling NodeBB or
changing the manifest
- live purge calls the NodeBB topic purge endpoint for a stale tracked generated
topic
- live purge removes the manifest entry after success
- purge refuses a stale manifest entry without a tracked topic id
- current generated pages are never purge candidates
- archive/report behavior remains unchanged
Module wrapper/docs coverage should verify:
- release stale-policy validation accepts `purge`
- docs describe purge as destructive, generated-manifest-scoped cleanup
## Compatibility
Default behavior remains `report`. Existing archive cleanup remains available and
unchanged. The destructive purge path activates only when the operator selects
`purge`.
The purge boundary is intentionally stricter than normal page adoption logic:
generated manifest state authorizes the target; NodeBB discovery never does.
@@ -1,105 +0,0 @@
# Class Progression Feat Availability Design
## Goal
Class progression wiki providers should let module-owned wiki configuration
distinguish automatically granted class feats from level-gated selectable feats
while also exposing bonus feat gains declared by class `bfeat` tables in the
progression table.
## Ownership
The authored wiki page layout and progression projection policy stay in
`sow-module/topdata/wiki/`. The toolkit owns generic progression row data
assembly from configured class feat and bonus-feat sources. The change should
stay inside those boundaries:
- module wiki YAML decides which class feat rows project into progression
fields;
- module page templates decide how progression row fields are rendered;
- toolkit progression row assembly groups configured projections by level
without hardcoding the module's class feat list semantics or note wording.
## Behavior
Class feat rows with positive `GrantedOnLevel` values are not all equivalent.
This module uses rows in class feat tables with `List` value `3` as
automatically granted feats and positive-level selectable rows as feats that
become available at the declared level. That distinction is repository policy
and must be declared in wiki YAML rather than hidden in toolkit code.
The `ClassProgression` data provider should support configured class feat
projection fields. This module can declare a shape such as:
```yaml
providers:
ClassProgression:
source: class_progression
levels: 1-12
class_feats:
fields:
GrantedFeats:
when: "{{GrantedOnLevel > 0 && List == 3}}"
AvailableFeats:
when: "{{GrantedOnLevel > 0 && List != 3}}"
```
For each progression row, the toolkit should:
- continue exposing the per-level bonus feat count from the class `bfeat` table
as `BonusFeat`;
- expose each configured class feat field as a deterministic list of resolved
wiki feat links for class feat rows whose configured expression matches the
progression level;
- leave non-matching class feat rows out of that configured field.
The default `class_progression` provider behavior should preserve the current
`GrantedFeats` projection when no custom field configuration is declared.
## Rendering
The class progression template can render `BonusFeat` inline in its existing
Feats cell rather than adding another progression column. It should emit the
separator only when `GrantedFeats` already rendered content so bonus-only rows
do not start with a comma.
The existing Notes column should render `AvailableFeats` in the module template
with module-owned wording such as `<feat link> becomes available.`. The lower
class feat table remains useful for listing class feat membership and list
metadata; its level label should not be interpreted as an automatic grant for
selectable rows.
## Data Flow
The toolkit resolves the class `FeatsTable` while building progression rows.
Each configured class feat field evaluates its `when` expression against class
feat rows, groups matched rows by positive `GrantedOnLevel`, resolves feat
references to wiki link text, and writes the grouped list onto the progression
row under the configured field name. Output order follows source table row
order so repeated builds are deterministic.
The class `BonusFeatsTable` lookup continues to populate `BonusFeat` from the
row whose id matches the progression level.
## Errors And Compatibility
The change should preserve current behavior for missing class feat tables,
missing bonus feat tables, unresolved feat references, and classes without
custom class feat projections. Invalid projection configuration should fail
early with the provider file path and field name. Existing module templates
that only read `GrantedFeats`, `BonusFeat`, and `Notes` continue to use the same
progression row fields.
## Tests
Toolkit coverage should verify:
- default progression behavior still exposes `GrantedFeats`;
- configured class feat fields filter by expression and group by level;
- configured output order follows source table row order;
- invalid configured class feat fields fail clearly;
- bonus feat counts remain available to the progression template.
Module validation should rebuild the generated wiki output with the local
toolkit binary after the toolkit change and inspect a class that has both
granted feats and level-gated selectable feats, such as fighter.