Add Motion: the go-to-market operating system on top of the ledger

The ledger answers which contracted capacity is sold, to whom, at what
margin. It says nothing about the motion — the repeatable practice that
turns a customer conversation into a scoped deployment, and turns that
deployment into something the next one reuses.

Motion is deliberately not a parallel entity tree. DEMAND_STAGES already
is the motion, so Motion binds reusable artefacts to the stages of a
demand deal that already exists: an engagement hangs off one deal,
cascade deleted, one per deal by unique constraint.

Nine closed kinds, each declaring which stages it serves, and a starter
library of twelve templates covering all eight open stages.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 18:27:03 -07:00
parent 99d165b5e5
commit 516685526c
61 changed files with 13013 additions and 29 deletions
+211
View File
@@ -0,0 +1,211 @@
/**
* The motion page tools, and the one rule none of them may relax.
*
* Motion is the first feature in PIG with a row-level access rule: a `private`
* template belongs to its owner and to a platform admin, and to nobody else.
* Every other read in this process is book-wide, so the habit of the codebase
* is against this clause rather than for it — which is exactly why it is pinned
* here rather than left to a review.
*
* Piggy cannot enforce ownership because it does not reliably know who is
* asking: the dock publishes a route and the relay checks a capability, and
* neither reaches the query. So the query is closed instead. The tests below
* assert the closed form under every filter combination a model can send,
* because the plausible-but-wrong version of this code is one where the clause
* is present in the unfiltered read and lost in a branch.
*
* The unit suite runs in CI BEFORE the migration step, against a database with
* no tables, so nothing here may execute a query. The WHERE clause is rendered
* with `PgDialect` rather than run.
*/
import assert from 'node:assert/strict';
import test from 'node:test';
import { MOTION_KINDS } from '@pig/core';
import type { Database } from '@pig/db';
import { PgDialect } from 'drizzle-orm/pg-core';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { assertPigToolBoundary } from '../src/chat';
import { createPagePigTools, motionLibraryWhere } from '../src/page-tools';
/** Schema and SQL-shape checks only: no query is executed. */
const db = {} as Database;
const dialect = new PgDialect();
function renderedWhere(filter: Parameters<typeof motionLibraryWhere>[0]): {
text: string;
params: unknown[];
} {
const query = dialect.sqlToQuery(motionLibraryWhere(filter));
return { text: query.sql, params: query.params };
}
function tool(route: '/motion' | '/motion/library' | '/motion/engagements') {
const [only, ...rest] = createPagePigTools(db, route);
assert.ok(only, `${route} has a tool`);
// One tool per page: a second is a second thing to choose wrongly, and a
// wrong choice costs one of four turns.
assert.equal(rest.length, 0);
return only;
}
// ---------------------------------------------------------------------------
// The visibility invariant. Do not relax this to "unless the user owns it".
// ---------------------------------------------------------------------------
test('the library query filters to shared templates UNCONDITIONALLY, under every filter', () => {
const filters: Parameters<typeof motionLibraryWhere>[0][] = [
{},
{ kind: 'proposal' },
{ stage: 'poc' },
{ query: 'sovereign' },
{ kind: 'playbook', stage: 'deployment', query: 'inference' },
// The nulls a schema-abiding model sends for "no filter" must not read as
// "no visibility filter either".
{ kind: null, stage: null, query: null },
];
for (const filter of filters) {
const { params } = renderedWhere(filter);
assert.ok(
params.includes('shared'),
`visibility = 'shared' is missing for ${JSON.stringify(filter)}`,
);
// Bound to the column, not merely present somewhere in the statement.
assert.match(renderedWhere(filter).text, /"visibility" = \$\d+/);
}
});
test('no filter a model can send widens the library beyond shared — private is not a parameter', () => {
const library = tool('/motion/library');
const accepts = (input: unknown) => library.inputSchema.safeParse(input).success;
// `.strict()`, so every one of these is refused rather than ignored. A tool
// that silently drops an unknown key teaches a model to keep trying.
assert.equal(accepts({ visibility: 'private' }), false);
assert.equal(accepts({ ownerUserId: '20000000-0000-4000-8000-000000000002' }), false);
assert.equal(accepts({ includePrivate: true }), false);
assert.equal(accepts({ all: '1' }), false);
assert.equal(accepts({ kind: 'proposal' }), true);
});
test('a private template is invisible even when its title is the search term', () => {
// The query filter is an AND alongside the visibility clause, never an OR
// beside it: an `or(...)` at the top level would make any matching title
// satisfy the whole WHERE and return the private row.
const { text, params } = renderedWhere({ query: 'Halcyon' });
const [visibility] = text.split('and');
assert.ok(visibility?.includes('"visibility"'), text);
assert.ok(text.startsWith('('), text);
assert.match(text, /^\("[a-z_]+"\."visibility" = \$1 and /);
assert.equal(params[0], 'shared');
});
test('archived templates are excluded from every library read', () => {
// Archiving is how deletion works here, so a query that ignores it hands the
// model practice somebody deliberately withdrew.
assert.match(renderedWhere({}).text, /"archived_at" is null/);
assert.match(renderedWhere({ kind: 'case_study' }).text, /"archived_at" is null/);
});
test('LIKE wildcards in the model-supplied query are escaped, not honoured', () => {
// Unescaped, `%` matches every shared template and the model is handed the
// first eight as though they answered the question.
assert.deepEqual(renderedWhere({ query: '%' }).params, ['shared', '%\\%%', '%\\%%', '%\\%%']);
});
// ---------------------------------------------------------------------------
// The boundary
// ---------------------------------------------------------------------------
test('every motion page tool sits inside the PIG tool boundary', () => {
const tools = [tool('/motion'), tool('/motion/library'), tool('/motion/engagements')];
assert.deepEqual(tools.map((entry) => entry.name), [
'pig_get_motion_summary',
'pig_search_motion_library',
'pig_get_engagement',
]);
// The assertion the chat provider runs on every request: a name that fails it
// takes the whole conversation down rather than one tool.
assert.doesNotThrow(() => assertPigToolBoundary(tools));
for (const entry of tools) {
assert.ok(entry.description.length > 40, `${entry.name} has a usable description`);
}
});
test('the library and engagement tools say out loud that private drafts are not read', () => {
// The description is the only place the model learns the limit, and "I found
// nothing" is a materially different answer from "I cannot see private
// drafts" to someone looking at their own.
assert.match(tool('/motion/library').description, /[Pp]rivate drafts are never searched/);
assert.match(tool('/motion').description, /[Pp]rivate drafts are not visible/);
});
// ---------------------------------------------------------------------------
// The input bounds
// ---------------------------------------------------------------------------
test('the motion summary takes no input at all', () => {
const summary = tool('/motion');
assert.equal(summary.inputSchema.safeParse({}).success, true);
assert.equal(summary.inputSchema.safeParse({ stage: 'poc' }).success, false);
});
test('the library filters accept only ontology values, and a bounded query', () => {
const library = tool('/motion/library');
const accepts = (input: unknown) => library.inputSchema.safeParse(input).success;
for (const kind of MOTION_KINDS) assert.equal(accepts({ kind }), true);
// A kind the model invented reaches the database as a cast error rather than
// a miss, so it is refused at the schema.
assert.equal(accepts({ kind: 'battlecard' }), false);
assert.equal(accepts({ stage: 'closed_won' }), true);
assert.equal(accepts({ stage: 'negotiation' }), false);
// Trimmed before the length check, so trailing whitespace cannot smuggle a
// one-character query past the floor and match the whole library.
assert.equal(accepts({ query: ' a ' }), false);
assert.equal(accepts({ query: 'x'.repeat(64) }), true);
assert.equal(accepts({ query: 'x'.repeat(65) }), false);
assert.equal(accepts({ query: 'x'.repeat(4000) }), false);
});
test('the engagement query is bounded and refuses anything it does not name', () => {
const engagement = tool('/motion/engagements');
const accepts = (input: unknown) => engagement.inputSchema.safeParse(input).success;
assert.equal(accepts({}), true);
assert.equal(accepts({ query: 'Halcyon' }), true);
assert.equal(accepts({ query: 'a' }), false);
assert.equal(accepts({ query: 'x'.repeat(65) }), false);
assert.equal(accepts({ engagementId: '20000000-0000-4000-8000-000000000002' }), false);
assert.equal(accepts({ limit: 500 }), false);
});
/**
* What the model is actually sent, rather than what the zod reads like.
*
* `zodToJsonSchema(..., { target: 'openAi' })` — the exact call both inference
* paths make — emits an optional field as REQUIRED and nullable, so a
* schema-abiding model sends `null` for every filter it does not want and
* `.optional()` would reject the call. A `.describe()` applied after the
* wrapper is dropped from the emitted schema entirely.
*/
test('an omitted motion filter arrives as the null the emitted schema asks for', () => {
const library = tool('/motion/library');
assert.equal(
library.inputSchema.safeParse({ kind: null, stage: null, query: null }).success,
true,
);
assert.equal(tool('/motion/engagements').inputSchema.safeParse({ query: null }).success, true);
const emitted = zodToJsonSchema(library.inputSchema, {
$refStrategy: 'none',
target: 'openAi',
}) as { properties?: Record<string, { description?: string }>; required?: string[] };
assert.deepEqual(emitted.required, ['kind', 'stage', 'query']);
for (const [parameter, shape] of Object.entries(emitted.properties ?? {})) {
assert.ok(
shape.description && shape.description.length > 10,
`${parameter} reaches the model with no description`,
);
}
});