From a92ba5f2b0ce3cf018775cf5ce835c4292afe69e Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Wed, 29 Jul 2026 16:26:46 -0700 Subject: [PATCH] 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) --- skill/scripts/concept-seed.mjs | 54 ++++++++++++++--------- skill/scripts/lib/composition-catalog.mjs | 6 +-- skill/scripts/lib/concept-catalog.mjs | 18 ++++---- skill/scripts/lib/roll-selection.mjs | 18 ++++---- tests/concept-seed.test.mjs | 40 ++++++++--------- 5 files changed, 74 insertions(+), 62 deletions(-) diff --git a/skill/scripts/concept-seed.mjs b/skill/scripts/concept-seed.mjs index 00a2f73fe..6efb7d643 100644 --- a/skill/scripts/concept-seed.mjs +++ b/skill/scripts/concept-seed.mjs @@ -29,7 +29,7 @@ * win over thin categories, which is the intended shape. * - RE-ROLL (--reroll ): 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 --from --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, diff --git a/skill/scripts/lib/composition-catalog.mjs b/skill/scripts/lib/composition-catalog.mjs index 0b0c2b5fa..349cb4913 100644 --- a/skill/scripts/lib/composition-catalog.mjs +++ b/skill/scripts/lib/composition-catalog.mjs @@ -2,7 +2,7 @@ import crypto from 'node:crypto'; import { readFileSync } from 'node:fs'; import { CONCEPT_STATUSES, normalizeConceptForm } from './concept-catalog.mjs'; -// Catalog B: stagings rather than styles. A composition organizes attention, +// Catalog B: compositions rather than styles. A composition organizes attention, // sequence, or manipulation on a surface and must survive being dressed in // any committed visual identity; it deliberately carries no palette or type // half. Surface-scope seeds draw from here (plus catalog A duals); direction @@ -15,8 +15,8 @@ export const COMPOSITION_GRAMMAR_PREFIXES = [ 'Adaptation:', ]; -// Surfaces align with the skill's modes: a persuade staging and an operate -// staging are different species, and read/experience surfaces get their own. +// Surfaces align with the skill's modes: a persuade composition and an operate +// composition are different species, and read/experience surfaces get their own. export const COMPOSITION_SURFACES = new Set(['persuade', 'operate', 'read', 'experience']); export function compositionContentHash(composition) { diff --git a/skill/scripts/lib/concept-catalog.mjs b/skill/scripts/lib/concept-catalog.mjs index 5e4a24e3c..42c828c74 100644 --- a/skill/scripts/lib/concept-catalog.mjs +++ b/skill/scripts/lib/concept-catalog.mjs @@ -1,11 +1,12 @@ import crypto from 'node:crypto'; import { readFileSync } from 'node:fs'; +import { WELL_TIERS } from './roll-selection.mjs'; export const CONCEPT_STATUSES = new Set(['approved', 'rejected']); // What a concept is actually strong at. Worlds carry a durable visual // identity (their palette/type half is the magnet); compositions carry a -// staging or interaction idea (their topology half is the magnet) that can be +// composition or interaction idea (their topology half is the magnet) that can be // dressed in any committed identity; duals fuse both inseparably. Direction // seeds draw world|dual, surface seeds draw composition|dual. export const CONCEPT_STRENGTHS = new Set(['world', 'composition', 'dual']); @@ -15,7 +16,13 @@ export const CONCEPT_STRENGTHS = new Set(['world', 'composition', 'dual']); // atmosphere worlds need the largest translation step. Every seed roll draws // one challenger from each tier so at least one directly-usable graphic // system is always on the table. -export const WELL_TIERS = ['graphic', 'interaction', 'atmosphere']; +// Defined in roll-selection.mjs, the dependency-free leaf both the seeder and +// the roll API import. It cannot depend on this file: this one 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 in this module's own scope, and +// validateConceptCatalog needs it. +export { WELL_TIERS }; const WEB_LEVERAGE_RE = /(?:\b3d\b|\badaptive\b|\banimat(?:e|ed|ion)\b|\bapi\b|\baria\b|\baudio\b|\bautomated?\b|\bbarcode\b|\bbroadcastchannel\b|\bbrowser\b|\bcamera\b|canvas\b|\bcaption\b|\bcollaborat(?:e|ive|ion)\b|\bcompar(?:e|ison)\b|\bcomput(?:e|ed|ation)\b|\bcomputer[- ]vision\b|\bconstraint[- ]solving\b|\bcryptographic?\b|\bcss\b|\bdeep[- ]link(?:ing)?\b|\bdirect manipulation\b|\bdom\b|\bdrag\b|\bfilter\b|\bfocus\b|\bgenerative\b|\bgeolocat(?:e|ed|ion)\b|\bgesture\b|\bgpu\b|\bgraph\b|\bhistory\b|\bindexeddb\b|\binteractive\b|\bintersectionobserver\b|\bkeyboard\b|\blive\b|\blocal\b|\bmicrophone\b|\bmotion\b|\bmultiplayer\b|\bnative\b|\bnotification\b|\boffline\b|\bpersonaliz(?:e|ed|ation)\b|\bplayable\b|\bpointer\b|\bprocedural\b|\bprovenance\b|\breal[- ]?time\b|\bresizeobserver\b|\bresponsive\b|\breveal\b|\bscrub\b|\bsearch\b|\bsearchparams\b|\bsensor\b|\bserver[- ]sent\b|\bservice worker\b|\bshader\b|\bsimulat(?:e|ed|ion|or)\b|\bspatial\b|\bstate\b|\bstream(?:ing)?\b|\bsvg\b|\bsynchroniz(?:e|ed|ation)\b|\btimeline\b|\btouch\b|\burl|\bvideo\b|\bweb(?:gl|socket|vtt)?\b|\bworker\b|\bzoom\b)/i; export const SYSTEM_PREFIXES = [ @@ -320,10 +327,3 @@ export function approvedPoolRevision(concepts) { return crypto.createHash('sha256').update(payload).digest('hex').slice(0, 12); } -export function deterministicRank(items, input, idFor = item => item.id) { - return [...items].sort((a, b) => { - const scoreA = crypto.createHash('sha256').update(`${input}:${idFor(a)}`).digest('hex'); - const scoreB = crypto.createHash('sha256').update(`${input}:${idFor(b)}`).digest('hex'); - return scoreB.localeCompare(scoreA) || idFor(a).localeCompare(idFor(b)); - }); -} diff --git a/skill/scripts/lib/roll-selection.mjs b/skill/scripts/lib/roll-selection.mjs index c6829dd9e..f579029e4 100644 --- a/skill/scripts/lib/roll-selection.mjs +++ b/skill/scripts/lib/roll-selection.mjs @@ -97,7 +97,7 @@ function compositionTickets(pool) { export function* selectApprovedChallengers({ scope, key, reroll = 0, minRating = null, concepts }) { const approved = concepts.filter(concept => concept.status === 'approved'); // Direction chooses a durable identity, so it draws worlds; surface designs - // one page inside a committed identity, so it draws stagings. Duals serve + // one page inside a committed identity, so it draws compositions. Duals serve // both. A tier with no matching-strength approvals falls back to its full // approved pool rather than starving the roll. const wanted = scope === 'direction' @@ -176,12 +176,12 @@ export function* selectApprovedChallengers({ scope, key, reroll = 0, minRating = } /** - * Three identity-free staging inputs from an explicit approved pool. + * Three identity-free composition inputs from an explicit approved pool. * Drive with runSyncSelection or runAsyncSelection. * * One input was too weak a counterweight to a model's habitual page skeleton: * it became a single optional flourish beside six identity challengers rather - * than a real search over composition. Distinct staging families are preferred + * than a real search over composition. Distinct composition families are preferred * so a roll tests materially different hierarchy, sequence, and interaction * laws. Cross-mode fallback would make the input misleading, so an absent mode * returns nothing rather than borrowing. Re-rolls exclude every earlier set @@ -196,11 +196,11 @@ export function* selectApprovedChallengers({ scope, key, reroll = 0, minRating = * @param {number} [options.count] * @returns {Generator} */ -export function* selectApprovedStagings({ scope, key, reroll = 0, mode = null, compositions, count = 3 }) { - // Stagings honour the same breadth gate as worlds: one too specific to serve +export function* selectApprovedCompositions({ scope, key, reroll = 0, mode = null, compositions, count = 3 }) { + // Compositions honour the same breadth gate as worlds: one too specific to serve // an arbitrary build stays approved for direct briefs and leaves the // challenger pool. Falls back to the full approved set rather than returning - // nothing if every approved staging is niche. + // nothing if every approved composition is niche. let approved = compositions.filter(composition => composition.status === 'approved'); const broad = approved.filter(composition => composition.review?.breadth !== 'niche'); if (broad.length > 0) approved = broad; @@ -218,15 +218,17 @@ export function* selectApprovedStagings({ scope, key, reroll = 0, mode = null, c const base = available.length >= Math.min(count, approved.length) ? available : approved; // Rating weights the draw as it does for worlds. It matters more here // because the per-surface pools are small, so an unweighted shuffle repeats - // a weak staging far more often. Each ticket carries its index so the rank + // a weak composition far more often. Each ticket carries its index so the rank // sees a distinct key per ticket: ranking bare duplicates would hash // identically and the pick loop's id-dedupe would silently discard the // second copy, making the weighting a no-op. let tickets = compositionTickets(base); - // A pool of nothing but 1-star keeps still has to yield stagings. + // A pool of nothing but 1-star keeps still has to yield compositions. if (tickets.length === 0) tickets = base.map(composition => ({ composition, ticket: 0 })); const ranked = (yield* rank( tickets, + // The salt keeps the word "staging" deliberately. It is hash input, so + // renaming it would re-deal every roll anyone has ever reproduced by key. round === 0 ? `${scope}:${key}:staging` : `${scope}:${key}:staging:reroll-${round}`, entry => `${entry.composition.id}#${entry.ticket}` )).map(entry => entry.composition); diff --git a/tests/concept-seed.test.mjs b/tests/concept-seed.test.mjs index 4441b0e98..dcbe43d22 100644 --- a/tests/concept-seed.test.mjs +++ b/tests/concept-seed.test.mjs @@ -11,7 +11,7 @@ import { validateConceptEntry, } from '../skill/scripts/lib/concept-catalog.mjs'; import { readCompositionCatalog } from '../skill/scripts/lib/composition-catalog.mjs'; -import { renderChallenger, selectApprovedChallengers, selectApprovedStaging, selectApprovedStagings } from '../skill/scripts/concept-seed.mjs'; +import { renderChallenger, selectApprovedChallengers, selectApprovedComposition, selectApprovedCompositions } from '../skill/scripts/concept-seed.mjs'; const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const SCRIPT = path.join(ROOT, 'skill', 'scripts', 'concept-seed.mjs'); @@ -98,34 +98,34 @@ describe('concept seed scopes', () => { assert.doesNotMatch(degraded.stdout, /CHALLENGERS:/); }); - it('keeps staging challengers inside the requested surface mode', () => { + it('keeps composition challengers inside the requested surface mode', () => { const pool = [ { id: 'persuade-stage', surface: 'persuade', status: 'approved' }, { id: 'experience-stage', surface: 'experience', status: 'approved' }, ]; - const experience = selectApprovedStaging({ + const experience = selectApprovedComposition({ scope: 'direction', key: 'mode-match', mode: 'experience', sourceCompositions: pool, }); - const read = selectApprovedStaging({ + const read = selectApprovedComposition({ scope: 'direction', key: 'mode-missing', mode: 'read', sourceCompositions: pool, }); assert.equal(experience?.id, 'experience-stage'); - assert.equal(read, null, 'a missing mode must not borrow an unrelated staging'); + assert.equal(read, null, 'a missing mode must not borrow an unrelated composition'); const rendered = run('direction', ['--mode', 'experience']); assert.equal(rendered.status, 0); assert.match(rendered.stdout, /mode: experience/); assert.match(rendered.stdout, /--scope direction --mode experience --from stable-test/); - assert.match(rendered.stdout, /FIRST-SURFACE STAGING/); + assert.match(rendered.stdout, /FIRST-SURFACE COMPOSITION/); }); - it('draws several staging inputs from distinct families when the approved pool allows it', () => { + it('draws several composition inputs from distinct families when the approved pool allows it', () => { const pool = [ { id: 'a', familyId: 'first', surface: 'persuade', status: 'approved' }, { id: 'b', familyId: 'scroll', surface: 'persuade', status: 'approved' }, @@ -133,7 +133,7 @@ describe('concept seed scopes', () => { { id: 'd', familyId: 'first', surface: 'persuade', status: 'approved' }, { id: 'e', familyId: 'other', surface: 'operate', status: 'approved' }, ]; - const picks = selectApprovedStagings({ scope: 'direction', key: 'several', mode: 'persuade', sourceCompositions: pool }); + const picks = selectApprovedCompositions({ scope: 'direction', key: 'several', mode: 'persuade', sourceCompositions: pool }); assert.equal(picks.length, 3); assert.equal(new Set(picks.map(pick => pick.familyId)).size, 3); assert.equal(picks.every(pick => pick.surface === 'persuade'), true); @@ -364,7 +364,7 @@ describe('concept seed scopes', () => { assert.equal(picks.some(pick => pick.id === 'lone-niche'), true); }); - it('weights staging draws by rating without letting the ticket dedupe erase the weight', () => { + it('weights composition draws by rating without letting the ticket dedupe erase the weight', () => { const pool = [ { id: 'flagship-stage', surface: 'persuade', status: 'approved', review: { status: 'approved', rating: 3 } }, { id: 'plain-stage', surface: 'persuade', status: 'approved', review: { status: 'approved' } }, @@ -372,43 +372,43 @@ describe('concept seed scopes', () => { ]; const counts = { 'flagship-stage': 0, 'plain-stage': 0, 'marginal-stage': 0 }; for (let index = 0; index < 300; index += 1) { - const picks = selectApprovedStagings({ scope: 'direction', key: `stage-weight-${index}`, mode: 'persuade', sourceCompositions: pool, count: 1 }); + const picks = selectApprovedCompositions({ scope: 'direction', key: `stage-weight-${index}`, mode: 'persuade', sourceCompositions: pool, count: 1 }); counts[picks[0].id] += 1; } - assert.equal(counts['marginal-stage'], 0, 'a 1-star staging keeps its approval but leaves the draw'); + assert.equal(counts['marginal-stage'], 0, 'a 1-star composition keeps its approval but leaves the draw'); // Two tickets should put the flagship first roughly twice as often as the // unrated peer; a generous margin keeps the assertion deterministic-safe. assert.equal(counts['flagship-stage'] > counts['plain-stage'] * 1.3, true, `flagship ${counts['flagship-stage']} vs plain ${counts['plain-stage']}`); - // A pool of nothing but 1-star keeps still yields stagings. + // A pool of nothing but 1-star keeps still yields compositions. const onlyMarginal = [ { id: 'lone-marginal-stage', surface: 'persuade', status: 'approved', review: { status: 'approved', rating: 1 } }, ]; - const fallback = selectApprovedStagings({ scope: 'direction', key: 'stage-lone', mode: 'persuade', sourceCompositions: onlyMarginal }); + const fallback = selectApprovedCompositions({ scope: 'direction', key: 'stage-lone', mode: 'persuade', sourceCompositions: onlyMarginal }); assert.equal(fallback.some(pick => pick.id === 'lone-marginal-stage'), true); }); - it('gates stagings by breadth and falls back when every staging is niche', () => { + it('gates compositions by breadth and falls back when every composition is niche', () => { const pool = [ { id: 'broad-stage', surface: 'persuade', status: 'approved' }, { id: 'niche-stage', surface: 'persuade', status: 'approved', review: { breadth: 'niche' } }, ]; for (let index = 0; index < 60; index += 1) { - const picks = selectApprovedStagings({ scope: 'direction', key: `stage-breadth-${index}`, mode: 'persuade', sourceCompositions: pool }); - assert.equal(picks.some(pick => pick.id === 'niche-stage'), false, `niche staging dealt at key stage-breadth-${index}`); + const picks = selectApprovedCompositions({ scope: 'direction', key: `stage-breadth-${index}`, mode: 'persuade', sourceCompositions: pool }); + assert.equal(picks.some(pick => pick.id === 'niche-stage'), false, `niche composition dealt at key stage-breadth-${index}`); } const allNiche = [ { id: 'only-niche-stage', surface: 'persuade', status: 'approved', review: { breadth: 'niche' } }, ]; - const fallback = selectApprovedStagings({ scope: 'direction', key: 'all-niche', mode: 'persuade', sourceCompositions: allNiche }); + const fallback = selectApprovedCompositions({ scope: 'direction', key: 'all-niche', mode: 'persuade', sourceCompositions: allNiche }); assert.equal(fallback.some(pick => pick.id === 'only-niche-stage'), true, 'an all-niche pool must fall back instead of dealing nothing'); }); - it('mode-filters the fixture staging pool per surface register', () => { - const operate = selectApprovedStaging({ scope: 'surface', key: 'fix-mode', mode: 'operate', sourceCompositions: fixtureCompositions }); + it('mode-filters the fixture composition pool per surface register', () => { + const operate = selectApprovedComposition({ scope: 'surface', key: 'fix-mode', mode: 'operate', sourceCompositions: fixtureCompositions }); assert.equal(operate.surface, 'operate'); - const experience = selectApprovedStaging({ scope: 'surface', key: 'fix-mode', mode: 'experience', sourceCompositions: fixtureCompositions }); + const experience = selectApprovedComposition({ scope: 'surface', key: 'fix-mode', mode: 'experience', sourceCompositions: fixtureCompositions }); assert.equal(experience.surface, 'experience'); });