@unson/brainbase-mcp 0.6.0 → 0.8.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.
Files changed (148) hide show
  1. package/contracts/foundation/graph-history.sql +634 -0
  2. package/contracts/judgment-value-proof/schema.json +43 -1
  3. package/dist/canonical-task-contract.d.ts +9 -0
  4. package/dist/canonical-task-contract.js +39 -0
  5. package/dist/canonical-task-principal.d.ts +16 -0
  6. package/dist/canonical-task-principal.js +36 -0
  7. package/dist/canonical-task-service.d.ts +251 -0
  8. package/dist/canonical-task-service.js +820 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +107 -0
  11. package/dist/company-os-evaluation.d.ts +193 -0
  12. package/dist/company-os-evaluation.js +1150 -0
  13. package/dist/company-os-impact-review.d.ts +248 -0
  14. package/dist/company-os-impact-review.js +663 -0
  15. package/dist/company-os-learning-adoption.d.ts +288 -0
  16. package/dist/company-os-learning-adoption.js +944 -0
  17. package/dist/company-os-objectives.d.ts +63 -0
  18. package/dist/company-os-objectives.js +234 -0
  19. package/dist/company-os-receipt-adapter.d.ts +109 -0
  20. package/dist/company-os-receipt-adapter.js +636 -0
  21. package/dist/constraint-exception-store.d.ts +23 -0
  22. package/dist/constraint-exception-store.js +108 -0
  23. package/dist/constraint-resolution.d.ts +175 -0
  24. package/dist/constraint-resolution.js +922 -0
  25. package/dist/decision-adapter.d.ts +145 -0
  26. package/dist/decision-adapter.js +586 -0
  27. package/dist/durable-wait-http.d.ts +51 -0
  28. package/dist/durable-wait-http.js +497 -0
  29. package/dist/durable-waits.d.ts +234 -0
  30. package/dist/durable-waits.js +944 -0
  31. package/dist/execution-authority-http.d.ts +28 -0
  32. package/dist/execution-authority-http.js +359 -0
  33. package/dist/execution-authority.d.ts +201 -0
  34. package/dist/execution-authority.js +634 -0
  35. package/dist/foundation-authenticated-tools.d.ts +56 -0
  36. package/dist/foundation-authenticated-tools.js +347 -0
  37. package/dist/foundation-catalog.d.ts +26 -0
  38. package/dist/foundation-catalog.js +206 -0
  39. package/dist/foundation-constraint-store.d.ts +50 -0
  40. package/dist/foundation-constraint-store.js +215 -0
  41. package/dist/foundation-graph-http.d.ts +43 -0
  42. package/dist/foundation-graph-http.js +243 -0
  43. package/dist/foundation-graph-write.d.ts +90 -0
  44. package/dist/foundation-graph-write.js +447 -0
  45. package/dist/foundation-http.d.ts +90 -0
  46. package/dist/foundation-http.js +439 -0
  47. package/dist/foundation-public-provider.d.ts +140 -0
  48. package/dist/foundation-public-provider.js +192 -0
  49. package/dist/foundation-store.d.ts +132 -0
  50. package/dist/foundation-store.js +654 -0
  51. package/dist/graph-corrections.d.ts +157 -0
  52. package/dist/graph-corrections.js +620 -0
  53. package/dist/graph-foundation-reader.d.ts +60 -0
  54. package/dist/graph-foundation-reader.js +494 -0
  55. package/dist/graph-retrieval.d.ts +6 -1
  56. package/dist/graph-revision-reader.d.ts +80 -0
  57. package/dist/graph-revision-reader.js +246 -0
  58. package/dist/graph-web-http.d.ts +26 -0
  59. package/dist/graph-web-http.js +215 -0
  60. package/dist/graph-web.d.ts +364 -0
  61. package/dist/graph-web.js +639 -0
  62. package/dist/judgment-dag-composition.d.ts +224 -0
  63. package/dist/judgment-dag-composition.js +731 -0
  64. package/dist/judgment-dag.d.ts +2 -0
  65. package/dist/judgment-dag.js +1 -0
  66. package/dist/judgment-problem-snapshot.d.ts +213 -0
  67. package/dist/judgment-problem-snapshot.js +1073 -0
  68. package/dist/judgment-value-proof-review.d.ts +166 -0
  69. package/dist/judgment-value-proof-review.js +437 -0
  70. package/dist/judgment-value-proof.d.ts +28 -1
  71. package/dist/judgment-value-proof.js +53 -8
  72. package/dist/judgment-view-http.d.ts +22 -0
  73. package/dist/judgment-view-http.js +128 -0
  74. package/dist/judgment-view.d.ts +222 -0
  75. package/dist/judgment-view.js +627 -0
  76. package/dist/knowledge-adapter.d.ts +112 -0
  77. package/dist/knowledge-adapter.js +691 -0
  78. package/dist/knowledge-continuation.d.ts +127 -0
  79. package/dist/knowledge-continuation.js +446 -0
  80. package/dist/knowledge-lookup.d.ts +178 -0
  81. package/dist/knowledge-lookup.js +758 -0
  82. package/dist/local-web-fetch-bridge.d.ts +5 -0
  83. package/dist/local-web-fetch-bridge.js +37 -0
  84. package/dist/local-web-host.d.ts +109 -0
  85. package/dist/local-web-host.js +688 -0
  86. package/dist/local-web-security.d.ts +25 -0
  87. package/dist/local-web-security.js +95 -0
  88. package/dist/ontology-foundation.d.ts +209 -0
  89. package/dist/ontology-foundation.js +1000 -0
  90. package/dist/ontology.d.ts +13 -0
  91. package/dist/ontology.js +14 -0
  92. package/dist/organization-connection.d.ts +164 -0
  93. package/dist/organization-connection.js +428 -0
  94. package/dist/organization-graph.d.ts +15 -0
  95. package/dist/organization-graph.js +24 -4
  96. package/dist/personal-knowledge.d.ts +132 -0
  97. package/dist/personal-knowledge.js +779 -0
  98. package/dist/personal-memory-handover.d.ts +108 -0
  99. package/dist/personal-memory-handover.js +369 -0
  100. package/dist/philosophy-revision-reader.d.ts +85 -0
  101. package/dist/philosophy-revision-reader.js +241 -0
  102. package/dist/portable-graph.d.ts +24 -0
  103. package/dist/portable-graph.js +77 -0
  104. package/dist/problem-candidates.d.ts +149 -0
  105. package/dist/problem-candidates.js +898 -0
  106. package/dist/problem-selection.d.ts +238 -0
  107. package/dist/problem-selection.js +995 -0
  108. package/dist/resource-reservation-http.d.ts +31 -0
  109. package/dist/resource-reservation-http.js +371 -0
  110. package/dist/resource-reservations.d.ts +221 -0
  111. package/dist/resource-reservations.js +577 -0
  112. package/dist/server.d.ts +104 -2
  113. package/dist/server.js +186 -4
  114. package/dist/ssot.d.ts +6 -1
  115. package/dist/ssot.js +63 -5
  116. package/dist/streamable-http.d.ts +42 -0
  117. package/dist/streamable-http.js +288 -0
  118. package/dist/types.d.ts +20 -0
  119. package/dist/value-proof-review-http.d.ts +25 -0
  120. package/dist/value-proof-review-http.js +185 -0
  121. package/dist/world-model.d.ts +199 -0
  122. package/dist/world-model.js +955 -0
  123. package/package.json +230 -3
  124. package/ui/brainbase-tokens.css +56 -0
  125. package/ui/graph-own-share.js +367 -0
  126. package/ui/graph-projects-view.css +23 -0
  127. package/ui/graph-projects-view.js +730 -0
  128. package/ui/graph-registry-view.css +101 -0
  129. package/ui/graph-registry-view.js +804 -0
  130. package/ui/graph-view-shared.css +522 -0
  131. package/ui/graph-view-shared.js +1227 -0
  132. package/ui/judgment-view.css +312 -0
  133. package/ui/judgment-view.js +508 -0
  134. package/ui/local-web-shell.css +396 -0
  135. package/ui/local-web-shell.js +378 -0
  136. package/ui/objective-editor-http-port.js +154 -0
  137. package/ui/objective-editor.css +286 -0
  138. package/ui/objective-editor.js +1586 -0
  139. package/ui/outcome-knowledge.css +50 -14
  140. package/ui/outcome-knowledge.js +15 -1
  141. package/ui/outcome-mana.css +91 -14
  142. package/ui/outcome-mana.js +958 -61
  143. package/ui/value-proof-review.css +504 -0
  144. package/ui/value-proof-review.js +1104 -0
  145. package/ui/workspace-kit.css +485 -0
  146. package/ui/workspace-kit.js +229 -0
  147. package/ui/world-model-view.css +188 -0
  148. package/ui/world-model-view.js +486 -0
@@ -0,0 +1,447 @@
1
+ import { cloneFoundationDefinition, nextFoundationRevision } from './foundation-catalog.js';
2
+ import { validateFoundationDefinition } from './ontology-foundation.js';
3
+ /**
4
+ * Contract for preparing a Foundation draft for the canonical Graph writer.
5
+ *
6
+ * This module deliberately stops at a pure, fail-closed validation boundary.
7
+ * It does not issue Graph queries, mutate a catalog, or infer a tenant from
8
+ * any value in the payload. The host must provide the already-authenticated
9
+ * principal, selected project, and current revision.
10
+ */
11
+ export const FOUNDATION_GRAPH_WRITE_CONTRACT_VERSION = 'foundation-graph-draft-write.v1';
12
+ const FOUNDATION_TYPES = ['objective', 'variable', 'model', 'constraint'];
13
+ const REVISION_PATTERN = /^[1-9]\d*$/u;
14
+ const FOUNDATION_USES_DRAFT = ['draft'];
15
+ /**
16
+ * Error thrown by the throwing normalizer. The non-throwing validator should
17
+ * be used at HTTP/MCP boundaries when callers need to render all issues.
18
+ */
19
+ export class FoundationGraphWriteError extends Error {
20
+ code;
21
+ issues;
22
+ constructor(issues) {
23
+ const first = issues[0] ?? {
24
+ code: 'INVALID_INPUT',
25
+ path: 'input',
26
+ message: 'Foundation Graph write input is invalid.'
27
+ };
28
+ super(first.message);
29
+ this.name = 'FoundationGraphWriteError';
30
+ this.code = first.code;
31
+ this.issues = issues;
32
+ }
33
+ }
34
+ /**
35
+ * Validate and normalize an untrusted Graph upsert candidate.
36
+ *
37
+ * A successful result is safe to hand to a canonical Graph writer only after
38
+ * that writer also enforces its transaction/RLS boundary. This function proves
39
+ * the payload contract, identity, current ACL authorization, scope, and
40
+ * revision identity without performing any I/O.
41
+ */
42
+ export function validateFoundationGraphWrite(input) {
43
+ const issues = [];
44
+ if (!isRecord(input)) {
45
+ return invalid(issues, {
46
+ code: 'INVALID_INPUT',
47
+ path: 'input',
48
+ message: 'Foundation Graph write input must be an object.'
49
+ });
50
+ }
51
+ const context = input.context;
52
+ const row = input.row;
53
+ if (!isRecord(context)) {
54
+ issues.push({ code: 'INVALID_INPUT', path: 'context', message: 'A trusted write context is required.' });
55
+ }
56
+ if (!isRecord(row)) {
57
+ issues.push({ code: 'INVALID_INPUT', path: 'row', message: 'A Graph row is required.' });
58
+ }
59
+ if (issues.length > 0)
60
+ return invalid(issues);
61
+ const trustedContext = context;
62
+ const graphRow = row;
63
+ validateContext(trustedContext, issues);
64
+ validateGraphRow(graphRow, issues);
65
+ if (isNonEmptyString(graphRow.projectCode)
66
+ && isNonEmptyString(trustedContext.projectCode)
67
+ && graphRow.projectCode !== trustedContext.projectCode) {
68
+ issues.push({
69
+ code: 'PROJECT_MISMATCH',
70
+ path: 'row.projectCode',
71
+ message: 'Graph row projectCode must match the trusted selected project.'
72
+ });
73
+ }
74
+ const expectedNextRevision = input.expectedNextRevision;
75
+ if (!isCanonicalRevision(expectedNextRevision)) {
76
+ issues.push({
77
+ code: 'INVALID_IDENTITY',
78
+ path: 'expectedNextRevision',
79
+ message: 'expectedNextRevision must be a positive canonical decimal revision.'
80
+ });
81
+ }
82
+ if (issues.length > 0)
83
+ return invalid(issues);
84
+ const rowType = resolveRowType(graphRow);
85
+ const rowId = graphRow.id;
86
+ const rowRevision = graphRow.revision;
87
+ const candidate = unwrapFoundation(graphRow.payload.foundation);
88
+ const definitionResult = validateFoundationDefinition(candidate, { use: 'draft' });
89
+ appendDefinitionIssues(issues, 'row.payload.foundation', definitionResult.issues);
90
+ if (!definitionResult.valid)
91
+ return invalid(issues);
92
+ if (!isFoundationDefinitionRecord(candidate)) {
93
+ issues.push({
94
+ code: 'DEFINITION_INVALID',
95
+ path: 'row.payload.foundation',
96
+ message: 'payload.foundation must be an object containing a registered Foundation definition.'
97
+ });
98
+ }
99
+ if (rowType !== undefined && isFoundationDefinitionRecord(candidate)) {
100
+ if (candidate.type !== rowType) {
101
+ issues.push({
102
+ code: 'ROW_DEFINITION_MISMATCH',
103
+ path: 'row.type',
104
+ message: `Graph row type ${rowType} does not match payload.foundation.type ${String(candidate.type)}.`
105
+ });
106
+ }
107
+ }
108
+ if (isFoundationDefinitionRecord(candidate)) {
109
+ if (candidate.id !== rowId) {
110
+ issues.push({
111
+ code: 'ROW_DEFINITION_MISMATCH',
112
+ path: 'row.id',
113
+ message: `Graph row id ${rowId} does not match payload.foundation.id ${String(candidate.id)}.`
114
+ });
115
+ }
116
+ if (candidate.revision !== rowRevision) {
117
+ issues.push({
118
+ code: 'ROW_DEFINITION_MISMATCH',
119
+ path: 'row.revision',
120
+ message: `Graph row revision ${rowRevision} does not match payload.foundation.revision ${String(candidate.revision)}.`
121
+ });
122
+ }
123
+ if (expectedNextRevision !== rowRevision) {
124
+ issues.push({
125
+ code: 'REVISION_CONFLICT',
126
+ path: 'expectedNextRevision',
127
+ message: `expectedNextRevision ${expectedNextRevision} must equal row.revision ${rowRevision}.`
128
+ });
129
+ }
130
+ validateDraftWriteInvariants(candidate, issues);
131
+ validateCandidateScope(candidate, trustedContext, issues);
132
+ }
133
+ const hasCurrentDefinition = input.currentDefinition !== undefined;
134
+ const operation = hasCurrentDefinition ? 'update' : 'create';
135
+ if (hasCurrentDefinition) {
136
+ const current = unwrapFoundation(input.currentDefinition);
137
+ validateCurrentDefinition(current, candidate, rowId, rowType, trustedContext, issues);
138
+ if (isFoundationDefinitionRecord(current) && isCanonicalRevision(current.revision) && isCanonicalRevision(rowRevision)) {
139
+ let expectedRevision;
140
+ try {
141
+ expectedRevision = nextFoundationRevision(current.revision);
142
+ }
143
+ catch {
144
+ expectedRevision = '';
145
+ }
146
+ if (expectedRevision !== rowRevision || expectedNextRevision !== expectedRevision) {
147
+ issues.push({
148
+ code: 'REVISION_CONFLICT',
149
+ path: 'row.revision',
150
+ message: `An update must append revision ${expectedRevision}; received row ${rowRevision} and expectedNextRevision ${expectedNextRevision}.`
151
+ });
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ if (rowRevision !== '1' || expectedNextRevision !== '1') {
157
+ issues.push({
158
+ code: 'REVISION_CONFLICT',
159
+ path: 'row.revision',
160
+ message: 'A new Foundation Graph row must start at revision 1.'
161
+ });
162
+ }
163
+ if (isFoundationDefinitionRecord(candidate) && candidate.acl.ownerId !== trustedContext.principal) {
164
+ issues.push({
165
+ code: 'OWNER_MISMATCH',
166
+ path: 'row.payload.foundation.acl.ownerId',
167
+ message: 'A new draft must be owned by the trusted principal.'
168
+ });
169
+ }
170
+ }
171
+ if (issues.length > 0 || !isFoundationDefinitionRecord(candidate) || !rowType) {
172
+ return invalid(issues, undefined, operation);
173
+ }
174
+ const normalizedDefinition = cloneFoundationDefinition(candidate);
175
+ // These assignments are intentionally explicit even after validation. They
176
+ // make the invariant visible in the object handed to the Host writer.
177
+ normalizedDefinition.adoptionState = 'draft';
178
+ normalizedDefinition.storage = 'candidate';
179
+ normalizedDefinition.authorizedUses = [...FOUNDATION_USES_DRAFT];
180
+ const normalized = {
181
+ contractVersion: FOUNDATION_GRAPH_WRITE_CONTRACT_VERSION,
182
+ operation,
183
+ projectCode: trustedContext.projectCode,
184
+ row: {
185
+ id: rowId,
186
+ type: rowType,
187
+ revision: rowRevision,
188
+ projectCode: trustedContext.projectCode,
189
+ payload: { foundation: normalizedDefinition }
190
+ },
191
+ definition: normalizedDefinition
192
+ };
193
+ return {
194
+ valid: true,
195
+ status: 'valid',
196
+ operation,
197
+ issues: [],
198
+ normalized
199
+ };
200
+ }
201
+ /** Normalize a valid candidate or throw with the complete validation issues. */
202
+ export function normalizeFoundationGraphWrite(input) {
203
+ const result = validateFoundationGraphWrite(input);
204
+ if (!result.valid || !result.normalized)
205
+ throw new FoundationGraphWriteError(result.issues);
206
+ return result.normalized;
207
+ }
208
+ /** Descriptive alias for callers that use the validation/normalization pair. */
209
+ export const validateAndNormalizeFoundationGraphWrite = normalizeFoundationGraphWrite;
210
+ function validateContext(context, issues) {
211
+ if (!isNonEmptyString(context.principal)) {
212
+ issues.push({ code: 'INVALID_INPUT', path: 'context.principal', message: 'context.principal must be a non-empty trusted identifier.' });
213
+ }
214
+ if (!isNonEmptyString(context.projectCode)) {
215
+ issues.push({ code: 'INVALID_INPUT', path: 'context.projectCode', message: 'context.projectCode must be a non-empty selected project code.' });
216
+ }
217
+ if (context.scope !== undefined && !isScope(context.scope)) {
218
+ issues.push({ code: 'INVALID_INPUT', path: 'context.scope', message: 'context.scope must be a valid trusted scope.' });
219
+ }
220
+ else if (context.scope !== undefined && context.scope.subjectIds.length === 0) {
221
+ issues.push({ code: 'INVALID_INPUT', path: 'context.scope.subjectIds', message: 'context.scope must contain at least one trusted subject.' });
222
+ }
223
+ }
224
+ function validateGraphRow(row, issues) {
225
+ if (!isNonEmptyString(row.id)) {
226
+ issues.push({ code: 'INVALID_IDENTITY', path: 'row.id', message: 'Graph row id must be a non-empty string.' });
227
+ }
228
+ const rowType = resolveRowType(row);
229
+ if (!rowType || !isFoundationType(rowType)) {
230
+ issues.push({ code: 'INVALID_IDENTITY', path: 'row.type', message: 'Graph row type must be one of objective, variable, model, or constraint.' });
231
+ }
232
+ if (row.type !== undefined && row.entityType !== undefined && row.type !== row.entityType) {
233
+ issues.push({ code: 'ROW_DEFINITION_MISMATCH', path: 'row.type', message: 'row.type and row.entityType must identify the same type.' });
234
+ }
235
+ if (!isCanonicalRevision(row.revision)) {
236
+ issues.push({ code: 'INVALID_IDENTITY', path: 'row.revision', message: 'Graph row revision must be a positive canonical decimal.' });
237
+ }
238
+ if (!isNonEmptyString(row.projectCode)) {
239
+ issues.push({ code: 'INVALID_INPUT', path: 'row.projectCode', message: 'Graph row projectCode must be a non-empty string.' });
240
+ }
241
+ if (!isRecord(row.payload) || !('foundation' in row.payload)) {
242
+ issues.push({ code: 'INVALID_INPUT', path: 'row.payload.foundation', message: 'Graph row payload.foundation is required.' });
243
+ }
244
+ }
245
+ function validateDraftWriteInvariants(definition, issues) {
246
+ if (definition.adoptionState !== 'draft') {
247
+ issues.push({ code: 'DRAFT_ADOPTION_REQUIRED', path: 'row.payload.foundation.adoptionState', message: 'Foundation Graph write accepts draft definitions only.' });
248
+ }
249
+ if (definition.storage !== 'candidate') {
250
+ issues.push({ code: 'CANDIDATE_STORAGE_REQUIRED', path: 'row.payload.foundation.storage', message: 'Foundation Graph draft writes require storage=candidate.' });
251
+ }
252
+ if (!sameStringArray(definition.authorizedUses, FOUNDATION_USES_DRAFT)) {
253
+ issues.push({ code: 'DRAFT_AUTHORIZATION_REQUIRED', path: 'row.payload.foundation.authorizedUses', message: 'Foundation Graph draft writes require authorizedUses=[draft].' });
254
+ }
255
+ if (!Array.isArray(definition.provenance) || definition.provenance.length === 0) {
256
+ issues.push({ code: 'PROVENANCE_REQUIRED', path: 'row.payload.foundation.provenance', message: 'At least one non-empty source provenance entry is required.' });
257
+ }
258
+ else {
259
+ for (const [index, item] of definition.provenance.entries()) {
260
+ if (!isRecord(item) || !isNonEmptyString(item.sourceId)) {
261
+ issues.push({ code: 'PROVENANCE_REQUIRED', path: `row.payload.foundation.provenance[${index}].sourceId`, message: 'Each provenance entry requires a non-empty sourceId.' });
262
+ }
263
+ }
264
+ }
265
+ }
266
+ function validateCandidateScope(definition, context, issues) {
267
+ const scope = definition.scope;
268
+ if (!isScope(scope) || scope.subjectIds.length === 0) {
269
+ issues.push({ code: 'SCOPE_VIOLATION', path: 'row.payload.foundation.scope.subjectIds', message: 'A draft Graph write must identify at least one scoped subject.' });
270
+ return;
271
+ }
272
+ if (context.scope !== undefined) {
273
+ if (!isScope(context.scope) || context.scope.subjectIds.length === 0 || !scopeWithin(scope, context.scope)) {
274
+ issues.push({ code: 'SCOPE_VIOLATION', path: 'row.payload.foundation.scope', message: 'Foundation scope exceeds the trusted principal/project scope.' });
275
+ }
276
+ return;
277
+ }
278
+ if (!scope.subjectIds.every((subject) => subject === context.projectCode)) {
279
+ issues.push({ code: 'SCOPE_VIOLATION', path: 'row.payload.foundation.scope.subjectIds', message: 'Without a trusted scope, the draft scope must contain only the selected projectCode.' });
280
+ }
281
+ }
282
+ function validateCurrentDefinition(current, candidate, rowId, rowType, context, issues) {
283
+ if (!isFoundationDefinitionRecord(current)) {
284
+ issues.push({ code: 'CURRENT_DEFINITION_REQUIRED', path: 'currentDefinition', message: 'An update requires the current Foundation definition.' });
285
+ return;
286
+ }
287
+ const currentValidation = validateFoundationDefinition(current, { use: 'draft' });
288
+ appendDefinitionIssues(issues, 'currentDefinition', currentValidation.issues);
289
+ if (!currentValidation.valid)
290
+ return;
291
+ if (current.id !== rowId || (rowType !== undefined && current.type !== rowType)) {
292
+ issues.push({ code: 'ROW_DEFINITION_MISMATCH', path: 'currentDefinition', message: 'currentDefinition identity must match the Graph row.' });
293
+ }
294
+ if (isFoundationDefinitionRecord(candidate) && (current.id !== candidate.id || current.type !== candidate.type)) {
295
+ issues.push({ code: 'ROW_DEFINITION_MISMATCH', path: 'currentDefinition', message: 'currentDefinition identity must match the candidate definition.' });
296
+ }
297
+ if (current.acl.ownerId !== candidateAclOwner(candidate)) {
298
+ issues.push({ code: 'OWNER_MISMATCH', path: 'row.payload.foundation.acl.ownerId', message: 'An update must preserve the current owner.' });
299
+ }
300
+ const canWrite = current.acl.ownerId === context.principal || current.acl.writerIds.includes(context.principal);
301
+ if (!canWrite) {
302
+ issues.push({ code: 'WRITER_NOT_AUTHORIZED', path: 'currentDefinition.acl', message: 'The trusted principal is not an owner or writer in the current ACL.' });
303
+ }
304
+ if (isFoundationDefinitionRecord(candidate) && candidate.acl.ownerId !== current.acl.ownerId) {
305
+ issues.push({ code: 'OWNER_MISMATCH', path: 'row.payload.foundation.acl.ownerId', message: 'An update cannot transfer ownership through a draft write.' });
306
+ }
307
+ if (isFoundationDefinitionRecord(candidate) && isFoundationAcl(candidate.acl)
308
+ && !sameAcl(current.acl, candidate.acl)
309
+ && current.acl.ownerId !== context.principal) {
310
+ issues.push({
311
+ code: 'ACL_CHANGE_FORBIDDEN',
312
+ path: 'row.payload.foundation.acl',
313
+ message: 'Only the current owner may change ACL fields during a draft update.'
314
+ });
315
+ }
316
+ validateCandidateScope(current, context, issues);
317
+ }
318
+ function candidateAclOwner(value) {
319
+ return isRecord(value) && isRecord(value.acl) && typeof value.acl.ownerId === 'string'
320
+ ? value.acl.ownerId
321
+ : undefined;
322
+ }
323
+ function isFoundationAcl(value) {
324
+ return isRecord(value)
325
+ && isNonEmptyString(value.ownerId)
326
+ && (value.visibility === 'private'
327
+ || value.visibility === 'project'
328
+ || value.visibility === 'organization'
329
+ || value.visibility === 'public')
330
+ && Array.isArray(value.readerIds)
331
+ && value.readerIds.every(isNonEmptyString)
332
+ && Array.isArray(value.writerIds)
333
+ && value.writerIds.every(isNonEmptyString);
334
+ }
335
+ function sameAcl(left, right) {
336
+ return left.ownerId === right.ownerId
337
+ && left.visibility === right.visibility
338
+ && sameStringArray(left.readerIds, right.readerIds)
339
+ && sameStringArray(left.writerIds, right.writerIds);
340
+ }
341
+ function appendDefinitionIssues(issues, prefix, validationIssues) {
342
+ for (const issue of validationIssues) {
343
+ issues.push({
344
+ code: 'DEFINITION_INVALID',
345
+ path: `${prefix}.${issue.path}`,
346
+ message: issue.message
347
+ });
348
+ }
349
+ }
350
+ function invalid(issues, first, operation) {
351
+ if (first)
352
+ issues.unshift(first);
353
+ return {
354
+ valid: false,
355
+ status: 'invalid',
356
+ ...(operation === undefined ? {} : { operation }),
357
+ issues
358
+ };
359
+ }
360
+ function resolveRowType(row) {
361
+ const candidate = row.type ?? row.entityType;
362
+ return isFoundationType(candidate) ? candidate : undefined;
363
+ }
364
+ function isFoundationDefinitionRecord(value) {
365
+ return isRecord(value)
366
+ && isNonEmptyString(value.id)
367
+ && isFoundationType(value.type)
368
+ && isNonEmptyString(value.revision);
369
+ }
370
+ function unwrapFoundation(value) {
371
+ if (isRecord(value) && isRecord(value.foundation))
372
+ return value.foundation;
373
+ return value;
374
+ }
375
+ function isFoundationType(value) {
376
+ return typeof value === 'string' && FOUNDATION_TYPES.includes(value);
377
+ }
378
+ function isCanonicalRevision(value) {
379
+ return typeof value === 'string' && REVISION_PATTERN.test(value);
380
+ }
381
+ function isNonEmptyString(value) {
382
+ return typeof value === 'string' && value.trim().length > 0;
383
+ }
384
+ function isRecord(value) {
385
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
386
+ }
387
+ function isScope(value) {
388
+ if (!isRecord(value) || !Array.isArray(value.subjectIds) || !value.subjectIds.every(isNonEmptyString))
389
+ return false;
390
+ if (!isNonEmptyString(value.validFrom) || !isRfc3339(value.validFrom))
391
+ return false;
392
+ if (value.validUntil !== undefined && (!isNonEmptyString(value.validUntil) || !isRfc3339(value.validUntil)))
393
+ return false;
394
+ if (value.validUntil === undefined)
395
+ return true;
396
+ return parseRfc3339(value.validUntil) > parseRfc3339(value.validFrom);
397
+ }
398
+ function scopeWithin(inner, outer) {
399
+ if (!inner.subjectIds.every((subject) => outer.subjectIds.includes(subject)))
400
+ return false;
401
+ const innerFrom = parseRfc3339(inner.validFrom);
402
+ const outerFrom = parseRfc3339(outer.validFrom);
403
+ if (innerFrom === undefined || outerFrom === undefined || innerFrom < outerFrom)
404
+ return false;
405
+ if (inner.validUntil === undefined)
406
+ return outer.validUntil === undefined;
407
+ const innerUntil = parseRfc3339(inner.validUntil);
408
+ if (innerUntil === undefined || outer.validUntil === undefined)
409
+ return innerUntil !== undefined && outer.validUntil === undefined;
410
+ const outerUntil = parseRfc3339(outer.validUntil);
411
+ return outerUntil !== undefined && innerUntil <= outerUntil;
412
+ }
413
+ function sameStringArray(left, right) {
414
+ return Array.isArray(left) && left.length === right.length && left.every((item, index) => item === right[index]);
415
+ }
416
+ function isRfc3339(value) {
417
+ return parseRfc3339(value) !== undefined;
418
+ }
419
+ function parseRfc3339(value) {
420
+ const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|[+-]\d{2}:\d{2})$/u.exec(value);
421
+ if (!match)
422
+ return undefined;
423
+ const year = Number(match[1]);
424
+ const month = Number(match[2]);
425
+ const day = Number(match[3]);
426
+ const hour = Number(match[4]);
427
+ const minute = Number(match[5]);
428
+ const second = Number(match[6]);
429
+ if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || minute > 59 || second > 59)
430
+ return undefined;
431
+ const zone = match[8];
432
+ let offsetMinutes = 0;
433
+ if (zone !== 'Z') {
434
+ const offsetHour = Number(zone.slice(1, 3));
435
+ const offsetMinute = Number(zone.slice(4, 6));
436
+ if (offsetHour > 23 || offsetMinute > 59)
437
+ return undefined;
438
+ offsetMinutes = (zone[0] === '-' ? -1 : 1) * (offsetHour * 60 + offsetMinute);
439
+ }
440
+ const milliseconds = Number(((match[7] ?? '') + '000').slice(0, 3));
441
+ return Date.UTC(year, month - 1, day, hour, minute, second, milliseconds) - offsetMinutes * 60_000;
442
+ }
443
+ function daysInMonth(year, month) {
444
+ if (month === 2)
445
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28;
446
+ return [4, 6, 9, 11].includes(month) ? 30 : 31;
447
+ }
@@ -0,0 +1,90 @@
1
+ import type { CompanyOsObjectives } from './company-os-objectives.js';
2
+ import { type FoundationStoreContext } from './foundation-store.js';
3
+ import type { FoundationRef } from './foundation-catalog.js';
4
+ import type { FoundationRevision, ObjectiveDefinition } from './ontology-foundation.js';
5
+ import type { FoundationCatalogRecord } from './types.js';
6
+ /**
7
+ * HTTP boundary shared by the OSS Objective UI and organization adapters.
8
+ *
9
+ * This module intentionally exposes a Fetch-style route handler instead of
10
+ * opening a listener. A local host can mount it beside Reservation,
11
+ * Decision, or another Company OS route on one server. Authentication,
12
+ * tenant scope, and CSRF are supplied by that host; none of them are read
13
+ * from a JSON request body.
14
+ */
15
+ export declare const FOUNDATION_HTTP_CONTRACT_VERSION: "brainbase.foundation-http.v1";
16
+ export declare const FOUNDATION_HTTP_PREFIX: "/api/foundation";
17
+ export declare const FOUNDATION_OBJECTIVE_ROUTE_PREFIX: "/api/foundation/objectives";
18
+ export declare const FOUNDATION_OBJECTIVE_ROUTES: readonly string[];
19
+ type MaybePromise<T> = T | Promise<T>;
20
+ export interface FoundationHttpRouteContext {
21
+ /** Resolved by the host's trusted auth/tenant boundary. */
22
+ readonly context: FoundationStoreContext;
23
+ readonly request: Request;
24
+ }
25
+ /**
26
+ * A route is deliberately a small composable unit. `null` means that the
27
+ * route does not own this request, allowing a reservation/decision route to
28
+ * be mounted beside it without a second server or a route-specific BFF.
29
+ */
30
+ export interface FoundationHttpRoute {
31
+ readonly name: string;
32
+ readonly methods: readonly string[];
33
+ readonly paths: readonly string[];
34
+ matches(request: Request): boolean;
35
+ handle(request: Request, input: FoundationHttpRouteContext): Promise<Response>;
36
+ }
37
+ export interface FoundationHttpCsrfVerifier {
38
+ verify(request: Request, context: FoundationStoreContext): MaybePromise<void | boolean>;
39
+ }
40
+ export interface FoundationHttpRouterOptions {
41
+ /** Return null to fail closed before any route handler is called. */
42
+ resolveContext(request: Request): MaybePromise<FoundationStoreContext | null>;
43
+ /** Mutation routes return 501 when no verifier is configured. */
44
+ csrf?: FoundationHttpCsrfVerifier;
45
+ routes: readonly FoundationHttpRoute[];
46
+ bodyLimitBytes?: number;
47
+ }
48
+ export interface FoundationHttpRouter {
49
+ readonly contractVersion: typeof FOUNDATION_HTTP_CONTRACT_VERSION;
50
+ readonly routes: readonly FoundationHttpRoute[];
51
+ handle(request: Request): Promise<Response>;
52
+ }
53
+ export interface ObjectiveDefinitionBuilderInput {
54
+ operation: 'create' | 'update';
55
+ /** Sanitized domain fields. Authorization fields have already been rejected. */
56
+ body: Readonly<Record<string, unknown>>;
57
+ context: FoundationStoreContext;
58
+ current?: ObjectiveDefinition;
59
+ expectedRevision?: string;
60
+ }
61
+ /**
62
+ * Hosts must construct ACL/scope/provenance/storage from trusted context. A
63
+ * request body is only a proposal for domain fields and cannot authorize a
64
+ * tenant, principal, visibility, or permitted use.
65
+ */
66
+ export type ObjectiveDefinitionBuilder = (input: ObjectiveDefinitionBuilderInput) => MaybePromise<ObjectiveDefinition>;
67
+ export interface ObjectiveFoundationHttpRouteOptions {
68
+ objectives: Pick<CompanyOsObjectives, 'createObjective' | 'readObjective' | 'updateObjective' | 'checkObjectiveReadiness'> & {
69
+ list?(type: 'objective', context: FoundationStoreContext): Promise<FoundationCatalogRecord[]>;
70
+ };
71
+ /** Required for writes; the host supplies trusted ACL/scope fields. */
72
+ buildDefinition?: ObjectiveDefinitionBuilder;
73
+ /** Typed relation reads remain owned by the Story provider. */
74
+ listStoryObjectiveLinks?: (storyId: string, context: FoundationStoreContext) => MaybePromise<unknown>;
75
+ /** Constraint relation reads/writes remain owned by the canonical adapter. */
76
+ listObjectiveConstraintRefs?: (reference: FoundationRevision, context: FoundationStoreContext) => MaybePromise<unknown>;
77
+ replaceObjectiveConstraintRefs?: (reference: FoundationRef, refs: readonly FoundationRevision[], context: FoundationStoreContext) => MaybePromise<unknown>;
78
+ bodyLimitBytes?: number;
79
+ }
80
+ /**
81
+ * Compose OSS Company OS routes under one local server boundary. The
82
+ * resolver is called only for a matching route and the resulting trusted
83
+ * context is passed to that route. Unknown routes remain an ordinary 404 so
84
+ * the host can mount this handler beside other app routes.
85
+ */
86
+ export declare function createFoundationHttpRouter(options: FoundationHttpRouterOptions): FoundationHttpRouter;
87
+ /** Convenience constructor for a local/organization host with Objective routes only. */
88
+ export declare function createObjectiveFoundationHttpHandler(options: ObjectiveFoundationHttpRouteOptions & Omit<FoundationHttpRouterOptions, 'routes'>): FoundationHttpRouter;
89
+ export declare function createObjectiveFoundationRoute(options: ObjectiveFoundationHttpRouteOptions): FoundationHttpRoute;
90
+ export {};