refactor: rename internal Fai* design system + fai_ helpers to chain
Some checks failed
Security / Security check (push) Failing after 2s

The Studio design system, widgets and helpers carried a Fai* / fai_
prefix (FaiSpace, FaiColors, FaiTheme, FaiLog, 17 fai_*.dart files, the
faiBinary* l10n keys). Studio is the Ch∆In product, so rename them to
Chain* / chain_ — carefully preserving English fail/failure/failed.
Also fix stale references: the 'fai' binary in l10n strings -> 'chain',
FAI_* env vars (FAI_BIN/DATA_DIR/MODULES_DIR/TODAY/BOOTSTRAP_TOKEN) ->
CHAIN_*, fai_platform -> fai_chain, fai_hub -> chain_hub. Vendor
security-hook tooling (FAI_BANNED_TERMS_FILE) + the .fai bundle ext left.
flutter analyze + test: clean (20 passed).

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-06-16 17:53:17 +02:00
parent 68d23ab7dd
commit 891acd2ba2
52 changed files with 1225 additions and 1225 deletions

View file

@ -1,36 +1,36 @@
// Unit tests for FaiLog the central error log that
// Unit tests for ChainLog the central error log that
// `~/.fai/logs/studio-errors.log` is the operator-visible
// surface of. The tests redirect the singleton at a temp file
// via `FaiLog.testPathOverride` so the real log under HOME is
// via `ChainLog.testPathOverride` so the real log under HOME is
// never touched.
import 'dart:convert';
import 'dart:io';
import 'package:chain_studio/data/fai_log.dart';
import 'package:chain_studio/data/chain_log.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('FaiLog', () {
group('ChainLog', () {
late Directory tmp;
late File logFile;
setUp(() {
tmp = Directory.systemTemp.createTempSync('fai_log_test_');
tmp = Directory.systemTemp.createTempSync('chain_log_test_');
logFile = File('${tmp.path}/studio-errors.log');
FaiLog.testPathOverride = logFile.path;
ChainLog.testPathOverride = logFile.path;
});
tearDown(() {
FaiLog.testPathOverride = null;
ChainLog.testPathOverride = null;
try {
tmp.deleteSync(recursive: true);
} catch (_) {/* best-effort */}
});
test('append writes one JSON-shaped line per event', () async {
await FaiLog.instance.error('flows.run', 'boom');
final lines = await FaiLog.instance.tail();
await ChainLog.instance.error('flows.run', 'boom');
final lines = await ChainLog.instance.tail();
expect(lines, hasLength(1));
final entry = jsonDecode(lines.single) as Map<String, Object?>;
expect(entry['source'], 'flows.run');
@ -40,22 +40,22 @@ void main() {
});
test('context field is preserved when supplied', () async {
await FaiLog.instance.error(
await ChainLog.instance.error(
'theme.plugin.load',
Exception('endpoint unreachable'),
context: 'capability=studio.theme.space',
);
final lines = await FaiLog.instance.tail();
final lines = await ChainLog.instance.tail();
final entry = jsonDecode(lines.single) as Map<String, Object?>;
expect(entry['context'], 'capability=studio.theme.space');
expect(entry['error'], contains('endpoint unreachable'));
});
test('tail returns oldest first', () async {
await FaiLog.instance.error('a', '1');
await FaiLog.instance.error('b', '2');
await FaiLog.instance.error('c', '3');
final lines = await FaiLog.instance.tail();
await ChainLog.instance.error('a', '1');
await ChainLog.instance.error('b', '2');
await ChainLog.instance.error('c', '3');
final lines = await ChainLog.instance.tail();
expect(lines, hasLength(3));
final sources = lines.map((l) => jsonDecode(l)['source']).toList();
expect(sources, ['a', 'b', 'c']);
@ -63,9 +63,9 @@ void main() {
test('tail caps output at maxLines and keeps the newest', () async {
for (var i = 0; i < 20; i++) {
await FaiLog.instance.error('burst', 'event-$i');
await ChainLog.instance.error('burst', 'event-$i');
}
final lines = await FaiLog.instance.tail(maxLines: 5);
final lines = await ChainLog.instance.tail(maxLines: 5);
expect(lines, hasLength(5));
final last = jsonDecode(lines.last) as Map<String, Object?>;
expect(last['error'], 'event-19');
@ -77,7 +77,7 @@ void main() {
// `.log.1` and a short fresh `.log`.
logFile.parent.createSync(recursive: true);
logFile.writeAsBytesSync(List.filled(257 * 1024, 0x20));
await FaiLog.instance.error('rotation', 'after-cap');
await ChainLog.instance.error('rotation', 'after-cap');
expect(File('${logFile.path}.1').existsSync(), isTrue);
final newSize = logFile.lengthSync();
expect(newSize, lessThan(2 * 1024));
@ -86,16 +86,16 @@ void main() {
});
test('path getter returns the override when set', () {
expect(FaiLog.instance.path, logFile.path);
expect(ChainLog.instance.path, logFile.path);
});
test('writes failing silently does not throw', () async {
// Redirect to a deliberately impossible path. The error()
// call must complete cleanly; the operator UI must never
// crash because the disk is full / read-only / missing.
FaiLog.testPathOverride = '/this/path/does/not/exist/ever/x.log';
ChainLog.testPathOverride = '/this/path/does/not/exist/ever/x.log';
await expectLater(
FaiLog.instance.error('bad.path', 'boom'),
ChainLog.instance.error('bad.path', 'boom'),
completes,
);
});

View file

@ -35,14 +35,14 @@ flutter test test/integration/
```
Prereq: a `fai` binary on PATH or at
`../fai_platform/target/release/fai`. The harness skips with a
`../fai_chain/target/release/chain`. The harness skips with a
clear message when neither exists, so this command does not
fail on a fresh checkout — it just reports skipped tests.
To get the binary:
```bash
cd ../fai_platform
cd ../fai_chain
cargo build --release --bin fai
```
@ -52,11 +52,11 @@ A cold `chain serve` spends its first ~30s building the
curated-model database and initialising SQLite migrations.
`HubFixture.start()` waits up to 60s by default; if your local
hub takes longer the first time, run `chain serve` once by hand
against any temp `FAI_DATA_DIR` to warm the per-user cargo /
against any temp `CHAIN_DATA_DIR` to warm the per-user cargo /
SBOM caches:
```bash
FAI_DATA_DIR=/tmp/fai_warmup chain serve --bind 127.0.0.1:0
CHAIN_DATA_DIR=/tmp/chain_warmup chain serve --bind 127.0.0.1:0
# wait for "hub started", Ctrl-C
```
@ -66,7 +66,7 @@ Subsequent integration-test runs are quick.
`.forgejo/workflows/ci.yml` doesn't run these yet. Adding them
needs an artifact-passing pattern: the platform build job
publishes `target/release/fai` as a CI artifact; the studio
publishes `target/release/chain` as a CI artifact; the studio
test job consumes it. Pattern is straightforward once we want
it; it's deferred because we don't yet have enough integration
tests to justify the CI runtime.

View file

@ -22,7 +22,7 @@
// flutter test test/integration/
//
// CI does not yet run these by default they need a built `fai`
// binary in PATH or in ../fai_platform/target/release/. Wire that
// binary in PATH or in ../chain_platform/target/release/. Wire that
// in via .forgejo/workflows/ci.yml once the platform CI publishes
// a Linux binary as an artifact for downstream jobs to consume.

View file

@ -72,7 +72,7 @@ class HubFixture {
if (skipIfBinaryMissing) {
markTestSkipped(
'Hub fixture skipped: no `fai` binary found on PATH or in '
'../fai_platform/target/release/fai. Build it with '
'../chain_platform/target/release/fai. Build it with '
'`cargo build --release` and re-run.',
);
return null;
@ -86,15 +86,15 @@ class HubFixture {
// Run as a child process. `serve --bind` lets us avoid the
// default 50051 (and any local daemon the developer happens
// to be running). FAI_DATA_DIR keeps the temp footprint
// to be running). CHAIN_DATA_DIR keeps the temp footprint
// contained no state leaks into ~/.fai.
final process = await Process.start(
binary,
['serve', '--bind', addr],
environment: {
...Platform.environment,
'FAI_DATA_DIR': tempDir.path,
'FAI_MODULES_DIR': '${tempDir.path}/modules',
'CHAIN_DATA_DIR': tempDir.path,
'CHAIN_MODULES_DIR': '${tempDir.path}/modules',
// Suppress info logs without a CLI flag the binary
// respects RUST_LOG for tracing-subscriber.
'RUST_LOG': 'warn',
@ -136,7 +136,7 @@ class HubFixture {
await tempDir.delete(recursive: true);
throw StateError(
'Hub did not become healthy within $readyTimeout. '
'Check `fai serve` works manually with FAI_DATA_DIR=$tempDir.',
'Check `fai serve` works manually with CHAIN_DATA_DIR=$tempDir.',
);
}
@ -172,8 +172,8 @@ class HubFixture {
}
final candidate = Platform.isWindows
? '../fai_platform/target/release/fai.exe'
: '../fai_platform/target/release/fai';
? '../chain_platform/target/release/fai.exe'
: '../chain_platform/target/release/fai';
final f = File(candidate);
if (await f.exists()) {
return f.absolute.path;

View file

@ -10,9 +10,9 @@
// flutter test test/integration/plugin_theme_test.dart
//
// Pre-reqs (handled by HubFixture's skip-when-missing path):
// - fai binary on PATH or at ../fai_platform/target/release/fai
// - fai binary on PATH or at ../chain_platform/target/release/fai
// - studio-theme-solarized plugin built to module.wasm in its
// fai_plugins dir.
// chain_plugins dir.
import 'dart:io';
@ -33,7 +33,7 @@ void main() {
// add network + bundle-fetch latency that doesn't
// belong in an in-process round-trip test.
final pluginSrc = Directory(
'../fai_plugins/studio-theme-solarized',
'../chain_plugins/studio-theme-solarized',
);
if (!await pluginSrc.exists()) {
markTestSkipped('studio-theme-solarized source not available');

View file

@ -7,7 +7,7 @@ import 'package:chain_studio/l10n/app_localizations.dart';
import 'package:chain_studio/theme/theme.dart';
/// Recreates the exact DocReaderSheet code path that
/// welcome.dart uses, including FaiTheme.markdownStyle, so a
/// welcome.dart uses, including ChainTheme.markdownStyle, so a
/// regression in either rootBundle, the markdown parser, or
/// the style sheet surfaces here instead of only at runtime
/// when an operator clicks a doc card.
@ -16,7 +16,7 @@ void main() {
testWidgets('DocReaderSheet renders all four german + english docs',
(tester) async {
final theme = FaiTheme.light();
final theme = ChainTheme.light();
for (final slug in ['architecture', 'security', 'audit', 'flows']) {
for (final locale in ['de', 'en']) {
final path = locale == 'de'
@ -39,7 +39,7 @@ void main() {
data: text,
padding: const EdgeInsets.all(8),
selectable: true,
styleSheet: FaiTheme.markdownStyle(theme),
styleSheet: ChainTheme.markdownStyle(theme),
),
),
));