@unson/brainbase-mcp 0.7.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-service.js +16 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-objectives.d.ts +11 -0
- package/dist/company-os-objectives.js +84 -47
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -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-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -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-problem-snapshot.d.ts +30 -2
- package/dist/judgment-problem-snapshot.js +302 -6
- 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/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/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +12 -0
- package/dist/personal-knowledge.js +29 -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/server.d.ts +7 -2
- package/dist/server.js +9 -6
- 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 +21 -3
- package/dist/world-model.js +94 -12
- package/package.json +94 -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 +119 -45
- 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 +180 -74
- package/ui/objective-editor.js +490 -43
- 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,192 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { createJudgmentProblemPhilosophyReferenceResolver } from './philosophy-revision-reader.js';
|
|
3
|
+
import { judgmentFoundationContract } from './ontology-foundation.js';
|
|
4
|
+
import { digestFoundationDefinition } from './foundation-catalog.js';
|
|
5
|
+
import { createJudgmentProblemFoundationReferenceProvider, validateJudgmentProblemSnapshot, JudgmentProblemSnapshotError } from './judgment-problem-snapshot.js';
|
|
6
|
+
export const FOUNDATION_PUBLIC_CONTRACT_VERSION = 'foundation-public.v1';
|
|
7
|
+
const revision = z.string().regex(/^[1-9]\d*$/u);
|
|
8
|
+
const digest = z.string().regex(/^sha256:[0-9a-f]{64}$/u).transform((value) => value);
|
|
9
|
+
const readSchema = z.object({
|
|
10
|
+
type: z.enum(['objective', 'variable', 'model', 'constraint', 'philosophy']),
|
|
11
|
+
id: z.string().min(1), revision, digest: digest.optional()
|
|
12
|
+
}).strict();
|
|
13
|
+
const referenceSchema = z.object({
|
|
14
|
+
kind: z.enum(['objective', 'variable', 'model', 'constraint', 'philosophy']),
|
|
15
|
+
id: z.string().min(1), revision, digest,
|
|
16
|
+
scope: z.object({ type: z.enum(['personal', 'project', 'organization']), id: z.string().min(1) }).strict(),
|
|
17
|
+
valid_from: z.string().datetime({ offset: true }),
|
|
18
|
+
valid_to: z.string().datetime({ offset: true }).nullable().optional()
|
|
19
|
+
}).strict();
|
|
20
|
+
const problemSchema = z.object({ snapshot: z.unknown() }).strict();
|
|
21
|
+
const validateSchema = z.object({
|
|
22
|
+
reference: referenceSchema,
|
|
23
|
+
phase: z.enum(['read', 'historical_read']).default('read')
|
|
24
|
+
}).strict();
|
|
25
|
+
/** The host owns canonical storage and authentication. No body can supply either. */
|
|
26
|
+
export function createFoundationPublicProvider(options) {
|
|
27
|
+
const resolvePhilosophy = options.philosophyReader
|
|
28
|
+
? createJudgmentProblemPhilosophyReferenceResolver({ reader: options.philosophyReader })
|
|
29
|
+
: undefined;
|
|
30
|
+
function assertContext(context) {
|
|
31
|
+
if (!context || typeof context.principal !== 'string' || !context.principal.trim()) {
|
|
32
|
+
throw new Error('foundation_trusted_context_required');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function referenceProvider(context) {
|
|
36
|
+
const provider = createJudgmentProblemFoundationReferenceProvider({
|
|
37
|
+
store: { read: (reference) => options.store.read(reference, context) },
|
|
38
|
+
resolveOther: (input) => input.reference.kind === 'philosophy'
|
|
39
|
+
? resolvePhilosophy?.(input) ?? { status: 'unresolved', message: 'Philosophy reader is not connected' }
|
|
40
|
+
: options.resolveOther?.(input) ?? { status: 'unresolved', message: `No provider is registered for ${input.reference.kind}` }
|
|
41
|
+
});
|
|
42
|
+
return { resolve(input) {
|
|
43
|
+
if (context.scope && !context.scope.subjectIds.includes(input.reference.scope.id)) {
|
|
44
|
+
return { status: 'unauthorized', message: 'Reference is outside the trusted scope' };
|
|
45
|
+
}
|
|
46
|
+
return provider.resolve(input);
|
|
47
|
+
} };
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
describe() {
|
|
51
|
+
return {
|
|
52
|
+
contractVersion: FOUNDATION_PUBLIC_CONTRACT_VERSION,
|
|
53
|
+
foundation: judgmentFoundationContract,
|
|
54
|
+
connection: 'configured',
|
|
55
|
+
storage: 'canonical-provider',
|
|
56
|
+
philosophy: { kind: 'philosophy', canonicalSource: 'host-provider', connected: Boolean(options.philosophyReader), immutableRevisionRequired: true },
|
|
57
|
+
historicalRead: 'current-acl-and-exact-digest',
|
|
58
|
+
executionPermission: 'none'
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
async read(raw, context) {
|
|
62
|
+
assertContext(context);
|
|
63
|
+
const args = readSchema.parse(raw);
|
|
64
|
+
if (args.type === 'philosophy') {
|
|
65
|
+
if (!options.philosophyReader?.readCanonical)
|
|
66
|
+
throw new Error('foundation_provider_unconfigured');
|
|
67
|
+
const result = await options.philosophyReader.readCanonical({ id: args.id, revision: args.revision, context });
|
|
68
|
+
if (result.status !== 'resolved') {
|
|
69
|
+
throw new Error(result.status === 'unauthorized' ? 'authorization_denied' : result.status === 'missing' ? 'foundation_revision_missing' : 'foundation_digest_or_identity_mismatch');
|
|
70
|
+
}
|
|
71
|
+
const record = result.record;
|
|
72
|
+
if (record.id !== args.id || record.revision !== args.revision || (args.digest && args.digest !== record.digest)) {
|
|
73
|
+
throw new Error('foundation_digest_or_identity_mismatch');
|
|
74
|
+
}
|
|
75
|
+
if (context.scope && !context.scope.subjectIds.includes(record.applicability.scope.id))
|
|
76
|
+
throw new Error('scope_violation');
|
|
77
|
+
const resolved = await createJudgmentProblemPhilosophyReferenceResolver({ reader: { read: () => result } })({
|
|
78
|
+
reference: { kind: 'philosophy', id: record.id, revision: record.revision, digest: record.digest,
|
|
79
|
+
scope: record.applicability.scope, valid_from: record.applicability.validFrom,
|
|
80
|
+
...(record.applicability.validUntil ? { valid_to: record.applicability.validUntil } : {}) },
|
|
81
|
+
phase: 'historical_read', context
|
|
82
|
+
});
|
|
83
|
+
if (resolved.status !== 'resolved')
|
|
84
|
+
throw new Error(resolved.status === 'unauthorized' ? 'authorization_denied' : 'foundation_digest_or_identity_mismatch');
|
|
85
|
+
return record;
|
|
86
|
+
}
|
|
87
|
+
const record = await options.store.read({ type: args.type, id: args.id, revision: args.revision }, context);
|
|
88
|
+
if (!record)
|
|
89
|
+
throw new Error('foundation_revision_missing');
|
|
90
|
+
if (record.definition.id !== args.id || record.definition.type !== args.type || record.definition.revision !== args.revision
|
|
91
|
+
|| digestFoundationDefinition(record.definition) !== record.digest || (args.digest && args.digest !== record.digest)) {
|
|
92
|
+
throw new Error('foundation_digest_or_identity_mismatch');
|
|
93
|
+
}
|
|
94
|
+
return record;
|
|
95
|
+
},
|
|
96
|
+
async validate(raw, context) {
|
|
97
|
+
assertContext(context);
|
|
98
|
+
const args = validateSchema.parse(raw);
|
|
99
|
+
return referenceProvider(context).resolve({ reference: args.reference, phase: args.phase, context });
|
|
100
|
+
},
|
|
101
|
+
async validateProblem(raw, context) {
|
|
102
|
+
assertContext(context);
|
|
103
|
+
const args = problemSchema.parse(raw);
|
|
104
|
+
const owner = args.snapshot?.owner_scope;
|
|
105
|
+
if (context.scope && owner && !context.scope.subjectIds.includes(owner.id)) {
|
|
106
|
+
throw new JudgmentProblemSnapshotError('unauthorized', 'Snapshot owner is outside the trusted scope');
|
|
107
|
+
}
|
|
108
|
+
await validateJudgmentProblemSnapshot({
|
|
109
|
+
snapshot: args.snapshot,
|
|
110
|
+
access: context,
|
|
111
|
+
referenceProvider: referenceProvider(context)
|
|
112
|
+
});
|
|
113
|
+
return { status: 'resolved', executionPermission: 'none' };
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export const foundationPublicToolDefinitions = [
|
|
118
|
+
{ name: 'foundation_describe', description: 'Describe the connected versioned judgment foundation contract.', inputSchema: { type: 'object', properties: {}, additionalProperties: false } },
|
|
119
|
+
{ name: 'foundation_read', description: 'Read an exact canonical foundation revision under current access rights.', inputSchema: {
|
|
120
|
+
type: 'object', required: ['type', 'id', 'revision'], additionalProperties: false,
|
|
121
|
+
properties: { type: { type: 'string', enum: ['objective', 'variable', 'model', 'constraint', 'philosophy'] }, id: { type: 'string' }, revision: { type: 'string' }, digest: { type: 'string' } }
|
|
122
|
+
} },
|
|
123
|
+
{ name: 'foundation_validate_reference', description: 'Validate an exact foundation or philosophy reference for judgment, including its dependencies.', inputSchema: {
|
|
124
|
+
type: 'object', required: ['reference'], additionalProperties: false,
|
|
125
|
+
properties: { reference: { type: 'object', required: ['kind', 'id', 'revision', 'digest', 'scope', 'valid_from'], additionalProperties: false,
|
|
126
|
+
properties: { kind: { type: 'string', enum: ['objective', 'variable', 'model', 'constraint', 'philosophy'] }, id: { type: 'string' }, revision: { type: 'string' }, digest: { type: 'string' }, scope: { type: 'object', required: ['type', 'id'], additionalProperties: false, properties: { type: { type: 'string', enum: ['personal', 'project', 'organization'] }, id: { type: 'string' } } }, valid_from: { type: 'string' }, valid_to: { type: ['string', 'null'] } } }, phase: { type: 'string', enum: ['read', 'historical_read'] } }
|
|
127
|
+
} },
|
|
128
|
+
{ name: 'foundation_validate_problem', description: 'Validate a complete judgment snapshot and canonical observation measurement conditions. Grants no execution permission.', inputSchema: { type: 'object', required: ['snapshot'], properties: { snapshot: { type: 'object' } }, additionalProperties: false } }
|
|
129
|
+
];
|
|
130
|
+
export async function callFoundationPublicTool(name, args, connection) {
|
|
131
|
+
if (!connection)
|
|
132
|
+
throw new Error('foundation_provider_unconfigured');
|
|
133
|
+
const context = await connection.resolveContext();
|
|
134
|
+
if (!context?.principal?.trim())
|
|
135
|
+
throw new Error('foundation_trusted_context_required');
|
|
136
|
+
if (name === 'foundation_describe') {
|
|
137
|
+
z.object({}).strict().parse(args);
|
|
138
|
+
return connection.provider.describe();
|
|
139
|
+
}
|
|
140
|
+
if (name === 'foundation_read')
|
|
141
|
+
return connection.provider.read(args, context);
|
|
142
|
+
if (name === 'foundation_validate_problem')
|
|
143
|
+
return connection.provider.validateProblem(args, context);
|
|
144
|
+
if (name === 'foundation_validate_reference')
|
|
145
|
+
return connection.provider.validate(args, context);
|
|
146
|
+
throw new Error('foundation_tool_unknown');
|
|
147
|
+
}
|
|
148
|
+
/** Mounted with createFoundationHttpRouter, whose host resolves auth and CSRF. */
|
|
149
|
+
export function createFoundationPublicRoute(provider) {
|
|
150
|
+
const prefix = '/api/foundation';
|
|
151
|
+
const matches = (request) => {
|
|
152
|
+
const pathname = new URL(request.url).pathname;
|
|
153
|
+
return pathname === `${prefix}/contract` || pathname === `${prefix}/judgment-references/validate` || pathname === `${prefix}/judgment-problems/validate`
|
|
154
|
+
|| /^\/api\/foundation\/definitions\/[^/]+\/[^/]+$/u.test(pathname);
|
|
155
|
+
};
|
|
156
|
+
const json = (status, value) => new Response(JSON.stringify(value), { status, headers: { 'content-type': 'application/json' } });
|
|
157
|
+
return {
|
|
158
|
+
name: 'foundation-public', methods: ['GET', 'POST'], paths: [`${prefix}/contract`, `${prefix}/definitions/:type/:id`, `${prefix}/judgment-references/validate`, `${prefix}/judgment-problems/validate`], matches,
|
|
159
|
+
async handle(request, { context }) {
|
|
160
|
+
try {
|
|
161
|
+
const url = new URL(request.url);
|
|
162
|
+
for (const key of url.searchParams.keys())
|
|
163
|
+
if (!['scope_id', 'revision', 'digest'].includes(key))
|
|
164
|
+
return json(400, { error: { code: 'invalid_query' } });
|
|
165
|
+
if (url.pathname === `${prefix}/contract` && request.method === 'GET')
|
|
166
|
+
return json(200, provider.describe());
|
|
167
|
+
if ([`${prefix}/judgment-references/validate`, `${prefix}/judgment-problems/validate`].includes(url.pathname) && request.method === 'POST') {
|
|
168
|
+
if (Number(request.headers.get('content-length')) > 65_536)
|
|
169
|
+
return json(413, { error: { code: 'body_too_large' } });
|
|
170
|
+
const body = await request.text();
|
|
171
|
+
if (new TextEncoder().encode(body).length > 65_536)
|
|
172
|
+
return json(413, { error: { code: 'body_too_large' } });
|
|
173
|
+
const result = url.pathname.endsWith('/judgment-problems/validate')
|
|
174
|
+
? await provider.validateProblem(JSON.parse(body), context)
|
|
175
|
+
: await provider.validate(JSON.parse(body), context);
|
|
176
|
+
return json(result.status === 'resolved' ? 200 : result.status === 'unauthorized' ? 403 : 422, result);
|
|
177
|
+
}
|
|
178
|
+
const parts = /^\/api\/foundation\/definitions\/([^/]+)\/([^/]+)$/u.exec(url.pathname);
|
|
179
|
+
if (parts && request.method === 'GET')
|
|
180
|
+
return json(200, await provider.read({ type: decodeURIComponent(parts[1]), id: decodeURIComponent(parts[2]), revision: url.searchParams.get('revision'), ...(url.searchParams.has('digest') ? { digest: url.searchParams.get('digest') } : {}) }, context));
|
|
181
|
+
return json(405, { error: { code: 'method_not_allowed' } });
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
const rawCode = error instanceof Error && 'code' in error ? String(error.code) : error instanceof Error ? error.message : '';
|
|
185
|
+
const knownCodes = ['foundation_provider_unconfigured', 'authorization_denied', 'scope_violation', 'foundation_revision_missing', 'foundation_digest_or_identity_mismatch', 'foundation_trusted_context_required', 'corrupt_catalog', 'unauthorized', 'invalid_request', 'unresolved_constraint', 'integrity_mismatch', 'missing_reference', 'not_applicable'];
|
|
186
|
+
const code = knownCodes.includes(rawCode) ? rawCode : 'foundation_read_failed';
|
|
187
|
+
const status = code === 'foundation_provider_unconfigured' ? 503 : ['authorization_denied', 'scope_violation', 'unauthorized'].includes(code) ? 403 : code === 'foundation_revision_missing' ? 404 : code === 'invalid_request' || error instanceof z.ZodError || error instanceof SyntaxError || error instanceof URIError ? 400 : 422;
|
|
188
|
+
return json(status, { error: { code: status === 400 ? 'invalid_request' : code, ...(error instanceof JudgmentProblemSnapshotError && error.reference ? { reference: { kind: error.reference.kind, id: error.reference.id, revision: error.reference.revision } } : {}) } });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { CanonicalEdge, CanonicalEntity, RelationshipRecord } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Owner corrections for the canonical local Graph v2.
|
|
4
|
+
*
|
|
5
|
+
* Graph entities and edges are stored without revisions, so every correction
|
|
6
|
+
* carries the digest of the record it was based on, a one-sentence reason and
|
|
7
|
+
* leaves an append-only history line. The history line, the Graph change and
|
|
8
|
+
* the relationships.json projection are committed in one SSOT transaction.
|
|
9
|
+
*/
|
|
10
|
+
export declare const GRAPH_CORRECTION_SCHEMA: "brainbase-graph-correction.v1";
|
|
11
|
+
/** Relative to the data directory. Committed as a sidecar of the canonical SSOT transaction. */
|
|
12
|
+
export declare const GRAPH_CORRECTIONS_FILE = "evidence/graph-corrections.jsonl";
|
|
13
|
+
export declare const GRAPH_CORRECTION_ENTITY_FIELDS: readonly ["name", "aliases", "summary", "validFrom", "validTo"];
|
|
14
|
+
export declare const GRAPH_CORRECTION_PROJECT_FIELDS: readonly ["goal", "status"];
|
|
15
|
+
export declare const GRAPH_CORRECTION_EDGE_FIELDS: readonly ["role", "context", "validTo"];
|
|
16
|
+
export declare const GRAPH_CORRECTION_NEW_EDGE_RELATIONS: readonly ["participates_in", "accountable_for", "member_of"];
|
|
17
|
+
/** MCP tools that read the same graph.json on every call and therefore see a saved correction next time. */
|
|
18
|
+
export declare const GRAPH_CORRECTION_APPLIES_TO: readonly ["search", "get_context", "resolve_entity"];
|
|
19
|
+
export type GraphCorrectionKind = 'update_entity' | 'update_edge' | 'create_edge';
|
|
20
|
+
export type GraphNewEdgeRelation = typeof GRAPH_CORRECTION_NEW_EDGE_RELATIONS[number];
|
|
21
|
+
export interface GraphEntityCorrectionChanges {
|
|
22
|
+
name?: string;
|
|
23
|
+
aliases?: string[];
|
|
24
|
+
summary?: string | null;
|
|
25
|
+
validFrom?: string | null;
|
|
26
|
+
validTo?: string | null;
|
|
27
|
+
/** Projects only. Stored as `metadata.goal`. */
|
|
28
|
+
goal?: string | null;
|
|
29
|
+
/** Projects only. Stored as `metadata.status`. */
|
|
30
|
+
status?: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface GraphEdgeCorrectionChanges {
|
|
33
|
+
role?: string | null;
|
|
34
|
+
context?: string | null;
|
|
35
|
+
/** Ending a relation is expressed with an end date; relations are never deleted. */
|
|
36
|
+
validTo?: string | null;
|
|
37
|
+
}
|
|
38
|
+
export interface GraphEdgeCreation {
|
|
39
|
+
fromId: string;
|
|
40
|
+
relation: GraphNewEdgeRelation;
|
|
41
|
+
toId: string;
|
|
42
|
+
role?: string;
|
|
43
|
+
context?: string;
|
|
44
|
+
validFrom?: string;
|
|
45
|
+
}
|
|
46
|
+
export type GraphCorrectionInput = {
|
|
47
|
+
kind: 'update_entity';
|
|
48
|
+
entityId: string;
|
|
49
|
+
expectedDigest: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
changes: GraphEntityCorrectionChanges;
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'update_edge';
|
|
54
|
+
edgeId: string;
|
|
55
|
+
expectedDigest: string;
|
|
56
|
+
reason: string;
|
|
57
|
+
changes: GraphEdgeCorrectionChanges;
|
|
58
|
+
} | {
|
|
59
|
+
kind: 'create_edge';
|
|
60
|
+
reason: string;
|
|
61
|
+
edge: GraphEdgeCreation;
|
|
62
|
+
};
|
|
63
|
+
export type JsonValue = string | number | boolean | null | JsonValue[] | {
|
|
64
|
+
[key: string]: JsonValue;
|
|
65
|
+
};
|
|
66
|
+
export interface GraphProjectionChange {
|
|
67
|
+
edgeId: string;
|
|
68
|
+
relationshipId: string | null;
|
|
69
|
+
action: 'created' | 'updated' | 'removed' | 'shared_unchanged';
|
|
70
|
+
beforeDigest: string | null;
|
|
71
|
+
afterDigest: string | null;
|
|
72
|
+
/** The projection record as it was before this correction, so a removal stays recoverable. */
|
|
73
|
+
before: RelationshipRecord | null;
|
|
74
|
+
}
|
|
75
|
+
export interface GraphCorrectionRecord {
|
|
76
|
+
schema: typeof GRAPH_CORRECTION_SCHEMA;
|
|
77
|
+
id: string;
|
|
78
|
+
at: string;
|
|
79
|
+
kind: GraphCorrectionKind;
|
|
80
|
+
target: {
|
|
81
|
+
recordType: 'entity' | 'edge';
|
|
82
|
+
id: string;
|
|
83
|
+
};
|
|
84
|
+
reason: string;
|
|
85
|
+
changedFields: string[];
|
|
86
|
+
changes: Record<string, {
|
|
87
|
+
before: JsonValue;
|
|
88
|
+
after: JsonValue;
|
|
89
|
+
}>;
|
|
90
|
+
beforeDigest: string | null;
|
|
91
|
+
afterDigest: string;
|
|
92
|
+
projections: GraphProjectionChange[];
|
|
93
|
+
}
|
|
94
|
+
export interface GraphCorrectionResult {
|
|
95
|
+
status: 'saved';
|
|
96
|
+
correction: GraphCorrectionRecord;
|
|
97
|
+
recordType: 'entity' | 'edge';
|
|
98
|
+
/** The record as read back from graph.json after the commit. */
|
|
99
|
+
record: CanonicalEntity | CanonicalEdge;
|
|
100
|
+
digest: string;
|
|
101
|
+
readback: {
|
|
102
|
+
verified: true;
|
|
103
|
+
digest: string;
|
|
104
|
+
historyRecorded: true;
|
|
105
|
+
};
|
|
106
|
+
appliesTo: readonly string[];
|
|
107
|
+
}
|
|
108
|
+
export type GraphCorrectionErrorKind = 'invalid' | 'not_found' | 'conflict' | 'migration_required' | 'readback_mismatch';
|
|
109
|
+
export declare class GraphCorrectionError extends Error {
|
|
110
|
+
readonly kind: GraphCorrectionErrorKind;
|
|
111
|
+
readonly code: string;
|
|
112
|
+
readonly current?: {
|
|
113
|
+
recordType: "entity" | "edge";
|
|
114
|
+
record: CanonicalEntity | CanonicalEdge;
|
|
115
|
+
digest: string;
|
|
116
|
+
} | undefined;
|
|
117
|
+
readonly name = "GraphCorrectionError";
|
|
118
|
+
constructor(kind: GraphCorrectionErrorKind, code: string, message: string, current?: {
|
|
119
|
+
recordType: "entity" | "edge";
|
|
120
|
+
record: CanonicalEntity | CanonicalEdge;
|
|
121
|
+
digest: string;
|
|
122
|
+
} | undefined);
|
|
123
|
+
}
|
|
124
|
+
/** SHA-256 over canonical JSON (recursively sorted keys) of one stored record. */
|
|
125
|
+
export declare function graphRecordDigest(record: CanonicalEntity | CanonicalEdge | RelationshipRecord): string;
|
|
126
|
+
/**
|
|
127
|
+
* Validates an untrusted correction request. Only the fields listed in the
|
|
128
|
+
* correction contract are accepted; ids, types, relation types, provenance and
|
|
129
|
+
* deletions are rejected with a specific code.
|
|
130
|
+
*/
|
|
131
|
+
export declare function parseGraphCorrectionInput(value: unknown): GraphCorrectionInput;
|
|
132
|
+
/**
|
|
133
|
+
* Applies one owner correction to the canonical Graph v2 in `dataDir`.
|
|
134
|
+
*
|
|
135
|
+
* The Graph write, the relationships.json projection and the history line in
|
|
136
|
+
* `evidence/graph-corrections.jsonl` are committed together through
|
|
137
|
+
* `mutatePersonalOsWithSidecar`, so a crash never leaves a Graph change without
|
|
138
|
+
* its history line (or the reverse). After the commit the record is read back
|
|
139
|
+
* and its digest must equal the digest computed before saving.
|
|
140
|
+
*/
|
|
141
|
+
export declare function applyGraphCorrection(dataDir: string, input: unknown, options?: {
|
|
142
|
+
now?: Date;
|
|
143
|
+
}): Promise<GraphCorrectionResult>;
|
|
144
|
+
export interface GraphCorrectionHistoryIssue {
|
|
145
|
+
file: typeof GRAPH_CORRECTIONS_FILE;
|
|
146
|
+
line?: number;
|
|
147
|
+
reason: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Reads the correction history in append order. A missing file means no
|
|
151
|
+
* correction has been saved; an unreadable line is reported as an issue and
|
|
152
|
+
* never silently dropped.
|
|
153
|
+
*/
|
|
154
|
+
export declare function readGraphCorrectionHistory(dataDir: string): Promise<{
|
|
155
|
+
records: GraphCorrectionRecord[];
|
|
156
|
+
issues: GraphCorrectionHistoryIssue[];
|
|
157
|
+
}>;
|