@unson/brainbase-mcp 0.45.0 → 0.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -247,8 +247,8 @@ Publication is serialized by the GitHub workflow. Direct local `release:publish`
|
|
|
247
247
|
Dispatch the reviewed ref once when automatic publication needs recovery:
|
|
248
248
|
|
|
249
249
|
```bash
|
|
250
|
-
RELEASE_REF="<reviewed-
|
|
251
|
-
gh workflow run npm-publish.yml --ref
|
|
250
|
+
RELEASE_REF="<reviewed-main-commit>"
|
|
251
|
+
gh workflow run npm-publish.yml --ref main -f release_ref="$RELEASE_REF"
|
|
252
252
|
```
|
|
253
253
|
|
|
254
254
|
After the workflow succeeds, verify npm `gitHead`, `dist.integrity`, and dist-tag, and verify that the GitHub Release targets the reviewed release commit. If npm already contains the version, treat that version as immutable; fix the source, increment the version, and run the reviewed workflow again.
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "README.md",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "38f2f0f783bba47f83950cdc286dc03a3e0ee6190b0fbb67d72002e0402c5805"
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
-
"digest": "
|
|
31
|
+
"digest": "fa45bdca9bc397be78314d8f24c5a383637e6c016c61d7635f85fc50e3c04800"
|
|
32
32
|
}
|
|
@@ -127,7 +127,13 @@ export declare class CanonicalTaskPostgresRepository {
|
|
|
127
127
|
limit?: number;
|
|
128
128
|
}): Promise<(JsonRecord | null)[]>;
|
|
129
129
|
findByIdempotencyKey(key: string, storageScope?: string): Promise<JsonRecord | null>;
|
|
130
|
+
private buildInsert;
|
|
130
131
|
create(input: JsonRecord, storageScope?: string): Promise<JsonRecord>;
|
|
132
|
+
/**
|
|
133
|
+
* Create the Task and run its audit writer in one transaction. A throwing
|
|
134
|
+
* writer rolls back the Task insert; the caller maps the audit error.
|
|
135
|
+
*/
|
|
136
|
+
createWithAudit(input: JsonRecord, runAudit: (created: JsonRecord, client: CanonicalTaskPgClient) => Promise<void>, storageScope?: string): Promise<JsonRecord>;
|
|
131
137
|
update(taskId: string, patch: JsonRecord, storageScope?: string): Promise<JsonRecord>;
|
|
132
138
|
listPurposeLabels({ projectCode, assigneePersonId, limit }: {
|
|
133
139
|
projectCode: string;
|
|
@@ -418,8 +418,7 @@ export class CanonicalTaskPostgresRepository {
|
|
|
418
418
|
const result = await this.query('SELECT * FROM canonical_tasks WHERE idempotency_key = $1 LIMIT 1', [key]);
|
|
419
419
|
return this.normalize(result.rows[0] || null);
|
|
420
420
|
}
|
|
421
|
-
|
|
422
|
-
this.rejectStorageScope(storageScope);
|
|
421
|
+
buildInsert(input) {
|
|
423
422
|
const columns = [
|
|
424
423
|
['id', randomUUID()],
|
|
425
424
|
['title', input.title],
|
|
@@ -447,14 +446,55 @@ export class CanonicalTaskPostgresRepository {
|
|
|
447
446
|
['last_operation_key', input.last_operation_key ?? null],
|
|
448
447
|
['last_operation_fingerprint', input.last_operation_fingerprint ?? null],
|
|
449
448
|
];
|
|
450
|
-
|
|
449
|
+
return {
|
|
450
|
+
sql: `INSERT INTO canonical_tasks (${columns.map(([column]) => column).join(', ')})
|
|
451
451
|
VALUES (${columns.map(([, , cast = ''], index) => `$${index + 1}${cast}`).join(', ')})
|
|
452
452
|
ON CONFLICT (idempotency_key) DO NOTHING
|
|
453
|
-
RETURNING *`,
|
|
453
|
+
RETURNING *`,
|
|
454
|
+
values: columns.map(([, value]) => value),
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
async create(input, storageScope) {
|
|
458
|
+
this.rejectStorageScope(storageScope);
|
|
459
|
+
const { sql, values } = this.buildInsert(input);
|
|
460
|
+
const result = await this.query(sql, values);
|
|
454
461
|
if (result.rows[0])
|
|
455
462
|
return this.normalize(result.rows[0]);
|
|
456
463
|
return (await this.findByIdempotencyKey(String(input.idempotency_key)));
|
|
457
464
|
}
|
|
465
|
+
/**
|
|
466
|
+
* Create the Task and run its audit writer in one transaction. A throwing
|
|
467
|
+
* writer rolls back the Task insert; the caller maps the audit error.
|
|
468
|
+
*/
|
|
469
|
+
async createWithAudit(input, runAudit, storageScope) {
|
|
470
|
+
this.rejectStorageScope(storageScope);
|
|
471
|
+
if (typeof this.pool.connect !== 'function') {
|
|
472
|
+
throw this.unavailable(new Error('PostgreSQL transaction client is unavailable'));
|
|
473
|
+
}
|
|
474
|
+
const { sql, values } = this.buildInsert(input);
|
|
475
|
+
const client = await this.pool.connect();
|
|
476
|
+
try {
|
|
477
|
+
await client.query('BEGIN');
|
|
478
|
+
const result = await client.query(sql, values);
|
|
479
|
+
const row = result.rows[0] ?? (await client.query('SELECT * FROM canonical_tasks WHERE idempotency_key = $1 LIMIT 1', [input.idempotency_key])).rows[0];
|
|
480
|
+
if (!row)
|
|
481
|
+
throw storeError('task_store_unavailable', 'PostgreSQL Task store is unavailable', 503);
|
|
482
|
+
const created = this.normalize(row);
|
|
483
|
+
await runAudit(created, client);
|
|
484
|
+
await client.query('COMMIT');
|
|
485
|
+
return created;
|
|
486
|
+
}
|
|
487
|
+
catch (error) {
|
|
488
|
+
try {
|
|
489
|
+
await client.query('ROLLBACK');
|
|
490
|
+
}
|
|
491
|
+
catch { /* preserve transaction error */ }
|
|
492
|
+
throw error;
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
client.release?.();
|
|
496
|
+
}
|
|
497
|
+
}
|
|
458
498
|
async update(taskId, patch, storageScope) {
|
|
459
499
|
this.rejectStorageScope(storageScope);
|
|
460
500
|
// The public kernel marks an applied mutation with `_last_operation_key`
|
|
@@ -123,6 +123,11 @@ export interface CanonicalTaskAuditEntry extends JsonRecord {
|
|
|
123
123
|
changes: JsonRecord;
|
|
124
124
|
source_refs: unknown[];
|
|
125
125
|
}
|
|
126
|
+
export interface CanonicalTaskTransactionClient {
|
|
127
|
+
query(text: string, values?: unknown[]): Promise<{
|
|
128
|
+
rows: Array<Record<string, unknown>>;
|
|
129
|
+
}>;
|
|
130
|
+
}
|
|
126
131
|
export interface CanonicalTaskRepository {
|
|
127
132
|
/** Durable PostgreSQL repositories require a release readiness binding. */
|
|
128
133
|
requiresReadiness?: boolean;
|
|
@@ -131,11 +136,20 @@ export interface CanonicalTaskRepository {
|
|
|
131
136
|
get(taskId: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
132
137
|
findByIdempotencyKey(key: string, storageScope?: string): Promise<CanonicalTaskRecord | null>;
|
|
133
138
|
create(input: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
139
|
+
/**
|
|
140
|
+
* Create a Task and run the audit writer inside one database transaction.
|
|
141
|
+
* A throwing writer must roll back the Task insert.
|
|
142
|
+
*/
|
|
143
|
+
createWithAudit?(input: JsonRecord, runAudit: (created: CanonicalTaskRecord, client: CanonicalTaskTransactionClient) => Promise<void>, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
134
144
|
update(taskId: string, patch: JsonRecord, storageScope?: string): Promise<CanonicalTaskRecord>;
|
|
135
145
|
delete(taskId: string, expectedVersion: number, storageScope?: string): Promise<void>;
|
|
136
146
|
}
|
|
137
147
|
export interface CanonicalTaskAuditRepository {
|
|
138
|
-
|
|
148
|
+
/** Declare support before offering the same-transaction audit contract. */
|
|
149
|
+
supportsTransactionClient?: boolean;
|
|
150
|
+
upsertAuditLog(entry: CanonicalTaskAuditEntry, options?: {
|
|
151
|
+
client?: CanonicalTaskTransactionClient;
|
|
152
|
+
}): Promise<void>;
|
|
139
153
|
}
|
|
140
154
|
export interface CanonicalTaskAuthorizationRequest {
|
|
141
155
|
action: CanonicalTaskAction;
|
|
@@ -283,6 +297,7 @@ export declare class CanonicalTaskService {
|
|
|
283
297
|
private requireExpectedVersion;
|
|
284
298
|
private assertVersion;
|
|
285
299
|
private executeOperation;
|
|
300
|
+
private buildAuditEntry;
|
|
286
301
|
private audit;
|
|
287
302
|
private read;
|
|
288
303
|
private normalizeResponse;
|
|
@@ -476,18 +476,29 @@ export class CanonicalTaskService {
|
|
|
476
476
|
await this.authorize('read', normalizedContext, existing);
|
|
477
477
|
return { recovered: true, result: this.normalizeResponse(existing) };
|
|
478
478
|
};
|
|
479
|
+
const transactionalAudit = typeof this.repository.createWithAudit === 'function'
|
|
480
|
+
&& this.auditRepository?.supportsTransactionClient === true;
|
|
481
|
+
let auditedInTransaction = false;
|
|
479
482
|
const run = async () => {
|
|
480
483
|
const recovered = await recover();
|
|
481
484
|
if (recovered.recovered && recovered.result)
|
|
482
485
|
return recovered.result;
|
|
483
486
|
await this.assertMutationReady();
|
|
484
|
-
const
|
|
487
|
+
const input = {
|
|
485
488
|
...payload,
|
|
486
489
|
status: 'pending',
|
|
487
490
|
version: 1,
|
|
488
491
|
idempotency_key: operation,
|
|
489
492
|
payload_fingerprint: inputFingerprint,
|
|
490
|
-
}
|
|
493
|
+
};
|
|
494
|
+
if (transactionalAudit) {
|
|
495
|
+
return this.read(() => this.repository.createWithAudit(input, async (created, client) => {
|
|
496
|
+
const normalizedCreated = this.normalizeResponse(created);
|
|
497
|
+
await this.audit(normalizedContext, 'canonical_task.created', normalizedCreated, { before: null, after: normalizedCreated }, operation, inputFingerprint, client);
|
|
498
|
+
auditedInTransaction = true;
|
|
499
|
+
}, ...this.scopeArgs(normalizedContext)));
|
|
500
|
+
}
|
|
501
|
+
const created = await this.read(() => this.repository.create(input, ...this.scopeArgs(normalizedContext)));
|
|
491
502
|
return this.normalizeResponse(created);
|
|
492
503
|
};
|
|
493
504
|
const result = await this.executeOperation({
|
|
@@ -501,10 +512,12 @@ export class CanonicalTaskService {
|
|
|
501
512
|
if (normalizedContext.storageScope !== undefined)
|
|
502
513
|
await this.authorize('read', normalizedContext, result);
|
|
503
514
|
const normalized = this.normalizeResponse(result);
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
515
|
+
if (!auditedInTransaction) {
|
|
516
|
+
// Audit each invocation, including a replay/recovery. The deterministic
|
|
517
|
+
// entry id lets an upsert sink collapse retries without losing evidence
|
|
518
|
+
// that the audit write was attempted again.
|
|
519
|
+
await this.audit(normalizedContext, 'canonical_task.created', normalized, { before: null, after: normalized }, operation, inputFingerprint);
|
|
520
|
+
}
|
|
508
521
|
return normalized;
|
|
509
522
|
}
|
|
510
523
|
async updateTask(taskId, input, expectedVersionOrContext, maybeContext) {
|
|
@@ -1054,9 +1067,9 @@ export class CanonicalTaskService {
|
|
|
1054
1067
|
? this.operationRepository.execute(request)
|
|
1055
1068
|
: request.run();
|
|
1056
1069
|
}
|
|
1057
|
-
|
|
1070
|
+
buildAuditEntry(context, action, task, changes, operationKey, operationFingerprint) {
|
|
1058
1071
|
if (!this.auditRepository)
|
|
1059
|
-
return;
|
|
1072
|
+
return null;
|
|
1060
1073
|
const actor = context.auditPrincipal
|
|
1061
1074
|
? normalizeCanonicalTaskPrincipal(context.auditPrincipal)
|
|
1062
1075
|
: context.principal;
|
|
@@ -1078,8 +1091,14 @@ export class CanonicalTaskService {
|
|
|
1078
1091
|
changes,
|
|
1079
1092
|
source_refs: Array.isArray(task.source_refs) ? task.source_refs : [],
|
|
1080
1093
|
};
|
|
1094
|
+
return entry;
|
|
1095
|
+
}
|
|
1096
|
+
async audit(context, action, task, changes, operationKey, operationFingerprint, client) {
|
|
1097
|
+
const entry = this.buildAuditEntry(context, action, task, changes, operationKey, operationFingerprint);
|
|
1098
|
+
if (!entry)
|
|
1099
|
+
return;
|
|
1081
1100
|
try {
|
|
1082
|
-
await this.auditRepository.upsertAuditLog(entry);
|
|
1101
|
+
await this.auditRepository.upsertAuditLog(entry, client ? { client } : undefined);
|
|
1083
1102
|
}
|
|
1084
1103
|
catch {
|
|
1085
1104
|
throw new CanonicalTaskError('task_audit_unavailable', 'Task audit is unavailable', 503);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.0",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -620,5 +620,5 @@
|
|
|
620
620
|
"url": "https://github.com/Unson-LLC/brainbase/issues"
|
|
621
621
|
},
|
|
622
622
|
"homepage": "https://github.com/Unson-LLC/brainbase#readme",
|
|
623
|
-
"gitHead": "
|
|
623
|
+
"gitHead": "955bf4ef5249f607c4983f86c05ecae10ce7a67c"
|
|
624
624
|
}
|