Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 12 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,13 @@ tsconfig.tsbuildinfo

tmp

# live oxlint test fixtures (transient, mkdtempSync per test)
.live-test-fixtures-*
# Live oxlint test fixtures (scripts/hygiene/delete-unused.test.ts) are
# DELIBERATELY NOT ignored here. `vp lint --no-ignore` disables `.eslintignore`
# / `--ignore-path` / `--ignore-pattern` only — it does NOT disable .gitignore,
# so any fixture path listed here is skipped by the linter ("No files found to
# lint", number_of_files: 0) and the live-integration gate silently inspects
# nothing. The former `.live-test-fixtures-*` entry did exactly that. The tests
# create `livetestfixtures-*` at the repo root and remove them in a `finally`.

# packed consumer lane staging (verify:packed)
e2e/packed-app/.staging/
Expand All @@ -73,6 +78,11 @@ e2e/packed-app/.staging/
docs/*
!docs/standalone-extraction.md
.codex

# contract-change campaign ratchet artifacts — enforcement hooks were never
# enabled and nothing consumes these; regenerated on demand by a future pass
.abstraction-owners.json
.collision-report.json
.repowise/

# opx generated workspace state
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ This map routes an edit to the smallest sufficient claim plus any source-owned d
| `packages/test-ds/src/**` | `vp run verify:unit:ts && vp run --fail-if-no-match -F '...@animus-ui/test-ds' verify` |
| `e2e/packed-app/**` or `scripts/verify/packed.sh` | `vp run verify:packed` |
| `scripts/verify/topology.*` | `vp run verify:lint` |
| `tools/oxlint/anti-slop/**` | `vp run verify:lint && vp run verify:compile` |
| `packages/{properties,system,extract,vite-plugin,next-plugin,cli,unplugin}/package.json` (deps, peers, exports, files) | `vp run verify:packed` |
| `.github/workflows/ci.yaml`, `scripts/**`, `.tool-versions` | `vp run verify:full` |
| Worker orchestration (`vite.config.ts`, `scripts/verify/**`, root deploy scripts, Worker ignores) | `vp run verify:full` |
Expand Down
95 changes: 74 additions & 21 deletions bun.lock

Large diffs are not rendered by default.

56 changes: 17 additions & 39 deletions e2e/next-app/scripts/assert-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
systemSchemeVariableSpans,
writeLaneReceipt,
} from '@animus-ui/assertions';
import { readFileSync } from 'node:fs';
import nextManifest from 'next/package.json' with { type: 'json' };
import { readdir, readFile, stat } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -43,46 +43,24 @@ async function assertDir(path: string, label: string): Promise<void> {
}

function emitLaneReceipt(): void {
// Retirement regression guard (openspec: retire-extract-v1): v2 is the only
// engine. The fixture config MUST NOT reference ANIMUS_ENGINE or set the
// engine option — either would reintroduce a retired v1 selection path.
const config = readFileSync(resolve(APP_ROOT, 'next.config.ts'), 'utf8');
if (config.includes('ANIMUS_ENGINE') || /\bengine\s*:/.test(config)) {
throw new AssertionError(
'next.config.ts must not reference ANIMUS_ENGINE or set the engine ' +
'option — the v1 engine was retired (openspec: retire-extract-v1)'
);
}

// v1 is retired (openspec: retire-extract-v1): v2 is the only engine, so the
// receipt records v2 as both default and loaded, with no override. Engine
// identity is never inferred from plugin/config source (guardrail G3).
const engineDefault = 'v2' as const;
const engineLoaded = 'v2' as const;
const engineOverride = false;

// Engine identity comes from writeLaneReceipt's retirement guard over the
// fixture config (openspec: retire-extract-v1) — never spelled here, and
// never inferred from plugin source (guardrail G3).
//
// hostVersion from the fixture's installed host, not the manifest range.
const hostVersion = (
JSON.parse(
readFileSync(
resolve(APP_ROOT, 'node_modules', 'next', 'package.json'),
'utf8'
)
) as { version: string }
).version;

writeLaneReceipt(resolve(APP_ROOT, '.receipts', 'verify-assert-next.json'), {
lane: '@animus-ui/next-app#verify:assert',
host: 'next',
hostVersion,
mode: 'production',
engineLoaded,
engineDefault,
engineOverride,
packageForm: 'workspace',
});
const receipt = writeLaneReceipt(
resolve(APP_ROOT, '.receipts', 'verify-assert-next.json'),
{
lane: '@animus-ui/next-app#verify:assert',
host: 'next',
hostVersion: nextManifest.version,
mode: 'production',
packageForm: 'workspace',
engineConfigPath: resolve(APP_ROOT, 'next.config.ts'),
}
);
console.log(
`[next-app:assert] receipt → .receipts/verify-assert-next.json (engine=${engineLoaded}, default=${engineDefault}, override=${engineOverride})`
`[next-app:assert] receipt → .receipts/verify-assert-next.json (engine=${receipt.engineLoaded}, default=${receipt.engineDefault}, override=${receipt.engineOverride})`
);
}

Expand Down
43 changes: 9 additions & 34 deletions e2e/next16-app/scripts/assert-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
readAllConcat,
writeLaneReceipt,
} from '@animus-ui/assertions';
import { readFileSync } from 'node:fs';
import nextManifest from 'next/package.json' with { type: 'json' };
import { readFile, stat } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand All @@ -32,49 +32,24 @@ async function assertDir(path: string, label: string): Promise<void> {
}

function emitLaneReceipt(): void {
// Retirement regression guard (openspec: retire-extract-v1): v2 is the only
// engine. The fixture config MUST NOT reference ANIMUS_ENGINE or set the
// engine option — either would reintroduce a retired v1 selection path.
const config = readFileSync(resolve(APP_ROOT, 'next.config.ts'), 'utf8');
if (config.includes('ANIMUS_ENGINE') || /\bengine\s*:/.test(config)) {
throw new AssertionError(
'next.config.ts must not reference ANIMUS_ENGINE or set the engine ' +
'option — the v1 engine was retired (openspec: retire-extract-v1)'
);
}

// v1 is retired (openspec: retire-extract-v1): v2 is the only engine, so the
// receipt records v2 as both default and loaded, with no override. Engine
// identity is never inferred from plugin/config source (guardrail G3).
const engineDefault = 'v2' as const;
const engineLoaded = 'v2' as const;
const engineOverride = false;

// Engine identity comes from writeLaneReceipt's retirement guard over the
// fixture config (openspec: retire-extract-v1) — never spelled here, and
// never inferred from plugin source (guardrail G3).
//
// hostVersion from the fixture's installed host, not the manifest range.
const hostVersion = (
JSON.parse(
readFileSync(
resolve(APP_ROOT, 'node_modules', 'next', 'package.json'),
'utf8'
)
) as { version: string }
).version;

writeLaneReceipt(
const receipt = writeLaneReceipt(
resolve(APP_ROOT, '.receipts', 'verify-assert-next16.json'),
{
lane: '@animus-ui/next16-app#verify:assert',
host: 'next',
hostVersion,
hostVersion: nextManifest.version,
mode: 'production',
engineLoaded,
engineDefault,
engineOverride,
packageForm: 'workspace',
engineConfigPath: resolve(APP_ROOT, 'next.config.ts'),
}
);
console.log(
`[next16-app:assert] receipt → .receipts/verify-assert-next16.json (engine=${engineLoaded}, default=${engineDefault}, override=${engineOverride})`
`[next16-app:assert] receipt → .receipts/verify-assert-next16.json (engine=${receipt.engineLoaded}, default=${receipt.engineDefault}, override=${receipt.engineOverride})`
);
}

Expand Down
12 changes: 6 additions & 6 deletions e2e/packed-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
"@animus-ui/system": "file:./tarballs/animus-ui-system.tgz",
"@animus-ui/unplugin": "file:./tarballs/animus-ui-unplugin.tgz",
"@animus-ui/vite-plugin": "file:./tarballs/animus-ui-vite-plugin.tgz",
"next": "^15.5.20",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"next": "15.5.20",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react": "18.3.28",
"@types/react-dom": "18.3.7",
"@vitejs/plugin-react": "^6.0.1",
"typescript": "^5.9.2",
"typescript": "5.9.3",
"vite": "^8.1.4"
}
}
85 changes: 83 additions & 2 deletions e2e/packed-app/scripts/assert-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@ import {
assertNoPlaceholders,
findCssFiles,
findJsFiles,
installedHostVersion,
layerBlock,
readAllConcat,
writeLaneReceipt,
} from '@animus-ui/assertions';
import { readdirSync, readFileSync } from 'node:fs';
import { readFile, stat } from 'node:fs/promises';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import type { LaneHost } from '@animus-ui/assertions';

// Positional assertions over the PACKED consumer's build outputs. Runs in
// workspace context (assertions are a private workspace package); the
// builds themselves ran inside the isolated staging install.
// workspace context (assertions are a private workspace package, reached by
// root hoisting — `e2e/packed-app` deliberately declares no workspace
// dependency because its manifest is copied into the isolated npm install);
// the builds themselves ran inside that staging install.
const STAGING = resolve(
dirname(fileURLToPath(import.meta.url)),
'..',
Expand All @@ -25,6 +32,78 @@ const STAGING = resolve(
const VITE_DIST = resolve(STAGING, 'dist');
const NEXT_DIR = resolve(STAGING, '.next');

/**
* The PUBLISHED plugin carries the v1 retirement guard. The guard call is
* imported from the externalized extract pipeline, so runtime bundles carry the
* identifier; inlined bundles would carry the message (which names the change).
* Either marker proves the guard shipped.
*/
function assertRetirementGuard(pluginDir: string): void {
const dir = resolve(STAGING, pluginDir);
for (const entry of readdirSync(dir)) {
if (!/\.(?:cjs|mjs|js)$/.test(entry)) continue;
const source = readFileSync(resolve(dir, entry), 'utf8');
if (
source.includes('assertNoRetiredEngineSelection') ||
source.includes('retire-extract-v1')
) {
return;
}
}
throw new AssertionError(
`installed plugin in ${pluginDir} lacks the v1 retirement guard — update the receipt probe`,
{ pluginDir }
);
}

/**
* Receipts for the packed dimension (openspec: dual-engine-build — "the packed
* consumer lane SHALL prove the v2 engine loads"). Engine facts are STRUCTURAL
* GUARDS over the staged artifacts, never inferred from plugin source
* (guardrail G3): `writeLaneReceipt` proves the staged consumer config selects
* no engine, and `assertRetirementGuard` proves the installed plugin still
* refuses one.
*/
function emitLaneReceipts(): void {
assertRetirementGuard('node_modules/@animus-ui/vite-plugin/dist');
assertRetirementGuard('node_modules/@animus-ui/next-plugin/dist');

const lanes: ReadonlyArray<{
host: LaneHost;
lane: string;
file: string;
config: string;
}> = [
{
host: 'vite',
lane: 'verify:packed:vite',
file: 'packed-vite.json',
config: 'vite.config.ts',
},
{
host: 'next',
lane: 'verify:packed:next',
file: 'packed-next.json',
config: 'next.config.ts',
},
];

for (const { host, lane, file, config } of lanes) {
const receipt = writeLaneReceipt(resolve(STAGING, 'receipts', file), {
lane,
host,
hostVersion: installedHostVersion(STAGING, host),
mode: 'production',
packageForm: 'packed',
engineConfigPath: resolve(STAGING, config),
engineConfigLabel: `.staging/${config}`,
});
console.log(
`[packed-app:assert] receipt → .staging/receipts/${file} (${receipt.lane}=${receipt.engineLoaded}, default=${receipt.engineDefault}, override=${receipt.engineOverride})`
);
}
}

async function assertDir(path: string, label: string): Promise<void> {
try {
const s = await stat(path);
Expand Down Expand Up @@ -124,6 +203,8 @@ async function main(): Promise<void> {
await assertViteOutput();
await assertNextOutput();
console.log('[packed-app:assert] all assertions passed');

emitLaneReceipts();
}

main().catch((err) => {
Expand Down
14 changes: 14 additions & 0 deletions e2e/packed-app/tsconfig.strict.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
// The strict-lib-check half of verify:packed step 6: every template file
// EXCEPT next.config.ts. That file's import — @animus-ui/next-plugin —
// declares its contract in next-owned types (next/dist/server/config-shared),
// and Next's internal d.ts are not strict-lib-clean by upstream design
// (unresolvable template placeholders, React-19-only types under React 18;
// Next mandates skipLibCheck:true for consumers). next.config.ts is checked
// by the full-template pass under tsconfig.json instead.
"extends": "./tsconfig.json",
"compilerOptions": {
"skipLibCheck": false
},
"include": ["src/**/*.ts", "src/**/*.tsx", "app/**/*.tsx", "vite.config.ts"]
}
28 changes: 27 additions & 1 deletion e2e/react-router-app/scripts/assert-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,39 @@ import {
layerBlock,
readAllConcat,
readRequiredCss,
writeLaneReceipt,
} from '@animus-ui/assertions';
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import routerManifest from 'react-router/package.json' with { type: 'json' };

const BUILD = resolve(import.meta.dirname, '..', 'build');
const APP_ROOT = resolve(import.meta.dirname, '..');
const BUILD = resolve(APP_ROOT, 'build');
// Wrangler serves build/client; semantic CSS must be proven there independently
// from build/server (canary delta: React Router served-client CSS proof).
const CLIENT_ROOT = resolve(BUILD, 'client');

function emitLaneReceipt(): void {
// Engine identity comes from writeLaneReceipt's retirement guard over the
// fixture config (openspec: retire-extract-v1) — never spelled here.
//
// hostVersion from the fixture's installed host, not the manifest range.
const receipt = writeLaneReceipt(
resolve(APP_ROOT, '.receipts', 'verify-assert-react-router.json'),
{
lane: '@animus-ui/react-router-app#verify:assert',
host: 'react-router',
hostVersion: routerManifest.version,
mode: 'production',
packageForm: 'workspace',
engineConfigPath: resolve(APP_ROOT, 'vite.config.ts'),
}
);
console.log(
`[react-router-app:assert] receipt → .receipts/verify-assert-react-router.json (engine=${receipt.engineLoaded}, default=${receipt.engineDefault}, override=${receipt.engineOverride})`
);
}

async function main(): Promise<void> {
const css = await readRequiredCss(
CLIENT_ROOT,
Expand Down Expand Up @@ -44,6 +68,8 @@ async function main(): Promise<void> {
console.log(
`[react-router-app:assert] served-client CSS (build/client) + ${jsFiles.length} JS — all assertions passed`
);

emitLaneReceipt();
}

main().catch((error) => {
Expand Down
7 changes: 3 additions & 4 deletions e2e/rollup-app/fixtures/error-root/ds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ export const theme = createTheme()
.addColors({ gray: { 100: '#f5f5f5' } })
.build();

const badGlow = createTransform(
'badGlow',
(value) => ({ boxShadow: String(value) }) as never
);
const badGlow = createTransform('badGlow', (value) => ({
boxShadow: String(value),
}));

export const { system: ds } = createSystem()
.addGroup('fx', {
Expand Down
Loading