@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.
- package/contracts/foundation/graph-history.sql +634 -0
- package/contracts/judgment-value-proof/schema.json +43 -1
- package/dist/canonical-task-contract.d.ts +9 -0
- package/dist/canonical-task-contract.js +39 -0
- package/dist/canonical-task-principal.d.ts +16 -0
- package/dist/canonical-task-principal.js +36 -0
- package/dist/canonical-task-service.d.ts +251 -0
- package/dist/canonical-task-service.js +820 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-evaluation.d.ts +193 -0
- package/dist/company-os-evaluation.js +1150 -0
- package/dist/company-os-impact-review.d.ts +248 -0
- package/dist/company-os-impact-review.js +663 -0
- package/dist/company-os-learning-adoption.d.ts +288 -0
- package/dist/company-os-learning-adoption.js +944 -0
- package/dist/company-os-objectives.d.ts +63 -0
- package/dist/company-os-objectives.js +234 -0
- package/dist/company-os-receipt-adapter.d.ts +109 -0
- package/dist/company-os-receipt-adapter.js +636 -0
- package/dist/constraint-exception-store.d.ts +23 -0
- package/dist/constraint-exception-store.js +108 -0
- package/dist/constraint-resolution.d.ts +175 -0
- package/dist/constraint-resolution.js +922 -0
- package/dist/decision-adapter.d.ts +145 -0
- package/dist/decision-adapter.js +586 -0
- package/dist/durable-wait-http.d.ts +51 -0
- package/dist/durable-wait-http.js +497 -0
- package/dist/durable-waits.d.ts +234 -0
- package/dist/durable-waits.js +944 -0
- package/dist/execution-authority-http.d.ts +28 -0
- package/dist/execution-authority-http.js +359 -0
- package/dist/execution-authority.d.ts +201 -0
- package/dist/execution-authority.js +634 -0
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -0
- package/dist/foundation-catalog.d.ts +26 -0
- package/dist/foundation-catalog.js +206 -0
- package/dist/foundation-constraint-store.d.ts +50 -0
- package/dist/foundation-constraint-store.js +215 -0
- package/dist/foundation-graph-http.d.ts +43 -0
- package/dist/foundation-graph-http.js +243 -0
- package/dist/foundation-graph-write.d.ts +90 -0
- package/dist/foundation-graph-write.js +447 -0
- package/dist/foundation-http.d.ts +90 -0
- package/dist/foundation-http.js +439 -0
- package/dist/foundation-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -0
- package/dist/foundation-store.d.ts +132 -0
- package/dist/foundation-store.js +654 -0
- package/dist/graph-corrections.d.ts +157 -0
- package/dist/graph-corrections.js +620 -0
- package/dist/graph-foundation-reader.d.ts +60 -0
- package/dist/graph-foundation-reader.js +494 -0
- package/dist/graph-retrieval.d.ts +6 -1
- package/dist/graph-revision-reader.d.ts +80 -0
- package/dist/graph-revision-reader.js +246 -0
- package/dist/graph-web-http.d.ts +26 -0
- package/dist/graph-web-http.js +215 -0
- package/dist/graph-web.d.ts +364 -0
- package/dist/graph-web.js +639 -0
- package/dist/judgment-dag-composition.d.ts +224 -0
- package/dist/judgment-dag-composition.js +731 -0
- package/dist/judgment-dag.d.ts +2 -0
- package/dist/judgment-dag.js +1 -0
- package/dist/judgment-problem-snapshot.d.ts +213 -0
- package/dist/judgment-problem-snapshot.js +1073 -0
- package/dist/judgment-value-proof-review.d.ts +166 -0
- package/dist/judgment-value-proof-review.js +437 -0
- package/dist/judgment-value-proof.d.ts +28 -1
- package/dist/judgment-value-proof.js +53 -8
- package/dist/judgment-view-http.d.ts +22 -0
- package/dist/judgment-view-http.js +128 -0
- package/dist/judgment-view.d.ts +222 -0
- package/dist/judgment-view.js +627 -0
- package/dist/knowledge-adapter.d.ts +112 -0
- package/dist/knowledge-adapter.js +691 -0
- package/dist/knowledge-continuation.d.ts +127 -0
- package/dist/knowledge-continuation.js +446 -0
- package/dist/knowledge-lookup.d.ts +178 -0
- package/dist/knowledge-lookup.js +758 -0
- package/dist/local-web-fetch-bridge.d.ts +5 -0
- package/dist/local-web-fetch-bridge.js +37 -0
- package/dist/local-web-host.d.ts +109 -0
- package/dist/local-web-host.js +688 -0
- package/dist/local-web-security.d.ts +25 -0
- package/dist/local-web-security.js +95 -0
- package/dist/ontology-foundation.d.ts +209 -0
- package/dist/ontology-foundation.js +1000 -0
- package/dist/ontology.d.ts +13 -0
- package/dist/ontology.js +14 -0
- package/dist/organization-connection.d.ts +164 -0
- package/dist/organization-connection.js +428 -0
- package/dist/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +132 -0
- package/dist/personal-knowledge.js +779 -0
- package/dist/personal-memory-handover.d.ts +108 -0
- package/dist/personal-memory-handover.js +369 -0
- package/dist/philosophy-revision-reader.d.ts +85 -0
- package/dist/philosophy-revision-reader.js +241 -0
- package/dist/portable-graph.d.ts +24 -0
- package/dist/portable-graph.js +77 -0
- package/dist/problem-candidates.d.ts +149 -0
- package/dist/problem-candidates.js +898 -0
- package/dist/problem-selection.d.ts +238 -0
- package/dist/problem-selection.js +995 -0
- package/dist/resource-reservation-http.d.ts +31 -0
- package/dist/resource-reservation-http.js +371 -0
- package/dist/resource-reservations.d.ts +221 -0
- package/dist/resource-reservations.js +577 -0
- package/dist/server.d.ts +104 -2
- package/dist/server.js +186 -4
- package/dist/ssot.d.ts +6 -1
- package/dist/ssot.js +63 -5
- package/dist/streamable-http.d.ts +42 -0
- package/dist/streamable-http.js +288 -0
- package/dist/types.d.ts +20 -0
- package/dist/value-proof-review-http.d.ts +25 -0
- package/dist/value-proof-review-http.js +185 -0
- package/dist/world-model.d.ts +199 -0
- package/dist/world-model.js +955 -0
- package/package.json +230 -3
- package/ui/brainbase-tokens.css +56 -0
- package/ui/graph-own-share.js +367 -0
- package/ui/graph-projects-view.css +23 -0
- package/ui/graph-projects-view.js +730 -0
- package/ui/graph-registry-view.css +101 -0
- package/ui/graph-registry-view.js +804 -0
- package/ui/graph-view-shared.css +522 -0
- package/ui/graph-view-shared.js +1227 -0
- package/ui/judgment-view.css +312 -0
- package/ui/judgment-view.js +508 -0
- package/ui/local-web-shell.css +396 -0
- package/ui/local-web-shell.js +378 -0
- package/ui/objective-editor-http-port.js +154 -0
- package/ui/objective-editor.css +286 -0
- package/ui/objective-editor.js +1586 -0
- package/ui/outcome-knowledge.css +50 -14
- package/ui/outcome-knowledge.js +15 -1
- package/ui/outcome-mana.css +91 -14
- package/ui/outcome-mana.js +958 -61
- package/ui/value-proof-review.css +504 -0
- package/ui/value-proof-review.js +1104 -0
- package/ui/workspace-kit.css +485 -0
- package/ui/workspace-kit.js +229 -0
- package/ui/world-model-view.css +188 -0
- package/ui/world-model-view.js +486 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { isActiveAt } from './canonical-graph.js';
|
|
3
|
+
import { canonicalPortableJson } from './portable-graph.js';
|
|
4
|
+
/** The host owns the canonical philosophy source; this package owns only the read port. */
|
|
5
|
+
export const PHILOSOPHY_REVISION_READ_CONTRACT_VERSION = 'philosophy-revision-read.v1';
|
|
6
|
+
const REVISION_PATTERN = /^[1-9]\d*$/u;
|
|
7
|
+
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/u;
|
|
8
|
+
const SCOPE_TYPES = ['personal', 'project', 'organization'];
|
|
9
|
+
const ACL_VISIBILITIES = ['private', 'project', 'organization', 'public'];
|
|
10
|
+
/**
|
|
11
|
+
* Computes the stable identity of a canonical philosophy revision.
|
|
12
|
+
* Current ACL and scope are intentionally excluded so authorization changes do
|
|
13
|
+
* not create a false content revision; they are checked on every read.
|
|
14
|
+
*/
|
|
15
|
+
export function philosophyRevisionDigest(input) {
|
|
16
|
+
assertPhilosophyRevisionIdentity(input);
|
|
17
|
+
assertPhilosophyRevisionPayload(input.payload);
|
|
18
|
+
assertPhilosophyRevisionApplicability(input.applicability);
|
|
19
|
+
const content = {
|
|
20
|
+
kind: input.kind,
|
|
21
|
+
id: input.id,
|
|
22
|
+
revision: input.revision,
|
|
23
|
+
payload: input.payload,
|
|
24
|
+
applicability: canonicalApplicability(input.applicability)
|
|
25
|
+
};
|
|
26
|
+
return `sha256:${createHash('sha256').update(canonicalPortableJson(content), 'utf8').digest('hex')}`;
|
|
27
|
+
}
|
|
28
|
+
/** Structural check for callers that need to validate a reference before I/O. */
|
|
29
|
+
export function isPhilosophyRevisionReference(value) {
|
|
30
|
+
if (!isPlainRecord(value) || value.kind !== 'philosophy')
|
|
31
|
+
return false;
|
|
32
|
+
if (!isNonEmptyString(value.id) || !isPositiveRevision(value.revision) || !isDigest(value.digest))
|
|
33
|
+
return false;
|
|
34
|
+
if (!isPhilosophyRevisionScope(value.scope) || !isRfc3339(value.valid_from))
|
|
35
|
+
return false;
|
|
36
|
+
if (value.valid_to === undefined || value.valid_to === null)
|
|
37
|
+
return true;
|
|
38
|
+
return typeof value.valid_to === 'string' && isValidInterval(value.valid_from, value.valid_to);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adapt canonical philosophy reads to `JudgmentProblemReferenceProvider.resolveOther`.
|
|
42
|
+
* Unknown reference kinds remain unresolved so the caller can compose other
|
|
43
|
+
* providers explicitly.
|
|
44
|
+
*/
|
|
45
|
+
export function createJudgmentProblemPhilosophyReferenceResolver(options) {
|
|
46
|
+
if (!options || !options.reader || typeof options.reader.read !== 'function') {
|
|
47
|
+
throw new TypeError('A PhilosophyRevisionReader is required');
|
|
48
|
+
}
|
|
49
|
+
return async (input) => {
|
|
50
|
+
if (input.reference.kind !== 'philosophy') {
|
|
51
|
+
return { status: 'unresolved', message: `Philosophy revision resolver does not handle ${input.reference.kind}` };
|
|
52
|
+
}
|
|
53
|
+
if (!isNonEmptyString(input.context.principal)) {
|
|
54
|
+
return { status: 'unauthorized', message: 'A trusted principal is required for philosophy revision reads' };
|
|
55
|
+
}
|
|
56
|
+
const reference = {
|
|
57
|
+
kind: 'philosophy',
|
|
58
|
+
id: input.reference.id,
|
|
59
|
+
revision: input.reference.revision,
|
|
60
|
+
digest: input.reference.digest,
|
|
61
|
+
scope: cloneScope(input.reference.scope),
|
|
62
|
+
valid_from: input.reference.valid_from,
|
|
63
|
+
...(input.reference.valid_to == null ? {} : { valid_to: input.reference.valid_to })
|
|
64
|
+
};
|
|
65
|
+
if (!isPhilosophyRevisionReference(reference)) {
|
|
66
|
+
return { status: 'unresolved', message: 'Philosophy revision reference is invalid' };
|
|
67
|
+
}
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await options.reader.read({
|
|
71
|
+
reference,
|
|
72
|
+
phase: input.phase,
|
|
73
|
+
// Snapshot access has no caller-supplied mutable scope. The reference
|
|
74
|
+
// scope is the requested boundary; the host must bind it to its trusted
|
|
75
|
+
// current tenant/project context before returning a record.
|
|
76
|
+
context: { principal: input.context.principal, scope: cloneScope(reference.scope) }
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { status: 'unresolved', message: 'Philosophy revision reader failed closed' };
|
|
81
|
+
}
|
|
82
|
+
if (!isPlainRecord(result) || typeof result.status !== 'string') {
|
|
83
|
+
return { status: 'unresolved', message: 'Philosophy revision reader returned an invalid result' };
|
|
84
|
+
}
|
|
85
|
+
if (result.status === 'missing') {
|
|
86
|
+
return { status: 'missing', message: result.message ?? 'Philosophy revision was not found' };
|
|
87
|
+
}
|
|
88
|
+
if (result.status === 'unauthorized') {
|
|
89
|
+
return { status: 'unauthorized', message: result.message ?? 'Philosophy revision is not readable' };
|
|
90
|
+
}
|
|
91
|
+
if (result.status === 'corrupt') {
|
|
92
|
+
return { status: 'unresolved', message: result.message ?? 'Philosophy revision failed integrity validation' };
|
|
93
|
+
}
|
|
94
|
+
if (result.status !== 'resolved' || !isPlainRecord(result.record)) {
|
|
95
|
+
return { status: 'unresolved', message: 'Philosophy revision reader returned an invalid resolved record' };
|
|
96
|
+
}
|
|
97
|
+
const integrity = validateResolvedRecord(result.record, reference, input.context.principal, input.phase);
|
|
98
|
+
if (integrity.status !== 'resolved')
|
|
99
|
+
return integrity;
|
|
100
|
+
return { status: 'resolved', digest: reference.digest };
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function validateResolvedRecord(record, reference, principal, phase) {
|
|
104
|
+
if (record.kind !== 'philosophy' || record.id !== reference.id || record.revision !== reference.revision) {
|
|
105
|
+
return { status: 'unresolved', message: 'Philosophy revision identity does not match the requested reference' };
|
|
106
|
+
}
|
|
107
|
+
if (!isDigest(record.digest) || record.digest !== reference.digest) {
|
|
108
|
+
return { status: 'unresolved', message: 'Philosophy revision digest does not match the requested reference' };
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
assertPhilosophyRevisionPayload(record.payload);
|
|
112
|
+
assertPhilosophyRevisionApplicability(record.applicability);
|
|
113
|
+
if (philosophyRevisionDigest(record) !== record.digest) {
|
|
114
|
+
return { status: 'unresolved', message: 'Philosophy revision payload digest is corrupt' };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return { status: 'unresolved', message: 'Philosophy revision payload or applicability is corrupt' };
|
|
119
|
+
}
|
|
120
|
+
if (!isPhilosophyRevisionAcl(record.currentAcl) || !isPhilosophyRevisionScope(record.currentScope)) {
|
|
121
|
+
return { status: 'unresolved', message: 'Philosophy revision authorization metadata is corrupt' };
|
|
122
|
+
}
|
|
123
|
+
if (!sameScope(record.currentScope, reference.scope)) {
|
|
124
|
+
return { status: 'unauthorized', message: 'Philosophy revision is outside the requested tenant/project scope' };
|
|
125
|
+
}
|
|
126
|
+
// A current save/read must prove that the revision applies to this
|
|
127
|
+
// judgment. Historical reads preserve the original reference and only
|
|
128
|
+
// re-check immutable content plus the current ACL/scope, matching the
|
|
129
|
+
// snapshot provider contract; applicability is not re-evaluated against a
|
|
130
|
+
// later policy boundary.
|
|
131
|
+
if (phase !== 'historical_read') {
|
|
132
|
+
if (!sameScope(record.applicability.scope, reference.scope)) {
|
|
133
|
+
return { status: 'not_applicable', message: 'Philosophy revision is outside the requested applicability scope' };
|
|
134
|
+
}
|
|
135
|
+
const referenceApplicability = referenceApplicabilityFrom(reference);
|
|
136
|
+
if (!periodContains(record.applicability, referenceApplicability)) {
|
|
137
|
+
return { status: 'not_applicable', message: 'Philosophy revision does not cover the requested applicability period' };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!canReadPhilosophyRevision(record.currentAcl, principal)) {
|
|
141
|
+
return { status: 'unauthorized', message: 'Philosophy revision is not readable by the current principal' };
|
|
142
|
+
}
|
|
143
|
+
return { status: 'resolved', digest: reference.digest };
|
|
144
|
+
}
|
|
145
|
+
function assertPhilosophyRevisionIdentity(value) {
|
|
146
|
+
if (value.kind !== 'philosophy' || !isNonEmptyString(value.id) || !isPositiveRevision(value.revision)) {
|
|
147
|
+
throw new TypeError('Philosophy revision identity requires kind, id, and a positive revision');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function assertPhilosophyRevisionPayload(value) {
|
|
151
|
+
try {
|
|
152
|
+
// canonicalPortableJson rejects undefined, non-JSON objects, non-finite
|
|
153
|
+
// numbers, and cyclic values while preserving opaque philosophy shape.
|
|
154
|
+
canonicalPortableJson(value);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
throw new TypeError('Philosophy revision payload must be a JSON value');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function assertPhilosophyRevisionApplicability(value) {
|
|
161
|
+
if (!isPlainRecord(value) || !isPhilosophyRevisionScope(value.scope) || !isRfc3339(value.validFrom) ||
|
|
162
|
+
(value.validUntil !== undefined && !isRfc3339(value.validUntil)) ||
|
|
163
|
+
(typeof value.validUntil === 'string' && Date.parse(value.validFrom) > Date.parse(value.validUntil))) {
|
|
164
|
+
throw new TypeError('Philosophy revision applicability is invalid');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function referenceApplicabilityFrom(reference) {
|
|
168
|
+
return {
|
|
169
|
+
scope: cloneScope(reference.scope),
|
|
170
|
+
validFrom: reference.valid_from,
|
|
171
|
+
...(reference.valid_to == null ? {} : { validUntil: reference.valid_to })
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function canonicalApplicability(value) {
|
|
175
|
+
return {
|
|
176
|
+
scope: cloneScope(value.scope),
|
|
177
|
+
validFrom: value.validFrom,
|
|
178
|
+
...(value.validUntil === undefined ? {} : { validUntil: value.validUntil })
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function periodContains(source, requested) {
|
|
182
|
+
const sourceFrom = Date.parse(source.validFrom);
|
|
183
|
+
const sourceUntil = source.validUntil === undefined ? Number.POSITIVE_INFINITY : Date.parse(source.validUntil);
|
|
184
|
+
const requestedFrom = Date.parse(requested.validFrom);
|
|
185
|
+
const requestedUntil = requested.validUntil === undefined ? Number.POSITIVE_INFINITY : Date.parse(requested.validUntil);
|
|
186
|
+
return sourceFrom <= requestedFrom && sourceUntil >= requestedUntil;
|
|
187
|
+
}
|
|
188
|
+
function isValidInterval(from, to) {
|
|
189
|
+
return isRfc3339(from) && isRfc3339(to) && Date.parse(from) <= Date.parse(to);
|
|
190
|
+
}
|
|
191
|
+
function isPhilosophyRevisionAcl(value) {
|
|
192
|
+
if (!isPlainRecord(value) || !isNonEmptyString(value.ownerId) ||
|
|
193
|
+
!ACL_VISIBILITIES.includes(value.visibility) ||
|
|
194
|
+
!isStringArray(value.readerIds) || !isStringArray(value.writerIds))
|
|
195
|
+
return false;
|
|
196
|
+
return new Set(value.readerIds).size === value.readerIds.length && new Set(value.writerIds).size === value.writerIds.length;
|
|
197
|
+
}
|
|
198
|
+
function canReadPhilosophyRevision(acl, principal) {
|
|
199
|
+
return acl.visibility === 'public' || acl.ownerId === principal ||
|
|
200
|
+
acl.readerIds.includes(principal) || acl.writerIds.includes(principal);
|
|
201
|
+
}
|
|
202
|
+
function isPhilosophyRevisionScope(value) {
|
|
203
|
+
return isPlainRecord(value) && SCOPE_TYPES.includes(value.type) && isNonEmptyString(value.id);
|
|
204
|
+
}
|
|
205
|
+
function sameScope(left, right) {
|
|
206
|
+
return left.type === right.type && left.id === right.id;
|
|
207
|
+
}
|
|
208
|
+
function cloneScope(scope) {
|
|
209
|
+
return { type: scope.type, id: scope.id };
|
|
210
|
+
}
|
|
211
|
+
function isDigest(value) {
|
|
212
|
+
return typeof value === 'string' && DIGEST_PATTERN.test(value);
|
|
213
|
+
}
|
|
214
|
+
function isPositiveRevision(value) {
|
|
215
|
+
return typeof value === 'string' && REVISION_PATTERN.test(value);
|
|
216
|
+
}
|
|
217
|
+
function isNonEmptyString(value) {
|
|
218
|
+
return typeof value === 'string' && value.trim().length > 0 && !value.includes('\0');
|
|
219
|
+
}
|
|
220
|
+
function isPlainRecord(value) {
|
|
221
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
222
|
+
return false;
|
|
223
|
+
const prototype = Object.getPrototypeOf(value);
|
|
224
|
+
return prototype === Object.prototype || prototype === null;
|
|
225
|
+
}
|
|
226
|
+
function isStringArray(value) {
|
|
227
|
+
return Array.isArray(value) && value.every((item) => isNonEmptyString(item));
|
|
228
|
+
}
|
|
229
|
+
function isRfc3339(value) {
|
|
230
|
+
if (typeof value !== 'string')
|
|
231
|
+
return false;
|
|
232
|
+
try {
|
|
233
|
+
// Reuse the canonical Graph calendar validation so philosophy revision
|
|
234
|
+
// periods reject the same malformed timestamps as Graph records.
|
|
235
|
+
isActiveAt({ validFrom: value }, value);
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
package/dist/portable-graph.d.ts
CHANGED
|
@@ -29,6 +29,30 @@ export declare function createPortableGraph(os: PersonalOs): PortableGraphBundle
|
|
|
29
29
|
export declare function validatePortableGraph(value: unknown): asserts value is PortableGraphBundle;
|
|
30
30
|
/** Rehydrate a validated bundle into the shared retrieval input shape. */
|
|
31
31
|
export declare function hydratePortableGraph(bundle: PortableGraphBundle): PersonalOs;
|
|
32
|
+
/** The ID an OSS Personal OS gives its owner; a host replaces it with the owner's own ID when reading a bundle. */
|
|
33
|
+
export declare const PORTABLE_GRAPH_OWNER_PLACEHOLDER = "self";
|
|
34
|
+
export interface PortableGraphOwner {
|
|
35
|
+
/** The owner's person ID in the host that stores the bundle (never `self`). */
|
|
36
|
+
personId: string;
|
|
37
|
+
/** The owner's name in that host, shown in place of the bundle's name for `self`. */
|
|
38
|
+
name?: string | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read a bundle as its owner: every `self` becomes the owner's person ID in
|
|
42
|
+
* the host, and the owner's record carries the host's name for them (the
|
|
43
|
+
* bundle's own name stays as an alias). Replaced: `graph.owner.id`, the
|
|
44
|
+
* entity `self`, relation endpoints, and in the foundation definitions
|
|
45
|
+
* `acl.ownerId` / `readerIds` / `writerIds`, `beneficiaryIds`,
|
|
46
|
+
* `accountableId` and the `subjectIds` of every scope (`scope`,
|
|
47
|
+
* `applicability`, constraint exception scopes).
|
|
48
|
+
*
|
|
49
|
+
* The result is a read projection, not a bundle to store or send: the input
|
|
50
|
+
* is not changed, relation IDs stay those of the stored bundle (they name the
|
|
51
|
+
* relation there) and foundation digests are not recomputed. A bundle that
|
|
52
|
+
* already has another record with the owner's person ID is refused rather
|
|
53
|
+
* than merged.
|
|
54
|
+
*/
|
|
55
|
+
export declare function bindPortableGraphOwner(bundle: PortableGraphBundle, owner: PortableGraphOwner): PortableGraphBundle;
|
|
32
56
|
/** Use exactly the OSS retrieval implementation against a portable bundle. */
|
|
33
57
|
export declare function retrievePortableGraph(bundle: PortableGraphBundle, input: GraphRetrievalInput, provider?: EmbeddingProvider): Promise<GraphRetrievalResponse>;
|
|
34
58
|
/** Canonical JSON with recursively sorted object keys and preserved array order. */
|
package/dist/portable-graph.js
CHANGED
|
@@ -102,6 +102,83 @@ export function hydratePortableGraph(bundle) {
|
|
|
102
102
|
sourceCount: 0
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
/** The ID an OSS Personal OS gives its owner; a host replaces it with the owner's own ID when reading a bundle. */
|
|
106
|
+
export const PORTABLE_GRAPH_OWNER_PLACEHOLDER = 'self';
|
|
107
|
+
/**
|
|
108
|
+
* Read a bundle as its owner: every `self` becomes the owner's person ID in
|
|
109
|
+
* the host, and the owner's record carries the host's name for them (the
|
|
110
|
+
* bundle's own name stays as an alias). Replaced: `graph.owner.id`, the
|
|
111
|
+
* entity `self`, relation endpoints, and in the foundation definitions
|
|
112
|
+
* `acl.ownerId` / `readerIds` / `writerIds`, `beneficiaryIds`,
|
|
113
|
+
* `accountableId` and the `subjectIds` of every scope (`scope`,
|
|
114
|
+
* `applicability`, constraint exception scopes).
|
|
115
|
+
*
|
|
116
|
+
* The result is a read projection, not a bundle to store or send: the input
|
|
117
|
+
* is not changed, relation IDs stay those of the stored bundle (they name the
|
|
118
|
+
* relation there) and foundation digests are not recomputed. A bundle that
|
|
119
|
+
* already has another record with the owner's person ID is refused rather
|
|
120
|
+
* than merged.
|
|
121
|
+
*/
|
|
122
|
+
export function bindPortableGraphOwner(bundle, owner) {
|
|
123
|
+
validatePortableGraph(bundle);
|
|
124
|
+
const personId = typeof owner?.personId === 'string' ? owner.personId.trim() : '';
|
|
125
|
+
if (!personId || personId === PORTABLE_GRAPH_OWNER_PLACEHOLDER) {
|
|
126
|
+
throw new Error('PORTABLE-GRAPH-OWNER: the owner person ID is required and cannot be self');
|
|
127
|
+
}
|
|
128
|
+
if (bundle.graph.entities.some((entity) => entity.id === personId)) {
|
|
129
|
+
throw new Error(`PORTABLE-GRAPH-OWNER-CONFLICT: the bundle already has a record ${personId}`);
|
|
130
|
+
}
|
|
131
|
+
const name = typeof owner.name === 'string' && owner.name.trim() ? owner.name.trim() : null;
|
|
132
|
+
const bound = structuredClone(bundle);
|
|
133
|
+
const swap = (id) => (id === PORTABLE_GRAPH_OWNER_PLACEHOLDER ? personId : id);
|
|
134
|
+
const swapAll = (ids) => {
|
|
135
|
+
if (!Array.isArray(ids))
|
|
136
|
+
return;
|
|
137
|
+
for (let index = 0; index < ids.length; index += 1)
|
|
138
|
+
if (typeof ids[index] === 'string')
|
|
139
|
+
ids[index] = swap(ids[index]);
|
|
140
|
+
};
|
|
141
|
+
const graph = bound.graph;
|
|
142
|
+
if (graph.owner) {
|
|
143
|
+
if (graph.owner.id === PORTABLE_GRAPH_OWNER_PLACEHOLDER)
|
|
144
|
+
graph.owner.id = personId;
|
|
145
|
+
if (name)
|
|
146
|
+
graph.owner.name = name;
|
|
147
|
+
}
|
|
148
|
+
for (const entity of graph.entities) {
|
|
149
|
+
if (entity.id !== PORTABLE_GRAPH_OWNER_PLACEHOLDER)
|
|
150
|
+
continue;
|
|
151
|
+
entity.id = personId;
|
|
152
|
+
if (name && name !== entity.name) {
|
|
153
|
+
const aliases = entity.aliases ?? [];
|
|
154
|
+
if (entity.name.trim() && !aliases.includes(entity.name))
|
|
155
|
+
aliases.push(entity.name);
|
|
156
|
+
entity.aliases = aliases;
|
|
157
|
+
entity.name = name;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const edge of graph.edges) {
|
|
161
|
+
edge.fromId = swap(edge.fromId);
|
|
162
|
+
edge.toId = swap(edge.toId);
|
|
163
|
+
}
|
|
164
|
+
for (const record of graph.foundation?.records ?? []) {
|
|
165
|
+
const definition = record.definition;
|
|
166
|
+
if (isPlainRecord(definition.acl)) {
|
|
167
|
+
if (typeof definition.acl.ownerId === 'string')
|
|
168
|
+
definition.acl.ownerId = swap(definition.acl.ownerId);
|
|
169
|
+
swapAll(definition.acl.readerIds);
|
|
170
|
+
swapAll(definition.acl.writerIds);
|
|
171
|
+
}
|
|
172
|
+
swapAll(definition.beneficiaryIds);
|
|
173
|
+
if (typeof definition.accountableId === 'string')
|
|
174
|
+
definition.accountableId = swap(definition.accountableId);
|
|
175
|
+
for (const scope of [definition.scope, definition.applicability, ...(Array.isArray(definition.exceptions) ? definition.exceptions.map((item) => item?.scope) : [])]) {
|
|
176
|
+
if (isPlainRecord(scope))
|
|
177
|
+
swapAll(scope.subjectIds);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return bound;
|
|
181
|
+
}
|
|
105
182
|
/** Use exactly the OSS retrieval implementation against a portable bundle. */
|
|
106
183
|
export function retrievePortableGraph(bundle, input, provider) {
|
|
107
184
|
return retrieveGraph(hydratePortableGraph(bundle), input, provider);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { FoundationRevisionStore } from './foundation-store.js';
|
|
2
|
+
import type { FoundationAcl, FoundationRevision, FoundationScope } from './ontology-foundation.js';
|
|
3
|
+
/**
|
|
4
|
+
* Problem candidates are evidence-sidecar records. They are deliberately not
|
|
5
|
+
* Graph entities: a candidate is a reviewable input to problem selection, not
|
|
6
|
+
* an approved problem or a canonical fact.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PROBLEM_CANDIDATES_CONTRACT_VERSION: "problem-candidates.v1";
|
|
9
|
+
export declare const PROBLEM_CANDIDATE_CATALOG_VERSION: 1;
|
|
10
|
+
export declare const PROBLEM_CANDIDATES_SIDECAR: "evidence/problem-candidates.json";
|
|
11
|
+
export type ProblemCandidateKind = 'gap' | 'opportunity' | 'threat' | 'uncertainty';
|
|
12
|
+
export type ProblemCandidateStatus = 'candidate' | 'merged' | 'dismissed';
|
|
13
|
+
export type ProblemCandidateSourceKind = 'event' | 'observation' | 'document' | 'decision' | 'import' | 'candidate' | 'evidence';
|
|
14
|
+
export interface ProblemCandidateKnown<T> {
|
|
15
|
+
readonly status: 'known';
|
|
16
|
+
readonly value: T;
|
|
17
|
+
}
|
|
18
|
+
export interface ProblemCandidateUnknown {
|
|
19
|
+
readonly status: 'unknown';
|
|
20
|
+
readonly reason: string;
|
|
21
|
+
}
|
|
22
|
+
/** Unknown is explicit so missing data cannot become zero, empty, or success. */
|
|
23
|
+
export type ProblemCandidateField<T> = ProblemCandidateKnown<T> | ProblemCandidateUnknown;
|
|
24
|
+
export interface ProblemCandidateSourceRef {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly kind: ProblemCandidateSourceKind;
|
|
27
|
+
/** Optional immutable source digest. Evidence content is never embedded. */
|
|
28
|
+
readonly digest?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface ProblemCandidateEventInput {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly occurredAt: string;
|
|
33
|
+
readonly recordedAt: string;
|
|
34
|
+
readonly sourceRefs: readonly ProblemCandidateSourceRef[];
|
|
35
|
+
}
|
|
36
|
+
export interface ProblemCandidateEvent extends ProblemCandidateEventInput {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly occurredAt: string;
|
|
39
|
+
readonly recordedAt: string;
|
|
40
|
+
readonly sourceRefs: readonly ProblemCandidateSourceRef[];
|
|
41
|
+
}
|
|
42
|
+
/** Exact Objective revision plus its canonical definition digest. */
|
|
43
|
+
export interface ProblemCandidateObjectiveReference extends FoundationRevision {
|
|
44
|
+
readonly type: 'objective';
|
|
45
|
+
readonly digest: string;
|
|
46
|
+
}
|
|
47
|
+
export interface ProblemCandidateDeadline {
|
|
48
|
+
readonly dueAt: string;
|
|
49
|
+
readonly evaluationAt?: string;
|
|
50
|
+
}
|
|
51
|
+
export interface ProblemCandidateResource {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly kind: string;
|
|
54
|
+
readonly quantity?: number;
|
|
55
|
+
readonly unit?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface ProblemCandidateInput {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly kind: ProblemCandidateKind;
|
|
60
|
+
readonly statement: string;
|
|
61
|
+
readonly objectiveRefs: readonly ProblemCandidateObjectiveReference[];
|
|
62
|
+
readonly event: ProblemCandidateEventInput;
|
|
63
|
+
readonly observedGap: ProblemCandidateField<string>;
|
|
64
|
+
readonly opportunity: ProblemCandidateField<string>;
|
|
65
|
+
readonly threat: ProblemCandidateField<string>;
|
|
66
|
+
readonly uncertainty: ProblemCandidateField<string>;
|
|
67
|
+
readonly deadline: ProblemCandidateField<ProblemCandidateDeadline>;
|
|
68
|
+
readonly expectedEffect: ProblemCandidateField<string>;
|
|
69
|
+
readonly requiredResources: ProblemCandidateField<readonly ProblemCandidateResource[]>;
|
|
70
|
+
readonly responsibleId: ProblemCandidateField<string>;
|
|
71
|
+
readonly ownerScope: FoundationScope;
|
|
72
|
+
readonly acl: FoundationAcl;
|
|
73
|
+
/** Only mergeCandidates may set this field. */
|
|
74
|
+
readonly mergedFrom?: readonly string[];
|
|
75
|
+
/** Optional creation time; defaults to event.recordedAt. */
|
|
76
|
+
readonly createdAt?: string;
|
|
77
|
+
/** Runtime input may carry candidate only; selection states are rejected. */
|
|
78
|
+
readonly status?: ProblemCandidateStatus;
|
|
79
|
+
}
|
|
80
|
+
export interface ProblemCandidateProvenance {
|
|
81
|
+
readonly eventIds: readonly string[];
|
|
82
|
+
readonly sourceRefs: readonly ProblemCandidateSourceRef[];
|
|
83
|
+
readonly candidateIds: readonly string[];
|
|
84
|
+
}
|
|
85
|
+
export interface ProblemCandidateRecord extends Omit<ProblemCandidateInput, 'mergedFrom' | 'createdAt' | 'status'> {
|
|
86
|
+
readonly contractVersion: typeof PROBLEM_CANDIDATES_CONTRACT_VERSION;
|
|
87
|
+
readonly status: ProblemCandidateStatus;
|
|
88
|
+
readonly mergedFrom: readonly string[];
|
|
89
|
+
readonly createdAt: string;
|
|
90
|
+
readonly updatedAt: string;
|
|
91
|
+
readonly provenance: ProblemCandidateProvenance;
|
|
92
|
+
readonly payloadDigest: string;
|
|
93
|
+
}
|
|
94
|
+
export interface ProblemCandidateQuery {
|
|
95
|
+
readonly ownerScope?: FoundationScope;
|
|
96
|
+
readonly responsibleId?: string;
|
|
97
|
+
readonly status?: ProblemCandidateStatus;
|
|
98
|
+
}
|
|
99
|
+
export interface ProblemCandidateMergeInput extends ProblemCandidateInput {
|
|
100
|
+
readonly sourceCandidateIds: readonly string[];
|
|
101
|
+
}
|
|
102
|
+
export interface ProblemCandidateStoreContext {
|
|
103
|
+
/** Identity and optional scope must come from a trusted caller boundary. */
|
|
104
|
+
readonly principal: string;
|
|
105
|
+
readonly scope?: FoundationScope;
|
|
106
|
+
}
|
|
107
|
+
export type ProblemCandidateEvidenceOperation = 'save' | 'read';
|
|
108
|
+
/**
|
|
109
|
+
* Trusted current-access boundary for source evidence. Candidate ACLs are not
|
|
110
|
+
* evidence ACLs: every source reference, including roots reached through a
|
|
111
|
+
* merge, must be authorized by this provider before candidate text is exposed.
|
|
112
|
+
* The provider must fail closed when a source is missing or its digest is no
|
|
113
|
+
* longer readable for the principal.
|
|
114
|
+
*/
|
|
115
|
+
export interface ProblemCandidateEvidenceAccessProvider {
|
|
116
|
+
authorize(input: {
|
|
117
|
+
readonly operation: ProblemCandidateEvidenceOperation;
|
|
118
|
+
readonly context: ProblemCandidateStoreContext;
|
|
119
|
+
readonly source: ProblemCandidateSourceRef;
|
|
120
|
+
}): void | boolean | Promise<void | boolean>;
|
|
121
|
+
}
|
|
122
|
+
export type ProblemCandidateStoreErrorCode = 'invalid_input' | 'not_found' | 'revision_conflict' | 'event_conflict' | 'authorization_denied' | 'scope_violation' | 'unsupported_graph' | 'corrupt_record' | 'readback_mismatch';
|
|
123
|
+
export declare class ProblemCandidateStoreError extends Error {
|
|
124
|
+
readonly code: ProblemCandidateStoreErrorCode;
|
|
125
|
+
constructor(code: ProblemCandidateStoreErrorCode, message: string);
|
|
126
|
+
}
|
|
127
|
+
export interface ProblemCandidateStore {
|
|
128
|
+
saveCandidate(input: ProblemCandidateInput, context: ProblemCandidateStoreContext): Promise<ProblemCandidateRecord>;
|
|
129
|
+
/** Alias kept explicit for callers that name the resource in the method. */
|
|
130
|
+
saveProblemCandidate(input: ProblemCandidateInput, context: ProblemCandidateStoreContext): Promise<ProblemCandidateRecord>;
|
|
131
|
+
mergeCandidates(input: ProblemCandidateMergeInput, context: ProblemCandidateStoreContext): Promise<ProblemCandidateRecord>;
|
|
132
|
+
readCandidate(id: string, context: ProblemCandidateStoreContext): Promise<ProblemCandidateRecord | null>;
|
|
133
|
+
listCandidates(query: ProblemCandidateQuery | undefined, context: ProblemCandidateStoreContext): Promise<readonly ProblemCandidateRecord[]>;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Validates, clones, and freezes a candidate input. The function is also a
|
|
137
|
+
* pure contract surface for providers that want to validate before storage.
|
|
138
|
+
*/
|
|
139
|
+
export declare function normalizeProblemCandidate(input: ProblemCandidateInput): ProblemCandidateInput;
|
|
140
|
+
/** Returns validation without throwing, useful for contract fixtures. */
|
|
141
|
+
export declare function validateProblemCandidate(input: ProblemCandidateInput): {
|
|
142
|
+
readonly valid: boolean;
|
|
143
|
+
readonly issues: readonly string[];
|
|
144
|
+
};
|
|
145
|
+
export declare function createProblemCandidateStore(options: {
|
|
146
|
+
readonly dataDir: string;
|
|
147
|
+
readonly foundationStore: FoundationRevisionStore;
|
|
148
|
+
readonly evidenceAccessProvider: ProblemCandidateEvidenceAccessProvider;
|
|
149
|
+
}): ProblemCandidateStore;
|