@unson/brainbase-mcp 0.21.2 → 0.22.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.
Files changed (37) hide show
  1. package/dist/local-web-fetch-bridge.js +1 -1
  2. package/dist/local-web-host.d.ts +59 -0
  3. package/dist/local-web-host.js +171 -4
  4. package/dist/meeting-minutes-http.d.ts +22 -0
  5. package/dist/meeting-minutes-http.js +252 -0
  6. package/dist/meeting-minutes-lineage-adapters.d.ts +157 -0
  7. package/dist/meeting-minutes-lineage-adapters.js +410 -0
  8. package/dist/meeting-minutes-lineage-http.d.ts +20 -0
  9. package/dist/meeting-minutes-lineage-http.js +221 -0
  10. package/dist/meeting-minutes-lineage-lifecycle.d.ts +58 -0
  11. package/dist/meeting-minutes-lineage-lifecycle.js +146 -0
  12. package/dist/meeting-minutes-lineage.d.ts +307 -0
  13. package/dist/meeting-minutes-lineage.js +1030 -0
  14. package/dist/meeting-minutes-storage-controller.d.ts +182 -0
  15. package/dist/meeting-minutes-storage-controller.js +366 -0
  16. package/dist/meeting-minutes-storage-http.d.ts +25 -0
  17. package/dist/meeting-minutes-storage-http.js +312 -0
  18. package/dist/meeting-minutes-storage.d.ts +169 -0
  19. package/dist/meeting-minutes-storage.js +402 -0
  20. package/dist/meeting-minutes.d.ts +223 -0
  21. package/dist/meeting-minutes.js +1172 -0
  22. package/package.json +46 -3
  23. package/ui/brainbase-tokens.css +35 -35
  24. package/ui/local-web-shell.css +20 -17
  25. package/ui/local-web-shell.js +34 -1
  26. package/ui/meeting-minutes-lineage-http.js +247 -0
  27. package/ui/meeting-minutes-lineage.css +177 -0
  28. package/ui/meeting-minutes-lineage.js +485 -0
  29. package/ui/meeting-minutes-storage.css +170 -0
  30. package/ui/meeting-minutes-storage.js +388 -0
  31. package/ui/meeting-minutes.css +215 -0
  32. package/ui/meeting-minutes.js +622 -0
  33. package/ui/world/world-district.js +119 -11
  34. package/ui/world/world-placement.js +103 -0
  35. package/ui/world/world-view.css +38 -0
  36. package/ui/world/world-view.js +65 -2
  37. package/ui/world/world-work-rail.js +34 -0
@@ -0,0 +1,157 @@
1
+ import type { CanonicalTaskContext, CanonicalTaskService } from './canonical-task-service.js';
2
+ import type { CompanyOsLearningAdoptionStore, LearningAccessContext, LearningFinding, LearningModelDisposition, LearningTargetPort, LearningTargetReference, LearningValidationConclusion } from './company-os-learning-adoption.js';
3
+ import type { FoundationScope } from './ontology-foundation.js';
4
+ import { type JudgmentLineagePort, type JudgmentTargetReference, type MinutesLineageAccess, type MinutesLineageActor, type MinutesLineageCandidate, type MinutesVersionPort, type MinutesVersionReference, type TaskLineagePort, type TaskTargetReference } from './meeting-minutes-lineage.js';
5
+ /**
6
+ * Adapters in this module are integration seams only. The learning store,
7
+ * Canonical Task service, and native minutes store remain the respective
8
+ * owners of their records and authorization decisions.
9
+ */
10
+ export declare const MEETING_MINUTES_LINEAGE_ADAPTER_CONTRACT_VERSION: "brainbase.meeting-minutes-lineage-adapters.v1";
11
+ type MaybePromise<T> = T | Promise<T>;
12
+ export interface NativeMeetingMinutesRequestContext {
13
+ /** Resolved by the host auth boundary; never read from request JSON. */
14
+ readonly principal_id: string;
15
+ readonly [key: string]: unknown;
16
+ }
17
+ export interface NativeMeetingMinutesSourceReference {
18
+ readonly provider: string;
19
+ readonly locator: string;
20
+ readonly revision: string;
21
+ readonly digest: string;
22
+ readonly provenance?: Readonly<Record<string, unknown>>;
23
+ readonly [key: string]: unknown;
24
+ }
25
+ export interface NativeMeetingMinutesVersionRecord {
26
+ readonly version_id: string;
27
+ readonly minutes_id: string;
28
+ readonly meeting_id: string;
29
+ readonly predecessor_version_id?: string;
30
+ readonly body_digest?: string;
31
+ readonly source_ref?: NativeMeetingMinutesSourceReference;
32
+ }
33
+ /**
34
+ * Structural shape of the native core's already-authorized exact-version
35
+ * port. The native core owns its concrete type; this copy lets the optional
36
+ * lineage package compose with it without making the native store depend on
37
+ * this package.
38
+ */
39
+ export interface NativeMeetingMinutesExactVersionPort {
40
+ readExact(reference: MinutesVersionReference, access: {
41
+ readonly principal: string;
42
+ readonly [key: string]: unknown;
43
+ }): Promise<{
44
+ readonly reference: MinutesVersionReference;
45
+ } | null>;
46
+ }
47
+ /** Structural copy of the core native store seam to keep this module stackable. */
48
+ export interface NativeMeetingMinutesVersionStore {
49
+ get_version(meetingId: string, minutesId: string, versionId: string, context: NativeMeetingMinutesRequestContext): Promise<NativeMeetingMinutesVersionRecord>;
50
+ }
51
+ export interface NativeMeetingMinutesVersionPortOptions {
52
+ readonly store: NativeMeetingMinutesVersionStore;
53
+ readonly contextFor: (input: {
54
+ readonly access: MinutesLineageAccess;
55
+ readonly reference: MinutesVersionReference;
56
+ }) => MaybePromise<NativeMeetingMinutesRequestContext>;
57
+ /**
58
+ * Organizations may preserve provider-specific provenance here. The
59
+ * default is truthful for native records and external source_ref records.
60
+ */
61
+ readonly referenceFor?: (record: NativeMeetingMinutesVersionRecord) => MinutesVersionReference;
62
+ }
63
+ /**
64
+ * Bridges the native meeting-minutes owner to the lineage exact-version port.
65
+ * `get_version` performs the current ACL check; a returned version is accepted
66
+ * only when the adapter's exact reference equals the requested one.
67
+ */
68
+ export declare function createNativeMeetingMinutesVersionPort(options: NativeMeetingMinutesVersionPortOptions): MinutesVersionPort;
69
+ /**
70
+ * Adapts the native core's provider-neutral port to the lineage port. This
71
+ * is the preferred composition path when the host already has a
72
+ * `createMeetingMinutesVersionPort(store)` result: ACL and exact-version
73
+ * checks remain in the native core and are not duplicated here.
74
+ */
75
+ export declare function adaptNativeMeetingMinutesVersionPort(port: NativeMeetingMinutesExactVersionPort): MinutesVersionPort;
76
+ export declare function nativeMeetingMinutesVersionReference(record: NativeMeetingMinutesVersionRecord): MinutesVersionReference;
77
+ export interface MeetingMinutesLearningAccessInput {
78
+ readonly access: MinutesLineageAccess;
79
+ readonly candidate?: MinutesLineageCandidate;
80
+ readonly target?: LearningTargetReference;
81
+ readonly actor?: MinutesLineageActor;
82
+ readonly phase: 'candidate' | 'validation' | 'adoption' | 'read';
83
+ }
84
+ export interface MeetingMinutesLearningCandidateProposal {
85
+ readonly sourceEvaluationRef: {
86
+ readonly id: string;
87
+ readonly digest: string;
88
+ };
89
+ readonly target: LearningTargetReference;
90
+ readonly proposedChange: unknown;
91
+ readonly grounds: readonly string[];
92
+ readonly counterexamples: readonly string[];
93
+ readonly uncertainty: readonly string[];
94
+ readonly applicability: FoundationScope;
95
+ readonly createdAt?: string;
96
+ }
97
+ export interface MeetingMinutesLearningValidationProposal {
98
+ readonly id?: string;
99
+ readonly findings: readonly LearningFinding[];
100
+ readonly conclusion: LearningValidationConclusion;
101
+ readonly modelDisposition: LearningModelDisposition;
102
+ readonly basis: string;
103
+ readonly validatedAt: string;
104
+ }
105
+ /**
106
+ * A judgment candidate must carry enough information for the existing
107
+ * LearningCandidateInput and LearningValidationInput contracts. The nested
108
+ * names are intentional so a minutes proposal cannot be mistaken for an
109
+ * already adopted judgment.
110
+ */
111
+ export interface MeetingMinutesLearningProposal {
112
+ readonly learningCandidate?: MeetingMinutesLearningCandidateProposal;
113
+ readonly candidate?: MeetingMinutesLearningCandidateProposal;
114
+ readonly learningValidation?: MeetingMinutesLearningValidationProposal;
115
+ readonly validation?: MeetingMinutesLearningValidationProposal;
116
+ }
117
+ export interface MeetingMinutesJudgmentLineagePortOptions {
118
+ readonly store: CompanyOsLearningAdoptionStore;
119
+ readonly target: LearningTargetPort;
120
+ readonly learningAccessFor: (input: MeetingMinutesLearningAccessInput) => MaybePromise<LearningAccessContext>;
121
+ /** Resolves a lineage judgment identity to the canonical target kind. */
122
+ readonly resolveTarget: (input: {
123
+ readonly target: JudgmentTargetReference;
124
+ readonly access: MinutesLineageAccess;
125
+ }) => MaybePromise<LearningTargetReference | null>;
126
+ /** Optional projection when a provider target carries an organization-specific identity. */
127
+ readonly targetReferenceFor?: (input: {
128
+ readonly target: LearningTargetReference;
129
+ readonly candidate: MinutesLineageCandidate;
130
+ }) => JudgmentTargetReference;
131
+ }
132
+ /**
133
+ * Connects the lineage judgment port to the real Company OS learning
134
+ * adoption store. It creates a canonical learning candidate and validation,
135
+ * then calls the store's adoption transaction; it never claims success from
136
+ * a local placeholder.
137
+ */
138
+ export declare function createMeetingMinutesJudgmentLineagePort(options: MeetingMinutesJudgmentLineagePortOptions): JudgmentLineagePort;
139
+ export interface MeetingMinutesTaskContextInput {
140
+ readonly access: MinutesLineageAccess;
141
+ readonly candidate?: MinutesLineageCandidate;
142
+ readonly target?: TaskTargetReference;
143
+ readonly actor?: MinutesLineageActor;
144
+ readonly phase: 'create' | 'read';
145
+ }
146
+ export interface MeetingMinutesTaskLineagePortOptions {
147
+ readonly service: Pick<CanonicalTaskService, 'createTask' | 'getTask'>;
148
+ readonly contextFor: (input: MeetingMinutesTaskContextInput) => MaybePromise<CanonicalTaskContext>;
149
+ }
150
+ /**
151
+ * Connects the lineage Task port to CanonicalTaskService. Task execution and
152
+ * receipts stay outside this adapter. Minutes source references are kept in
153
+ * the lineage sidecar and are deliberately not copied into Task source_refs,
154
+ * so revoking a minutes ACL cannot leave a source locator in the Task.
155
+ */
156
+ export declare function createCanonicalTaskLineagePort(options: MeetingMinutesTaskLineagePortOptions): TaskLineagePort;
157
+ export {};
@@ -0,0 +1,410 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { MeetingMinutesLineageError, } from './meeting-minutes-lineage.js';
3
+ /**
4
+ * Adapters in this module are integration seams only. The learning store,
5
+ * Canonical Task service, and native minutes store remain the respective
6
+ * owners of their records and authorization decisions.
7
+ */
8
+ export const MEETING_MINUTES_LINEAGE_ADAPTER_CONTRACT_VERSION = 'brainbase.meeting-minutes-lineage-adapters.v1';
9
+ /**
10
+ * Bridges the native meeting-minutes owner to the lineage exact-version port.
11
+ * `get_version` performs the current ACL check; a returned version is accepted
12
+ * only when the adapter's exact reference equals the requested one.
13
+ */
14
+ export function createNativeMeetingMinutesVersionPort(options) {
15
+ if (!options?.store || typeof options.store.get_version !== 'function') {
16
+ throw new TypeError('store.get_version is required');
17
+ }
18
+ if (typeof options.contextFor !== 'function')
19
+ throw new TypeError('contextFor is required');
20
+ const referenceFor = options.referenceFor ?? nativeMeetingMinutesVersionReference;
21
+ return {
22
+ async readExact(reference, access) {
23
+ const context = await options.contextFor({ access, reference });
24
+ if (!context || typeof context.principal_id !== 'string' || context.principal_id.trim() === '') {
25
+ throw new MeetingMinutesLineageError('authorization_denied', 'Native minutes context has no trusted principal');
26
+ }
27
+ let record;
28
+ try {
29
+ record = await options.store.get_version(reference.meetingId, reference.minutesId, reference.versionId, context);
30
+ }
31
+ catch (error) {
32
+ throw mapAdapterError(error, 'Native meeting minutes version is unavailable', 'source_unavailable');
33
+ }
34
+ if (!record)
35
+ return null;
36
+ const actual = referenceFor(record);
37
+ return sameJson(actual, reference) ? { reference: actual } : null;
38
+ },
39
+ };
40
+ }
41
+ /**
42
+ * Adapts the native core's provider-neutral port to the lineage port. This
43
+ * is the preferred composition path when the host already has a
44
+ * `createMeetingMinutesVersionPort(store)` result: ACL and exact-version
45
+ * checks remain in the native core and are not duplicated here.
46
+ */
47
+ export function adaptNativeMeetingMinutesVersionPort(port) {
48
+ if (!port || typeof port.readExact !== 'function')
49
+ throw new TypeError('port.readExact is required');
50
+ return {
51
+ async readExact(reference, access) {
52
+ try {
53
+ const result = await port.readExact(reference, access);
54
+ if (!result)
55
+ return null;
56
+ return sameJson(result.reference, reference) ? { reference: result.reference } : null;
57
+ }
58
+ catch (error) {
59
+ throw mapAdapterError(error, 'Native meeting minutes version is unavailable', 'source_unavailable');
60
+ }
61
+ },
62
+ };
63
+ }
64
+ export function nativeMeetingMinutesVersionReference(record) {
65
+ const source = record.source_ref;
66
+ if (source) {
67
+ return {
68
+ meetingId: record.meeting_id,
69
+ minutesId: record.minutes_id,
70
+ versionId: record.version_id,
71
+ contentDigest: source.digest,
72
+ locator: source.locator,
73
+ provenance: {
74
+ providerKind: source.provider,
75
+ providerId: source.locator,
76
+ revision: source.revision,
77
+ digest: source.digest,
78
+ ...(source.provenance ?? {}),
79
+ },
80
+ };
81
+ }
82
+ return {
83
+ meetingId: record.meeting_id,
84
+ minutesId: record.minutes_id,
85
+ versionId: record.version_id,
86
+ contentDigest: record.body_digest ?? null,
87
+ locator: `${record.meeting_id}/${record.minutes_id}/${record.version_id}`,
88
+ provenance: {
89
+ providerKind: 'brainbase.native',
90
+ providerId: `${record.meeting_id}/${record.minutes_id}`,
91
+ revision: record.version_id,
92
+ ...(record.body_digest ? { digest: record.body_digest } : {}),
93
+ },
94
+ };
95
+ }
96
+ /**
97
+ * Connects the lineage judgment port to the real Company OS learning
98
+ * adoption store. It creates a canonical learning candidate and validation,
99
+ * then calls the store's adoption transaction; it never claims success from
100
+ * a local placeholder.
101
+ */
102
+ export function createMeetingMinutesJudgmentLineagePort(options) {
103
+ if (!options?.store || typeof options.store.createCandidate !== 'function'
104
+ || typeof options.store.createValidation !== 'function' || typeof options.store.adopt !== 'function') {
105
+ throw new TypeError('a complete learning adoption store is required');
106
+ }
107
+ if (!options.target || typeof options.target.read !== 'function')
108
+ throw new TypeError('learning target.read is required');
109
+ if (typeof options.learningAccessFor !== 'function')
110
+ throw new TypeError('learningAccessFor is required');
111
+ if (typeof options.resolveTarget !== 'function')
112
+ throw new TypeError('resolveTarget is required');
113
+ return {
114
+ async adopt({ candidate, idempotencyKey, actor, access }) {
115
+ const proposal = normalizeLearningProposal(candidate);
116
+ const candidateAccess = await options.learningAccessFor({
117
+ access,
118
+ candidate,
119
+ target: proposal.candidate.target,
120
+ actor,
121
+ phase: 'candidate',
122
+ });
123
+ const learningCandidateInput = {
124
+ id: candidate.id,
125
+ sourceEvaluationRef: proposal.candidate.sourceEvaluationRef,
126
+ target: proposal.candidate.target,
127
+ proposedChange: proposal.candidate.proposedChange,
128
+ grounds: proposal.candidate.grounds,
129
+ counterexamples: proposal.candidate.counterexamples,
130
+ uncertainty: proposal.candidate.uncertainty,
131
+ applicability: proposal.candidate.applicability,
132
+ createdAt: proposal.candidate.createdAt ?? candidate.createdAt,
133
+ };
134
+ const storedCandidate = await options.store.createCandidate({ ...learningCandidateInput, access: candidateAccess });
135
+ const validationAccess = await options.learningAccessFor({
136
+ access,
137
+ candidate,
138
+ target: storedCandidate.target,
139
+ actor,
140
+ phase: 'validation',
141
+ });
142
+ const validationId = proposal.validation.id ?? `meeting-minutes-validation:${candidate.id}`;
143
+ const learningValidationInput = {
144
+ id: validationId,
145
+ candidateRef: { id: storedCandidate.id, digest: storedCandidate.digest },
146
+ findings: proposal.validation.findings,
147
+ conclusion: proposal.validation.conclusion,
148
+ modelDisposition: proposal.validation.modelDisposition,
149
+ basis: proposal.validation.basis,
150
+ validatedAt: proposal.validation.validatedAt,
151
+ };
152
+ const storedValidation = await options.store.createValidation({ ...learningValidationInput, access: validationAccess });
153
+ const adoptionAccess = await options.learningAccessFor({
154
+ access,
155
+ candidate,
156
+ target: storedCandidate.target,
157
+ actor,
158
+ phase: 'adoption',
159
+ });
160
+ const record = await options.store.adopt({
161
+ id: `meeting-minutes-adoption:judgment:${candidate.id}`,
162
+ idempotencyKey,
163
+ candidateRef: { id: storedCandidate.id, digest: storedCandidate.digest },
164
+ validationRef: { id: storedValidation.id, digest: storedValidation.digest },
165
+ access: adoptionAccess,
166
+ });
167
+ const target = (options.targetReferenceFor ?? defaultJudgmentTargetReference)({
168
+ target: record.adoptedTarget,
169
+ candidate,
170
+ });
171
+ return {
172
+ target,
173
+ adoption: {
174
+ kind: 'judgment_adoption',
175
+ id: record.id,
176
+ revision: String(record.version),
177
+ digest: record.digest,
178
+ },
179
+ };
180
+ },
181
+ async readExact(target, access) {
182
+ const learningTarget = await options.resolveTarget({ target, access });
183
+ if (!learningTarget)
184
+ return null;
185
+ const learningAccess = await options.learningAccessFor({ access, target: learningTarget, phase: 'read' });
186
+ let record;
187
+ try {
188
+ record = await options.target.read(learningTarget, learningAccess);
189
+ }
190
+ catch (error) {
191
+ throw mapAdapterError(error, 'Canonical judgment target is unavailable', 'target_unavailable');
192
+ }
193
+ if (!record || !sameJson(record.reference, learningTarget))
194
+ return null;
195
+ return record;
196
+ },
197
+ };
198
+ }
199
+ function normalizeLearningProposal(candidate) {
200
+ if (!isRecord(candidate.proposal)) {
201
+ throw new MeetingMinutesLineageError('invalid_input', `Judgment candidate ${candidate.id} must include learning candidate and validation inputs`);
202
+ }
203
+ const root = candidate.proposal;
204
+ const candidateValue = (isRecord(root.learningCandidate) ? root.learningCandidate : root.candidate);
205
+ const validationValue = (isRecord(root.learningValidation) ? root.learningValidation : root.validation);
206
+ if (!isRecord(candidateValue) || !isRecord(validationValue)) {
207
+ throw new MeetingMinutesLineageError('invalid_input', `Judgment candidate ${candidate.id} must include learningCandidate and learningValidation`);
208
+ }
209
+ const sourceEvaluationRef = normalizeEvaluationReference(candidateValue.sourceEvaluationRef);
210
+ const target = normalizeLearningTargetReference(candidateValue.target);
211
+ const grounds = stringArray(candidateValue.grounds, 'learningCandidate.grounds');
212
+ const counterexamples = stringArray(candidateValue.counterexamples, 'learningCandidate.counterexamples');
213
+ const uncertainty = stringArray(candidateValue.uncertainty, 'learningCandidate.uncertainty');
214
+ const applicability = candidateValue.applicability;
215
+ if (!isRecord(applicability))
216
+ throw new MeetingMinutesLineageError('invalid_input', 'learningCandidate.applicability is required');
217
+ const createdAt = candidateValue.createdAt === undefined ? undefined : requiredString(candidateValue.createdAt, 'learningCandidate.createdAt');
218
+ const validation = {
219
+ ...(validationValue.id === undefined ? {} : { id: requiredString(validationValue.id, 'learningValidation.id') }),
220
+ findings: normalizeFindings(validationValue.findings),
221
+ conclusion: enumValue(validationValue.conclusion, ['supported', 'partially_supported', 'indeterminate', 'rejected'], 'learningValidation.conclusion'),
222
+ modelDisposition: enumValue(validationValue.modelDisposition, ['unchanged', 'revise', 'refuted', 'indeterminate'], 'learningValidation.modelDisposition'),
223
+ basis: requiredString(validationValue.basis, 'learningValidation.basis'),
224
+ validatedAt: requiredString(validationValue.validatedAt, 'learningValidation.validatedAt'),
225
+ };
226
+ return {
227
+ candidate: {
228
+ sourceEvaluationRef,
229
+ target,
230
+ proposedChange: candidateValue.proposedChange,
231
+ grounds,
232
+ counterexamples,
233
+ uncertainty,
234
+ applicability,
235
+ ...(createdAt === undefined ? {} : { createdAt }),
236
+ },
237
+ validation,
238
+ };
239
+ }
240
+ function defaultJudgmentTargetReference({ target }) {
241
+ return { kind: 'judgment', id: target.id, revision: target.revision, digest: target.digest };
242
+ }
243
+ /**
244
+ * Connects the lineage Task port to CanonicalTaskService. Task execution and
245
+ * receipts stay outside this adapter. Minutes source references are kept in
246
+ * the lineage sidecar and are deliberately not copied into Task source_refs,
247
+ * so revoking a minutes ACL cannot leave a source locator in the Task.
248
+ */
249
+ export function createCanonicalTaskLineagePort(options) {
250
+ if (!options?.service || typeof options.service.createTask !== 'function' || typeof options.service.getTask !== 'function') {
251
+ throw new TypeError('CanonicalTaskService createTask and getTask are required');
252
+ }
253
+ if (typeof options.contextFor !== 'function')
254
+ throw new TypeError('contextFor is required');
255
+ return {
256
+ async create({ candidate, idempotencyKey, actor, access }) {
257
+ const input = normalizeTaskInput(candidate);
258
+ const context = await options.contextFor({ access, candidate, actor, phase: 'create' });
259
+ if (!context || !context.principal)
260
+ throw new MeetingMinutesLineageError('authorization_denied', 'Canonical Task context has no trusted principal');
261
+ const record = await options.service.createTask(input, { ...context, idempotencyKey });
262
+ assertTaskRecord(record);
263
+ const target = {
264
+ kind: 'task',
265
+ id: record.id,
266
+ version: record.version,
267
+ digest: taskReferenceDigest(record.id, record.version),
268
+ };
269
+ return {
270
+ target,
271
+ adoption: {
272
+ kind: 'task_adoption',
273
+ id: record.id,
274
+ revision: String(record.version),
275
+ digest: taskReferenceDigest(record.id, record.version),
276
+ },
277
+ };
278
+ },
279
+ async readExact(target, access) {
280
+ const context = await options.contextFor({ access, target, phase: 'read' });
281
+ if (!context || !context.principal)
282
+ throw new MeetingMinutesLineageError('authorization_denied', 'Canonical Task context has no trusted principal');
283
+ let record;
284
+ try {
285
+ record = await options.service.getTask(target.id, context);
286
+ }
287
+ catch (error) {
288
+ const code = errorCode(error);
289
+ if (code === 'task_not_found' || code === 'not_found')
290
+ return null;
291
+ throw mapAdapterError(error, 'Canonical Task is unavailable', 'target_unavailable');
292
+ }
293
+ if (!record || record.id !== target.id || record.version !== target.version)
294
+ return null;
295
+ if (target.digest !== undefined && target.digest !== taskReferenceDigest(record.id, record.version))
296
+ return null;
297
+ return record;
298
+ },
299
+ };
300
+ }
301
+ function normalizeTaskInput(candidate) {
302
+ if (!isRecord(candidate.proposal))
303
+ throw new MeetingMinutesLineageError('invalid_input', `Task candidate ${candidate.id} must include a task input`);
304
+ const value = isRecord(candidate.proposal.task) ? candidate.proposal.task : candidate.proposal;
305
+ if (value.source_refs !== undefined) {
306
+ throw new MeetingMinutesLineageError('invalid_input', 'Task source_refs belong to the minutes lineage sidecar and cannot be supplied here');
307
+ }
308
+ const input = {
309
+ title: requiredString(value.title, 'task.title'),
310
+ ...(value.description === undefined ? {} : { description: value.description }),
311
+ ...(value.priority === undefined ? {} : { priority: value.priority }),
312
+ ...(value.assignee_person_id === undefined ? {} : { assignee_person_id: value.assignee_person_id }),
313
+ ...(value.due_at === undefined ? {} : { due_at: value.due_at }),
314
+ ...(value.waiting_on === undefined ? {} : { waiting_on: value.waiting_on }),
315
+ ...(value.review_at === undefined ? {} : { review_at: value.review_at }),
316
+ ...(value.project_codes === undefined ? {} : { project_codes: value.project_codes }),
317
+ };
318
+ return input;
319
+ }
320
+ function assertTaskRecord(record) {
321
+ if (!record || typeof record !== 'object' || typeof record.id !== 'string' || record.id.trim() === ''
322
+ || !Number.isInteger(record.version) || record.version < 1) {
323
+ throw new MeetingMinutesLineageError('integrity_mismatch', 'Canonical Task service returned an invalid record');
324
+ }
325
+ }
326
+ function taskReferenceDigest(id, version) {
327
+ return `sha256:${createHash('sha256').update(`meeting-minutes-task:${id}:${version}`, 'utf8').digest('hex')}`;
328
+ }
329
+ function normalizeEvaluationReference(value) {
330
+ if (!isRecord(value))
331
+ throw new MeetingMinutesLineageError('invalid_input', 'learningCandidate.sourceEvaluationRef is required');
332
+ return { id: requiredString(value.id, 'sourceEvaluationRef.id'), digest: requiredString(value.digest, 'sourceEvaluationRef.digest') };
333
+ }
334
+ function normalizeLearningTargetReference(value) {
335
+ if (!isRecord(value))
336
+ throw new MeetingMinutesLineageError('invalid_input', 'learningCandidate.target is required');
337
+ const kind = enumValue(value.kind, ['world_model', 'judgment_method', 'execution_method', 'objective'], 'target.kind');
338
+ return {
339
+ kind,
340
+ id: requiredString(value.id, 'target.id'),
341
+ revision: requiredString(value.revision, 'target.revision'),
342
+ digest: requiredString(value.digest, 'target.digest'),
343
+ ...(value.foundationType === undefined ? {} : { foundationType: value.foundationType }),
344
+ };
345
+ }
346
+ function normalizeFindings(value) {
347
+ if (!Array.isArray(value))
348
+ throw new MeetingMinutesLineageError('invalid_input', 'learningValidation.findings is required');
349
+ return value.map((item, index) => {
350
+ if (!isRecord(item))
351
+ throw new MeetingMinutesLineageError('invalid_input', `learningValidation.findings[${index}] is invalid`);
352
+ return {
353
+ kind: enumValue(item.kind, ['measurement_error', 'execution_difference', 'external_change', 'other'], `findings[${index}].kind`),
354
+ description: requiredString(item.description, `findings[${index}].description`),
355
+ evidenceRefs: stringArray(item.evidenceRefs, `findings[${index}].evidenceRefs`),
356
+ };
357
+ });
358
+ }
359
+ function stringArray(value, label) {
360
+ if (!Array.isArray(value) || !value.every((item) => typeof item === 'string' && item.trim() !== '')) {
361
+ throw new MeetingMinutesLineageError('invalid_input', `${label} must be an array of non-empty strings`);
362
+ }
363
+ return value.map((item) => item.normalize('NFKC').trim());
364
+ }
365
+ function requiredString(value, label) {
366
+ if (typeof value !== 'string' || value.normalize('NFKC').trim() === '') {
367
+ throw new MeetingMinutesLineageError('invalid_input', `${label} is required`);
368
+ }
369
+ if (/^[\u0000-\u001f\u007f]/u.test(value))
370
+ throw new MeetingMinutesLineageError('invalid_input', `${label} contains control characters`);
371
+ return value.normalize('NFKC').trim();
372
+ }
373
+ function enumValue(value, allowed, label) {
374
+ if (typeof value !== 'string' || !allowed.includes(value))
375
+ throw new MeetingMinutesLineageError('invalid_input', `${label} is invalid`);
376
+ return value;
377
+ }
378
+ function isRecord(value) {
379
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
380
+ }
381
+ function sameJson(left, right) {
382
+ return canonicalJson(left) === canonicalJson(right);
383
+ }
384
+ function canonicalJson(value) {
385
+ if (value === null || typeof value !== 'object')
386
+ return JSON.stringify(value);
387
+ if (Array.isArray(value))
388
+ return `[${value.map(canonicalJson).join(',')}]`;
389
+ return `{${Object.entries(value)
390
+ .sort(([left], [right]) => left.localeCompare(right))
391
+ .map(([key, child]) => `${JSON.stringify(key)}:${canonicalJson(child)}`)
392
+ .join(',')}}`;
393
+ }
394
+ function errorCode(error) {
395
+ return error && typeof error === 'object' && 'code' in error ? String(error.code ?? '') : '';
396
+ }
397
+ function mapAdapterError(error, fallback, fallbackCode) {
398
+ if (error instanceof MeetingMinutesLineageError)
399
+ return error;
400
+ const code = errorCode(error);
401
+ if (code === 'authorization_denied' || code === 'forbidden' || code === 'permission_denied') {
402
+ return new MeetingMinutesLineageError('authorization_denied', fallback);
403
+ }
404
+ if (code === 'revision_conflict' || code === 'integrity_mismatch' || code === 'readback_mismatch') {
405
+ return new MeetingMinutesLineageError(code, fallback);
406
+ }
407
+ if (code === 'not_found' || code === 'task_not_found')
408
+ return new MeetingMinutesLineageError(fallbackCode, fallback);
409
+ return new MeetingMinutesLineageError(fallbackCode, fallback);
410
+ }
@@ -0,0 +1,20 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { type MinutesLineageAccess, type MinutesLineageActor, type MinutesLineageStore } from './meeting-minutes-lineage.js';
3
+ /** HTTP seam mounted beside the native `/api/meeting-minutes` routes. */
4
+ export declare const MEETING_MINUTES_LINEAGE_HTTP_VERSION: "brainbase.meeting-minutes-lineage-http.v1";
5
+ export declare const MEETING_MINUTES_LINEAGE_HTTP_PREFIX: "/api/meeting-minutes-lineage";
6
+ type MaybePromise<T> = T | Promise<T>;
7
+ export interface MeetingMinutesLineageHttpOptions {
8
+ readonly store: MinutesLineageStore;
9
+ /** The host resolves current principal and scope; request JSON never supplies access. */
10
+ readonly resolveAccess: (request: IncomingMessage) => MaybePromise<MinutesLineageAccess>;
11
+ /** The host resolves the current actor; request JSON is an audit hint only. */
12
+ readonly resolveActor?: (request: IncomingMessage, access: MinutesLineageAccess) => MaybePromise<MinutesLineageActor>;
13
+ /** Optional host CSRF/origin policy for mutations. */
14
+ readonly assertWriteAllowed?: (request: IncomingMessage, access: MinutesLineageAccess) => MaybePromise<void>;
15
+ readonly basePath?: string;
16
+ readonly bodyLimitBytes?: number;
17
+ }
18
+ export type MeetingMinutesLineageHttpHandler = (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
19
+ export declare function createMeetingMinutesLineageHttpHandler(options: MeetingMinutesLineageHttpOptions): MeetingMinutesLineageHttpHandler;
20
+ export {};