mirror of
https://github.com/garrytan/gbrain.git
synced 2026-08-14 08:53:22 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2012b51220 |
@@ -56,7 +56,8 @@ that tuple lights up the `pack_upgrade_available` onboard check.
|
||||
│ gbrain onboard --check --explain shows per-cluster narrative │
|
||||
│ User reviews; if OK, runs: │
|
||||
│ gbrain jobs submit unify-types --allow-protected \ │
|
||||
│ --params '{"target_pack":"gbrain-base-v2"}' │
|
||||
│ --params '{"target_pack":"gbrain-base-v2","apply":true}' │
|
||||
│ (omit "apply":true for a dry-run; that is the default) │
|
||||
│ (Autopilot never auto-fires this; manual_only) │
|
||||
└──────────────────────────┬─────────────────────────────────────┘
|
||||
↓
|
||||
|
||||
@@ -76,7 +76,8 @@ gbrain onboard --check --explain # per-cluster narrative dry-run
|
||||
↓
|
||||
gbrain jobs submit unify-types \ # PROTECTED + manual_only
|
||||
--allow-protected \
|
||||
--params '{"target_pack":"gbrain-base-v2"}'
|
||||
--params '{"target_pack":"gbrain-base-v2","apply":true}'
|
||||
# omit "apply":true → dry-run (default)
|
||||
↓
|
||||
Handler runs 4 phases:
|
||||
┌─────────────────────────────────────┐
|
||||
@@ -127,7 +128,8 @@ For brains with substantial custom types that deserve their own canonical
|
||||
2. Edit your fork to add page_types + mapping_rules covering your
|
||||
custom domain.
|
||||
3. Target your fork: `gbrain jobs submit unify-types --allow-protected
|
||||
--params '{"target_pack":"my-pack"}'`
|
||||
--params '{"target_pack":"my-pack","apply":true}'` (omit `"apply":true`
|
||||
for a dry-run preview — that is the default)
|
||||
|
||||
Your fork can also declare `migration_from: {pack: gbrain-base-v2,
|
||||
version: "1.x"}` to register itself as a successor — future agents
|
||||
|
||||
@@ -2194,8 +2194,9 @@ export async function registerBuiltinHandlers(
|
||||
// migration that retypes 25K+ pages, creates alias rows, converts edge-
|
||||
// shaped pages to link rows, AND flips the active pack at end of run.
|
||||
// manual_only via src/core/onboard/render.ts:MANUAL_ONLY_PROTECTED_JOBS.
|
||||
// Operator path: `gbrain jobs submit unify-types --allow-protected --params
|
||||
// '{"target_pack":"gbrain-base-v2"}'`.
|
||||
// Dry-run preview: `gbrain jobs submit unify-types --allow-protected
|
||||
// --params '{"target_pack":"gbrain-base-v2"}'`; apply with
|
||||
// '{"target_pack":"gbrain-base-v2","apply":true}'.
|
||||
worker.register('unify-types', async (job) => {
|
||||
const { runUnifyTypes } = await import('../core/schema-pack/unify-types-handler.ts');
|
||||
const data = (job.data ?? {}) as {
|
||||
@@ -2213,7 +2214,11 @@ export async function registerBuiltinHandlers(
|
||||
} as unknown as import('../core/operations.ts').OperationContext;
|
||||
return await runUnifyTypes(ctx, {
|
||||
target_pack: data.target_pack,
|
||||
apply: data.apply ?? true,
|
||||
// #1575: default matches the handler interface's "Default false
|
||||
// (dry-run)" — a destructive one-shot migration must be opted into
|
||||
// with apply:true (the onboard remediation + the printed migration
|
||||
// command both carry it explicitly).
|
||||
apply: data.apply ?? false,
|
||||
sourceId: data.sourceId,
|
||||
onProgress: (msg: string) => {
|
||||
job.updateProgress({ phase: 'unify-types', message: msg }).catch(() => {});
|
||||
|
||||
@@ -251,7 +251,7 @@ async function renderPackUpgradeExplain(
|
||||
` Page-to-link: ${result.per_phase.page_to_link.would_convert} edges across ${result.per_phase.page_to_link.rules} rules\n` +
|
||||
` Page-to-alias: ${result.per_phase.page_to_alias.would_alias} aliases across ${result.per_phase.page_to_alias.rules} rules\n` +
|
||||
`\nRun the migration with:\n` +
|
||||
` gbrain jobs submit unify-types --allow-protected --params '${JSON.stringify({ target_pack: targetPack })}'\n`,
|
||||
` gbrain jobs submit unify-types --allow-protected --params '${JSON.stringify({ target_pack: targetPack, apply: true })}'\n`,
|
||||
);
|
||||
if (result.warnings.length > 0) {
|
||||
process.stdout.write(`\nWarnings:\n`);
|
||||
|
||||
@@ -426,7 +426,9 @@ export async function checkPackUpgradeAvailable(
|
||||
makeRemediationStep({
|
||||
id: 'onboard.pack_upgrade_' + successor.manifest.name,
|
||||
job: 'unify-types',
|
||||
params: { target_pack: successor.manifest.name },
|
||||
// #1575: the worker defaults `apply` to false (dry-run); a
|
||||
// remediation step is a consented apply, so carry it explicitly.
|
||||
params: { target_pack: successor.manifest.name, apply: true },
|
||||
severity: 'medium',
|
||||
est_seconds: 600, // ~10min on 186K-page brain (production proxy)
|
||||
est_usd_cost: 0, // pure SQL; no LLM spend
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* #1575 — the unify-types WORKER registration defaulted `apply` to true,
|
||||
* while the handler interface documents "Default false (dry-run)". The
|
||||
* canonical operator invocation —
|
||||
* gbrain jobs submit unify-types --allow-protected --params '{"target_pack":"X"}'
|
||||
* — therefore applied a one-shot destructive taxonomy migration on first
|
||||
* invocation, with no dry-run checkpoint.
|
||||
*
|
||||
* Behavioral pin: invoking the registered worker handler with job.data that
|
||||
* omits `apply` runs a DRY-RUN (no page mutation, active pack not flipped).
|
||||
* Consented apply paths pass `apply: true` explicitly (onboard remediation +
|
||||
* the printed migration command carry it).
|
||||
*/
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'bun:test';
|
||||
import { PGLiteEngine } from '../src/core/pglite-engine.ts';
|
||||
import { MinionWorker } from '../src/core/minions/worker.ts';
|
||||
import { registerBuiltinHandlers } from '../src/commands/jobs.ts';
|
||||
import { _resetPackCacheForTests } from '../src/core/schema-pack/registry.ts';
|
||||
|
||||
let engine: PGLiteEngine;
|
||||
|
||||
beforeAll(async () => {
|
||||
engine = new PGLiteEngine();
|
||||
await engine.connect({});
|
||||
await engine.initSchema();
|
||||
_resetPackCacheForTests();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await engine.disconnect();
|
||||
});
|
||||
|
||||
describe('unify-types worker default (#1575)', () => {
|
||||
it('omitting apply in job.data runs a dry-run, not a destructive apply', async () => {
|
||||
await engine.putPage('tweets/default-check', {
|
||||
title: 'tweets/default-check',
|
||||
type: 'tweet-single' as never,
|
||||
compiled_truth: 'body that is sufficiently long for any backstop guards we have in the codebase',
|
||||
timeline: '',
|
||||
frontmatter: {},
|
||||
source_path: 'tweets/default-check.md',
|
||||
});
|
||||
|
||||
const worker = new MinionWorker(engine, { concurrency: 1 });
|
||||
await registerBuiltinHandlers(worker, engine);
|
||||
const handler = (worker as unknown as {
|
||||
handlers: Map<string, (j: unknown) => Promise<unknown>>;
|
||||
}).handlers.get('unify-types');
|
||||
if (!handler) throw new Error('unify-types handler not registered');
|
||||
|
||||
const result = (await handler({
|
||||
id: 1,
|
||||
data: { target_pack: 'gbrain-base-v2' }, // no `apply` — the #1575 trap
|
||||
updateProgress: async () => {},
|
||||
})) as { apply: boolean; active_pack_flipped: boolean };
|
||||
|
||||
// Handler interface: "Apply mutations. Default false (dry-run)."
|
||||
expect(result.apply).toBe(false);
|
||||
expect(result.active_pack_flipped).toBe(false);
|
||||
|
||||
// The page was NOT retyped.
|
||||
const rows = await engine.executeRaw<{ type: string }>(
|
||||
`SELECT type FROM pages WHERE slug = 'tweets/default-check'`,
|
||||
);
|
||||
expect(rows[0]!.type).toBe('tweet-single');
|
||||
}, 60_000);
|
||||
});
|
||||
Reference in New Issue
Block a user