Call them compositions; single-source WELL_TIERS

The data layer has said compositions since the catalog was split, while the
code, the model-facing text, and the UI still said stagings. The rename was
held back by the selection logic existing twice; it exists once now, so this
is one pass instead of two coordinated ones.

Renamed: selectApprovedStagings, selectApprovedStaging, renderStaging, and
the model-facing STAGING GRAMMAR / STAGING CHALLENGERS / FIRST-SURFACE
STAGING INPUTS headings. The block that introduces them now states what
they are for rather than only what they are not: what is the cleverest way
to present, organize, or make interactive the problem in front of you.

Three places keep the old word on purpose:

- The rank salt, `${scope}:${key}:staging`. It is hash input, so renaming
  it would re-deal every roll anyone has ever reproduced by key. Verified:
  240 seeder rolls and 252 API rolls reproduce exactly.
- `Staging/hierarchy:`, the first composition grammar prefix. Inside a
  composition, staging names one of its four aspects, which is a different
  word-sense from staging as the name for the whole artifact. It is also a
  schema constant that 317 catalog entries are validated against.
- The wire fields. The API keeps emitting `stagings` and `staging` beside
  `compositions`, because the wire is the one place a rename cannot be
  coordinated with already-installed skills. Clients prefer the new field
  and fall back through both old ones.

Separately, WELL_TIERS had two definitions after the extraction.
roll-selection.mjs owns it now and concept-catalog.mjs imports it, in that
direction because concept-catalog reads the filesystem and a Pages Function
must not pull node:fs into its bundle. Imported and re-exported rather than
re-exported alone: a bare `export { X } from` does not bind X locally, and
validateConceptCatalog needs it, which cost one round of red tests.

Dropped concept-catalog's synchronous deterministicRank. Nothing imports it
since selection moved out, and leaving a second ranking implementation
around is how the first drift started.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-29 16:26:46 -07:00
co-authored by Claude Opus 5
parent 71d7b5e310
commit a92ba5f2b0
5 changed files with 74 additions and 62 deletions
+32 -22
View File
@@ -29,7 +29,7 @@
* win over thin categories, which is the intended shape.
* - RE-ROLL (--reroll <n>): round n of the same base key. The script
* recomputes what rounds 0..n-1 drew, excludes all of it, and rolls a
* fresh assigned index, challengers, and staging. One base key therefore
* fresh assigned index, challengers, and compositions. One base key therefore
* reproduces the entire chain of rounds.
* - RATINGS: the reviewer's approval ratings weight the challenger draw
* (3-star doubles the odds, 1-star sits out); the approved pool itself
@@ -43,8 +43,8 @@
* node scripts/concept-seed.mjs --chosen <challenger-id> --from <key> --scope direction
*
* --mode names the requested surface's mode (persuade, operate, read,
* experience) so the appended staging matches its register of work; omitted,
* the staging rolls from the full approved pool.
* experience) so the appended compositions match its register of work; omitted,
* they roll from the full approved pool.
*
* Challenger data resolves in order: a local catalog directory (the private
* service repo, evals, and tests set IMPECCABLE_CATALOG_DIR), then the roll
@@ -72,7 +72,7 @@ import { readCompositionCatalog } from './lib/composition-catalog.mjs';
import {
runSyncSelection,
selectApprovedChallengers as selectApprovedChallengersCore,
selectApprovedStagings as selectApprovedStagingsCore,
selectApprovedCompositions as selectApprovedCompositionsCore,
} from './lib/roll-selection.mjs';
const here = dirname(fileURLToPath(import.meta.url));
@@ -191,12 +191,12 @@ ${system}
QUALITY BAR: board ${board} · hero ${hero}`;
}
export function renderStaging(composition, index = null) {
export function renderComposition(composition, index = null) {
const grammar = composition.grammar.map(rule => ` - ${rule}`).join('\n');
return ` ${index == null ? '' : `${index + 1}. `}${composition.form}
SOURCE ID: ${composition.id}
SPARK: ${composition.spark}
STAGING GRAMMAR:
COMPOSITION GRAMMAR:
${grammar}
WEB LEVERAGE: ${composition.webLeverage}`;
}
@@ -210,14 +210,14 @@ function driveSelection(generator) {
return runSyncSelection(generator, input => crypto.createHash('sha256').update(input).digest('hex'));
}
export function selectApprovedStagings({ scope, key, reroll = 0, mode = null, sourceCompositions = null, count = 3 }) {
export function selectApprovedCompositions({ scope, key, reroll = 0, mode = null, sourceCompositions = null, count = 3 }) {
const compositions = sourceCompositions ?? requireLocalConcepts().compositions;
return driveSelection(selectApprovedStagingsCore({ scope, key, reroll, mode, compositions, count }));
return driveSelection(selectApprovedCompositionsCore({ scope, key, reroll, mode, compositions, count }));
}
// Compatibility for callers that need a single smoke-test sample.
export function selectApprovedStaging(options) {
return selectApprovedStagings({ ...options, count: 1 })[0] ?? null;
export function selectApprovedComposition(options) {
return selectApprovedCompositions({ ...options, count: 1 })[0] ?? null;
}
export function selectApprovedChallengers({ scope, key, reroll = 0, sourceConcepts = null }) {
@@ -280,7 +280,7 @@ export function renderConceptSeed({
approvedCount: approved.length,
catalogCount,
challengers: picks,
stagings: selectApprovedStagings({ scope, key, reroll, mode, sourceCompositions: local.compositions }),
compositions: selectApprovedCompositions({ scope, key, reroll, mode, sourceCompositions: local.compositions }),
};
} else {
// Keep local renders synchronous for prepared eval sessions and tests;
@@ -298,7 +298,11 @@ export function renderConceptSeed({
approvedCount: roll.approvedCount,
catalogCount: roll.catalogCount,
challengers: roll.challengers,
stagings: Array.isArray(roll.stagings) ? roll.stagings : roll.staging ? [roll.staging] : [],
compositions: Array.isArray(roll.compositions)
? roll.compositions
: Array.isArray(roll.stagings)
? roll.stagings
: roll.staging ? [roll.staging] : [],
} : null,
}));
}
@@ -381,15 +385,21 @@ ${buildIndex} of your own grounded list; seed key ${key}.
`;
}
const stagings = Array.isArray(data.stagings)
? data.stagings
: data.staging ? [data.staging] : [];
const stagingBlock = stagings.length > 0
? `\n${scope === 'direction' ? 'FIRST-SURFACE STAGING INPUTS (identity-free; test them with shortlisted worlds and keep world plus staging one decision):' : 'STAGING CHALLENGERS (identity-free; dress them in the committed visual identity before judging):'}
${stagings.map((staging, index) => renderStaging(staging, index)).join('\n')}
Stagings organize attention, sequence, and manipulation; they never bring a
palette, typeface, or material. Use them as serious alternatives to the model's
habitual composition, but keep only structures that strengthen this product.\n`
// Field order is the migration: `compositions` is current, `stagings` is what
// the API emitted while these were called stagings, and `staging` is the
// single-pick shape from before it dealt three. Older installs keep working.
const compositions = Array.isArray(data.compositions)
? data.compositions
: Array.isArray(data.stagings)
? data.stagings
: data.staging ? [data.staging] : [];
const compositionBlock = compositions.length > 0
? `\n${scope === 'direction' ? 'FIRST-SURFACE COMPOSITION INPUTS (identity-free; test them with shortlisted worlds and keep world plus composition one decision):' : 'COMPOSITION CHALLENGERS (identity-free; dress them in the committed visual identity before judging):'}
${compositions.map((composition, index) => renderComposition(composition, index)).join('\n')}
Each one asks the same question of this build: what is the cleverest way to
present, organize, or make interactive the problem in front of you? They carry
structure only, never a palette, typeface, or material. Treat them as serious
rivals to your habitual layout, and keep only what makes this product clearer.\n`
: '';
const rerollBlock = reroll > 0
? `RE-ROLL ROUND ${reroll}: every candidate presented in earlier rounds, grounded
@@ -410,7 +420,7 @@ ${rerollBlock}ASSIGNED INDEX: ${buildIndex}
the user or the brief. Never expose assignment metadata in user-facing labels.
CHALLENGERS:
${data.challengers.map(renderChallenger).join('\n')}
${stagingBlock}${challengerInstruction}
${compositionBlock}${challengerInstruction}
When you can view images, open the QUALITY BAR board and hero for any
challenger you weigh seriously and for the world you build. They exist as a
craft bar, the finish level and commitment the build is expected to reach,