Fill the demo book's motion: eight engagements, one at every stage
CI / verify (push) Successful in 7m20s
CI / publish (push) Has been skipped

The Motion half of the demo book was two engagements, which was enough to show
that the loop works and not enough to show what the product is for. The page
that matters asks whether the motion is repeating, and a stage rail of zeroes
cannot answer it.

Eight engagements now, one at every open stage, hanging off demand deals the
demand book already creates — the pipeline happened to have exactly one open
deal at each of the eight, so no deal was invented and the quoted pipeline
counts are unchanged. Forty-four artefacts, nineteen scores, one promotion.

Three things the book is laid out to prove that a folder of templates cannot.

Every stage is occupied, and every one of the twelve starter templates is
instantiated at least once, so "stages covered 8/8" is a measurement rather
than a claim about the seed.

Scores move, and sometimes move down. Nineteen scores across eight
trajectories, with nine dimensions regressing somewhere — the Verity
fine-tuning record runs 62.5 -> 60.5 -> 84.0 -> 80.8, because a scorecard that
only ever rises is a ratchet and teaches a reader to distrust it. Every score
is computed with `motionScoreBasisPoints` and banded with `motionBand` rather
than written as a literal, so the seed and the product cannot disagree about
what the same dimensions are worth.

The artefact bodies are the customer's own facts — named people, real volumes,
the specific thing going wrong, and a live unresolved risk in each. An artefact
whose body is the template with the blanks still in it is precisely what this
data exists to disprove. Six of the forty-four have no template at all, which
is the honest shape of an engagement and the reason `kind` is carried on the
artefact rather than derived: `engagement_artifacts.kind` is NOT NULL and a
derived kind would have been null for exactly those six.

The bodies live in JSON beside the loader for the same reason the starter
library's do — forty-four markdown bodies as backtick strings is a module
nobody can review.

The promotion copies `body` from the artefact verbatim, as `promoteArtifact`
does, rather than writing a hand-authored version 2. A demo that produced a row
the real path could not have produced would teach the wrong shape of the table.

Two name collisions the authors could not see are fixed: a Quillon contact
shared a full name with a demo seller, and an Aurelian one shared a surname
with another. `usage_count` is raised once per template rather than once per
artefact, so it stays symmetrical with the decrement `clear()` already does —
verified by tearing the book down and confirming the library returns to twelve
templates with every counter back at zero, since a counter left above zero
makes a starter template permanently un-editable.
This commit is contained in:
2026-08-19 01:05:48 -07:00
parent b7d1ffd2d8
commit 666310b264
10 changed files with 2013 additions and 340 deletions
+239 -340
View File
@@ -1,30 +1,48 @@
/**
* Two demo engagements, chosen so the loop is visible without a live customer.
* The demo book's Motion half: eight engagements, one at every open stage.
*
* The loop is the only reason Motion exists:
*
* library template --instantiate--> engagement artifact --promote--> v2
* library template --instantiate--> engagement artefact --promote--> v2
*
* A screenshot of the library alone shows a folder of documents, which is not
* the claim. So one engagement is **mid-POC with three qualification scores**,
* because a single score is a number and three are a trajectory — the movement
* is the evidence that qualification happened at all — and the other is
* **closed-won with a promoted artifact**, which puts a version 2 into the
* library carrying `supersedes_id` back to the shipped version 1 and
* `origin_artifact_id` back to the engagement that proved it. Both halves of
* the FK cycle are therefore populated on a first run.
* the claim being made. So the book is laid out to prove three things a folder
* cannot. **Every stage is occupied**, because the page that matters asks
* whether the motion is repeating and a rail of zeroes cannot answer it.
* **Scores move, and sometimes move down** — a single score is a number and
* four are a trajectory; a scorecard that only ever rises is a ratchet, so the
* Verity fine-tuning record deliberately goes 62.5 → 60.5 → 84.0 → 80.8, and
* nine dimensions regress somewhere in the book. And **one engagement closes
* the loop**, putting a version 2 into the library carrying `supersedes_id`
* back to the shipped version 1 and `origin_artifact_id` back to the artefact
* that proved it, so both halves of the FK cycle are populated on a first run.
*
* Both hang off demand deals the demand book already created, rather than deals
* of their own: an engagement has no independent existence, and inventing a
* deal here would put a thirteenth row on a pipeline whose counts are quoted in
* `demo/index.ts`.
* The artefact bodies are the point. An artefact whose body is the template
* with the blanks still in it is exactly what this data exists to disprove, so
* every one of them is written as the customer's own facts — named people, real
* volumes, the specific thing going wrong. That is also why they live in JSON
* beside this file rather than in TypeScript literals: forty-four markdown
* bodies as backtick strings would be a module nobody can review.
*
* The scores are computed with `motionScoreBasisPoints` rather than written as
* literals. Hard-coding 6600 would let the seed and the product disagree about
* what the same dimensions are worth, and the first place anyone would notice
* is a demo.
* Every engagement hangs off a demand deal the demand book already created,
* rather than a deal of its own. An engagement has no independent existence,
* and inventing deals here would put rows on a pipeline whose counts are quoted
* in `demo/index.ts`.
*
* Scores are computed with `motionScoreBasisPoints` and banded with
* `motionBand`, never written as literals. Hard-coding 8280 would let the seed
* and the product disagree about what the same dimensions are worth, and the
* first place anyone would notice is a demo.
*/
import { motionBand, motionScoreBasisPoints, type MotionDimensionScore } from '@pig/core';
import {
motionBand,
motionScoreBasisPoints,
type ArtifactStatus,
type DemandStage,
type EngagementStatus,
type MotionDimensionScore,
type MotionKind,
} from '@pig/core';
import { and, eq, like, sql } from 'drizzle-orm';
import {
demandDeals,
@@ -36,120 +54,83 @@ import {
} from '../../schema/index';
import type { DemoContext } from './index';
/**
* The scorecard's dimensions and their authored weights, copied from
* `seed/motion/trainability-qualification.json`.
*
* Copied rather than read out of the seeded row, deliberately: a stored score
* is a snapshot of the framework *as it was scored*, which is the whole reason
* `qualification_scores.dimensions` holds the weights rather than a reference.
* Reading them live would make the demo history silently re-weight itself the
* day someone edits the template, which is precisely the behaviour the column
* exists to prevent.
*/
const SCORECARD_WEIGHTS: readonly { readonly id: string; readonly weight: number }[] = [
{ id: 'task_definability', weight: 10 },
{ id: 'verifier_quality', weight: 13 },
{ id: 'trace_data_rights', weight: 8 },
{ id: 'baseline_measured', weight: 8 },
{ id: 'headroom', weight: 10 },
{ id: 'env_constructibility', weight: 8 },
{ id: 'metric_owner', weight: 9 },
{ id: 'budget_source', weight: 9 },
{ id: 'exec_sponsor', weight: 4 },
{ id: 'security_legal_path', weight: 8 },
{ id: 'forcing_function', weight: 8 },
{ id: 'expansion_surface', weight: 5 },
];
import aurelianCommittedCapacity from './motion/aurelian-committed-capacity.json' with { type: 'json' };
import halcyonPostTraining from './motion/halcyon-post-training.json' with { type: 'json' };
import northwindBlackwell from './motion/northwind-blackwell.json' with { type: 'json' };
import northwindFleetExpansion from './motion/northwind-fleet-expansion.json' with { type: 'json' };
import quillonEvalHarness from './motion/quillon-eval-harness.json' with { type: 'json' };
import tessellateBurstInference from './motion/tessellate-burst-inference.json' with { type: 'json' };
import verityEuFineTuning from './motion/verity-eu-fine-tuning.json' with { type: 'json' };
import verityEuInference from './motion/verity-eu-inference.json' with { type: 'json' };
/**
* Three passes over the same scorecard, in the order they were run.
*
* The trajectory is the teaching: the deal opens borderline on an unmeasured
* verifier and no named budget, the POC scoping call fixes the verifier, and
* the third pass lands once finance names a source. Two dimensions deliberately
* do NOT move — `trace_data_rights` and `expansion_surface` — because a
* scorecard where every number rises on every pass is a scorecard nobody is
* really filling in.
*/
const SCORE_PASSES: readonly {
interface DemoArtefact {
readonly templateSlug: string | null;
/*
* Carried on the artefact rather than looked up from its template, because
* six of the forty-four have no template at all — the work a lead does that
* no library entry covers, which is the honest shape of an engagement. A
* `kind` derived from the template would have been null for exactly those
* six, and `engagement_artifacts.kind` is NOT NULL.
*/
readonly kind: MotionKind;
readonly stage: DemandStage;
readonly title: string;
readonly status: ArtifactStatus;
readonly authoredDaysAgo: number;
readonly body: string;
}
interface DemoScore {
readonly daysAgo: number;
readonly note: string;
readonly scores: Readonly<Record<string, number>>;
}[] = [
{
daysAgo: 52,
note:
'First pass, straight out of technical discovery. The task is well specified and there ' +
'is real headroom, but the verifier is a rubric nobody has run against human labels and ' +
'no budget line has been named. Not yet — and the two things to fix are explicit.',
scores: {
task_definability: 3,
verifier_quality: 1,
trace_data_rights: 2,
baseline_measured: 2,
headroom: 3,
env_constructibility: 2,
metric_owner: 2,
budget_source: 1,
exec_sponsor: 2,
security_legal_path: 2,
forcing_function: 2,
expansion_surface: 3,
},
},
{
daysAgo: 27,
note:
'Re-scored after the POC scoping call. They ran the judge against 300 human-labelled ' +
'items and the baseline is now measured rather than remembered. Budget is still a ' +
'reallocation nobody has signed.',
scores: {
task_definability: 3,
verifier_quality: 3,
trace_data_rights: 2,
baseline_measured: 3,
headroom: 3,
env_constructibility: 3,
metric_owner: 3,
budget_source: 1,
exec_sponsor: 2,
security_legal_path: 3,
forcing_function: 2,
expansion_surface: 3,
},
},
{
daysAgo: 6,
note:
'Third pass, in POC week two. Finance named the source and the VP Engineering owns the ' +
'metric in their own planning doc. Data rights are unchanged: the customer still cannot ' +
'export traces beyond the pilot without a DPA amendment, and that is the live risk.',
scores: {
task_definability: 4,
verifier_quality: 3,
trace_data_rights: 2,
baseline_measured: 3,
headroom: 4,
env_constructibility: 3,
metric_owner: 4,
budget_source: 3,
exec_sponsor: 3,
security_legal_path: 3,
forcing_function: 3,
expansion_surface: 3,
},
},
];
function dimensionsFor(scores: Readonly<Record<string, number>>): MotionDimensionScore[] {
return SCORECARD_WEIGHTS.map((dimension) => ({
id: dimension.id,
weight: dimension.weight,
score: scores[dimension.id] ?? 0,
}));
readonly dimensions: readonly MotionDimensionScore[];
}
interface DemoEngagement {
readonly key: string;
readonly dealName: string;
readonly accountName: string;
readonly stage: DemandStage;
readonly status: EngagementStatus;
readonly summary: string;
readonly openedDaysAgo: number;
readonly artefacts: readonly DemoArtefact[];
readonly scores: readonly DemoScore[];
readonly promote: { readonly artefactTitle: string; readonly intoSlug: string } | null;
}
/**
* Ordered oldest-opened first, so the engagement list reads as a book somebody
* built up rather than eight things that appeared at once.
*/
const DEMO_ENGAGEMENTS = [
northwindFleetExpansion,
northwindBlackwell,
halcyonPostTraining,
verityEuFineTuning,
verityEuInference,
aurelianCommittedCapacity,
tessellateBurstInference,
quillonEvalHarness,
] as unknown as readonly DemoEngagement[];
/**
* Which demo seller owns which account.
*
* Assigned by account rather than by engagement, because the two accounts that
* appear twice (Verity Health AI, Northwind Robotics) are one relationship each
* — a second engagement handed to a different owner would contradict the
* continuity the artefacts themselves describe.
*/
const OWNER_BY_ACCOUNT: Readonly<Record<string, string>> = {
'Quillon AI': 'Wren Abbot',
'Aurelian Systems': 'Marcus Oyelaran',
'Verity Health AI': 'Ines Fabre',
'Tessellate Labs': 'Rosalind Achebe',
'Halcyon Research': 'Ines Fabre',
'Northwind Robotics': 'Marcus Oyelaran',
};
async function findDeal(context: DemoContext, name: string): Promise<string | undefined> {
const [deal] = await context.db
.select({ id: demandDeals.id })
@@ -168,12 +149,17 @@ async function findSeller(context: DemoContext, name: string): Promise<string |
return seller?.id ?? null;
}
/** The shipped version 1 of a lineage. Promotion needs its id and its version. */
async function findTemplate(
context: DemoContext,
slug: string,
): Promise<{ id: string; version: number } | undefined> {
): Promise<{ id: string; version: number; summary: string } | undefined> {
const [template] = await context.db
.select({ id: motionTemplates.id, version: motionTemplates.version })
.select({
id: motionTemplates.id,
version: motionTemplates.version,
summary: motionTemplates.summary,
})
.from(motionTemplates)
.where(and(eq(motionTemplates.slug, slug), eq(motionTemplates.isSystem, true)))
.limit(1);
@@ -182,19 +168,25 @@ async function findTemplate(
export async function seedMotionEngagements(
context: DemoContext,
): Promise<{ engagements: number; artifacts: number; scores: number; promoted: number }> {
): Promise<{
engagements: number;
artifacts: number;
scores: number;
promoted: number;
missingDeals: readonly string[];
}> {
const { db, prefix, at } = context;
/*
* The whole section is skipped if either engagement already exists.
* The whole section is skipped if any demo engagement already exists.
*
* `engagements_demand_deal_key` would refuse a duplicate engagement, but
* nothing would refuse a second set of artifacts or — worse — a second
* nothing would refuse a second set of artefacts or — worse — a second
* promotion, and a second promotion writes case-study-frame v3 on top of the
* v2 this seed already created. `qualification_scores` is append-only by
* design and so has no constraint to conflict on at all; three more rows on
* every run would turn the trajectory into noise. An existence check on the
* parent is the only thing that covers all three.
* design and so has no constraint to conflict on at all; nineteen more rows
* on every run would turn every trajectory into noise. An existence check on
* the parent is the only thing that covers all three.
*/
const existing = await db
.select({ id: engagements.id })
@@ -202,228 +194,144 @@ export async function seedMotionEngagements(
.where(like(engagements.summary, `${prefix}%`))
.limit(1);
if (existing.length > 0) {
return { engagements: 0, artifacts: 0, scores: 0, promoted: 0 };
return { engagements: 0, artifacts: 0, scores: 0, promoted: 0, missingDeals: [] };
}
const playbook = await findTemplate(context, 'strategic-deployment-playbook');
const scorecard = await findTemplate(context, 'trainability-qualification');
const pocTemplate = await findTemplate(context, 'post-training-poc');
const caseStudy = await findTemplate(context, 'case-study-frame');
const templateCache = new Map<string, { id: string; version: number; summary: string }>();
const templateBySlug = async (slug: string) => {
if (!templateCache.has(slug)) {
const found = await findTemplate(context, slug);
if (found) templateCache.set(slug, found);
}
return templateCache.get(slug);
};
const usageByTemplate = new Map<string, number>();
const missingDeals: string[] = [];
let engagementCount = 0;
let artifactCount = 0;
let scoreCount = 0;
let promotedCount = 0;
// ------------------------------------------------- mid-POC, still scoring
const halcyonDealId = await findDeal(context, `${prefix}Managed post-training run`);
const ines = await findSeller(context, 'Ines Fabre');
/*
* A missing deal is reported, not skipped in silence.
*
* Both engagements hang off deals the demand book creates, and that book
* skips an account it has already seen so a database where the accounts
* survive but the deals were removed leaves these lookups empty and this
* whole section a no-op that reports zeroes and looks like a bug in the
* loader. It was found exactly that way, on a shared development database.
*/
if (!halcyonDealId) {
console.warn(
` No "${prefix}Managed post-training run" deal — skipping the mid-POC engagement. ` +
'Run `pnpm db:demo -- --clear` and reseed to rebuild the demand book.',
);
}
if (halcyonDealId) {
const [engagement] = await db
.insert(engagements)
.values({
demandDealId: halcyonDealId,
playbookTemplateId: playbook?.id ?? null,
ownerUserId: ines,
status: 'open',
summary: `${prefix}Post-training motion on the Halcyon summarisation workflow — POC week two.`,
openedAt: at(-58),
})
.returning({ id: engagements.id });
if (engagement) {
engagementCount += 1;
await db.insert(engagementArtifacts).values({
engagementId: engagement.id,
templateId: scorecard?.id ?? null,
kind: 'discovery',
stage: 'qualification',
title: `${prefix}Discovery notes — Halcyon summarisation`,
body:
'## What they are trying to do\n\n' +
'Reduce reviewer time on internal research summaries. Today a senior analyst reads a ' +
'40-page source and writes a one-page brief; the bar is "would the head of research ' +
'send this out unedited".\n\n' +
'## Unit of work\n\n' +
'One source document in, one brief out. They have 4,100 historical pairs and the ' +
'reviewer decision on each.\n\n' +
'## Verifier\n\n' +
'A model judge calibrated against 300 of those human decisions. Agreement is 0.81 ' +
'Cohens kappa, measured on a held-out slice rather than the calibration set.\n\n' +
'## The live risk\n\n' +
'Trace export beyond the pilot needs a DPA amendment their counsel has not seen yet.',
status: 'final',
authoredByUserId: ines,
fields: { source: 'Technical discovery call, plus the follow-up with the data owner.' },
});
await db.insert(engagementArtifacts).values({
engagementId: engagement.id,
templateId: pocTemplate?.id ?? null,
kind: 'poc',
stage: 'poc',
title: `${prefix}POC plan — summarisation reward model`,
body:
'## Success criterion, agreed in writing\n\n' +
'Judge-scored acceptance rate on a held-out set of 400 briefs rises from the measured ' +
'baseline of 61% to at least 78%, at no more than 1.4× the current inference cost ' +
'per brief.\n\n' +
'## What we run\n\n' +
'Two weeks on 32× H100. Week one builds the environment and reproduces the ' +
'baseline; week two is the training run and the evaluation.\n\n' +
'## What we hand back\n\n' +
'The environment, the eval harness, and the number — whichever way it comes out. ' +
'A POC that reports a failure honestly is what makes the next number believable.\n\n' +
'## Open\n\n' +
'Trace rights beyond the pilot. Blocked on the DPA amendment.',
status: 'review',
authoredByUserId: ines,
fields: { baselineAcceptancePct: 61, targetAcceptancePct: 78, gpuCount: 32, weeks: 2 },
});
artifactCount += 2;
for (const pass of SCORE_PASSES) {
const dimensions = dimensionsFor(pass.scores);
const basisPoints = motionScoreBasisPoints(dimensions);
await db.insert(qualificationScores).values({
engagementId: engagement.id,
frameworkTemplateId: scorecard?.id ?? null,
// The column is `Record<string, unknown>[]`; Drizzle's jsonb `$type`
// will not take an interface with readonly properties, and widening
// the column to suit the seed would lose the shape everywhere else.
dimensions: dimensions as unknown as Record<string, unknown>[],
basisPoints,
band: motionBand(basisPoints).label,
note: `${prefix}${pass.note}`,
scoredByUserId: ines,
scoredAt: at(-pass.daysAgo),
});
scoreCount += 1;
}
for (const demo of DEMO_ENGAGEMENTS) {
/*
* A missing deal is reported, not skipped in silence.
*
* Every engagement hangs off a deal the demand book creates, and that book
* skips an account it has already seen — so a database where the accounts
* survive but the deals were removed leaves these lookups empty and this
* whole section a no-op that reports zeroes and looks like a bug in the
* loader. It was found exactly that way, on a shared development database.
*/
const dealId = await findDeal(context, demo.dealName);
if (!dealId) {
missingDeals.push(demo.dealName);
continue;
}
}
// ------------------------------------- closed won, and promoted afterwards
const northwindDealId = await findDeal(context, `${prefix}A100 inference pilot`);
const marcus = await findSeller(context, 'Marcus Oyelaran');
const ownerName = OWNER_BY_ACCOUNT[demo.accountName.replace(prefix, '')];
const ownerId = ownerName ? await findSeller(context, ownerName) : null;
if (!northwindDealId) {
console.warn(
` No "${prefix}A100 inference pilot" deal — skipping the closed-won engagement, and ` +
'with it the promotion that demonstrates the loop.',
);
}
if (northwindDealId) {
const [engagement] = await db
.insert(engagements)
.values({
demandDealId: northwindDealId,
demandDealId: dealId,
playbookTemplateId: playbook?.id ?? null,
ownerUserId: marcus,
status: 'won',
summary: `${prefix}Northwind inference pilot — won, and written up.`,
openedAt: at(-190),
closedAt: at(-96),
ownerUserId: ownerId,
status: demo.status,
summary: demo.summary,
openedAt: at(-demo.openedDaysAgo),
closedAt: demo.status === 'won' ? at(-Math.floor(demo.openedDaysAgo / 8)) : null,
})
.returning({ id: engagements.id });
if (!engagement) continue;
engagementCount += 1;
if (engagement) {
engagementCount += 1;
const [artifact] = await db
const artefactIdByTitle = new Map<string, { id: string; body: string }>();
for (const artefact of demo.artefacts) {
const template = artefact.templateSlug ? await templateBySlug(artefact.templateSlug) : undefined;
const [written] = await db
.insert(engagementArtifacts)
.values({
engagementId: engagement.id,
templateId: caseStudy?.id ?? null,
kind: 'case_study',
stage: 'expansion',
title: `${prefix}Northwind Robotics — A100 inference pilot`,
body:
'## The problem\n\n' +
'Northwind ran perception inference on reserved A100s they had bought for training, ' +
'and were paying training prices for a serving workload with a 40:1 peak-to-trough ' +
'shape.\n\n' +
'## What we did\n\n' +
'Moved the trough onto committed capacity and the peak onto burst, with the split ' +
'set by their own 90-day request trace rather than by a headline number.\n\n' +
'## The number\n\n' +
'Cost per million inferences fell 34%. Measured against their own billing, over a ' +
'full quarter, not against a list price.\n\n' +
'## What made it work, and what to reuse\n\n' +
'They had the request trace. Every engagement since has asked for it in the first ' +
'call, which is the change this write-up put into the frame itself.',
status: 'final',
authoredByUserId: marcus,
fields: {
metric: 'Cost per million inferences',
improvementPct: 34,
window: 'One quarter, customer billing',
approvedForExternalUse: false,
},
templateId: template?.id ?? null,
kind: artefact.kind,
stage: artefact.stage,
title: artefact.title,
body: artefact.body,
status: artefact.status,
authoredByUserId: ownerId,
createdAt: at(-artefact.authoredDaysAgo),
updatedAt: at(-artefact.authoredDaysAgo),
})
.returning({ id: engagementArtifacts.id, title: engagementArtifacts.title });
.returning({ id: engagementArtifacts.id });
if (!written) continue;
artifactCount += 1;
artefactIdByTitle.set(artefact.title, { id: written.id, body: artefact.body });
if (template) {
usageByTemplate.set(template.id, (usageByTemplate.get(template.id) ?? 0) + 1);
}
}
/*
* The promotion, written by hand rather than by calling the API service:
* the seed has no HTTP surface and no principal. It follows the same
* rules the service does — version = previous + 1, `supersedesId` to the
* shipped v1, `originArtifactId` to the artifact, `visibility: 'shared'`,
* `isSystem: false` — because a demo that produced a row the real path
* could not have produced would teach the wrong shape of the table.
*
* `isSystem` is false and the title is prefixed, which is what lets
* `clear()` find it. It is a version 2 of a shipped lineage, so removing
* the demo book genuinely returns the library to what ships.
*/
if (artifact && caseStudy) {
for (const score of demo.scores) {
const basisPoints = motionScoreBasisPoints(score.dimensions);
await db.insert(qualificationScores).values({
engagementId: engagement.id,
frameworkTemplateId: (await templateBySlug('trainability-qualification'))?.id ?? null,
/*
* Copied, not referenced. `qualification_scores.dimensions` stores the
* framework AS IT WAS SCORED — weights included — so that re-authoring
* the scorecard later cannot retroactively change what a past score
* meant. Handing the same array to every row would make that snapshot
* a shared object.
*/
dimensions: score.dimensions.map((dimension) => ({ ...dimension })),
basisPoints,
band: motionBand(basisPoints).label,
note: score.note,
scoredByUserId: ownerId,
scoredAt: at(-score.daysAgo),
});
scoreCount += 1;
}
/*
* The promotion, written by hand rather than by calling the API service:
* the seed has no HTTP surface and no principal. It follows the same rules
* `promoteArtifact` does — version = newest + 1, `supersedesId` to the
* shipped v1, `originArtifactId` to the artefact, **`body` copied from the
* artefact verbatim**, `visibility: 'shared'`, `isSystem: false` — because
* a demo that produced a row the real path could not have produced would
* teach the wrong shape of the table.
*
* `isSystem` is false and the title is prefixed, which is what lets
* `clear()` find it. It is a version 2 of a shipped lineage, so removing
* the demo book genuinely returns the library to what ships.
*/
if (demo.promote) {
const artefact = artefactIdByTitle.get(demo.promote.artefactTitle);
const supersedes = await templateBySlug(demo.promote.intoSlug);
if (artefact && supersedes) {
const [promoted] = await db
.insert(motionTemplates)
.values({
kind: 'case_study',
stage: 'expansion',
slug: 'case-study-frame',
version: caseStudy.version + 1,
title: `${prefix}Case Study Frame — ask for the request trace first`,
slug: demo.promote.intoSlug,
version: supersedes.version + 1,
title: demo.promote.artefactTitle,
summary:
'Version 2, promoted out of the Northwind pilot write-up. Adds the one question ' +
'that made that engagement measurable: get the customers own usage trace ' +
'before anyone quotes a saving.',
body:
'_Promoted from an engagement. The change from version 1 is the section below._\n\n' +
'## Before you frame anything: get the trace\n\n' +
'A case study is only as good as the baseline it is measured against, and the ' +
'only baseline a customer cannot argue with afterwards is their own telemetry. ' +
'Ask for it in the first call, not at write-up time.\n\n' +
'## Everything else\n\n' +
'As version 1: the problem, what we did, the number, what to reuse.',
fields: {
promotedFrom: artifact.title,
addedSection: 'Before you frame anything: get the trace',
},
`Version ${supersedes.version + 1}, promoted out of the ` +
`${demo.accountName.replace(prefix, '')} expansion. The frame as it was actually ` +
'filled in, with the numbers the customer approved for named use.',
body: artefact.body,
fields: { promotedFrom: demo.promote.artefactTitle, engagement: demo.key },
visibility: 'shared',
ownerUserId: marcus,
supersedesId: caseStudy.id,
originArtifactId: artifact.id,
ownerUserId: ownerId,
supersedesId: supersedes.id,
originArtifactId: artefact.id,
isSystem: false,
})
.onConflictDoNothing({ target: [motionTemplates.slug, motionTemplates.version] })
@@ -433,7 +341,7 @@ export async function seedMotionEngagements(
await db
.update(engagementArtifacts)
.set({ promotedTemplateId: promoted.id })
.where(eq(engagementArtifacts.id, artifact.id));
.where(eq(engagementArtifacts.id, artefact.id));
promotedCount += 1;
}
}
@@ -441,21 +349,11 @@ export async function seedMotionEngagements(
}
/*
* `usage_count` is what the API reads to refuse a `PATCH` on a used
* template, so the demo must move it or a library where three templates have
* visibly been instantiated still reads as never used — and the rule the
* whole feature turns on would be undemonstrable. Incremented in one sweep at
* the end rather than per insert, so the count is derived from the artefacts
* that actually landed and `clear()` can undo it by counting the same rows.
* `usage_count` is raised once per template at the end rather than once per
* artefact inside the loop, because it is the counter that makes a template
* permanently un-editable and `clear()` gives back exactly what was borrowed.
* One statement per template keeps the two halves symmetrical.
*/
const instantiated = await db
.select({ templateId: engagementArtifacts.templateId })
.from(engagementArtifacts)
.where(like(engagementArtifacts.title, `${prefix}%`));
const usageByTemplate = new Map<string, number>();
for (const row of instantiated) {
if (row.templateId) usageByTemplate.set(row.templateId, (usageByTemplate.get(row.templateId) ?? 0) + 1);
}
for (const [templateId, count] of usageByTemplate) {
await db
.update(motionTemplates)
@@ -468,5 +366,6 @@ export async function seedMotionEngagements(
artifacts: artifactCount,
scores: scoreCount,
promoted: promotedCount,
missingDeals,
};
}