mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-30 19:49:14 +00:00
fix(extract): make receipt shortRunId canonical under slugifySegment (#3443)
shortRunId() truncated run ids to their first 8 chars, so propose_takes
run ids ('propose-<timestamp>-<uuid>') shortened to 'propose-' with a
trailing hyphen. slugifySegment() strips boundary hyphens during repo
sync, so the DB receipt slug and its Git-backed markdown slug disagreed
— writing the receipt through to the system-of-record repo created a
normalized sibling/collision instead of materializing the existing page.
shortRunId now trims boundary hyphens after truncation (invariant:
slugifySegment(shortRunId(x)) === shortRunId(x)), with a non-empty
fallback for pathological all-separator prefixes. All other run-id
families (atoms-, efacts-, concepts-, ecf-) are unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
6920744dd8
commit
281dc557c3
@@ -85,11 +85,21 @@ const RUN_ID_SHORT_LEN = 8;
|
||||
/**
|
||||
* Truncate a run id to the standard 8-char short form used in slug
|
||||
* paths. Idempotent — passing an already-short id returns it unchanged.
|
||||
* Non-hex / non-alphanumeric chars survive (op-checkpoint ids may
|
||||
* include dashes or other separators).
|
||||
* Non-hex / non-alphanumeric chars survive INSIDE the short form
|
||||
* (op-checkpoint ids may include dashes or other separators), but
|
||||
* boundary hyphens are trimmed (#3443): `slugifySegment()` strips
|
||||
* leading/trailing hyphens during repo sync, so a short form like
|
||||
* 'propose-' (from propose-<timestamp> run ids) made the DB receipt
|
||||
* slug and its Git-backed slug disagree — writing the receipt through
|
||||
* to the repo created a normalized sibling instead of materializing
|
||||
* the existing page. Invariant: slugifySegment(shortRunId(x)) ===
|
||||
* shortRunId(x) for slug-safe run ids.
|
||||
*/
|
||||
export function shortRunId(runId: string): string {
|
||||
return runId.slice(0, RUN_ID_SHORT_LEN);
|
||||
// ponytail: truncation-based discrimination is only as good as the run id's
|
||||
// first 8 chars; families that need per-run uniqueness must front-load it.
|
||||
const short = runId.slice(0, RUN_ID_SHORT_LEN).replace(/^-+|-+$/g, '');
|
||||
return short || (runId ? 'run' : '');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
writeReceipt,
|
||||
type ExtractReceiptInput,
|
||||
} from '../../src/core/extract/receipt-writer.ts';
|
||||
import { slugifySegment } from '../../src/core/sync.ts';
|
||||
|
||||
const BASE_INPUT: ExtractReceiptInput = {
|
||||
kind: 'facts.conversation',
|
||||
@@ -81,6 +82,31 @@ describe('shortRunId / dateFromIso — pure helpers', () => {
|
||||
expect(shortRunId('op_check_abc')).toBe('op_check');
|
||||
});
|
||||
|
||||
// #3443 — a short form ending in '-' (e.g. propose-<timestamp> run ids)
|
||||
// desynced the DB receipt slug from its Git-backed slug: slugifySegment()
|
||||
// strips boundary hyphens during repo sync, so the write-through created a
|
||||
// normalized sibling instead of materializing the existing page.
|
||||
test('shortRunId is canonical under slugifySegment for every receipt-producing run-id family (#3443)', () => {
|
||||
const familyRunIds = [
|
||||
'propose-20260724103000-ab12cd34', // cycle/propose-takes.ts
|
||||
`atoms-${Date.now().toString(36)}-pers`, // cycle/extract-atoms.ts
|
||||
`efacts-${Date.now().toString(36)}-pers`, // cycle/extract-facts.ts
|
||||
`concepts-${Date.now().toString(36)}`, // cycle/synthesize-concepts.ts
|
||||
`ecf-${Date.now().toString(36)}-pers`, // extract-conversation-facts.ts
|
||||
];
|
||||
for (const runId of familyRunIds) {
|
||||
const short = shortRunId(runId);
|
||||
expect(slugifySegment(short)).toBe(short);
|
||||
expect(short.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
test('shortRunId trims boundary hyphens introduced by truncation', () => {
|
||||
expect(shortRunId('propose-20260724103000-ab12cd34')).toBe('propose');
|
||||
// Pathological all-separator prefix still yields a non-empty segment.
|
||||
expect(shortRunId('--------tail')).toBe('run');
|
||||
});
|
||||
|
||||
test('dateFromIso extracts YYYY-MM-DD prefix', () => {
|
||||
expect(dateFromIso('2026-05-27T14:30:00Z')).toBe('2026-05-27');
|
||||
expect(dateFromIso('2026-05-27T14:30:00.123456Z')).toBe('2026-05-27');
|
||||
|
||||
Reference in New Issue
Block a user