@tenphi/akno-core 0.6.0 → 0.7.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/config/default.jsonc +7 -5
- package/dist/bench/answer.js +5 -2
- package/dist/bench/answer.js.map +1 -1
- package/dist/config/schema.d.ts +8 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/index/derive.d.ts.map +1 -1
- package/dist/index/derive.js +17 -0
- package/dist/index/derive.js.map +1 -1
- package/dist/index/graph.d.ts +1 -0
- package/dist/index/graph.d.ts.map +1 -1
- package/dist/index/graph.js +50 -0
- package/dist/index/graph.js.map +1 -1
- package/dist/index/indexer.d.ts +3 -0
- package/dist/index/indexer.d.ts.map +1 -1
- package/dist/index/indexer.js +34 -3
- package/dist/index/indexer.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/kb/page.d.ts +4 -1
- package/dist/kb/page.d.ts.map +1 -1
- package/dist/kb/page.js +5 -0
- package/dist/kb/page.js.map +1 -1
- package/dist/maintenance/dream.d.ts +1 -1
- package/dist/maintenance/dream.d.ts.map +1 -1
- package/dist/maintenance/dream.js +547 -198
- package/dist/maintenance/dream.js.map +1 -1
- package/dist/maintenance/observation-migration.d.ts +17 -0
- package/dist/maintenance/observation-migration.d.ts.map +1 -0
- package/dist/maintenance/observation-migration.js +217 -0
- package/dist/maintenance/observation-migration.js.map +1 -0
- package/dist/maintenance/observe.d.ts +20 -3
- package/dist/maintenance/observe.d.ts.map +1 -1
- package/dist/maintenance/observe.js +95 -24
- package/dist/maintenance/observe.js.map +1 -1
- package/dist/maintenance/path-policy.d.ts.map +1 -1
- package/dist/maintenance/path-policy.js +2 -2
- package/dist/maintenance/path-policy.js.map +1 -1
- package/dist/maintenance/plans.d.ts +28 -1
- package/dist/maintenance/plans.d.ts.map +1 -1
- package/dist/maintenance/plans.js +360 -17
- package/dist/maintenance/plans.js.map +1 -1
- package/dist/observations/marker.d.ts +27 -0
- package/dist/observations/marker.d.ts.map +1 -0
- package/dist/observations/marker.js +238 -0
- package/dist/observations/marker.js.map +1 -0
- package/dist/observations/projection.d.ts +23 -0
- package/dist/observations/projection.d.ts.map +1 -0
- package/dist/observations/projection.js +349 -0
- package/dist/observations/projection.js.map +1 -0
- package/dist/open.d.ts +3 -0
- package/dist/open.d.ts.map +1 -1
- package/dist/open.js +2 -0
- package/dist/open.js.map +1 -1
- package/dist/ops/answer.d.ts.map +1 -1
- package/dist/ops/answer.js +74 -12
- package/dist/ops/answer.js.map +1 -1
- package/dist/ops/context.js +3 -1
- package/dist/ops/context.js.map +1 -1
- package/dist/ops/graph.d.ts.map +1 -1
- package/dist/ops/graph.js +22 -2
- package/dist/ops/graph.js.map +1 -1
- package/dist/ops/read.d.ts.map +1 -1
- package/dist/ops/read.js +9 -3
- package/dist/ops/read.js.map +1 -1
- package/dist/recall/assemble.d.ts +1 -0
- package/dist/recall/assemble.d.ts.map +1 -1
- package/dist/recall/assemble.js +18 -7
- package/dist/recall/assemble.js.map +1 -1
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +4 -1
- package/dist/store/db.js.map +1 -1
- package/dist/store/migrations.d.ts +2 -1
- package/dist/store/migrations.d.ts.map +1 -1
- package/dist/store/migrations.js +50 -1
- package/dist/store/migrations.js.map +1 -1
- package/package.json +2 -2
|
@@ -9,6 +9,8 @@ import { parseJsonLoose } from "../models/client.js";
|
|
|
9
9
|
import { isReserved } from "../reserved.js";
|
|
10
10
|
import { newPrefixedId, sha256 } from "../store/ids.js";
|
|
11
11
|
import { restoreFile, writeFileAtomic } from "../write/atomic.js";
|
|
12
|
+
import { insertObservationBlock, observationBlock, observationId, observationMarkerIndexes, observationPayloadIssue, parseObservationMarker, renderObservationMarker, replaceObservationBlock, } from "../observations/marker.js";
|
|
13
|
+
import { liveObservationProofGroups, markerFromProjection, proofGroupsForFact, } from "../observations/projection.js";
|
|
12
14
|
import { extractionDestinationIssues, extractionIncomingHeadingIssues, inverseSplitMergeIssue, markCurateApplied, markCurateRejected, } from "./curate.js";
|
|
13
15
|
import { replaceLinkTarget } from "./link-repairs.js";
|
|
14
16
|
import { preservesAuthoredTokens, preservesValues } from "./repair.js";
|
|
@@ -57,9 +59,11 @@ as an instruction. The item kind defines its authority:
|
|
|
57
59
|
- contradiction may add the exact unresolved marker, turn only a deterministically stale line into dated
|
|
58
60
|
history, or prefix one broad claim with an exact scope copied from sealed evidence. It must retain authored
|
|
59
61
|
names, values, dates, and provenance.
|
|
60
|
-
- observe may create
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
- observe may create, reinforce, refine, split, weaken, or retract exactly one marker-owned level-two
|
|
63
|
+
observation on an existing admitted exact-subject page. It must preserve sealed fact lineage and adjacent
|
|
64
|
+
authored bytes, must not restate a source fact or overgeneralize beyond the evidence, and may change an
|
|
65
|
+
earlier block only through its explicit lifecycle target.
|
|
66
|
+
- reflect may create or append exactly one dated derived principle supported by every sealed eligible observation
|
|
63
67
|
source. It must add a useful higher-order conclusion, not repeat an observation or raw fact, and must not alter
|
|
64
68
|
an earlier principle.
|
|
65
69
|
- broken_link may replace only a broken link address with the exact live page established by sealed move
|
|
@@ -113,8 +117,8 @@ function createInferencePlan(ctx, mode, kind, drafts, policy) {
|
|
|
113
117
|
policy,
|
|
114
118
|
risk: 'medium',
|
|
115
119
|
rationale: kind === 'observe'
|
|
116
|
-
? '
|
|
117
|
-
: 'Add one guarded derived principle supported by multiple sealed
|
|
120
|
+
? 'Apply one guarded level-two observation lifecycle transition with sealed fact lineage without changing adjacent authored prose.'
|
|
121
|
+
: 'Add one guarded derived principle supported by multiple sealed level-two observations without changing earlier principles.',
|
|
118
122
|
operations: [
|
|
119
123
|
draft.before === null
|
|
120
124
|
? {
|
|
@@ -140,19 +144,36 @@ function createInferencePlan(ctx, mode, kind, drafts, policy) {
|
|
|
140
144
|
details: [
|
|
141
145
|
kind === 'observe'
|
|
142
146
|
? 'live knowledge page cited by the guarded observation candidate'
|
|
143
|
-
: 'live observation
|
|
147
|
+
: 'live level-two observation cited by the guarded reflection candidate',
|
|
144
148
|
],
|
|
149
|
+
...(entry.factId ? { observationFactId: entry.factId } : {}),
|
|
150
|
+
...(entry.sourceLineHash ? { observationLineHash: entry.sourceLineHash } : {}),
|
|
151
|
+
...(entry.proofGroups ? { observationProofGroups: entry.proofGroups } : {}),
|
|
152
|
+
...(draft.observationId ? { observationId: draft.observationId } : {}),
|
|
153
|
+
...(draft.observationSubject ? { observationSubject: draft.observationSubject } : {}),
|
|
154
|
+
...(draft.observationDisposition ? { observationDisposition: draft.observationDisposition } : {}),
|
|
155
|
+
...(draft.observationProofCount !== undefined
|
|
156
|
+
? { observationProofCount: draft.observationProofCount }
|
|
157
|
+
: {}),
|
|
158
|
+
...(draft.observationOutcome ? { observationOutcome: draft.observationOutcome } : {}),
|
|
159
|
+
...(draft.observationTargetId ? { observationTargetId: draft.observationTargetId } : {}),
|
|
160
|
+
...(draft.observationPayloadHash ? { observationPayloadHash: draft.observationPayloadHash } : {}),
|
|
161
|
+
...(draft.observationQualificationIssue
|
|
162
|
+
? { observationQualificationIssue: draft.observationQualificationIssue }
|
|
163
|
+
: {}),
|
|
164
|
+
...(entry.reflectionObservationId ? { reflectionObservationId: entry.reflectionObservationId } : {}),
|
|
165
|
+
...(entry.reflectionPayloadHash ? { reflectionPayloadHash: entry.reflectionPayloadHash } : {}),
|
|
145
166
|
})),
|
|
146
167
|
checks: [
|
|
147
168
|
{ name: `${kind} mission guardrails`, status: 'passed' },
|
|
148
169
|
{
|
|
149
170
|
name: kind === 'observe'
|
|
150
|
-
? 'at least two
|
|
171
|
+
? 'at least two independent sealed level-one proof groups'
|
|
151
172
|
: 'at least three distinct live observation sources',
|
|
152
173
|
status: 'passed',
|
|
153
174
|
},
|
|
154
175
|
{
|
|
155
|
-
name:
|
|
176
|
+
name: kind === 'observe' ? 'marker-owned observation block shape' : 'append-only derived principle shape',
|
|
156
177
|
status: 'passed',
|
|
157
178
|
},
|
|
158
179
|
],
|
|
@@ -2277,7 +2298,7 @@ async function verifyApplied(ctx, item, operations) {
|
|
|
2277
2298
|
}
|
|
2278
2299
|
const parsed = parsePage(operation.relPath, content);
|
|
2279
2300
|
const row = ctx.store.db
|
|
2280
|
-
.prepare('SELECT id, slug, rel_path, body_hash, role, type, remember_management, dream_management FROM pages WHERE rel_path = ?')
|
|
2301
|
+
.prepare('SELECT id, slug, rel_path, body_hash, role, type, remember_management, observe_management, dream_management FROM pages WHERE rel_path = ?')
|
|
2281
2302
|
.get(operation.relPath);
|
|
2282
2303
|
if (!row)
|
|
2283
2304
|
return `${operation.relPath} is missing from the structural index.`;
|
|
@@ -2294,10 +2315,13 @@ async function verifyApplied(ctx, item, operations) {
|
|
|
2294
2315
|
canonical = parsed;
|
|
2295
2316
|
canonicalPageId = row.id;
|
|
2296
2317
|
}
|
|
2297
|
-
const expectedRole =
|
|
2318
|
+
const expectedRole = item.kind === 'reflect' ? 'inference' : 'knowledge';
|
|
2298
2319
|
if (row.role !== expectedRole) {
|
|
2299
2320
|
return `${operation.relPath} is no longer live ${expectedRole}.`;
|
|
2300
2321
|
}
|
|
2322
|
+
if (item.kind === 'observe' && row.observe_management !== 'integrate') {
|
|
2323
|
+
return `${operation.relPath} no longer permits observation integration.`;
|
|
2324
|
+
}
|
|
2301
2325
|
if (item.kind === 'rule_drift') {
|
|
2302
2326
|
const expected = item.evidence.find((entry) => entry.type === 'rule')?.expectedType;
|
|
2303
2327
|
if (!depthRuleDrift && (!expected || row.type !== expected)) {
|
|
@@ -2370,7 +2394,7 @@ async function verifyApplied(ctx, item, operations) {
|
|
|
2370
2394
|
const shapeIssue = observationOperationIssue(ctx, item, operations);
|
|
2371
2395
|
if (shapeIssue)
|
|
2372
2396
|
return shapeIssue;
|
|
2373
|
-
const evidenceIssue = await observationEvidenceIssue(ctx, item);
|
|
2397
|
+
const evidenceIssue = await observationEvidenceIssue(ctx, item, 'after');
|
|
2374
2398
|
if (evidenceIssue)
|
|
2375
2399
|
return evidenceIssue;
|
|
2376
2400
|
}
|
|
@@ -3030,6 +3054,8 @@ function depthRuleDriftOperationIssue(ctx, item, operations, entry, state) {
|
|
|
3030
3054
|
return null;
|
|
3031
3055
|
}
|
|
3032
3056
|
function observationOperationIssue(ctx, item, operations) {
|
|
3057
|
+
if (item.kind === 'observe')
|
|
3058
|
+
return coLocatedObservationOperationIssue(ctx, item, operations);
|
|
3033
3059
|
const operation = operations[0];
|
|
3034
3060
|
if (!operation ||
|
|
3035
3061
|
(operation.type !== 'create' && operation.type !== 'replace') ||
|
|
@@ -3048,7 +3074,16 @@ function observationOperationIssue(ctx, item, operations) {
|
|
|
3048
3074
|
const minEvidence = item.kind === 'reflect'
|
|
3049
3075
|
? Math.max(3, ctx.config.maintenance.observe.minEvidence)
|
|
3050
3076
|
: ctx.config.maintenance.observe.minEvidence;
|
|
3051
|
-
|
|
3077
|
+
const indexedReflection = item.kind === 'reflect' && sources.every((entry) => entry.reflectionObservationId);
|
|
3078
|
+
if (indexedReflection) {
|
|
3079
|
+
if (sources.length < minEvidence ||
|
|
3080
|
+
sources.length !== item.evidence.length ||
|
|
3081
|
+
new Set(sources.map((entry) => entry.reflectionObservationId)).size !== sources.length ||
|
|
3082
|
+
sources.some((entry) => !entry.fingerprint || !entry.reflectionPayloadHash)) {
|
|
3083
|
+
return 'a reflection requires distinct eligible level-two observation evidence';
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
else if (sources.length < minEvidence ||
|
|
3052
3087
|
sources.length !== item.evidence.length ||
|
|
3053
3088
|
new Set(sources.map((entry) => entry.source)).size !== sources.length ||
|
|
3054
3089
|
sources.some((entry) => !entry.fingerprint)) {
|
|
@@ -3063,7 +3098,7 @@ function observationOperationIssue(ctx, item, operations) {
|
|
|
3063
3098
|
}
|
|
3064
3099
|
const afterData = after.frontmatter.data;
|
|
3065
3100
|
const evidence = stringArray(afterData.evidence);
|
|
3066
|
-
const sourceSlugs = sources.map((entry) => entry.source);
|
|
3101
|
+
const sourceSlugs = [...new Set(sources.map((entry) => entry.source))];
|
|
3067
3102
|
if (afterData.derived !== true) {
|
|
3068
3103
|
return 'the inference page must remain explicitly derived';
|
|
3069
3104
|
}
|
|
@@ -3112,7 +3147,306 @@ function observationOperationIssue(ctx, item, operations) {
|
|
|
3112
3147
|
}
|
|
3113
3148
|
return null;
|
|
3114
3149
|
}
|
|
3115
|
-
|
|
3150
|
+
function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
3151
|
+
const operation = operations[0];
|
|
3152
|
+
if (!operation || operation.type !== 'replace' || operations.length !== 1) {
|
|
3153
|
+
return 'an observation must change exactly one existing admitted page';
|
|
3154
|
+
}
|
|
3155
|
+
const target = ctx.store.db
|
|
3156
|
+
.prepare('SELECT id, slug, rel_path, role, observe_management, about FROM pages WHERE slug = ?')
|
|
3157
|
+
.get(item.subject);
|
|
3158
|
+
if (!target ||
|
|
3159
|
+
target.rel_path !== operation.relPath ||
|
|
3160
|
+
target.role !== 'knowledge' ||
|
|
3161
|
+
target.observe_management !== 'integrate') {
|
|
3162
|
+
return 'the observation target is not an admitted knowledge page';
|
|
3163
|
+
}
|
|
3164
|
+
const sources = item.evidence.filter((entry) => entry.type === 'page');
|
|
3165
|
+
const outcome = sources[0]?.observationOutcome ?? 'create';
|
|
3166
|
+
const closesInvalidLineage = outcome === 'weaken' || outcome === 'retract';
|
|
3167
|
+
if (sources.length < 2 ||
|
|
3168
|
+
sources.length !== item.evidence.length ||
|
|
3169
|
+
sources.some((entry) => (!closesInvalidLineage && !entry.fingerprint) ||
|
|
3170
|
+
!entry.observationFactId ||
|
|
3171
|
+
!entry.observationLineHash ||
|
|
3172
|
+
!entry.observationProofGroups?.length ||
|
|
3173
|
+
!entry.observationId ||
|
|
3174
|
+
!entry.observationSubject ||
|
|
3175
|
+
(closesInvalidLineage
|
|
3176
|
+
? entry.observationDisposition !== (outcome === 'weaken' ? 'weakened' : 'retracted')
|
|
3177
|
+
: entry.observationDisposition !== 'active') ||
|
|
3178
|
+
entry.observationProofCount === undefined)) {
|
|
3179
|
+
return 'an observation requires exact sealed fact lineage';
|
|
3180
|
+
}
|
|
3181
|
+
const first = sources[0];
|
|
3182
|
+
if (sources.some((entry) => entry.observationId !== first.observationId ||
|
|
3183
|
+
entry.observationSubject !== first.observationSubject ||
|
|
3184
|
+
entry.observationProofCount !== first.observationProofCount)) {
|
|
3185
|
+
return 'observation evidence disagrees about marker identity';
|
|
3186
|
+
}
|
|
3187
|
+
const factIds = sources.map((entry) => entry.observationFactId);
|
|
3188
|
+
if (new Set(factIds).size !== factIds.length)
|
|
3189
|
+
return 'observation evidence contains a duplicate fact';
|
|
3190
|
+
const proofGroups = new Set(sources.flatMap((entry) => entry.observationProofGroups));
|
|
3191
|
+
if (proofGroups.size < Math.max(2, ctx.config.maintenance.observe.minEvidence)) {
|
|
3192
|
+
return 'an observation needs the configured number of independent proof groups';
|
|
3193
|
+
}
|
|
3194
|
+
if (proofGroups.size !== first.observationProofCount)
|
|
3195
|
+
return 'observation proof count is not recomputable';
|
|
3196
|
+
const afterPage = parsePage(operation.relPath, operation.after);
|
|
3197
|
+
const targetId = first.observationTargetId ?? first.observationId;
|
|
3198
|
+
const markerIndex = observationMarkerIndexes(afterPage.lines, targetId)[0] ?? -1;
|
|
3199
|
+
if (markerIndex < 0)
|
|
3200
|
+
return 'the observation marker is missing from the proposed page';
|
|
3201
|
+
const marker = parseObservationMarker(afterPage.lines[markerIndex]);
|
|
3202
|
+
const payload = afterPage.lines[markerIndex + 1]?.trim() ?? '';
|
|
3203
|
+
if (!marker || marker.subject !== first.observationSubject) {
|
|
3204
|
+
return 'the proposed observation marker does not match its sealed lineage';
|
|
3205
|
+
}
|
|
3206
|
+
if (closesInvalidLineage) {
|
|
3207
|
+
const disposition = outcome === 'weaken' ? 'weakened' : 'retracted';
|
|
3208
|
+
if (marker.disposition !== disposition ||
|
|
3209
|
+
marker.proofCount !== first.observationProofCount ||
|
|
3210
|
+
sources.some((entry) => entry.observationPayloadHash !== first.observationPayloadHash ||
|
|
3211
|
+
entry.observationQualificationIssue !== first.observationQualificationIssue)) {
|
|
3212
|
+
return `a ${outcome} proposal does not preserve its sealed invalid lineage`;
|
|
3213
|
+
}
|
|
3214
|
+
const expected = replaceObservationBlock(operation.before, targetId, `${renderObservationMarker(marker)}\n${payload}`);
|
|
3215
|
+
return expected === operation.after
|
|
3216
|
+
? null
|
|
3217
|
+
: `the observation ${outcome} changed bytes outside its owned block`;
|
|
3218
|
+
}
|
|
3219
|
+
if (outcome === 'split') {
|
|
3220
|
+
if (marker.disposition !== 'superseded')
|
|
3221
|
+
return 'a split must preserve its prior marker as superseded';
|
|
3222
|
+
const beforePage = parsePage(operation.relPath, operation.before);
|
|
3223
|
+
const beforeIndex = observationMarkerIndexes(beforePage.lines, targetId)[0] ?? -1;
|
|
3224
|
+
const beforeMarker = beforeIndex >= 0 ? parseObservationMarker(beforePage.lines[beforeIndex]) : null;
|
|
3225
|
+
const beforePayload = beforeIndex >= 0 ? (beforePage.lines[beforeIndex + 1]?.trim() ?? '') : '';
|
|
3226
|
+
if (!beforeMarker ||
|
|
3227
|
+
beforeMarker.disposition !== 'active' ||
|
|
3228
|
+
renderObservationMarker({ ...beforeMarker, disposition: 'superseded' }) !==
|
|
3229
|
+
renderObservationMarker(marker) ||
|
|
3230
|
+
payload !== beforePayload) {
|
|
3231
|
+
return 'a split must preserve the exact prior block as superseded';
|
|
3232
|
+
}
|
|
3233
|
+
const replacementLines = afterPage.lines.slice(markerIndex, markerIndex + 8);
|
|
3234
|
+
const activeMarkers = replacementLines
|
|
3235
|
+
.map(parseObservationMarker)
|
|
3236
|
+
.filter((entry) => entry !== null && entry.disposition === 'active');
|
|
3237
|
+
if (activeMarkers.length !== 2 ||
|
|
3238
|
+
activeMarkers.some((entry) => entry.subject !== first.observationSubject || entry.proofCount !== first.observationProofCount)) {
|
|
3239
|
+
return 'a split must produce exactly two active observations with the sealed lineage';
|
|
3240
|
+
}
|
|
3241
|
+
const activePayloads = [replacementLines[4]?.trim() ?? '', replacementLines[7]?.trim() ?? ''];
|
|
3242
|
+
const evidenceSlugs = sources.map((entry) => entry.source);
|
|
3243
|
+
const expectedEvidence = sources.map((entry) => ({
|
|
3244
|
+
factId: entry.observationFactId,
|
|
3245
|
+
sourceLineHash: entry.observationLineHash,
|
|
3246
|
+
proofGroups: entry.observationProofGroups,
|
|
3247
|
+
}));
|
|
3248
|
+
const patterns = activePayloads.map((entry) => /^- \*\*Observation:\*\* (.+?) Evidence: /.exec(entry)?.[1]?.trim() ?? '');
|
|
3249
|
+
const activeIds = activeMarkers.map((entry) => entry.id);
|
|
3250
|
+
if (patterns.some((entry) => !entry) ||
|
|
3251
|
+
activePayloads.some((entry) => observationPayloadIssue(entry, evidenceSlugs) !== null) ||
|
|
3252
|
+
activeMarkers.some((entry) => renderObservationMarker(entry) !==
|
|
3253
|
+
renderObservationMarker({
|
|
3254
|
+
id: entry.id,
|
|
3255
|
+
subject: first.observationSubject,
|
|
3256
|
+
disposition: 'active',
|
|
3257
|
+
evidence: expectedEvidence,
|
|
3258
|
+
proofCount: first.observationProofCount,
|
|
3259
|
+
})) ||
|
|
3260
|
+
new Set(activeIds).size !== 2 ||
|
|
3261
|
+
activeIds.includes(targetId) ||
|
|
3262
|
+
activeIds.some((id, index) => id !== observationId(first.observationSubject, patterns[index]))) {
|
|
3263
|
+
return 'a split must produce two distinct deterministic blocks with exact sealed lineage';
|
|
3264
|
+
}
|
|
3265
|
+
const newline = operation.after.includes('\r\n') ? '\r\n' : '\n';
|
|
3266
|
+
const replacement = [
|
|
3267
|
+
`${renderObservationMarker(marker)}${newline}${payload}`,
|
|
3268
|
+
observationBlock(activeMarkers[0], patterns[0], evidenceSlugs),
|
|
3269
|
+
observationBlock(activeMarkers[1], patterns[1], evidenceSlugs),
|
|
3270
|
+
].join(`${newline}${newline}`);
|
|
3271
|
+
const expected = replaceObservationBlock(operation.before, targetId, replacement);
|
|
3272
|
+
return expected === operation.after
|
|
3273
|
+
? null
|
|
3274
|
+
: 'the observation split changed bytes outside its owned block';
|
|
3275
|
+
}
|
|
3276
|
+
if (marker.proofCount !== first.observationProofCount || marker.disposition !== 'active') {
|
|
3277
|
+
return 'the proposed observation marker does not preserve active proof lineage';
|
|
3278
|
+
}
|
|
3279
|
+
const pattern = /^- \*\*Observation:\*\* (.+?) Evidence: /.exec(payload)?.[1]?.trim();
|
|
3280
|
+
if (!pattern)
|
|
3281
|
+
return 'the proposed observation lacks visible derived wording';
|
|
3282
|
+
const expectedMarker = {
|
|
3283
|
+
...marker,
|
|
3284
|
+
evidence: sources.map((entry) => ({
|
|
3285
|
+
factId: entry.observationFactId,
|
|
3286
|
+
sourceLineHash: entry.observationLineHash,
|
|
3287
|
+
proofGroups: entry.observationProofGroups,
|
|
3288
|
+
})),
|
|
3289
|
+
};
|
|
3290
|
+
const expectedBlock = observationBlock(expectedMarker, pattern, sources.map((entry) => entry.source));
|
|
3291
|
+
const expected = outcome === 'create'
|
|
3292
|
+
? insertObservationBlock(operation.before, expectedBlock)
|
|
3293
|
+
: replaceObservationBlock(operation.before, targetId, expectedBlock);
|
|
3294
|
+
return expected === operation.after
|
|
3295
|
+
? null
|
|
3296
|
+
: 'the observation proposal changed bytes outside its owned block or section delimiter';
|
|
3297
|
+
}
|
|
3298
|
+
async function observationEvidenceIssue(ctx, item, phase = 'before') {
|
|
3299
|
+
if (item.kind === 'observe') {
|
|
3300
|
+
const first = item.evidence[0];
|
|
3301
|
+
const lifecycle = first?.observationOutcome;
|
|
3302
|
+
if ((lifecycle === 'weaken' || lifecycle === 'retract') && first) {
|
|
3303
|
+
const expectedDisposition = lifecycle === 'weaken' ? 'weakened' : 'retracted';
|
|
3304
|
+
const observation = ctx.store.db
|
|
3305
|
+
.prepare(`SELECT id, source_slug, subject_entity, disposition, payload_hash, proof_count, eligible, issue
|
|
3306
|
+
FROM observation_entries WHERE id = ?`)
|
|
3307
|
+
.get(first.observationId);
|
|
3308
|
+
if (!observation ||
|
|
3309
|
+
observation.source_slug !== item.subject ||
|
|
3310
|
+
observation.subject_entity !== first.observationSubject ||
|
|
3311
|
+
observation.payload_hash !== first.observationPayloadHash ||
|
|
3312
|
+
observation.proof_count !== first.observationProofCount) {
|
|
3313
|
+
return 'the observation no longer matches the sealed lifecycle target.';
|
|
3314
|
+
}
|
|
3315
|
+
// Verification runs after re-indexing, when the requested disposition is already current.
|
|
3316
|
+
if (observation.disposition === expectedDisposition)
|
|
3317
|
+
return null;
|
|
3318
|
+
if (observation.disposition !== 'active' ||
|
|
3319
|
+
observation.eligible !== 0 ||
|
|
3320
|
+
observation.issue !== first.observationQualificationIssue) {
|
|
3321
|
+
return 'the observation qualification changed after its lifecycle transition was sealed.';
|
|
3322
|
+
}
|
|
3323
|
+
const currentEvidence = ctx.store.db
|
|
3324
|
+
.prepare(`SELECT fact_id, source_line_hash, proof_groups
|
|
3325
|
+
FROM observation_evidence WHERE observation_id = ? ORDER BY ordinal`)
|
|
3326
|
+
.all(first.observationId);
|
|
3327
|
+
if (currentEvidence.length !== item.evidence.length ||
|
|
3328
|
+
currentEvidence.some((entry, index) => {
|
|
3329
|
+
const sealed = item.evidence[index];
|
|
3330
|
+
return (entry.fact_id !== sealed.observationFactId ||
|
|
3331
|
+
entry.source_line_hash !== sealed.observationLineHash ||
|
|
3332
|
+
!sameStringSet(JSON.parse(entry.proof_groups), sealed.observationProofGroups ?? []));
|
|
3333
|
+
})) {
|
|
3334
|
+
return 'the invalid observation lineage changed after it was sealed.';
|
|
3335
|
+
}
|
|
3336
|
+
const currentMarker = markerFromProjection(ctx.store, first.observationId);
|
|
3337
|
+
if (!currentMarker)
|
|
3338
|
+
return 'the observation marker is no longer projected.';
|
|
3339
|
+
const survivingProofs = liveObservationProofGroups(ctx.store, currentMarker);
|
|
3340
|
+
if ((lifecycle === 'weaken' && survivingProofs.size === 0) ||
|
|
3341
|
+
(lifecycle === 'retract' && survivingProofs.size !== 0)) {
|
|
3342
|
+
return `the observation no longer qualifies for ${lifecycle}.`;
|
|
3343
|
+
}
|
|
3344
|
+
for (const entry of item.evidence) {
|
|
3345
|
+
if (!entry.fingerprint || entry.source.startsWith('fact:'))
|
|
3346
|
+
continue;
|
|
3347
|
+
const page = ctx.store.db.prepare('SELECT rel_path FROM pages WHERE slug = ?').get(entry.source);
|
|
3348
|
+
const bytes = page
|
|
3349
|
+
? await fsp.readFile(path.join(ctx.config.aknoPath, page.rel_path), 'utf8').catch(() => null)
|
|
3350
|
+
: null;
|
|
3351
|
+
if (bytes === null || sha256(bytes) !== entry.fingerprint) {
|
|
3352
|
+
return `${entry.source} changed after the observation lifecycle transition was sealed.`;
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return null;
|
|
3356
|
+
}
|
|
3357
|
+
for (const entry of item.evidence) {
|
|
3358
|
+
const factId = entry.observationFactId;
|
|
3359
|
+
if (!factId || !entry.observationLineHash || !entry.observationProofGroups) {
|
|
3360
|
+
return 'observation evidence is missing its exact fact locator.';
|
|
3361
|
+
}
|
|
3362
|
+
const fact = ctx.store.db
|
|
3363
|
+
.prepare(`SELECT f.source_line_hash, f.valid_to, f.item_id, p.id AS page_id, p.slug, p.rel_path,
|
|
3364
|
+
p.role, p.body_hash, p.derived_hash, g.subject_entity, g.eligibility, g.traversable
|
|
3365
|
+
FROM facts f JOIN pages p ON p.id = f.page_id
|
|
3366
|
+
LEFT JOIN graph_fact_status g ON g.fact_id = f.id WHERE f.id = ?`)
|
|
3367
|
+
.get(factId);
|
|
3368
|
+
if (!fact ||
|
|
3369
|
+
fact.slug !== entry.source ||
|
|
3370
|
+
fact.source_line_hash !== entry.observationLineHash ||
|
|
3371
|
+
fact.valid_to !== null ||
|
|
3372
|
+
fact.role !== 'knowledge' ||
|
|
3373
|
+
fact.body_hash !== fact.derived_hash ||
|
|
3374
|
+
fact.subject_entity !== entry.observationSubject ||
|
|
3375
|
+
fact.eligibility !== 'eligible' ||
|
|
3376
|
+
fact.traversable !== 1) {
|
|
3377
|
+
return `${entry.source} no longer contains the sealed eligible fact.`;
|
|
3378
|
+
}
|
|
3379
|
+
const proofs = proofGroupsForFact(ctx.store, factId, fact.page_id, fact.item_id);
|
|
3380
|
+
if (proofs.size !== entry.observationProofGroups.length ||
|
|
3381
|
+
entry.observationProofGroups.some((proof) => !proofs.has(proof))) {
|
|
3382
|
+
return `${entry.source} no longer has the sealed independent support.`;
|
|
3383
|
+
}
|
|
3384
|
+
const bytes = await fsp
|
|
3385
|
+
.readFile(path.join(ctx.config.aknoPath, fact.rel_path), 'utf8')
|
|
3386
|
+
.catch(() => null);
|
|
3387
|
+
if (bytes === null || sha256(bytes) !== entry.fingerprint) {
|
|
3388
|
+
return `${entry.source} no longer matches its sealed observation evidence.`;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
if (phase === 'after') {
|
|
3392
|
+
const operation = item.operations[0];
|
|
3393
|
+
if (!operation || operation.type !== 'replace')
|
|
3394
|
+
return 'the applied observation operation is missing.';
|
|
3395
|
+
const appliedLifecycle = first?.observationOutcome ?? 'create';
|
|
3396
|
+
const page = parsePage(operation.relPath, operation.after);
|
|
3397
|
+
let expectedIds = [];
|
|
3398
|
+
if (appliedLifecycle === 'split' && first?.observationTargetId) {
|
|
3399
|
+
const targetIndex = observationMarkerIndexes(page.lines, first.observationTargetId)[0] ?? -1;
|
|
3400
|
+
expectedIds = page.lines
|
|
3401
|
+
.slice(targetIndex + 1, targetIndex + 8)
|
|
3402
|
+
.map(parseObservationMarker)
|
|
3403
|
+
.filter((entry) => entry !== null && entry.disposition === 'active')
|
|
3404
|
+
.map((entry) => entry.id);
|
|
3405
|
+
}
|
|
3406
|
+
else if (first?.observationId) {
|
|
3407
|
+
expectedIds = [first.observationId];
|
|
3408
|
+
}
|
|
3409
|
+
if (expectedIds.length !== (appliedLifecycle === 'split' ? 2 : 1)) {
|
|
3410
|
+
return 'the applied observation did not retain its expected active marker identity.';
|
|
3411
|
+
}
|
|
3412
|
+
for (const id of expectedIds) {
|
|
3413
|
+
const projected = ctx.store.db
|
|
3414
|
+
.prepare(`SELECT source_slug, subject_entity, disposition, eligible
|
|
3415
|
+
FROM observation_entries WHERE id = ?`)
|
|
3416
|
+
.get(id);
|
|
3417
|
+
if (!projected ||
|
|
3418
|
+
projected.source_slug !== item.subject ||
|
|
3419
|
+
projected.subject_entity !== first?.observationSubject ||
|
|
3420
|
+
projected.disposition !== 'active' ||
|
|
3421
|
+
projected.eligible !== 1) {
|
|
3422
|
+
return `the applied observation ${id} is not eligible in the rebuilt projection.`;
|
|
3423
|
+
}
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
return null;
|
|
3427
|
+
}
|
|
3428
|
+
if (item.kind === 'reflect' && item.evidence.every((entry) => entry.reflectionObservationId)) {
|
|
3429
|
+
for (const entry of item.evidence) {
|
|
3430
|
+
const observation = ctx.store.db
|
|
3431
|
+
.prepare(`SELECT oe.source_slug, oe.payload_hash, oe.eligible, oe.disposition, p.rel_path
|
|
3432
|
+
FROM observation_entries oe JOIN pages p ON p.id = oe.source_page WHERE oe.id = ?`)
|
|
3433
|
+
.get(entry.reflectionObservationId);
|
|
3434
|
+
if (!observation ||
|
|
3435
|
+
observation.source_slug !== entry.source ||
|
|
3436
|
+
observation.payload_hash !== entry.reflectionPayloadHash ||
|
|
3437
|
+
observation.eligible !== 1 ||
|
|
3438
|
+
observation.disposition !== 'active') {
|
|
3439
|
+
return `${entry.source} is no longer eligible level-two observation evidence.`;
|
|
3440
|
+
}
|
|
3441
|
+
const bytes = await fsp
|
|
3442
|
+
.readFile(path.join(ctx.config.aknoPath, observation.rel_path), 'utf8')
|
|
3443
|
+
.catch(() => null);
|
|
3444
|
+
if (bytes === null || sha256(bytes) !== entry.fingerprint) {
|
|
3445
|
+
return `${entry.source} no longer matches its sealed reflection evidence.`;
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
return null;
|
|
3449
|
+
}
|
|
3116
3450
|
const expectedRole = item.kind === 'reflect' ? 'inference' : 'knowledge';
|
|
3117
3451
|
const observationRoot = ctx.config.paths.observations.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
3118
3452
|
for (const entry of item.evidence) {
|
|
@@ -3123,7 +3457,7 @@ async function observationEvidenceIssue(ctx, item) {
|
|
|
3123
3457
|
if (item.kind === 'reflect' &&
|
|
3124
3458
|
(entry.source === item.subject ||
|
|
3125
3459
|
(entry.source !== observationRoot && !entry.source.startsWith(`${observationRoot}/`)))) {
|
|
3126
|
-
return `${entry.source} is not an independent
|
|
3460
|
+
return `${entry.source} is not an independent legacy inference-page source.`;
|
|
3127
3461
|
}
|
|
3128
3462
|
let sourcePath;
|
|
3129
3463
|
try {
|
|
@@ -3324,7 +3658,7 @@ async function preflightItem(ctx, item) {
|
|
|
3324
3658
|
const issue = observationOperationIssue(ctx, item, operations);
|
|
3325
3659
|
if (issue)
|
|
3326
3660
|
return { status: 'blocked', detail: issue };
|
|
3327
|
-
const evidenceIssue = await observationEvidenceIssue(ctx, item);
|
|
3661
|
+
const evidenceIssue = await observationEvidenceIssue(ctx, item, 'before');
|
|
3328
3662
|
if (evidenceIssue)
|
|
3329
3663
|
return { status: 'stale', detail: evidenceIssue };
|
|
3330
3664
|
}
|
|
@@ -3434,7 +3768,7 @@ async function preflightItem(ctx, item) {
|
|
|
3434
3768
|
canonical = parsed;
|
|
3435
3769
|
if (operation.type === 'delete')
|
|
3436
3770
|
mergeSource = parsed;
|
|
3437
|
-
const allowedDeclaredRole =
|
|
3771
|
+
const allowedDeclaredRole = item.kind === 'reflect' ? 'inference' : 'knowledge';
|
|
3438
3772
|
if (parsed.declaredRole && parsed.declaredRole !== allowedDeclaredRole) {
|
|
3439
3773
|
return { status: 'blocked', detail: `${operation.relPath} is not declared as ${allowedDeclaredRole}` };
|
|
3440
3774
|
}
|
|
@@ -3447,6 +3781,15 @@ async function preflightItem(ctx, item) {
|
|
|
3447
3781
|
};
|
|
3448
3782
|
}
|
|
3449
3783
|
}
|
|
3784
|
+
if (item.kind === 'observe') {
|
|
3785
|
+
const currentPolicy = resolvePagePolicy(parsed, effectiveRule(parsed.slug, ctx.config.rules), ctx.config.paths.observations);
|
|
3786
|
+
if (currentPolicy.role !== 'knowledge' || currentPolicy.observe !== 'integrate') {
|
|
3787
|
+
return {
|
|
3788
|
+
status: 'blocked',
|
|
3789
|
+
detail: `${operation.relPath} no longer allows observation integration`,
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
}
|
|
3450
3793
|
if (item.kind === 'broken_link' &&
|
|
3451
3794
|
index === 0 &&
|
|
3452
3795
|
parsed.declaredManagement.dream !== 'hygiene' &&
|