@tenphi/akno-core 0.13.1 → 0.13.3
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/dist/bench/language.d.ts +16 -8
- package/dist/bench/language.d.ts.map +1 -1
- package/dist/context.d.ts +3 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js.map +1 -1
- package/dist/index/indexer.d.ts +10 -0
- package/dist/index/indexer.d.ts.map +1 -1
- package/dist/index/indexer.js +21 -1
- package/dist/index/indexer.js.map +1 -1
- package/dist/maintenance/brain-migration.d.ts +4 -1
- package/dist/maintenance/brain-migration.d.ts.map +1 -1
- package/dist/maintenance/brain-migration.js +29 -6
- package/dist/maintenance/brain-migration.js.map +1 -1
- package/dist/maintenance/dream.d.ts +2 -0
- package/dist/maintenance/dream.d.ts.map +1 -1
- package/dist/maintenance/dream.js +270 -64
- package/dist/maintenance/dream.js.map +1 -1
- package/dist/maintenance/observation-scope.d.ts +74 -0
- package/dist/maintenance/observation-scope.d.ts.map +1 -0
- package/dist/maintenance/observation-scope.js +259 -0
- package/dist/maintenance/observation-scope.js.map +1 -0
- package/dist/maintenance/observe.d.ts +2 -0
- package/dist/maintenance/observe.d.ts.map +1 -1
- package/dist/maintenance/observe.js +28 -24
- package/dist/maintenance/observe.js.map +1 -1
- package/dist/maintenance/plans.d.ts +20 -1
- package/dist/maintenance/plans.d.ts.map +1 -1
- package/dist/maintenance/plans.js +251 -35
- package/dist/maintenance/plans.js.map +1 -1
- package/dist/observations/marker.d.ts +2 -0
- package/dist/observations/marker.d.ts.map +1 -1
- package/dist/observations/marker.js +16 -4
- package/dist/observations/marker.js.map +1 -1
- package/dist/observations/projection.d.ts +1 -1
- package/dist/observations/projection.d.ts.map +1 -1
- package/dist/observations/projection.js +14 -5
- package/dist/observations/projection.js.map +1 -1
- package/dist/open.d.ts.map +1 -1
- package/dist/open.js +5 -0
- package/dist/open.js.map +1 -1
- package/dist/ops/folder.d.ts.map +1 -1
- package/dist/ops/folder.js +3 -0
- package/dist/ops/folder.js.map +1 -1
- package/dist/ops/forget.d.ts.map +1 -1
- package/dist/ops/forget.js +9 -0
- package/dist/ops/forget.js.map +1 -1
- package/dist/ops/graph.js +5 -1
- package/dist/ops/graph.js.map +1 -1
- package/dist/ops/move.d.ts.map +1 -1
- package/dist/ops/move.js +15 -7
- package/dist/ops/move.js.map +1 -1
- package/dist/ops/remember.d.ts.map +1 -1
- package/dist/ops/remember.js +34 -8
- package/dist/ops/remember.js.map +1 -1
- package/dist/ops/retain.d.ts.map +1 -1
- package/dist/ops/retain.js +119 -2
- package/dist/ops/retain.js.map +1 -1
- package/dist/ops/write.d.ts.map +1 -1
- package/dist/ops/write.js +17 -4
- package/dist/ops/write.js.map +1 -1
- package/dist/store/db.d.ts.map +1 -1
- package/dist/store/db.js +18 -1
- package/dist/store/db.js.map +1 -1
- package/dist/store/migrations.d.ts +4 -2
- package/dist/store/migrations.d.ts.map +1 -1
- package/dist/store/migrations.js +41 -2
- package/dist/store/migrations.js.map +1 -1
- package/dist/write/journal.d.ts +2 -0
- package/dist/write/journal.d.ts.map +1 -1
- package/dist/write/journal.js +11 -0
- package/dist/write/journal.js.map +1 -1
- package/dist/write/mutation-receipts.d.ts +32 -0
- package/dist/write/mutation-receipts.d.ts.map +1 -0
- package/dist/write/mutation-receipts.js +331 -0
- package/dist/write/mutation-receipts.js.map +1 -0
- package/dist/write/retain.d.ts +6 -2
- package/dist/write/retain.d.ts.map +1 -1
- package/dist/write/retain.js +86 -13
- package/dist/write/retain.js.map +1 -1
- package/package.json +2 -2
|
@@ -19,6 +19,7 @@ import { hasInlineMergeConflict, matchesConflictPath, quarantineReasonsForPath,
|
|
|
19
19
|
import { activeDreamRuns, getDreamRun, latestDreamRun, latestFullDreamRun, listDreamRuns, } from "./runs.js";
|
|
20
20
|
import { declaringRule, effectiveRule } from "../rules/compile.js";
|
|
21
21
|
import { configuredMaintenanceAuthority } from "./profile.js";
|
|
22
|
+
import { OBSERVATION_SCOPE_PROMPT_VERSION, observationScopeCandidateHash, observationScopeContextHash, } from "./observation-scope.js";
|
|
22
23
|
import { createMaintenanceBudget, maintenanceBudgetReceipt, reserveMaintenanceBudget, } from "./budget.js";
|
|
23
24
|
import { managedItemOperationsIssue, } from "./managed-items.js";
|
|
24
25
|
import { rewritePageLinks, rewriteRelocatedPageReferences } from "./rule-drift.js";
|
|
@@ -164,6 +165,22 @@ function createInferencePlan(ctx, mode, kind, drafts, policy) {
|
|
|
164
165
|
...(draft.observationQualificationIssue
|
|
165
166
|
? { observationQualificationIssue: draft.observationQualificationIssue }
|
|
166
167
|
: {}),
|
|
168
|
+
...(draft.observationScopeAssessment
|
|
169
|
+
? { observationScopeAssessment: draft.observationScopeAssessment }
|
|
170
|
+
: {}),
|
|
171
|
+
...(draft.observationScopeCandidateHash
|
|
172
|
+
? { observationScopeCandidateHash: draft.observationScopeCandidateHash }
|
|
173
|
+
: {}),
|
|
174
|
+
...(draft.observationScopeContextHash
|
|
175
|
+
? { observationScopeContextHash: draft.observationScopeContextHash }
|
|
176
|
+
: {}),
|
|
177
|
+
...(draft.observationScopePromptVersion
|
|
178
|
+
? { observationScopePromptVersion: draft.observationScopePromptVersion }
|
|
179
|
+
: {}),
|
|
180
|
+
...(draft.observationScopeModel ? { observationScopeModel: draft.observationScopeModel } : {}),
|
|
181
|
+
...(draft.observationScopeNarrowed !== undefined
|
|
182
|
+
? { observationScopeNarrowed: draft.observationScopeNarrowed }
|
|
183
|
+
: {}),
|
|
167
184
|
...(entry.reflectionObservationId ? { reflectionObservationId: entry.reflectionObservationId } : {}),
|
|
168
185
|
...(entry.reflectionPayloadHash ? { reflectionPayloadHash: entry.reflectionPayloadHash } : {}),
|
|
169
186
|
})),
|
|
@@ -175,6 +192,9 @@ function createInferencePlan(ctx, mode, kind, drafts, policy) {
|
|
|
175
192
|
: 'at least three distinct live observation sources',
|
|
176
193
|
status: 'passed',
|
|
177
194
|
},
|
|
195
|
+
...(draft.observationScopeAssessment
|
|
196
|
+
? [{ name: 'exact candidate passed separate evidence-scope assessment', status: 'passed' }]
|
|
197
|
+
: []),
|
|
178
198
|
{
|
|
179
199
|
name: kind === 'observe' ? 'marker-owned observation block shape' : 'append-only derived principle shape',
|
|
180
200
|
status: 'passed',
|
|
@@ -1425,11 +1445,22 @@ function intersects(left, right) {
|
|
|
1425
1445
|
return true;
|
|
1426
1446
|
return false;
|
|
1427
1447
|
}
|
|
1428
|
-
/**
|
|
1448
|
+
/**
|
|
1449
|
+
* Oldest plan with work that can actually progress after restart.
|
|
1450
|
+
*
|
|
1451
|
+
* Older versions could leave a mixed plan as `partially_completed` after its only remaining
|
|
1452
|
+
* items became blocked, stale, or verification-failed. Plan status alone therefore cannot decide
|
|
1453
|
+
* whether a plan is resumable: selecting that residual forever would starve independent work.
|
|
1454
|
+
*/
|
|
1429
1455
|
export function findActiveMaintenancePlan(ctx, mode, phase) {
|
|
1430
1456
|
const row = ctx.store.db
|
|
1431
1457
|
.prepare(`SELECT id FROM maintenance_plans WHERE mode = ? AND phase = ?
|
|
1432
1458
|
AND status NOT IN ('completed', 'failed', 'superseded')
|
|
1459
|
+
AND EXISTS (
|
|
1460
|
+
SELECT 1 FROM maintenance_items item
|
|
1461
|
+
WHERE item.plan_id = maintenance_plans.id
|
|
1462
|
+
AND item.status IN ('proposed', 'approved', 'applying', 'verification_pending')
|
|
1463
|
+
)
|
|
1433
1464
|
ORDER BY rowid LIMIT 1`)
|
|
1434
1465
|
.get(mode, phase);
|
|
1435
1466
|
return row ? getMaintenancePlan(ctx, row.id) : null;
|
|
@@ -2746,10 +2777,24 @@ async function indexMaintenanceItem(ctx, item, documentState = 'after') {
|
|
|
2746
2777
|
await Promise.all(paths.map((relPath) => safeOperationPath(ctx, relPath)));
|
|
2747
2778
|
alignRelocatedPageRow(ctx, item, documentState);
|
|
2748
2779
|
alignMovedDocumentRows(ctx, item.operations, documentState);
|
|
2780
|
+
const preserveDerivedViews = item.kind === 'observe' &&
|
|
2781
|
+
item.evidence.some((entry) => entry.source === item.subject) &&
|
|
2782
|
+
observationOperationIssue(ctx, item, item.operations) === null
|
|
2783
|
+
? item.operations.flatMap((operation) => operation.type === 'replace'
|
|
2784
|
+
? [
|
|
2785
|
+
{
|
|
2786
|
+
relPath: operation.relPath,
|
|
2787
|
+
fromBodyHash: parsePage(operation.relPath, documentState === 'after' ? operation.before : operation.after).bodyHash,
|
|
2788
|
+
toBodyHash: parsePage(operation.relPath, documentState === 'after' ? operation.after : operation.before).bodyHash,
|
|
2789
|
+
},
|
|
2790
|
+
]
|
|
2791
|
+
: [])
|
|
2792
|
+
: [];
|
|
2749
2793
|
await ctx.indexer.run({
|
|
2750
2794
|
only: paths,
|
|
2751
2795
|
modelPaths: [],
|
|
2752
2796
|
...(item.kind === 'adopt' ? { reindexUnchanged: true } : {}),
|
|
2797
|
+
...(preserveDerivedViews.length > 0 ? { preserveDerivedViews } : {}),
|
|
2753
2798
|
});
|
|
2754
2799
|
}
|
|
2755
2800
|
/** Restore a maintenance-relocated page's derived identity before generic undo reindexes the tree. */
|
|
@@ -3085,6 +3130,24 @@ function observationOperationIssue(ctx, item, operations) {
|
|
|
3085
3130
|
return 'a reflection item must write only the configured observations/principles page';
|
|
3086
3131
|
}
|
|
3087
3132
|
const sources = item.evidence.filter((entry) => entry.type === 'page');
|
|
3133
|
+
const firstScope = sources[0];
|
|
3134
|
+
if (item.kind === 'reflect' &&
|
|
3135
|
+
(!firstScope?.observationScopeAssessment ||
|
|
3136
|
+
!/^[a-f0-9]{64}$/.test(firstScope.observationScopeAssessment) ||
|
|
3137
|
+
!firstScope.observationScopeCandidateHash ||
|
|
3138
|
+
!/^[a-f0-9]{64}$/.test(firstScope.observationScopeCandidateHash) ||
|
|
3139
|
+
!firstScope.observationScopeContextHash ||
|
|
3140
|
+
!/^[a-f0-9]{64}$/.test(firstScope.observationScopeContextHash) ||
|
|
3141
|
+
firstScope.observationScopePromptVersion !== OBSERVATION_SCOPE_PROMPT_VERSION ||
|
|
3142
|
+
!firstScope.observationScopeModel ||
|
|
3143
|
+
sources.some((entry) => entry.observationScopeAssessment !== firstScope.observationScopeAssessment ||
|
|
3144
|
+
entry.observationScopeCandidateHash !== firstScope.observationScopeCandidateHash ||
|
|
3145
|
+
entry.observationScopeContextHash !== firstScope.observationScopeContextHash ||
|
|
3146
|
+
entry.observationScopePromptVersion !== firstScope.observationScopePromptVersion ||
|
|
3147
|
+
entry.observationScopeModel !== firstScope.observationScopeModel ||
|
|
3148
|
+
entry.observationScopeNarrowed !== firstScope.observationScopeNarrowed))) {
|
|
3149
|
+
return 'a reflection requires one exact current evidence-scope assessment';
|
|
3150
|
+
}
|
|
3088
3151
|
const minEvidence = item.kind === 'reflect'
|
|
3089
3152
|
? Math.max(3, ctx.config.maintenance.observe.minEvidence)
|
|
3090
3153
|
: ctx.config.maintenance.observe.minEvidence;
|
|
@@ -3121,6 +3184,10 @@ function observationOperationIssue(ctx, item, operations) {
|
|
|
3121
3184
|
if (!match || match[2].trim().length === 0) {
|
|
3122
3185
|
return 'the inference item must append one dated, cited conclusion line';
|
|
3123
3186
|
}
|
|
3187
|
+
if (item.kind === 'reflect' &&
|
|
3188
|
+
observationScopeCandidateHash({ pattern: match[2].trim() }) !== firstScope?.observationScopeCandidateHash) {
|
|
3189
|
+
return 'the reflected principle changed after its evidence-scope assessment';
|
|
3190
|
+
}
|
|
3124
3191
|
const citations = [...match[3].matchAll(/\[\[([^\]|#]+)(?:[#|][^\]]*)?\]\]/g)].map((entry) => entry[1]);
|
|
3125
3192
|
if (!sameStringSet(citations, sourceSlugs)) {
|
|
3126
3193
|
return 'the appended inference line must cite exactly its sealed evidence pages';
|
|
@@ -3198,6 +3265,23 @@ function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
|
3198
3265
|
entry.observationProofCount !== first.observationProofCount)) {
|
|
3199
3266
|
return 'observation evidence disagrees about marker identity';
|
|
3200
3267
|
}
|
|
3268
|
+
if (!closesInvalidLineage &&
|
|
3269
|
+
(!first.observationScopeAssessment ||
|
|
3270
|
+
!/^[a-f0-9]{64}$/.test(first.observationScopeAssessment) ||
|
|
3271
|
+
!first.observationScopeCandidateHash ||
|
|
3272
|
+
!/^[a-f0-9]{64}$/.test(first.observationScopeCandidateHash) ||
|
|
3273
|
+
!first.observationScopeContextHash ||
|
|
3274
|
+
!/^[a-f0-9]{64}$/.test(first.observationScopeContextHash) ||
|
|
3275
|
+
first.observationScopePromptVersion !== OBSERVATION_SCOPE_PROMPT_VERSION ||
|
|
3276
|
+
!first.observationScopeModel ||
|
|
3277
|
+
sources.some((entry) => entry.observationScopeAssessment !== first.observationScopeAssessment ||
|
|
3278
|
+
entry.observationScopeCandidateHash !== first.observationScopeCandidateHash ||
|
|
3279
|
+
entry.observationScopeContextHash !== first.observationScopeContextHash ||
|
|
3280
|
+
entry.observationScopePromptVersion !== first.observationScopePromptVersion ||
|
|
3281
|
+
entry.observationScopeModel !== first.observationScopeModel ||
|
|
3282
|
+
entry.observationScopeNarrowed !== first.observationScopeNarrowed))) {
|
|
3283
|
+
return 'an observation requires one exact current evidence-scope assessment';
|
|
3284
|
+
}
|
|
3201
3285
|
const factIds = sources.map((entry) => entry.observationFactId);
|
|
3202
3286
|
if (new Set(factIds).size !== factIds.length)
|
|
3203
3287
|
return 'observation evidence contains a duplicate fact';
|
|
@@ -3214,7 +3298,11 @@ function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
|
3214
3298
|
return 'the observation marker is missing from the proposed page';
|
|
3215
3299
|
const marker = parseObservationMarker(afterPage.lines[markerIndex]);
|
|
3216
3300
|
const payload = afterPage.lines[markerIndex + 1]?.trim() ?? '';
|
|
3217
|
-
if (!marker ||
|
|
3301
|
+
if (!marker ||
|
|
3302
|
+
marker.subject !== first.observationSubject ||
|
|
3303
|
+
(!closesInvalidLineage &&
|
|
3304
|
+
outcome !== 'split' &&
|
|
3305
|
+
marker.scopeAssessment !== first.observationScopeAssessment)) {
|
|
3218
3306
|
return 'the proposed observation marker does not match its sealed lineage';
|
|
3219
3307
|
}
|
|
3220
3308
|
if (closesInvalidLineage) {
|
|
@@ -3262,6 +3350,8 @@ function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
|
3262
3350
|
const patterns = activePayloads.map((entry) => /^- \*\*Observation:\*\* (.+?) Evidence: /.exec(entry)?.[1]?.trim() ?? '');
|
|
3263
3351
|
const activeIds = activeMarkers.map((entry) => entry.id);
|
|
3264
3352
|
if (patterns.some((entry) => !entry) ||
|
|
3353
|
+
observationScopeCandidateHash({ pattern: patterns[0], splitPattern: patterns[1] }) !==
|
|
3354
|
+
first.observationScopeCandidateHash ||
|
|
3265
3355
|
activePayloads.some((entry) => observationPayloadIssue(entry, evidenceSlugs) !== null) ||
|
|
3266
3356
|
activeMarkers.some((entry) => renderObservationMarker(entry) !==
|
|
3267
3357
|
renderObservationMarker({
|
|
@@ -3270,6 +3360,7 @@ function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
|
3270
3360
|
disposition: 'active',
|
|
3271
3361
|
evidence: expectedEvidence,
|
|
3272
3362
|
proofCount: first.observationProofCount,
|
|
3363
|
+
scopeAssessment: first.observationScopeAssessment,
|
|
3273
3364
|
})) ||
|
|
3274
3365
|
new Set(activeIds).size !== 2 ||
|
|
3275
3366
|
activeIds.includes(targetId) ||
|
|
@@ -3293,6 +3384,9 @@ function coLocatedObservationOperationIssue(ctx, item, operations) {
|
|
|
3293
3384
|
const pattern = /^- \*\*Observation:\*\* (.+?) Evidence: /.exec(payload)?.[1]?.trim();
|
|
3294
3385
|
if (!pattern)
|
|
3295
3386
|
return 'the proposed observation lacks visible derived wording';
|
|
3387
|
+
if (observationScopeCandidateHash({ pattern }) !== first.observationScopeCandidateHash) {
|
|
3388
|
+
return 'the observation wording changed after its evidence-scope assessment';
|
|
3389
|
+
}
|
|
3296
3390
|
const expectedMarker = {
|
|
3297
3391
|
...marker,
|
|
3298
3392
|
evidence: sources.map((entry) => ({
|
|
@@ -3398,10 +3492,23 @@ async function observationEvidenceIssue(ctx, item, phase = 'before') {
|
|
|
3398
3492
|
const bytes = await fsp
|
|
3399
3493
|
.readFile(path.join(ctx.config.aknoPath, fact.rel_path), 'utf8')
|
|
3400
3494
|
.catch(() => null);
|
|
3401
|
-
|
|
3495
|
+
const coLocatedOperation = phase === 'after' && entry.source === item.subject
|
|
3496
|
+
? item.operations.find((operation) => operation.type === 'replace' &&
|
|
3497
|
+
operation.relPath === fact.rel_path &&
|
|
3498
|
+
sha256(operation.before) === entry.fingerprint)
|
|
3499
|
+
: undefined;
|
|
3500
|
+
if (bytes === null ||
|
|
3501
|
+
(coLocatedOperation
|
|
3502
|
+
? sha256(bytes) !== coLocatedOperation.afterHash
|
|
3503
|
+
: sha256(bytes) !== entry.fingerprint)) {
|
|
3402
3504
|
return `${entry.source} no longer matches its sealed observation evidence.`;
|
|
3403
3505
|
}
|
|
3404
3506
|
}
|
|
3507
|
+
if (item.policy === 'review') {
|
|
3508
|
+
const scopeIssue = currentObservationScopeIssue(ctx, item);
|
|
3509
|
+
if (scopeIssue)
|
|
3510
|
+
return scopeIssue;
|
|
3511
|
+
}
|
|
3405
3512
|
if (phase === 'after') {
|
|
3406
3513
|
const operation = item.operations[0];
|
|
3407
3514
|
if (!operation || operation.type !== 'replace')
|
|
@@ -3459,6 +3566,11 @@ async function observationEvidenceIssue(ctx, item, phase = 'before') {
|
|
|
3459
3566
|
return `${entry.source} no longer matches its sealed reflection evidence.`;
|
|
3460
3567
|
}
|
|
3461
3568
|
}
|
|
3569
|
+
if (item.policy === 'review') {
|
|
3570
|
+
const scopeIssue = currentObservationScopeIssue(ctx, item);
|
|
3571
|
+
if (scopeIssue)
|
|
3572
|
+
return scopeIssue;
|
|
3573
|
+
}
|
|
3462
3574
|
return null;
|
|
3463
3575
|
}
|
|
3464
3576
|
const expectedRole = item.kind === 'reflect' ? 'inference' : 'knowledge';
|
|
@@ -3487,6 +3599,114 @@ async function observationEvidenceIssue(ctx, item, phase = 'before') {
|
|
|
3487
3599
|
}
|
|
3488
3600
|
return null;
|
|
3489
3601
|
}
|
|
3602
|
+
/** Rebuild the complete bounded assessment input so delayed review plans cannot outlive context changes. */
|
|
3603
|
+
function currentObservationScopeIssue(ctx, item) {
|
|
3604
|
+
const expected = item.evidence[0]?.observationScopeContextHash;
|
|
3605
|
+
if (!expected)
|
|
3606
|
+
return 'the inference plan is missing its sealed complete evidence scope.';
|
|
3607
|
+
const evidence = item.kind === 'observe'
|
|
3608
|
+
? currentLevelTwoScope(ctx, item)
|
|
3609
|
+
: item.kind === 'reflect'
|
|
3610
|
+
? currentLevelThreeScope(ctx, item)
|
|
3611
|
+
: null;
|
|
3612
|
+
if (!evidence || observationScopeContextHash(evidence) !== expected) {
|
|
3613
|
+
return 'the complete current evidence scope changed after its assessment was sealed.';
|
|
3614
|
+
}
|
|
3615
|
+
return null;
|
|
3616
|
+
}
|
|
3617
|
+
function currentLevelTwoScope(ctx, item) {
|
|
3618
|
+
const subject = item.evidence[0]?.observationSubject;
|
|
3619
|
+
if (!subject)
|
|
3620
|
+
return null;
|
|
3621
|
+
const selected = new Set(item.evidence.map((entry) => entry.observationFactId).filter(Boolean));
|
|
3622
|
+
const rows = ctx.store.db
|
|
3623
|
+
.prepare(`SELECT f.id, f.claim, f.item_id, p.id AS page_id, p.slug,
|
|
3624
|
+
g.eligibility, g.traversable
|
|
3625
|
+
FROM facts f JOIN pages p ON p.id = f.page_id
|
|
3626
|
+
JOIN graph_fact_status g ON g.fact_id = f.id
|
|
3627
|
+
WHERE f.valid_to IS NULL
|
|
3628
|
+
AND f.confidence >= 0.5
|
|
3629
|
+
AND p.role = 'knowledge'
|
|
3630
|
+
AND p.derived_hash = p.body_hash
|
|
3631
|
+
AND g.subject_entity = ?
|
|
3632
|
+
ORDER BY p.slug, f.id`)
|
|
3633
|
+
.all(subject);
|
|
3634
|
+
const evidence = [];
|
|
3635
|
+
let characters = 0;
|
|
3636
|
+
for (const row of rows) {
|
|
3637
|
+
const nextCharacters = row.id.length + row.claim.length + row.slug.length;
|
|
3638
|
+
if (evidence.length >= 80 || characters + nextCharacters > 80_000)
|
|
3639
|
+
return null;
|
|
3640
|
+
const proofs = proofGroupsForFact(ctx.store, row.id, row.page_id, row.item_id);
|
|
3641
|
+
const conflictReason = row.eligibility.startsWith('conflict_') ? row.eligibility : null;
|
|
3642
|
+
const canSupport = !conflictReason && row.eligibility === 'eligible' && row.traversable === 1 && proofs.size > 0;
|
|
3643
|
+
const isSelected = selected.has(row.id);
|
|
3644
|
+
evidence.push({
|
|
3645
|
+
id: row.id,
|
|
3646
|
+
claim: row.claim,
|
|
3647
|
+
slug: row.slug,
|
|
3648
|
+
status: isSelected
|
|
3649
|
+
? 'selected_support'
|
|
3650
|
+
: conflictReason
|
|
3651
|
+
? 'counterevidence'
|
|
3652
|
+
: canSupport
|
|
3653
|
+
? 'eligible_context'
|
|
3654
|
+
: 'ineligible_context',
|
|
3655
|
+
...(!isSelected && !canSupport
|
|
3656
|
+
? {
|
|
3657
|
+
statusReason: conflictReason ?? `graph_${row.eligibility}_${row.traversable ? 'linked' : 'held'}`,
|
|
3658
|
+
}
|
|
3659
|
+
: {}),
|
|
3660
|
+
});
|
|
3661
|
+
characters += nextCharacters;
|
|
3662
|
+
}
|
|
3663
|
+
return evidence.sort((a, b) => `${a.slug}\0${a.id}`.localeCompare(`${b.slug}\0${b.id}`));
|
|
3664
|
+
}
|
|
3665
|
+
function currentLevelThreeScope(ctx, item) {
|
|
3666
|
+
const rows = ctx.store.db
|
|
3667
|
+
.prepare(`SELECT oe.id, oe.source_slug AS slug, oe.payload
|
|
3668
|
+
FROM observation_entries oe JOIN pages p ON p.id = oe.source_page
|
|
3669
|
+
WHERE oe.eligible = 1 AND oe.disposition = 'active'
|
|
3670
|
+
ORDER BY p.updated_at DESC, oe.source_slug, oe.marker_line DESC, oe.id LIMIT 41`)
|
|
3671
|
+
.all();
|
|
3672
|
+
if (rows.length > 40)
|
|
3673
|
+
return null;
|
|
3674
|
+
const selected = new Set(item.evidence.map((entry) => entry.reflectionObservationId).filter(Boolean));
|
|
3675
|
+
const evidence = [];
|
|
3676
|
+
let leafCount = 0;
|
|
3677
|
+
let characters = 0;
|
|
3678
|
+
for (const row of rows) {
|
|
3679
|
+
const leaves = ctx.store.db
|
|
3680
|
+
.prepare(`SELECT f.id, f.claim, p.slug
|
|
3681
|
+
FROM observation_evidence oe JOIN facts f ON f.id = oe.fact_id
|
|
3682
|
+
JOIN pages p ON p.id = f.page_id
|
|
3683
|
+
WHERE oe.observation_id = ? AND f.valid_to IS NULL
|
|
3684
|
+
ORDER BY oe.ordinal`)
|
|
3685
|
+
.all(row.id);
|
|
3686
|
+
const expected = ctx.store.db
|
|
3687
|
+
.prepare('SELECT count(*) AS count FROM observation_evidence WHERE observation_id = ?')
|
|
3688
|
+
.get(row.id);
|
|
3689
|
+
const nextCharacters = row.id.length +
|
|
3690
|
+
row.slug.length +
|
|
3691
|
+
row.payload.length +
|
|
3692
|
+
leaves.reduce((sum, leaf) => sum + leaf.id.length + leaf.slug.length + leaf.claim.length, 0);
|
|
3693
|
+
if (leaves.length !== expected.count ||
|
|
3694
|
+
leafCount + leaves.length > 120 ||
|
|
3695
|
+
characters + nextCharacters > 80_000) {
|
|
3696
|
+
return null;
|
|
3697
|
+
}
|
|
3698
|
+
evidence.push({
|
|
3699
|
+
id: row.id,
|
|
3700
|
+
claim: row.payload,
|
|
3701
|
+
slug: row.slug,
|
|
3702
|
+
status: selected.has(row.id) ? 'selected_support' : 'eligible_context',
|
|
3703
|
+
leaves,
|
|
3704
|
+
});
|
|
3705
|
+
leafCount += leaves.length;
|
|
3706
|
+
characters += nextCharacters;
|
|
3707
|
+
}
|
|
3708
|
+
return evidence.sort((a, b) => `${a.slug}\0${a.id}`.localeCompare(`${b.slug}\0${b.id}`));
|
|
3709
|
+
}
|
|
3490
3710
|
async function curationPageEvidenceIssue(ctx, item) {
|
|
3491
3711
|
for (const entry of item.evidence.filter((candidate) => candidate.type === 'page')) {
|
|
3492
3712
|
if (!entry.fingerprint) {
|
|
@@ -4201,15 +4421,11 @@ export function maintenancePlanStatusAfterApply(plan) {
|
|
|
4201
4421
|
}
|
|
4202
4422
|
else if (statuses.includes('proposed'))
|
|
4203
4423
|
return 'ready';
|
|
4204
|
-
else if (plan.items.some((item) => ['dependency_conflict', 'dependency_unmet', 'snapshot_drift'].includes(item.statusCode ?? ''))) {
|
|
4205
|
-
// These items need a new plan, unlike budget and verification deferrals. Keeping this plan
|
|
4206
|
-
// active would make the next cycle resume a terminal item forever instead of replanning it.
|
|
4207
|
-
return 'failed';
|
|
4208
|
-
}
|
|
4209
4424
|
else if (statuses.every((value) => value === 'applied' || value === 'rejected'))
|
|
4210
4425
|
return 'completed';
|
|
4211
|
-
|
|
4212
|
-
|
|
4426
|
+
// Every status left here is terminal for this sealed item. That includes untyped `blocked` rows
|
|
4427
|
+
// persisted before status codes existed: keeping a mixed applied/blocked plan nonterminal makes
|
|
4428
|
+
// it look resumable even though apply has no legal next step.
|
|
4213
4429
|
return 'failed';
|
|
4214
4430
|
}
|
|
4215
4431
|
function setPlanStatus(ctx, planId, status) {
|
|
@@ -4219,31 +4435,31 @@ function setPlanStatus(ctx, planId, status) {
|
|
|
4219
4435
|
}
|
|
4220
4436
|
export function finalizeRetryableMaintenancePlans(ctx) {
|
|
4221
4437
|
requireWritable(ctx);
|
|
4222
|
-
const
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4438
|
+
const now = new Date().toISOString();
|
|
4439
|
+
return ctx.store.transaction(() => {
|
|
4440
|
+
const completed = ctx.store.db
|
|
4441
|
+
.prepare(`UPDATE maintenance_plans AS plan
|
|
4442
|
+
SET status = 'completed', updated_at = ?
|
|
4443
|
+
WHERE plan.status NOT IN ('completed', 'failed', 'superseded')
|
|
4444
|
+
AND EXISTS (SELECT 1 FROM maintenance_items item WHERE item.plan_id = plan.id)
|
|
4445
|
+
AND NOT EXISTS (
|
|
4446
|
+
SELECT 1 FROM maintenance_items item
|
|
4447
|
+
WHERE item.plan_id = plan.id
|
|
4448
|
+
AND item.status NOT IN ('applied', 'rejected')
|
|
4449
|
+
)`)
|
|
4450
|
+
.run(now).changes;
|
|
4451
|
+
const failed = ctx.store.db
|
|
4452
|
+
.prepare(`UPDATE maintenance_plans AS plan
|
|
4453
|
+
SET status = 'failed', updated_at = ?
|
|
4454
|
+
WHERE plan.status NOT IN ('completed', 'failed', 'superseded')
|
|
4455
|
+
AND NOT EXISTS (
|
|
4456
|
+
SELECT 1 FROM maintenance_items item
|
|
4457
|
+
WHERE item.plan_id = plan.id
|
|
4458
|
+
AND item.status IN ('proposed', 'approved', 'applying', 'verification_pending')
|
|
4459
|
+
)`)
|
|
4460
|
+
.run(now).changes;
|
|
4461
|
+
return completed + failed;
|
|
4462
|
+
});
|
|
4247
4463
|
}
|
|
4248
4464
|
/**
|
|
4249
4465
|
* A dependency-deferred plan was sealed against the pre-apply snapshot. Once the same run starts
|