@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-develop-commit>"
251
- gh workflow run npm-publish.yml --ref develop -f release_ref="$RELEASE_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": "26eacd2a057380b4fd386b2176bd6e790a41c0bb8d684857c53bb5cd947e2d28"
28
+ "sha256": "38f2f0f783bba47f83950cdc286dc03a3e0ee6190b0fbb67d72002e0402c5805"
29
29
  }
30
30
  ],
31
- "digest": "38bf5aff53993b1ee737071ca7952d44e36ac2e5ed3a7adcac65fdd86e8954af"
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
- async create(input, storageScope) {
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
- const result = await this.query(`INSERT INTO canonical_tasks (${columns.map(([column]) => column).join(', ')})
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 *`, columns.map(([, value]) => value));
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
- upsertAuditLog(entry: CanonicalTaskAuditEntry): Promise<void>;
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 created = await this.read(() => this.repository.create({
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
- }, ...this.scopeArgs(normalizedContext)));
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
- // Audit each invocation, including a replay/recovery. The deterministic
505
- // entry id lets an upsert sink collapse retries without losing evidence
506
- // that the audit write was attempted again.
507
- await this.audit(normalizedContext, 'canonical_task.created', normalized, { before: null, after: normalized }, operation, inputFingerprint);
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
- async audit(context, action, task, changes, operationKey, operationFingerprint) {
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.45.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": "10ff1144069d177b6a1c3857a69182513ff55372"
623
+ "gitHead": "955bf4ef5249f607c4983f86c05ecae10ce7a67c"
624
624
  }