Phase 5: scaffold Crucible (sow-tools) — dispatcher + builder shims, container, PR-first CI, fail-closed (D11, D19).
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# Crucible command surface
|
||||
|
||||
Crucible re-homes the single `nwn-tool` (a.k.a. `sow-toolkit`) command surface
|
||||
into five builders plus a dispatcher (D11). This table maps every legacy command
|
||||
to its Crucible home. It is the migration contract — keep it in sync with
|
||||
`internal/dispatch.Registry`.
|
||||
|
||||
## Builders
|
||||
|
||||
| Builder | Legacy `nwn-tool` commands subsumed |
|
||||
| ------------------ | ------------------------------------------------------------------------------------- |
|
||||
| `crucible-module` | `build`, `build-module`, `extract`, `validate`, `compare`, `apply-hak-manifest`† |
|
||||
| `crucible-topdata` | `validate-topdata`, `build-topdata`, `build-top-package`, `compare-topdata`, `convert-topdata` |
|
||||
| `crucible-hak` | `build-haks`, `apply-hak-manifest` |
|
||||
| `crucible-wiki` | `build-wiki`, `deploy-wiki` |
|
||||
| `crucible-depot` | (new) depot blob verify/move — was shell `mc`/S3 in `sow-assets-manifest` |
|
||||
|
||||
† `apply-hak-manifest` is HAK-list maintenance on a module; it is reachable from
|
||||
both `crucible-module` and `crucible-hak`. Canonical home is `crucible-hak`.
|
||||
|
||||
## Folded / global commands
|
||||
|
||||
These legacy commands are **not** top-level builders:
|
||||
|
||||
- `music *` (`list-datasets`, `scan`, `build`, `credits`, `validate`, `manifest`,
|
||||
`normalize`) → folds into the **module/hak build pipeline** (music BMUs are
|
||||
packed into HAKs at build time). Exposed as `crucible-module music ...` /
|
||||
`crucible-hak music ...` when wired. Needs `ffmpeg` at runtime.
|
||||
- `config *` (`validate`, `effective`, `inspect`, `explain`, `sources`) → a
|
||||
**global** concern shared by every builder; exposed as a global subcommand
|
||||
group, not its own binary.
|
||||
- `build-changelog` → release tooling; lands as a global `crucible changelog`
|
||||
rather than a content builder.
|
||||
|
||||
## Global commands (implemented in the scaffold)
|
||||
|
||||
```text
|
||||
crucible help | -h usage + builder list
|
||||
crucible version | -V build version (git sha via -ldflags)
|
||||
crucible list builders, one per line: name<TAB>bin<TAB>summary
|
||||
```
|
||||
|
||||
`crucible list` is machine-readable so CI can enumerate builders without parsing
|
||||
help text.
|
||||
|
||||
## Global flags (planned, at wiring time)
|
||||
|
||||
`--quiet`, `--verbose`, `--debug` (the legacy verbosity model), passed after the
|
||||
builder name, e.g. `crucible topdata build-topdata --verbose`.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Crucible consumer contract
|
||||
|
||||
How the artifact repos (`sow-module`, `sow-topdata`, `sow-assets-manifest`)
|
||||
locate and invoke a Crucible builder. The goal: **no local-machine assumptions**
|
||||
(Phase 5 exit criterion). A consumer either finds a Crucible binary or fails
|
||||
closed — it never fakes an artifact.
|
||||
|
||||
## Resolution order (single-token command)
|
||||
|
||||
Each consumer wrapper resolves its builder to one executable token, in order:
|
||||
|
||||
1. **Explicit override** — `$SOW_MODULE_BUILD` / `$SOW_TOPDATA_BUILD` /
|
||||
`$CRUCIBLE` (a path or name), if set and executable. Back-compat with the
|
||||
pre-Crucible skeletons.
|
||||
2. **Standalone Crucible binary** — `crucible-module` / `crucible-topdata` /
|
||||
`crucible-hak` / `crucible-depot` on `PATH`.
|
||||
3. **Legacy name** — `sow-module-build` / `sow-topdata-build` on `PATH`
|
||||
(pre-D11; kept so nothing breaks during cutover).
|
||||
|
||||
If none resolve, the wrapper exits non-zero with a Phase 5 message. The
|
||||
`crucible` dispatcher (`crucible module …`) is for humans and CI introspection;
|
||||
wrappers prefer the single-token `crucible-<name>` shim so `"$builder" args`
|
||||
quoting stays correct.
|
||||
|
||||
## In CI (preferred)
|
||||
|
||||
Run the consumer job inside the pinned image and the binaries are on `PATH`:
|
||||
|
||||
```yaml
|
||||
container:
|
||||
image: registry.westgate.pw/sow/crucible:<sha> # pinned, immutable
|
||||
```
|
||||
|
||||
No host install, no `$HOME` layout, no developer machine assumptions.
|
||||
|
||||
## `NWN_ROOT` rule
|
||||
|
||||
Crucible **never guesses `NWN_ROOT` from `$HOME`** (this was a deploy-notes
|
||||
pain point). The NWN install/data root is passed explicitly:
|
||||
|
||||
- env `NWN_ROOT=/path/to/nwn`, or
|
||||
- flag `--nwn-root /path/to/nwn`.
|
||||
|
||||
A builder that needs `NWN_ROOT` and receives neither must fail closed with a
|
||||
clear message, not fall back to a home-directory default.
|
||||
|
||||
## Determinism
|
||||
|
||||
Same inputs → identical output bytes. Consumers may checksum artifacts across
|
||||
runs; non-determinism is a builder bug.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Migrating from `nwn-tool` to Crucible (operator cutover)
|
||||
|
||||
The legacy toolchain lives at `gitea/sow-tools` as a single `nwn-tool` binary
|
||||
(entry `cmd/nwn-tool`, logic under `internal/`). This repo is the Crucible
|
||||
rewrite. Per the migration hard rules, the `internal/` source is **not**
|
||||
transplanted automatically — the operator migrates it. This is the checklist.
|
||||
|
||||
## Why a clean scaffold first
|
||||
|
||||
- The hard rules forbid copying old-repo source into the migration tree
|
||||
(`build from scratch; reference, don't transplant`).
|
||||
- The downstream skeletons (`sow-module`, `sow-topdata`) already fail closed
|
||||
until they can resolve a Crucible binary; the scaffold satisfies that contract
|
||||
without faking artifacts.
|
||||
- The scaffold pins the **shape** (module path, dispatcher, multi-binary layout,
|
||||
container, CI) so the migrated logic drops into a stable frame.
|
||||
|
||||
## Cutover steps
|
||||
|
||||
1. **Move the internal packages.** Bring `internal/{app,pipeline,project,erf,
|
||||
gff,topdata,music,changelog,validator}` from `gitea/sow-tools` into this repo.
|
||||
Re-home `internal/app` command wiring behind `internal/dispatch` instead of
|
||||
the old flag parser in `cmd/nwn-tool`.
|
||||
2. **Restore dependencies.** Add `golang.org/x/text` and `gopkg.in/yaml.v3` back
|
||||
to `go.mod`; commit the resulting `go.sum`. (`docker/Dockerfile` already
|
||||
tolerates an absent `go.sum` for the scaffold; it will be present after this.)
|
||||
3. **Wire each builder.** In `internal/dispatch`, register a handler per builder
|
||||
and remove it from the unwired fail-closed path. Map the legacy commands per
|
||||
[`command-surface.md`](command-surface.md).
|
||||
4. **Kill the home-dir / `NWN_ROOT` defaulting.** Honor only explicit
|
||||
env/flag roots per [`consumer-contract.md`](consumer-contract.md).
|
||||
5. **Drop the committed binary.** Delete `nwn-tool` / `tools/sow-toolkit` from
|
||||
the source tree; `.gitignore` already excludes them. Binaries become CI
|
||||
artifacts and image layers only.
|
||||
6. **Carry the determinism test.** Port
|
||||
`TestBuildNativeKeepsGeneratedIDsStableAcrossRepeatedBuilds` and keep it green.
|
||||
7. **Runtime base for music.** If music conversion is wired, switch the final
|
||||
Docker stage from `distroless/static` to a debian-slim base that includes
|
||||
`ffmpeg`.
|
||||
8. **Update `make smoke`.** Once a builder is wired, change the smoke
|
||||
expectation for it from exit `70` to a real success path.
|
||||
|
||||
## Wrapper contract carried over
|
||||
|
||||
Consumer repos keep the `SCRIPT_WRAPPER_CONTRACT` resolution behavior, now
|
||||
pointed at Crucible binaries — see [`consumer-contract.md`](consumer-contract.md).
|
||||
The old `SOW_TOOLS_DEV_BINARY` dev override maps to `$CRUCIBLE` /
|
||||
`$SOW_MODULE_BUILD` / `$SOW_TOPDATA_BUILD`.
|
||||
Reference in New Issue
Block a user