@unson/brainbase-mcp 0.20.0 → 0.21.1

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.
@@ -27,6 +27,8 @@ export interface CanonicalTaskRecord extends JsonRecord {
27
27
  completed_at?: string | null;
28
28
  source_refs?: unknown[];
29
29
  project_codes?: string[];
30
+ /** What the work is for, in a few words (one per task); null when unlabelled. */
31
+ purpose_label?: string | null;
30
32
  created_at?: string | null;
31
33
  updated_at?: string | null;
32
34
  web_url?: string | null;
@@ -41,6 +43,7 @@ export interface CanonicalTaskCreateInput extends JsonRecord {
41
43
  review_at?: string | null;
42
44
  source_refs?: unknown[];
43
45
  project_codes?: unknown;
46
+ purpose_label?: string | null;
44
47
  }
45
48
  export interface CanonicalTaskUpdateInput extends JsonRecord {
46
49
  title?: string;
@@ -52,6 +55,7 @@ export interface CanonicalTaskUpdateInput extends JsonRecord {
52
55
  review_at?: string | null;
53
56
  source_refs?: unknown[];
54
57
  project_codes?: unknown;
58
+ purpose_label?: string | null;
55
59
  }
56
60
  export interface CanonicalTaskListFilters extends JsonRecord {
57
61
  statuses?: unknown;
@@ -35,6 +35,7 @@ const CREATE_FIELDS = new Set([
35
35
  'review_at',
36
36
  'source_refs',
37
37
  'project_codes',
38
+ 'purpose_label',
38
39
  ]);
39
40
  const MUTABLE_FIELDS = new Set([
40
41
  'expected_version',
@@ -47,6 +48,7 @@ const MUTABLE_FIELDS = new Set([
47
48
  'review_at',
48
49
  'source_refs',
49
50
  'project_codes',
51
+ 'purpose_label',
50
52
  ]);
51
53
  function fail(code, message, status = 400, details = {}) {
52
54
  throw new CanonicalTaskError(code, message, status, details);
@@ -72,6 +74,11 @@ function normalizeString(value, field, options = {}) {
72
74
  }
73
75
  return normalized;
74
76
  }
77
+ /** A purpose label: a single line of at most 30 characters; empty or null clears it. */
78
+ const MAX_PURPOSE_LABEL_LENGTH = 30;
79
+ function normalizePurposeLabel(value) {
80
+ return normalizeString(value, 'purpose_label', { max: MAX_PURPOSE_LABEL_LENGTH });
81
+ }
75
82
  function normalizeIsoDate(value, field) {
76
83
  const text = normalizeString(value, field);
77
84
  if (text == null)
@@ -627,6 +634,7 @@ export class CanonicalTaskService {
627
634
  const priority = input.priority == null || input.priority === '' ? 'medium' : String(input.priority);
628
635
  if (!isCanonicalTaskPriority(priority))
629
636
  fail('validation_error', 'priority is invalid', 400, { field: 'priority' });
637
+ const purposeLabel = normalizePurposeLabel(input.purpose_label);
630
638
  return {
631
639
  title,
632
640
  description: normalizeString(input.description, 'description', { max: 10000, multiline: true }),
@@ -637,6 +645,8 @@ export class CanonicalTaskService {
637
645
  review_at: normalizeIsoDate(input.review_at, 'review_at'),
638
646
  source_refs: normalizeSourceRefs(input.source_refs) ?? [],
639
647
  project_codes: normalizeProjectCodes(input.project_codes) ?? [],
648
+ // Only a label that is there is written, so a store without the column still accepts unlabelled work.
649
+ ...(purposeLabel ? { purpose_label: purposeLabel } : {}),
640
650
  };
641
651
  }
642
652
  normalizeUpdateInput(input) {
@@ -668,6 +678,8 @@ export class CanonicalTaskService {
668
678
  patch.source_refs = normalizeSourceRefs(input.source_refs) ?? [];
669
679
  if ('project_codes' in input)
670
680
  patch.project_codes = normalizeProjectCodes(input.project_codes) ?? [];
681
+ if ('purpose_label' in input)
682
+ patch.purpose_label = normalizePurposeLabel(input.purpose_label);
671
683
  if (Object.keys(patch).length === 0)
672
684
  fail('validation_error', 'Task update must include at least one mutable field');
673
685
  return patch;
@@ -843,6 +855,8 @@ export class CanonicalTaskService {
843
855
  response.source_refs = [];
844
856
  if (response.project_codes === undefined)
845
857
  response.project_codes = [];
858
+ if (response.purpose_label === undefined)
859
+ response.purpose_label = null;
846
860
  return response;
847
861
  }
848
862
  }
@@ -33,7 +33,7 @@ export declare const GRAPH_FOUNDATION_LIST_SQL = "\nSELECT\n history.entity_id,
33
33
  * Unlike the Foundation list, this query requires a canonical project join so
34
34
  * a missing project membership cannot be interpreted as a selected-scope hit.
35
35
  */
36
- export declare const GRAPH_PHILOSOPHY_LIST_SQL = "\nSELECT\n history.entity_id,\n history.entity_type,\n history.revision,\n history.payload,\n history.project_id,\n history.role_min,\n history.sensitivity,\n history.lifecycle_status,\n history.storage_digest,\n history.captured_at,\n (\n history.storage_digest = 'sha256:' ||\n encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')\n ) AS storage_digest_valid,\n current_entity.id AS current_entity_id,\n current_entity.entity_type AS current_entity_type,\n current_entity.payload AS current_payload,\n current_entity.project_id AS current_project_id,\n current_entity.role_min AS current_role_min,\n current_entity.sensitivity AS current_sensitivity,\n current_entity.lifecycle_status AS current_lifecycle_status,\n projects.code AS current_project_code,\n true AS current_visible\nFROM public.graph_entities AS current_entity\nJOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nLEFT JOIN LATERAL (\n SELECT history.*\n FROM public.graph_foundation_revisions AS history\n WHERE history.entity_id = current_entity.id\n AND history.entity_type = current_entity.entity_type\n ORDER BY history.revision::numeric DESC\n LIMIT 1\n) AS history ON true\nWHERE current_entity.entity_type = 'philosophy'\n AND projects.code = $1\nORDER BY current_entity.id\n";
36
+ export declare const GRAPH_PHILOSOPHY_LIST_SQL = "\nSELECT\n history.entity_id,\n history.entity_type,\n history.revision,\n history.payload,\n history.project_id,\n history.role_min,\n history.sensitivity,\n history.lifecycle_status,\n history.storage_digest,\n history.captured_at,\n (\n history.storage_digest = 'sha256:' ||\n encode(sha256(convert_to(history.payload::text, 'UTF8')), 'hex')\n ) AS storage_digest_valid,\n current_entity.id AS current_entity_id,\n current_entity.entity_type AS current_entity_type,\n current_entity.payload AS current_payload,\n current_entity.project_id AS current_project_id,\n current_entity.role_min AS current_role_min,\n current_entity.sensitivity AS current_sensitivity,\n current_entity.lifecycle_status AS current_lifecycle_status,\n projects.code AS current_project_code,\n true AS current_visible\nFROM public.graph_entities AS current_entity\nJOIN public.projects AS projects\n ON projects.id = current_entity.project_id\nLEFT JOIN LATERAL (\n SELECT history.*\n FROM public.graph_foundation_revisions AS history\n WHERE history.entity_id = current_entity.id\n AND history.entity_type = current_entity.entity_type\n ORDER BY history.revision::numeric DESC\n LIMIT 1\n) AS history ON true\nWHERE current_entity.entity_type = 'philosophy'\n AND projects.code = $1\n AND current_entity.payload ? 'judgmentApplicability'\nORDER BY current_entity.id\n";
37
37
  /** Flat row shape returned by GRAPH_FOUNDATION_READ_SQL. */
38
38
  export interface GraphFoundationHistoryRow {
39
39
  readonly entity_id?: unknown;
@@ -187,6 +187,7 @@ LEFT JOIN LATERAL (
187
187
  ) AS history ON true
188
188
  WHERE current_entity.entity_type = 'philosophy'
189
189
  AND projects.code = $1
190
+ AND current_entity.payload ? 'judgmentApplicability'
190
191
  ORDER BY current_entity.id
191
192
  `;
192
193
  const FOUNDATION_TYPES = ['objective', 'variable', 'model', 'constraint'];
@@ -1,6 +1,7 @@
1
1
  import { JudgmentValueProofJournalCache } from './judgment-value-proof-review.js';
2
2
  import { type GraphVocabularyTerm, type LocalWebExtension, type LocalWebModuleContext } from './local-web-host.js';
3
3
  import type { GraphFileV2 } from './types.js';
4
+ export * from './world-work.js';
4
5
  /**
5
6
  * World view (ledger P17, adopted 2026-10-04): the home's upper layer.
6
7
  *
@@ -177,4 +178,3 @@ export declare function readJudgmentPlaces(journalRoot: string, options?: {
177
178
  export declare function createWorldExtension(options?: {
178
179
  readonly vocabulary?: WorldVocabularyConfig;
179
180
  }): LocalWebExtension;
180
- export {};
@@ -5,6 +5,8 @@ import { projectOrganizationGraph, projectVocabularyTerms } from './organization
5
5
  import { readLocalGraphState } from './local-web-host.js';
6
6
  import { loadPersonalOs } from './ssot.js';
7
7
  import { requestUrl, writeJson } from './local-web-security.js';
8
+ import { WORLD_WORK_VERSION } from './world-work.js';
9
+ export * from './world-work.js';
8
10
  /**
9
11
  * World view (ledger P17, adopted 2026-10-04): the home's upper layer.
10
12
  *
@@ -325,6 +327,13 @@ function createWorldModule(context, read) {
325
327
  writeJson(response, 200, await read());
326
328
  return true;
327
329
  }
330
+ // This Mac keeps no Canonical Task store, so the work inside a city is not connected here
331
+ // (an organization web answers these from its Task API). Said as a state, never as no work.
332
+ if (path === `/api/extensions/${WORLD_EXTENSION_ID}/work-summary` || /^\/api\/extensions\/world\/businesses\/[^/]+\/work$/u.test(path)) {
333
+ response.setHeader('Cache-Control', 'no-store');
334
+ writeJson(response, 200, { status: 'not_connected', version: WORLD_WORK_VERSION, reason: 'task_store_not_connected' });
335
+ return true;
336
+ }
328
337
  if (path === `/api/extensions/${WORLD_EXTENSION_ID}/judgment-places`) {
329
338
  response.setHeader('Cache-Control', 'no-store');
330
339
  writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readJudgmentPlaces(context.journalRoot, { cache: context.journalCache, workspaces })) });
@@ -338,7 +347,7 @@ export function createWorldExtension(options = {}) {
338
347
  return {
339
348
  id: WORLD_EXTENSION_ID,
340
349
  uiDir: fileURLToPath(new URL('../ui/world/', import.meta.url)),
341
- uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js'],
350
+ uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js'],
342
351
  screenEntry: 'world-view.js',
343
352
  // P17: the world sits above the home and opens first.
344
353
  navPosition: 'first',
@@ -0,0 +1,248 @@
1
+ /**
2
+ * The work inside a city of the world (story-world-work-sites-and-gaps-v1).
3
+ *
4
+ * A pure projection of what the host read with the member's grants: the
5
+ * business's Canonical Tasks (Task API) and, from the organization Graph, its
6
+ * people, decisions and relations. Two things are kept apart throughout:
7
+ *
8
+ * - the work as recorded (A): a task's `status` and `waiting_on`, as written;
9
+ * - what we can see of it (B): gaps found only from structured fields
10
+ * (an empty assignee field, no source link, a review date passed), never
11
+ * from reading the free text as a judgment of the work.
12
+ *
13
+ * A gap says what is known, what is not connected or not confirmed, where it
14
+ * comes from and when, and what to check next. Nothing here concludes that
15
+ * work has stopped, and nothing unreadable becomes zero.
16
+ */
17
+ export declare const WORLD_WORK_VERSION: "world-work.v1";
18
+ export type WorldWorkReadState = 'complete' | 'partial' | 'failed' | 'forbidden' | 'not_connected';
19
+ export interface WorldWorkRead<T = unknown> {
20
+ readonly state: WorldWorkReadState;
21
+ readonly read_at?: string | null;
22
+ readonly reason?: string | null;
23
+ readonly items?: readonly T[];
24
+ }
25
+ export type WorldWorkStatus = 'pending' | 'in_progress' | 'waiting' | 'completed' | 'cancelled';
26
+ export type WorldWorkGapKind = 'assignee_unlinked_mentioned' | 'assignee_unrecorded' | 'source_unlinked' | 'outcome_unlinked' | 'review_overdue';
27
+ export declare const WORLD_WORK_GAP_KINDS: readonly WorldWorkGapKind[];
28
+ export declare const WORLD_WORK_GAP_LABELS: Readonly<Record<WorldWorkGapKind, string>>;
29
+ export type WorldWorkLink = 'recorded' | 'inferred' | 'unreadable';
30
+ export interface WorldWorkSource {
31
+ readonly system: 'task_api' | 'organization_graph';
32
+ readonly record_id: string;
33
+ readonly fields: readonly string[];
34
+ }
35
+ export interface WorldWorkPersonRef {
36
+ readonly person_id: string | null;
37
+ readonly name: string;
38
+ }
39
+ export interface WorldWorkGap {
40
+ readonly kind: WorldWorkGapKind;
41
+ readonly label: string;
42
+ /** What the records do say. */
43
+ readonly fact: string;
44
+ /** What is not connected or not confirmed, and how far that reaches. */
45
+ readonly unknown: string;
46
+ /** What to check next to see further. Never a task to create. */
47
+ readonly next_check: string;
48
+ /** Who could answer, when the records name someone; null when they do not. */
49
+ readonly check_with: WorldWorkPersonRef | null;
50
+ readonly source: WorldWorkSource;
51
+ /** The newest check the record itself notes (a dated note in its text), else its last update. */
52
+ readonly checked_at: string | null;
53
+ readonly checked_at_basis: 'text_note' | 'updated_at' | null;
54
+ }
55
+ export interface WorldWorkPersonLink extends WorldWorkPersonRef {
56
+ readonly link: Exclude<WorldWorkLink, 'unreadable'>;
57
+ readonly basis: 'assignee_field' | 'text_person_id' | 'text_full_name';
58
+ /** The text puts 「担当」 right before the person (e.g. 実担当は…). Still not the assignee field. */
59
+ readonly stated_as_assignee?: boolean;
60
+ }
61
+ /** Words in the text that match more than one Graph person: left undecided, never picked. */
62
+ export interface WorldWorkAmbiguousMention {
63
+ readonly text: string;
64
+ readonly candidates: readonly WorldWorkPersonRef[];
65
+ }
66
+ export interface WorldWorkSite {
67
+ readonly task_id: string;
68
+ readonly title: string;
69
+ /** What the work is for, as the task record states it (`purpose_label`); null when it states none. */
70
+ readonly purpose_label: string | null;
71
+ readonly project_codes: readonly string[];
72
+ /** Codes of other visible businesses this task also belongs to (work across a boundary). */
73
+ readonly other_business_codes: readonly string[];
74
+ readonly work: {
75
+ readonly status: WorldWorkStatus | null;
76
+ readonly label: string;
77
+ readonly open: boolean;
78
+ readonly waiting_on: string | null;
79
+ readonly due_at: string | null;
80
+ readonly past_due: boolean;
81
+ readonly review_at: string | null;
82
+ readonly created_at: string | null;
83
+ readonly updated_at: string | null;
84
+ readonly completed_at: string | null;
85
+ };
86
+ readonly people: readonly WorldWorkPersonLink[];
87
+ readonly ambiguous_mentions: readonly WorldWorkAmbiguousMention[];
88
+ readonly source_refs: readonly {
89
+ readonly type: string | null;
90
+ readonly url: string | null;
91
+ readonly label: string;
92
+ }[];
93
+ /** The completion condition the text states, quoted; null when it states none. */
94
+ readonly expected_outcome: string | null;
95
+ /** Dated check notes in the text, newest first. */
96
+ readonly audit_notes: readonly {
97
+ readonly date: string;
98
+ readonly text: string;
99
+ }[];
100
+ readonly gaps: readonly WorldWorkGap[];
101
+ readonly description: string | null;
102
+ readonly source: WorldWorkSource & {
103
+ readonly version: number | null;
104
+ readonly web_url: string | null;
105
+ };
106
+ }
107
+ export interface WorldWorkRelation {
108
+ readonly link: WorldWorkLink;
109
+ /** What the relation is, in plain words. */
110
+ readonly label: string;
111
+ readonly kind: string;
112
+ readonly counterpart: {
113
+ readonly id: string | null;
114
+ readonly type: string | null;
115
+ readonly name: string;
116
+ } | null;
117
+ /** The counterpart is another business of this world (drawn as a road between cities). */
118
+ readonly business_code: string | null;
119
+ readonly direction: 'incoming' | 'outgoing' | null;
120
+ readonly basis: string;
121
+ readonly source: WorldWorkSource | null;
122
+ /** Tasks that carry this relation (people named in tasks). */
123
+ readonly task_ids: readonly string[];
124
+ }
125
+ export interface WorldWorkPolicy {
126
+ readonly decision_id: string;
127
+ readonly title: string;
128
+ readonly decided_at: string | null;
129
+ readonly link: Exclude<WorldWorkLink, 'unreadable'>;
130
+ readonly basis: string;
131
+ readonly scope_code: string | null;
132
+ }
133
+ export interface WorldWorkSummary {
134
+ readonly business_code: string;
135
+ readonly state: WorldWorkReadState;
136
+ readonly read_at: string | null;
137
+ readonly reason: string | null;
138
+ /** Counts only when the tasks were read; null otherwise (not zero). */
139
+ readonly open: number | null;
140
+ readonly total: number | null;
141
+ readonly by_status: Readonly<Partial<Record<WorldWorkStatus | 'unknown', number>>> | null;
142
+ /** For each kind of gap, the open tasks that show it: the reason behind every count. */
143
+ readonly gaps: Readonly<Partial<Record<WorldWorkGapKind, readonly string[]>>> | null;
144
+ /** Open tasks with at least one gap. */
145
+ readonly needs_check: readonly string[] | null;
146
+ }
147
+ export interface WorldWorkReads {
148
+ readonly tasks: {
149
+ readonly state: WorldWorkReadState;
150
+ readonly read_at: string | null;
151
+ readonly reason: string | null;
152
+ readonly count: number | null;
153
+ };
154
+ readonly persons: {
155
+ readonly state: WorldWorkReadState;
156
+ readonly reason: string | null;
157
+ };
158
+ readonly decisions: {
159
+ readonly state: WorldWorkReadState;
160
+ readonly reason: string | null;
161
+ };
162
+ readonly relations: {
163
+ readonly state: WorldWorkReadState;
164
+ readonly reason: string | null;
165
+ };
166
+ }
167
+ export type WorldWorkResponse = {
168
+ readonly status: 'ok';
169
+ readonly version: typeof WORLD_WORK_VERSION;
170
+ readonly as_of: string;
171
+ readonly business: {
172
+ readonly id: string;
173
+ readonly code: string;
174
+ readonly name: string;
175
+ };
176
+ readonly reads: WorldWorkReads;
177
+ readonly purpose: {
178
+ readonly text: string | null;
179
+ readonly state: 'registered' | 'unregistered';
180
+ readonly summary: string | null;
181
+ readonly policies: readonly WorldWorkPolicy[];
182
+ };
183
+ readonly people: readonly (WorldWorkPersonRef & {
184
+ readonly roles: readonly string[];
185
+ readonly link: Exclude<WorldWorkLink, 'unreadable'>;
186
+ readonly task_ids: readonly string[];
187
+ })[];
188
+ readonly members_state: 'registered' | 'unregistered' | 'unreadable';
189
+ readonly relations: readonly WorldWorkRelation[];
190
+ readonly sites: readonly WorldWorkSite[];
191
+ readonly summary: WorldWorkSummary;
192
+ } | {
193
+ readonly status: 'not_connected' | 'unavailable' | 'forbidden' | 'not_found';
194
+ readonly version: typeof WORLD_WORK_VERSION;
195
+ readonly reason: string;
196
+ };
197
+ export interface WorldWorkBusinessInput {
198
+ readonly id: string;
199
+ readonly code: string;
200
+ readonly name: string;
201
+ readonly summary?: string | null;
202
+ readonly purpose?: string | null;
203
+ readonly aliases?: readonly string[];
204
+ /** Codes of this business's engagements: their tasks are this city's work too. */
205
+ readonly engagement_codes?: readonly string[];
206
+ }
207
+ export interface WorldWorkProjectInput {
208
+ readonly owner?: WorldWorkPersonRef | null;
209
+ readonly people?: readonly (WorldWorkPersonRef & {
210
+ readonly roles: readonly string[];
211
+ })[];
212
+ readonly members_state?: 'registered' | 'unregistered' | 'unreadable';
213
+ /** Decisions the business's own records cite as their source (`source_decision_ids`, merged records included). */
214
+ readonly source_decision_ids?: readonly string[];
215
+ /** Aliases of terms the Graph records as belonging to this business (used only to infer, never to record). */
216
+ readonly term_aliases?: readonly {
217
+ readonly term: string;
218
+ readonly alias: string;
219
+ }[];
220
+ }
221
+ /** Other businesses of the world, to name a counterpart city and tasks that cross a boundary. */
222
+ export interface WorldWorkNeighbour {
223
+ readonly id: string;
224
+ readonly code: string;
225
+ readonly name: string;
226
+ readonly engagement_codes?: readonly string[];
227
+ readonly record_ids?: readonly string[];
228
+ }
229
+ /** Per business, what the world shows above the city: counts with the tasks behind them. */
230
+ export declare function summarizeWorldWork(input: {
231
+ readonly businesses: readonly WorldWorkNeighbour[];
232
+ readonly tasks: Readonly<Record<string, WorldWorkRead>>;
233
+ readonly persons?: WorldWorkRead;
234
+ }, options?: {
235
+ readonly now?: Date;
236
+ }): Readonly<Record<string, WorldWorkSummary>>;
237
+ /** The work of one business for its city: sites, purpose, people, relations and the summary. */
238
+ export declare function projectWorldWork(input: {
239
+ readonly business: WorldWorkBusinessInput;
240
+ readonly tasks: WorldWorkRead;
241
+ readonly persons?: WorldWorkRead;
242
+ readonly decisions?: WorldWorkRead;
243
+ readonly relations?: WorldWorkRead;
244
+ readonly project?: WorldWorkProjectInput;
245
+ readonly neighbours?: readonly WorldWorkNeighbour[];
246
+ }, options?: {
247
+ readonly now?: Date;
248
+ }): WorldWorkResponse;