@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.22
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/assets/gep/genes.jsonl +5 -5
- package/dist/algo/bans.d.ts +10 -1
- package/dist/algo/bans.js +56 -6
- package/dist/algo/candidateAssembly.d.ts +25 -2
- package/dist/algo/candidateAssembly.js +171 -26
- package/dist/algo/capabilityCandidates.js +10 -0
- package/dist/algo/conversationSniffer.js +25 -8
- package/dist/algo/cycleEngine.d.ts +63 -5
- package/dist/algo/cycleEngine.js +320 -42
- package/dist/algo/cycleFailureClassifier.d.ts +1 -1
- package/dist/algo/evolutionEvent.d.ts +26 -0
- package/dist/algo/evolutionEvent.js +32 -0
- package/dist/algo/exploration.d.ts +7 -0
- package/dist/algo/exploration.js +16 -3
- package/dist/algo/geneHealth.d.ts +36 -3
- package/dist/algo/geneHealth.js +47 -4
- package/dist/algo/geneIntake.d.ts +46 -1
- package/dist/algo/geneIntake.js +121 -10
- package/dist/algo/geneSelection.d.ts +126 -6
- package/dist/algo/geneSelection.js +479 -36
- package/dist/algo/index.d.ts +6 -1
- package/dist/algo/index.js +6 -1
- package/dist/algo/kautoProjection.d.ts +41 -0
- package/dist/algo/kautoProjection.js +95 -0
- package/dist/algo/kautoValidator.d.ts +68 -0
- package/dist/algo/kautoValidator.js +256 -0
- package/dist/algo/memoryGraph.d.ts +62 -0
- package/dist/algo/memoryGraph.js +86 -0
- package/dist/algo/orchestrator.d.ts +17 -1
- package/dist/algo/orchestrator.js +30 -4
- package/dist/algo/publishEligibility.d.ts +34 -0
- package/dist/algo/publishEligibility.js +52 -0
- package/dist/algo/solidify.d.ts +11 -2
- package/dist/algo/solidify.js +37 -7
- package/dist/algo/ucb1.d.ts +53 -0
- package/dist/algo/ucb1.js +156 -0
- package/dist/assetrepair/hubRejection.d.ts +12 -0
- package/dist/assetrepair/hubRejection.js +109 -0
- package/dist/assetrepair/index.d.ts +2 -0
- package/dist/assetrepair/index.js +2 -0
- package/dist/assetrepair/repair.d.ts +33 -0
- package/dist/assetrepair/repair.js +155 -0
- package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
- package/dist/assetstore/assetSidecarRecords.js +384 -0
- package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
- package/dist/assetstore/assetSidecarRecovery.js +288 -0
- package/dist/assetstore/assetStoreHealth.d.ts +81 -0
- package/dist/assetstore/assetStoreHealth.js +319 -0
- package/dist/assetstore/assetStoreLayout.d.ts +2 -0
- package/dist/assetstore/assetStoreLayout.js +6 -0
- package/dist/assetstore/assetStoreStorage.d.ts +42 -0
- package/dist/assetstore/assetStoreStorage.js +336 -0
- package/dist/assetstore/assetSyncLedger.d.ts +91 -1
- package/dist/assetstore/assetSyncLedger.js +718 -59
- package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
- package/dist/assetstore/foreignJsonlSource.js +150 -0
- package/dist/assetstore/index.d.ts +5 -0
- package/dist/assetstore/index.js +5 -0
- package/dist/assetstore/learningHistory.js +3 -3
- package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
- package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
- package/dist/assetstore/localJsonl.d.ts +12 -1
- package/dist/assetstore/localJsonl.js +251 -42
- package/dist/assetstore/provenance.d.ts +93 -4
- package/dist/assetstore/provenance.js +368 -84
- package/dist/assetstore/provider.d.ts +63 -0
- package/dist/assetstore/provider.js +97 -6
- package/dist/assetstore/reviewFilter.d.ts +19 -1
- package/dist/assetstore/reviewFilter.js +39 -1
- package/dist/assetstore/reviewLedger.d.ts +8 -2
- package/dist/assetstore/reviewLedger.js +71 -45
- package/dist/assetstore/unionReadStore.d.ts +25 -0
- package/dist/assetstore/unionReadStore.js +119 -0
- package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
- package/dist/benchmark/antiGeneBenchmark.js +4 -3
- package/dist/benchmark/antiGeneRollout.d.ts +2 -0
- package/dist/benchmark/antiGeneRollout.js +4 -3
- package/dist/benchmark/index.d.ts +3 -1
- package/dist/benchmark/index.js +3 -1
- package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
- package/dist/benchmark/selectionFlatAbstention.js +481 -0
- package/dist/benchmark/triggerShift.d.ts +62 -0
- package/dist/benchmark/triggerShift.js +106 -0
- package/dist/bootstrap/envFingerprint.d.ts +9 -0
- package/dist/bootstrap/envFingerprint.js +5 -0
- package/dist/bootstrap/index.d.ts +3 -1
- package/dist/bootstrap/index.js +3 -1
- package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
- package/dist/bootstrap/lifecycleBootstrap.js +433 -0
- package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
- package/dist/bootstrap/v1EnvCompat.js +303 -0
- package/dist/events/eventArchive.d.ts +2 -0
- package/dist/events/eventArchive.js +13 -3
- package/dist/events/eventSchema.d.ts +7 -7
- package/dist/events/eventStore.d.ts +2 -0
- package/dist/events/eventStore.js +5 -1
- package/dist/events/ingest.d.ts +2 -1
- package/dist/events/ingest.js +15 -0
- package/dist/events/paths.d.ts +12 -10
- package/dist/events/paths.js +24 -20
- package/dist/events/public.d.ts +2 -2
- package/dist/events/public.js +2 -2
- package/dist/events/reports.d.ts +2 -0
- package/dist/events/reports.js +4 -0
- package/dist/exec/autoExec.d.ts +68 -4
- package/dist/exec/autoExec.js +392 -33
- package/dist/exec/autonomousCycle.d.ts +31 -4
- package/dist/exec/autonomousCycle.js +71 -13
- package/dist/exec/claudeBridge.d.ts +80 -15
- package/dist/exec/claudeBridge.js +845 -76
- package/dist/exec/executionBinding.d.ts +414 -0
- package/dist/exec/executionBinding.js +588 -0
- package/dist/exec/index.d.ts +1 -0
- package/dist/exec/index.js +1 -0
- package/dist/exec/openPrRegistry.d.ts +8 -2
- package/dist/exec/openPrRegistry.js +32 -22
- package/dist/exec/prompt.js +14 -1
- package/dist/exec/proofOfWork.d.ts +1 -1
- package/dist/exec/proofOfWork.js +2 -2
- package/dist/exec/runnerRegistry.d.ts +153 -36
- package/dist/exec/runnerRegistry.js +848 -65
- package/dist/exec/selfPr.js +1 -7
- package/dist/feedback/envelope.d.ts +61 -0
- package/dist/feedback/envelope.js +168 -0
- package/dist/feedback/index.d.ts +1 -0
- package/dist/feedback/index.js +1 -0
- package/dist/hooks/hooks.js +1 -0
- package/dist/hub/assetCallLog.d.ts +35 -1
- package/dist/hub/assetCallLog.js +124 -1
- package/dist/hub/bindings.d.ts +8 -1
- package/dist/hub/bindings.js +29 -8
- package/dist/hub/capability.d.ts +130 -4
- package/dist/hub/conversationDistiller.d.ts +19 -0
- package/dist/hub/conversationDistiller.js +115 -37
- package/dist/hub/fake.d.ts +3 -2
- package/dist/hub/fake.js +2 -1
- package/dist/hub/index.d.ts +1 -0
- package/dist/hub/index.js +1 -0
- package/dist/hub/questionGenerator.d.ts +5 -1
- package/dist/hub/questionGenerator.js +8 -6
- package/dist/hub/recipeCompose.d.ts +27 -0
- package/dist/hub/recipeCompose.js +90 -0
- package/dist/hub/sanitize.js +122 -7
- package/dist/index.d.ts +6 -1
- package/dist/index.js +7 -1
- package/dist/issueReporter/index.d.ts +156 -0
- package/dist/issueReporter/index.js +1688 -0
- package/dist/mailbox/dispatch.d.ts +1 -1
- package/dist/mailbox/dispatch.js +22 -6
- package/dist/mailbox/envelope.d.ts +7 -1
- package/dist/mailbox/envelope.js +9 -2
- package/dist/mailbox/ipcServer.d.ts +12 -2
- package/dist/mailbox/ipcServer.js +183 -13
- package/dist/mailbox/store.d.ts +89 -3
- package/dist/mailbox/store.js +895 -41
- package/dist/modelCompatibility.d.ts +164 -0
- package/dist/modelCompatibility.js +309 -0
- package/dist/observers/valueDigestObserver.d.ts +9 -0
- package/dist/observers/valueDigestObserver.js +35 -2
- package/dist/ops/cleanup.js +1 -1
- package/dist/ops/evolutionGraphProjection.d.ts +20 -0
- package/dist/ops/evolutionGraphProjection.js +315 -0
- package/dist/ops/index.d.ts +2 -1
- package/dist/ops/index.js +2 -1
- package/dist/ops/savingsCore.js +1 -2
- package/dist/ops/selfUpdate.d.ts +18 -1
- package/dist/ops/selfUpdate.js +88 -23
- package/dist/ops/valueOutreach.d.ts +3 -1
- package/dist/ops/valueOutreach.js +5 -1
- package/dist/personality/schema.d.ts +24 -24
- package/dist/schema/evolutionGraph.d.ts +784 -0
- package/dist/schema/evolutionGraph.js +187 -0
- package/dist/schema/index.d.ts +1 -0
- package/dist/schema/index.js +1 -0
- package/dist/schema/proofOfWork.d.ts +125 -6
- package/dist/schema/proofOfWork.js +102 -4
- package/dist/schema/signal.d.ts +3 -3
- package/dist/schema/signal.js +1 -1
- package/dist/shadow/shadowHub.js +1 -0
- package/dist/signals/curriculum.d.ts +55 -0
- package/dist/signals/curriculum.js +202 -0
- package/dist/signals/cycleHistoryFromEvents.js +17 -8
- package/dist/signals/expand.d.ts +15 -1
- package/dist/signals/expand.js +169 -1
- package/dist/signals/extractor.d.ts +2 -2
- package/dist/signals/extractor.js +31 -6
- package/dist/signals/index.d.ts +4 -1
- package/dist/signals/index.js +4 -1
- package/dist/signals/metaSignals.d.ts +4 -0
- package/dist/signals/metaSignals.js +42 -0
- package/dist/signals/scopeVocabulary.d.ts +75 -0
- package/dist/signals/scopeVocabulary.js +91 -0
- package/dist/signals/signalGate.js +1 -1
- package/dist/signals/taskDomain.d.ts +22 -0
- package/dist/signals/taskDomain.js +43 -0
- package/dist/strategy/constraintAblation.d.ts +64 -0
- package/dist/strategy/constraintAblation.js +2820 -0
- package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
- package/dist/strategy/constraintAblationPredicates.js +339 -0
- package/dist/strategy/index.d.ts +2 -1
- package/dist/strategy/index.js +2 -1
- package/dist/trace/index.d.ts +3 -1
- package/dist/trace/index.js +3 -1
- package/dist/trace/learningTrace.d.ts +216 -0
- package/dist/trace/learningTrace.js +298 -0
- package/dist/trace/proxyTurns.d.ts +31 -0
- package/dist/trace/proxyTurns.js +137 -0
- package/dist/trace/trajectory.d.ts +8 -0
- package/dist/trace/trajectory.js +14 -2
- package/dist/util/fetchPort.d.ts +1 -0
- package/dist/util/fetchPort.js +11 -0
- package/dist/util/fileLock.d.ts +113 -7
- package/dist/util/fileLock.js +1035 -80
- package/dist/util/index.d.ts +3 -1
- package/dist/util/index.js +2 -1
- package/dist/verify/index.d.ts +2 -1
- package/dist/verify/index.js +1 -1
- package/dist/verify/sandboxRunner.d.ts +30 -0
- package/dist/verify/sandboxRunner.js +370 -27
- package/dist/verify/sandboxedValidation.d.ts +16 -2
- package/dist/verify/sandboxedValidation.js +200 -22
- package/dist/verify/validation.d.ts +35 -4
- package/dist/verify/validation.js +164 -16
- package/dist/wire/geneHints.d.ts +88 -16
- package/dist/wire/geneHints.js +124 -15
- package/dist/wire/index.d.ts +8 -3
- package/dist/wire/index.js +2 -2
- package/dist/wire/schemaGate.d.ts +21 -0
- package/dist/wire/schemaGate.js +119 -10
- package/dist/workflow/dsl.d.ts +24 -3
- package/dist/workflow/dsl.js +4 -0
- package/dist/workflow/engine.d.ts +5 -1
- package/dist/workflow/engine.js +3 -0
- package/dist/workflow/index.d.ts +3 -1
- package/dist/workflow/index.js +3 -1
- package/dist/workflow/runtime.d.ts +110 -0
- package/dist/workflow/runtime.js +1298 -0
- package/dist/workflow/stateStore.d.ts +172 -0
- package/dist/workflow/stateStore.js +1044 -0
- package/package.json +12 -4
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AssetRecord } from '../assetstore/index.js';
|
|
2
|
+
export type RepairStatus = 'already_valid' | 'repaired' | 'unrepairable';
|
|
3
|
+
export interface RepairIssue {
|
|
4
|
+
path: string;
|
|
5
|
+
keyword: string;
|
|
6
|
+
message: string;
|
|
7
|
+
/** `schema` = found by the local gep-sdk gate; `hub` = reported by the network in its rejection body. */
|
|
8
|
+
source: 'schema' | 'hub';
|
|
9
|
+
}
|
|
10
|
+
export interface RepairChange {
|
|
11
|
+
path: string;
|
|
12
|
+
action: 'added' | 'removed' | 'replaced';
|
|
13
|
+
note: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RepairReport {
|
|
16
|
+
status: RepairStatus;
|
|
17
|
+
/** The accepted-by-schema record. Absent only when blockers remain (`status: 'unrepairable'`). */
|
|
18
|
+
asset?: AssetRecord;
|
|
19
|
+
changes: RepairChange[];
|
|
20
|
+
blockers: RepairIssue[];
|
|
21
|
+
}
|
|
22
|
+
export interface RepairOptions {
|
|
23
|
+
/** Field-level issues the network itself reported — see `hubRejectionIssues`. Stricter than the GEP schema
|
|
24
|
+
* (the Hub adds envelope rules of its own), so they are folded in rather than re-derived locally. */
|
|
25
|
+
hubIssues?: readonly RepairIssue[];
|
|
26
|
+
}
|
|
27
|
+
type JsonRecord = Record<string, unknown>;
|
|
28
|
+
/**
|
|
29
|
+
* Repair `input` into a record the GEP schema (and, when supplied, the Hub's own rules) accepts.
|
|
30
|
+
* Pure: `input` is never mutated, and nothing is written anywhere — the caller decides what to do with the result.
|
|
31
|
+
*/
|
|
32
|
+
export declare function repairAssetRecord(input: JsonRecord, options?: RepairOptions): RepairReport;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Asset repair — turn a GEP record the network refuses into one it accepts, WITHOUT inventing content.
|
|
2
|
+
//
|
|
3
|
+
// Two producers of refused records exist and both were wasting assets outright:
|
|
4
|
+
// * publish: the Hub rejects a bundle field-by-field (`details[]`), the operator gets a flat error, and the
|
|
5
|
+
// distilled asset is dropped on the floor.
|
|
6
|
+
// * reuse/fetch: the Hub delivers a lossy projection of a published record (dropped `schema_version`, dropped
|
|
7
|
+
// logical `id`, emptied `constraints.forbidden_paths`, extra non-schema keys such as `model_name`), which
|
|
8
|
+
// then fails the local wire gate and is discarded — reported to the operator as "not found".
|
|
9
|
+
//
|
|
10
|
+
// The line this module refuses to cross: a repair may only restore fields whose value is DERIVABLE (recomputed
|
|
11
|
+
// `asset_id`), CONSTANT (`schema_version`), or a strictly-narrowing default (`constraints`). Anything carrying
|
|
12
|
+
// an evidence or meaning claim — a Capsule's `outcome`/`confidence`/`blast_radius`, a Gene's `strategy`, a
|
|
13
|
+
// human summary — is never fabricated: it is reported as a blocker so the operator (or a re-distill) supplies it.
|
|
14
|
+
import { computeAssetId, wireSchemaIssues } from '../wire/index.js';
|
|
15
|
+
import { SCHEMA_VERSION } from '@evomap/gep-sdk';
|
|
16
|
+
const DEFAULT_MAX_FILES = 12;
|
|
17
|
+
const DEFAULT_FORBIDDEN_PATHS = ['.git', 'node_modules'];
|
|
18
|
+
const CONTENT_ASSET_ID = /^sha256:[0-9a-f]{64}$/;
|
|
19
|
+
const SEMVER = /^\d+\.\d+\.\d+$/;
|
|
20
|
+
/**
|
|
21
|
+
* Repair `input` into a record the GEP schema (and, when supplied, the Hub's own rules) accepts.
|
|
22
|
+
* Pure: `input` is never mutated, and nothing is written anywhere — the caller decides what to do with the result.
|
|
23
|
+
*/
|
|
24
|
+
export function repairAssetRecord(input, options = {}) {
|
|
25
|
+
const hubIssues = options.hubIssues ?? [];
|
|
26
|
+
const before = wireSchemaIssues(input);
|
|
27
|
+
if (before.length === 0 && hubIssues.length === 0 && assetIdIsCurrent(input)) {
|
|
28
|
+
return { status: 'already_valid', asset: input, changes: [], blockers: [] };
|
|
29
|
+
}
|
|
30
|
+
const changes = [];
|
|
31
|
+
const draft = { ...input };
|
|
32
|
+
restoreSchemaVersion(draft, changes);
|
|
33
|
+
dropUnknownProperties(draft, before, changes);
|
|
34
|
+
restoreGeneConstraints(draft, changes);
|
|
35
|
+
restoreLogicalId(draft, changes);
|
|
36
|
+
restoreAssetId(draft, changes);
|
|
37
|
+
const remaining = wireSchemaIssues(draft);
|
|
38
|
+
const blockers = [...remaining.map(schemaBlocker), ...unresolvedHubIssues(hubIssues, changes)];
|
|
39
|
+
if (blockers.length > 0)
|
|
40
|
+
return { status: 'unrepairable', changes, blockers };
|
|
41
|
+
return { status: 'repaired', asset: draft, changes, blockers: [] };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Hub rules the local schema cannot see (a minimum summary length, a bundle arity) stay blockers — but a hub
|
|
45
|
+
* complaint about a field this pass just mended is answered, and keeping it would report every repairable
|
|
46
|
+
* asset as unrepairable.
|
|
47
|
+
*/
|
|
48
|
+
function unresolvedHubIssues(issues, changes) {
|
|
49
|
+
return issues.filter((issue) => !changes.some((change) => pathsOverlap(change.path, issue.path)));
|
|
50
|
+
}
|
|
51
|
+
function pathsOverlap(repaired, reported) {
|
|
52
|
+
return repaired === reported
|
|
53
|
+
|| reported.startsWith(`${repaired}.`)
|
|
54
|
+
|| repaired.startsWith(`${reported}.`);
|
|
55
|
+
}
|
|
56
|
+
/** True when the record already declares the id its own content hashes to — the invariant `restoreAssetId` holds. */
|
|
57
|
+
function assetIdIsCurrent(record) {
|
|
58
|
+
const declared = record['asset_id'];
|
|
59
|
+
return typeof declared === 'string' && declared === safeComputeAssetId(record);
|
|
60
|
+
}
|
|
61
|
+
function restoreSchemaVersion(draft, changes) {
|
|
62
|
+
const current = draft['schema_version'];
|
|
63
|
+
if (typeof current === 'string' && SEMVER.test(current))
|
|
64
|
+
return;
|
|
65
|
+
draft['schema_version'] = SCHEMA_VERSION;
|
|
66
|
+
changes.push({
|
|
67
|
+
path: 'schema_version',
|
|
68
|
+
action: current === undefined ? 'added' : 'replaced',
|
|
69
|
+
note: `set to the SDK schema version ${SCHEMA_VERSION}`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// `additionalProperties: false` means an unknown key is fatal, so a delivery that adds `model_name` is
|
|
73
|
+
// unusable as-is. Dropping is the only content-preserving move: the key is not part of the GEP contract, so
|
|
74
|
+
// nothing that reads the record by contract can miss it. Every dropped key is named in the report.
|
|
75
|
+
function dropUnknownProperties(draft, issues, changes) {
|
|
76
|
+
for (const issue of issues) {
|
|
77
|
+
if (issue.keyword !== 'additionalProperties' || !issue.property)
|
|
78
|
+
continue;
|
|
79
|
+
if (!(issue.property in draft))
|
|
80
|
+
continue;
|
|
81
|
+
delete draft[issue.property];
|
|
82
|
+
changes.push({ path: issue.path, action: 'removed', note: 'not declared by the GEP schema' });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// A Gene's constraints are a CEILING on what an executing agent may touch. Restoring the default is therefore
|
|
86
|
+
// safe in one direction only: the default must be at least as restrictive as a missing/empty value, which
|
|
87
|
+
// (no ceiling at all) it always is.
|
|
88
|
+
function restoreGeneConstraints(draft, changes) {
|
|
89
|
+
if (draft['type'] !== 'Gene')
|
|
90
|
+
return;
|
|
91
|
+
const current = asRecord(draft['constraints']);
|
|
92
|
+
if (!current) {
|
|
93
|
+
draft['constraints'] = { max_files: DEFAULT_MAX_FILES, forbidden_paths: [...DEFAULT_FORBIDDEN_PATHS] };
|
|
94
|
+
changes.push({ path: 'constraints', action: 'added', note: 'restored the default execution ceiling' });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const repaired = { ...current };
|
|
98
|
+
const maxFiles = repaired['max_files'];
|
|
99
|
+
if (typeof maxFiles !== 'number' || !Number.isInteger(maxFiles) || maxFiles < 1) {
|
|
100
|
+
repaired['max_files'] = DEFAULT_MAX_FILES;
|
|
101
|
+
changes.push({ path: 'constraints.max_files', action: maxFiles === undefined ? 'added' : 'replaced', note: `restored the default ceiling ${DEFAULT_MAX_FILES}` });
|
|
102
|
+
}
|
|
103
|
+
const forbidden = repaired['forbidden_paths'];
|
|
104
|
+
const usable = Array.isArray(forbidden) && forbidden.length > 0 && forbidden.every((entry) => typeof entry === 'string');
|
|
105
|
+
if (!usable) {
|
|
106
|
+
repaired['forbidden_paths'] = [...DEFAULT_FORBIDDEN_PATHS];
|
|
107
|
+
changes.push({ path: 'constraints.forbidden_paths', action: forbidden === undefined ? 'added' : 'replaced', note: 'restored the default forbidden paths' });
|
|
108
|
+
}
|
|
109
|
+
draft['constraints'] = repaired;
|
|
110
|
+
}
|
|
111
|
+
// A logical id names the asset for humans and for Capsule→Gene binding; it carries no claim about the content.
|
|
112
|
+
// A delivery that dropped it cannot be re-bound to its original name (that name is not recoverable from the
|
|
113
|
+
// bytes), so the derived one is marked `repaired_` — visibly synthetic, and stable for the same content.
|
|
114
|
+
function restoreLogicalId(draft, changes) {
|
|
115
|
+
const current = draft['id'];
|
|
116
|
+
if (typeof current === 'string' && current.length > 0)
|
|
117
|
+
return;
|
|
118
|
+
const type = typeof draft['type'] === 'string' ? draft['type'].toLowerCase() : 'asset';
|
|
119
|
+
const digest = safeComputeAssetId(draft)?.slice('sha256:'.length, 'sha256:'.length + 12);
|
|
120
|
+
if (!digest)
|
|
121
|
+
return;
|
|
122
|
+
draft['id'] = `${type}_repaired_${digest}`;
|
|
123
|
+
changes.push({ path: 'id', action: 'added', note: 'derived a stable placeholder id from the content digest' });
|
|
124
|
+
}
|
|
125
|
+
// Content-addressing means `asset_id` is a FUNCTION of the rest of the record, so it is the one field that must
|
|
126
|
+
// be recomputed last — every repair above changes what the content hashes to.
|
|
127
|
+
function restoreAssetId(draft, changes) {
|
|
128
|
+
const declared = draft['asset_id'];
|
|
129
|
+
const computed = safeComputeAssetId(draft);
|
|
130
|
+
if (!computed || !CONTENT_ASSET_ID.test(computed))
|
|
131
|
+
return;
|
|
132
|
+
if (declared === computed)
|
|
133
|
+
return;
|
|
134
|
+
draft['asset_id'] = computed;
|
|
135
|
+
changes.push({
|
|
136
|
+
path: 'asset_id',
|
|
137
|
+
action: typeof declared === 'string' ? 'replaced' : 'added',
|
|
138
|
+
note: 'recomputed the content id over the repaired record',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function safeComputeAssetId(record) {
|
|
142
|
+
try {
|
|
143
|
+
const id = computeAssetId(record);
|
|
144
|
+
return typeof id === 'string' && id.length > 0 ? id : undefined;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function schemaBlocker(issue) {
|
|
151
|
+
return { path: issue.path, keyword: issue.keyword, message: issue.message, source: 'schema' };
|
|
152
|
+
}
|
|
153
|
+
function asRecord(value) {
|
|
154
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
155
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AssetSyncInventorySegmentRecord, AssetSyncRecord, AssetSyncRunRecord } from './assetSyncLedger.js';
|
|
2
|
+
import type { ProvenanceRecord } from './provenance.js';
|
|
3
|
+
import type { ReviewRecord } from './reviewLedger.js';
|
|
4
|
+
export type AssetSidecarKind = 'provenance' | 'review' | 'asset-sync';
|
|
5
|
+
export type AssetSidecarCorruptionReason = 'invalid_row' | 'unterminated';
|
|
6
|
+
export interface ParsedSidecarJsonl<T> {
|
|
7
|
+
records: T[];
|
|
8
|
+
rows: number;
|
|
9
|
+
validRows: number;
|
|
10
|
+
corruptRows: number;
|
|
11
|
+
unterminated: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class CorruptAssetSidecarError extends Error {
|
|
14
|
+
readonly sidecar: AssetSidecarKind;
|
|
15
|
+
readonly reason: AssetSidecarCorruptionReason;
|
|
16
|
+
readonly code = "CORRUPT_ASSET_SIDECAR";
|
|
17
|
+
constructor(sidecar: AssetSidecarKind, reason: AssetSidecarCorruptionReason);
|
|
18
|
+
}
|
|
19
|
+
export declare function parseSidecarJsonl<T>(raw: string, parseRecord: (value: unknown) => T | null): ParsedSidecarJsonl<T>;
|
|
20
|
+
export declare function assertTrustSidecarHealthy<T>(sidecar: Extract<AssetSidecarKind, 'provenance' | 'review'>, parsed: ParsedSidecarJsonl<T>): void;
|
|
21
|
+
export declare function parseProvenanceRecord(value: unknown): ProvenanceRecord | null;
|
|
22
|
+
export declare function parseReviewRecord(value: unknown): ReviewRecord | null;
|
|
23
|
+
export declare function parseAssetSyncRecord(value: unknown): AssetSyncRecord | null;
|
|
24
|
+
export declare const ASSET_SYNC_INVENTORY_MAX_SEGMENT_BYTES: number;
|
|
25
|
+
export type AssetSyncSidecarRecord = AssetSyncRecord | AssetSyncRunRecord | AssetSyncInventorySegmentRecord;
|
|
26
|
+
export declare function parseAssetSyncRunRecord(value: unknown): AssetSyncRunRecord | null;
|
|
27
|
+
export declare function parseAssetSyncInventorySegmentRecord(value: unknown): AssetSyncInventorySegmentRecord | null;
|
|
28
|
+
export declare function parseAssetSyncSidecarRecord(value: unknown): AssetSyncSidecarRecord | null;
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
export class CorruptAssetSidecarError extends Error {
|
|
2
|
+
sidecar;
|
|
3
|
+
reason;
|
|
4
|
+
code = 'CORRUPT_ASSET_SIDECAR';
|
|
5
|
+
constructor(sidecar, reason) {
|
|
6
|
+
super(`corrupt asset sidecar: ${sidecar}/${reason}`);
|
|
7
|
+
this.sidecar = sidecar;
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
this.name = 'CorruptAssetSidecarError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function parseSidecarJsonl(raw, parseRecord) {
|
|
13
|
+
const records = [];
|
|
14
|
+
let rows = 0;
|
|
15
|
+
let corruptRows = 0;
|
|
16
|
+
for (const line of raw.split('\n')) {
|
|
17
|
+
if (!line.trim())
|
|
18
|
+
continue;
|
|
19
|
+
rows += 1;
|
|
20
|
+
try {
|
|
21
|
+
const record = parseRecord(JSON.parse(line));
|
|
22
|
+
if (record)
|
|
23
|
+
records.push(record);
|
|
24
|
+
else
|
|
25
|
+
corruptRows += 1;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
corruptRows += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
records,
|
|
33
|
+
rows,
|
|
34
|
+
validRows: records.length,
|
|
35
|
+
corruptRows,
|
|
36
|
+
unterminated: raw.length > 0 && !raw.endsWith('\n'),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function assertTrustSidecarHealthy(sidecar, parsed) {
|
|
40
|
+
if (parsed.unterminated)
|
|
41
|
+
throw new CorruptAssetSidecarError(sidecar, 'unterminated');
|
|
42
|
+
if (parsed.corruptRows > 0)
|
|
43
|
+
throw new CorruptAssetSidecarError(sidecar, 'invalid_row');
|
|
44
|
+
}
|
|
45
|
+
export function parseProvenanceRecord(value) {
|
|
46
|
+
const record = objectRecord(value);
|
|
47
|
+
if (!record)
|
|
48
|
+
return null;
|
|
49
|
+
const assetId = stringField(record, 'assetId');
|
|
50
|
+
const source = stringField(record, 'source');
|
|
51
|
+
const at = stringField(record, 'at');
|
|
52
|
+
if (!assetId
|
|
53
|
+
|| (source !== 'local' && source !== 'migrated' && source !== 'hub')
|
|
54
|
+
|| typeof record['trusted'] !== 'boolean'
|
|
55
|
+
|| !at
|
|
56
|
+
|| Number.isNaN(Date.parse(at))) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const decision = record['decision'] === 'promoted' || record['decision'] === 'revoked'
|
|
60
|
+
? record['decision']
|
|
61
|
+
: undefined;
|
|
62
|
+
const decidedBy = stringField(record, 'decidedBy');
|
|
63
|
+
const promotedBy = stringField(record, 'promotedBy');
|
|
64
|
+
const reason = stringField(record, 'reason');
|
|
65
|
+
const frozenContentId = stringField(record, 'frozenContentId');
|
|
66
|
+
if (frozenContentId !== undefined && !/^sha256:[0-9a-f]{64}$/.test(frozenContentId))
|
|
67
|
+
return null;
|
|
68
|
+
return {
|
|
69
|
+
assetId,
|
|
70
|
+
source,
|
|
71
|
+
trusted: record['trusted'],
|
|
72
|
+
at,
|
|
73
|
+
...(decision ? { decision } : {}),
|
|
74
|
+
...(decidedBy ? { decidedBy } : {}),
|
|
75
|
+
...(promotedBy ? { promotedBy } : {}),
|
|
76
|
+
...(reason ? { reason } : {}),
|
|
77
|
+
...(frozenContentId ? { frozenContentId } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function parseReviewRecord(value) {
|
|
81
|
+
const record = objectRecord(value);
|
|
82
|
+
if (!record)
|
|
83
|
+
return null;
|
|
84
|
+
const assetId = stringField(record, 'assetId');
|
|
85
|
+
const state = stringField(record, 'state');
|
|
86
|
+
const at = stringField(record, 'at');
|
|
87
|
+
if (!assetId
|
|
88
|
+
|| (state !== 'quarantined' && state !== 'approved' && state !== 'rejected')
|
|
89
|
+
|| !at
|
|
90
|
+
|| Number.isNaN(Date.parse(at))) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const by = stringField(record, 'by');
|
|
94
|
+
const reason = stringField(record, 'reason');
|
|
95
|
+
return {
|
|
96
|
+
assetId,
|
|
97
|
+
state,
|
|
98
|
+
at,
|
|
99
|
+
...(by ? { by } : {}),
|
|
100
|
+
...(reason ? { reason } : {}),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function parseAssetSyncRecord(value) {
|
|
104
|
+
const record = objectRecord(value);
|
|
105
|
+
if (!record)
|
|
106
|
+
return null;
|
|
107
|
+
const assetId = stringField(record, 'assetId');
|
|
108
|
+
const type = stringField(record, 'type');
|
|
109
|
+
const source = stringField(record, 'source');
|
|
110
|
+
const scope = stringField(record, 'scope');
|
|
111
|
+
const syncedAt = stringField(record, 'syncedAt');
|
|
112
|
+
const remoteAssetId = stringField(record, 'remoteAssetId');
|
|
113
|
+
if (!assetId
|
|
114
|
+
|| (type !== 'Gene' && type !== 'Capsule')
|
|
115
|
+
|| source !== 'hub'
|
|
116
|
+
|| (scope !== 'purchased' && scope !== 'published')
|
|
117
|
+
|| !syncedAt
|
|
118
|
+
|| !remoteAssetId) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const logicalId = rawStringField(record, 'logicalId');
|
|
122
|
+
const status = stringField(record, 'status');
|
|
123
|
+
const runKey = optionalStrictString(record, 'runKey');
|
|
124
|
+
const inventoryKey = optionalStrictString(record, 'inventoryKey');
|
|
125
|
+
if (runKey === null || inventoryKey === null)
|
|
126
|
+
return null;
|
|
127
|
+
const forced = record['forced'] === true;
|
|
128
|
+
const collisionWithAssetId = stringField(record, 'collisionWithAssetId');
|
|
129
|
+
return {
|
|
130
|
+
assetId,
|
|
131
|
+
type,
|
|
132
|
+
source,
|
|
133
|
+
scope,
|
|
134
|
+
syncedAt,
|
|
135
|
+
remoteAssetId,
|
|
136
|
+
...(runKey ? { runKey } : {}),
|
|
137
|
+
...(inventoryKey ? { inventoryKey } : {}),
|
|
138
|
+
...(logicalId ? { logicalId } : {}),
|
|
139
|
+
...(status ? { status } : {}),
|
|
140
|
+
...(forced ? { forced: true } : {}),
|
|
141
|
+
...(collisionWithAssetId ? { collisionWithAssetId } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const MAX_INVENTORY_SEGMENTS = 24;
|
|
145
|
+
const MAX_INVENTORY_ITEMS_PER_SEGMENT = 10_000;
|
|
146
|
+
const MAX_INVENTORY_STRING_LENGTH = 4096;
|
|
147
|
+
export const ASSET_SYNC_INVENTORY_MAX_SEGMENT_BYTES = 2 * 1024 * 1024;
|
|
148
|
+
export function parseAssetSyncRunRecord(value) {
|
|
149
|
+
const record = objectRecord(value);
|
|
150
|
+
if (!record || record['recordType'] !== 'run')
|
|
151
|
+
return null;
|
|
152
|
+
const runId = optionalStrictString(record, 'runId');
|
|
153
|
+
const runKey = optionalStrictString(record, 'runKey');
|
|
154
|
+
const state = optionalStrictString(record, 'state');
|
|
155
|
+
const currentTimestamp = optionalStrictString(record, 'syncedAt');
|
|
156
|
+
const legacyTimestamp = optionalStrictString(record, 'at');
|
|
157
|
+
if (!runId
|
|
158
|
+
|| !runKey
|
|
159
|
+
|| state === null
|
|
160
|
+
|| !isRunState(state)
|
|
161
|
+
|| currentTimestamp === null
|
|
162
|
+
|| legacyTimestamp === null
|
|
163
|
+
|| (currentTimestamp && legacyTimestamp && currentTimestamp !== legacyTimestamp)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const syncedAt = currentTimestamp ?? legacyTimestamp;
|
|
167
|
+
if (!syncedAt || Number.isNaN(Date.parse(syncedAt)))
|
|
168
|
+
return null;
|
|
169
|
+
const remoteAssetId = optionalStrictString(record, 'remoteAssetId');
|
|
170
|
+
const outcomeValue = optionalStrictString(record, 'outcome');
|
|
171
|
+
const reason = optionalStrictString(record, 'reason');
|
|
172
|
+
if (remoteAssetId === null || outcomeValue === null || reason === null)
|
|
173
|
+
return null;
|
|
174
|
+
const outcome = isRunOutcome(outcomeValue) ? outcomeValue : undefined;
|
|
175
|
+
if (outcomeValue !== undefined && !outcome)
|
|
176
|
+
return null;
|
|
177
|
+
let plan;
|
|
178
|
+
const rawPlan = record['plan'];
|
|
179
|
+
if (rawPlan !== undefined) {
|
|
180
|
+
if (!Array.isArray(rawPlan) || rawPlan.length === 0)
|
|
181
|
+
return null;
|
|
182
|
+
const normalized = rawPlan.map((entry) => typeof entry === 'string' ? entry.trim() : '');
|
|
183
|
+
if (normalized.some((entry, index) => !entry || entry !== rawPlan[index]))
|
|
184
|
+
return null;
|
|
185
|
+
if (new Set(normalized).size !== normalized.length)
|
|
186
|
+
return null;
|
|
187
|
+
plan = Object.freeze(normalized);
|
|
188
|
+
}
|
|
189
|
+
if (state === 'started' && (remoteAssetId || outcome || reason))
|
|
190
|
+
return null;
|
|
191
|
+
if (state === 'progress') {
|
|
192
|
+
if (!remoteAssetId || !outcome || plan)
|
|
193
|
+
return null;
|
|
194
|
+
if ((outcome === 'failed') !== Boolean(reason))
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (state === 'completed' && (remoteAssetId || outcome || reason || plan))
|
|
198
|
+
return null;
|
|
199
|
+
return immutableRunRecord({
|
|
200
|
+
recordType: 'run',
|
|
201
|
+
runId,
|
|
202
|
+
runKey,
|
|
203
|
+
state,
|
|
204
|
+
...(remoteAssetId ? { remoteAssetId } : {}),
|
|
205
|
+
...(outcome ? { outcome } : {}),
|
|
206
|
+
...(reason ? { reason } : {}),
|
|
207
|
+
...(plan ? { plan } : {}),
|
|
208
|
+
syncedAt,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export function parseAssetSyncInventorySegmentRecord(value) {
|
|
212
|
+
const record = objectRecord(value);
|
|
213
|
+
if (!record || record['recordType'] !== 'inventory_scan')
|
|
214
|
+
return null;
|
|
215
|
+
try {
|
|
216
|
+
if (Buffer.byteLength(JSON.stringify(record), 'utf8') > ASSET_SYNC_INVENTORY_MAX_SEGMENT_BYTES)
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const scanId = strictBoundedString(record['scanId']);
|
|
223
|
+
const inventoryKey = strictBoundedString(record['inventoryKey']);
|
|
224
|
+
const scope = strictBoundedString(record['scope']);
|
|
225
|
+
const syncedAt = strictBoundedString(record['syncedAt']);
|
|
226
|
+
const index = record['index'];
|
|
227
|
+
const inputCursorFingerprints = parseInventoryCursorFingerprints(record['inputCursorFingerprints']);
|
|
228
|
+
const nextCursorFingerprints = parseInventoryCursorFingerprints(record['nextCursorFingerprints']);
|
|
229
|
+
const anonymousBlocked = record['anonymousBlocked'];
|
|
230
|
+
const cursorHeld = record['cursorHeld'];
|
|
231
|
+
const batchId = record['batchId'];
|
|
232
|
+
const batchIndex = record['batchIndex'];
|
|
233
|
+
const batchSize = record['batchSize'];
|
|
234
|
+
const rawItems = record['items'];
|
|
235
|
+
const hasBatchMetadata = batchId !== undefined || batchIndex !== undefined || batchSize !== undefined;
|
|
236
|
+
if (!scanId
|
|
237
|
+
|| !inventoryKey
|
|
238
|
+
|| (scope !== 'purchased' && scope !== 'published' && scope !== 'all')
|
|
239
|
+
|| !syncedAt
|
|
240
|
+
|| Number.isNaN(Date.parse(syncedAt))
|
|
241
|
+
|| !Number.isInteger(index)
|
|
242
|
+
|| index < 0
|
|
243
|
+
|| index >= MAX_INVENTORY_SEGMENTS
|
|
244
|
+
|| !inputCursorFingerprints
|
|
245
|
+
|| !nextCursorFingerprints
|
|
246
|
+
|| !Number.isInteger(anonymousBlocked)
|
|
247
|
+
|| anonymousBlocked < 0
|
|
248
|
+
|| anonymousBlocked > MAX_INVENTORY_ITEMS_PER_SEGMENT
|
|
249
|
+
|| !Array.isArray(rawItems)
|
|
250
|
+
|| rawItems.length > MAX_INVENTORY_ITEMS_PER_SEGMENT
|
|
251
|
+
|| (cursorHeld !== undefined && cursorHeld !== true)
|
|
252
|
+
|| (hasBatchMetadata && (!strictBoundedString(batchId)
|
|
253
|
+
|| !Number.isInteger(batchIndex)
|
|
254
|
+
|| !Number.isInteger(batchSize)
|
|
255
|
+
|| batchSize < 2
|
|
256
|
+
|| batchSize > MAX_INVENTORY_SEGMENTS
|
|
257
|
+
|| batchIndex < 0
|
|
258
|
+
|| batchIndex >= batchSize
|
|
259
|
+
|| index - batchIndex < 0
|
|
260
|
+
|| index + (batchSize - batchIndex) > MAX_INVENTORY_SEGMENTS))) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
if (scope === 'purchased'
|
|
264
|
+
&& (inputCursorFingerprints.published !== null || nextCursorFingerprints.published !== null))
|
|
265
|
+
return null;
|
|
266
|
+
if (cursorHeld === true
|
|
267
|
+
&& (inputCursorFingerprints.purchased !== nextCursorFingerprints.purchased
|
|
268
|
+
|| inputCursorFingerprints.published !== nextCursorFingerprints.published))
|
|
269
|
+
return null;
|
|
270
|
+
if (scope === 'published'
|
|
271
|
+
&& (inputCursorFingerprints.purchased !== null || nextCursorFingerprints.purchased !== null))
|
|
272
|
+
return null;
|
|
273
|
+
if (index === 0
|
|
274
|
+
&& (inputCursorFingerprints.purchased !== null || inputCursorFingerprints.published !== null))
|
|
275
|
+
return null;
|
|
276
|
+
const items = [];
|
|
277
|
+
const seen = new Set();
|
|
278
|
+
for (const rawItem of rawItems) {
|
|
279
|
+
const item = objectRecord(rawItem);
|
|
280
|
+
if (!item)
|
|
281
|
+
return null;
|
|
282
|
+
const remoteAssetId = strictBoundedString(item['remoteAssetId']);
|
|
283
|
+
const outcome = strictBoundedString(item['outcome']);
|
|
284
|
+
if (!remoteAssetId || !isInventoryOutcome(outcome) || seen.has(remoteAssetId))
|
|
285
|
+
return null;
|
|
286
|
+
seen.add(remoteAssetId);
|
|
287
|
+
items.push(Object.freeze({ remoteAssetId, outcome }));
|
|
288
|
+
}
|
|
289
|
+
if (items.length + anonymousBlocked > MAX_INVENTORY_ITEMS_PER_SEGMENT)
|
|
290
|
+
return null;
|
|
291
|
+
return Object.freeze({
|
|
292
|
+
recordType: 'inventory_scan',
|
|
293
|
+
scanId,
|
|
294
|
+
inventoryKey,
|
|
295
|
+
scope,
|
|
296
|
+
index: index,
|
|
297
|
+
inputCursorFingerprints,
|
|
298
|
+
nextCursorFingerprints,
|
|
299
|
+
items: Object.freeze(items),
|
|
300
|
+
anonymousBlocked: anonymousBlocked,
|
|
301
|
+
...(cursorHeld === true ? { cursorHeld: true } : {}),
|
|
302
|
+
...(hasBatchMetadata ? {
|
|
303
|
+
batchId: batchId,
|
|
304
|
+
batchIndex: batchIndex,
|
|
305
|
+
batchSize: batchSize,
|
|
306
|
+
} : {}),
|
|
307
|
+
syncedAt,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
export function parseAssetSyncSidecarRecord(value) {
|
|
311
|
+
const record = objectRecord(value);
|
|
312
|
+
if (!record)
|
|
313
|
+
return null;
|
|
314
|
+
if (record['recordType'] !== undefined) {
|
|
315
|
+
if (record['recordType'] === 'run')
|
|
316
|
+
return parseAssetSyncRunRecord(record);
|
|
317
|
+
if (record['recordType'] === 'inventory_scan')
|
|
318
|
+
return parseAssetSyncInventorySegmentRecord(record);
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return parseAssetSyncRecord(record);
|
|
322
|
+
}
|
|
323
|
+
function parseInventoryCursorFingerprints(value) {
|
|
324
|
+
const record = objectRecord(value);
|
|
325
|
+
if (!record || !Object.hasOwn(record, 'purchased') || !Object.hasOwn(record, 'published'))
|
|
326
|
+
return null;
|
|
327
|
+
const purchased = strictNullableCursorFingerprint(record['purchased']);
|
|
328
|
+
const published = strictNullableCursorFingerprint(record['published']);
|
|
329
|
+
if (purchased === undefined || published === undefined)
|
|
330
|
+
return null;
|
|
331
|
+
return Object.freeze({ purchased, published });
|
|
332
|
+
}
|
|
333
|
+
function strictNullableCursorFingerprint(value) {
|
|
334
|
+
if (value === null)
|
|
335
|
+
return null;
|
|
336
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/.test(value) ? value : undefined;
|
|
337
|
+
}
|
|
338
|
+
function strictBoundedString(value) {
|
|
339
|
+
return typeof value === 'string'
|
|
340
|
+
&& value.length <= MAX_INVENTORY_STRING_LENGTH
|
|
341
|
+
&& value.trim()
|
|
342
|
+
&& value === value.trim()
|
|
343
|
+
? value
|
|
344
|
+
: undefined;
|
|
345
|
+
}
|
|
346
|
+
function isInventoryOutcome(value) {
|
|
347
|
+
return value === 'imported'
|
|
348
|
+
|| value === 'already_local'
|
|
349
|
+
|| value === 'blocked'
|
|
350
|
+
|| value === 'failed'
|
|
351
|
+
|| value === 'pending';
|
|
352
|
+
}
|
|
353
|
+
function objectRecord(value) {
|
|
354
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
355
|
+
? value
|
|
356
|
+
: null;
|
|
357
|
+
}
|
|
358
|
+
function stringField(value, key) {
|
|
359
|
+
const raw = value[key];
|
|
360
|
+
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
|
|
361
|
+
}
|
|
362
|
+
function rawStringField(value, key) {
|
|
363
|
+
const raw = value[key];
|
|
364
|
+
return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
|
|
365
|
+
}
|
|
366
|
+
function optionalStrictString(value, key) {
|
|
367
|
+
const raw = value[key];
|
|
368
|
+
if (raw === undefined)
|
|
369
|
+
return undefined;
|
|
370
|
+
return typeof raw === 'string' && raw.trim() && raw === raw.trim() ? raw : null;
|
|
371
|
+
}
|
|
372
|
+
function immutableRunRecord(record) {
|
|
373
|
+
const plan = record.plan ? Object.freeze([...record.plan]) : undefined;
|
|
374
|
+
return Object.freeze({ ...record, ...(plan ? { plan } : {}) });
|
|
375
|
+
}
|
|
376
|
+
function isRunState(value) {
|
|
377
|
+
return value === 'started' || value === 'progress' || value === 'completed';
|
|
378
|
+
}
|
|
379
|
+
function isRunOutcome(value) {
|
|
380
|
+
return value === 'imported'
|
|
381
|
+
|| value === 'already_local'
|
|
382
|
+
|| value === 'failed'
|
|
383
|
+
|| value === 'remote_missing';
|
|
384
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ReleaseLockReason } from '../util/fileLock.js';
|
|
2
|
+
import { type AssetStoreLockDeps } from './assetStoreStorage.js';
|
|
3
|
+
import { type AssetSidecarKind } from './assetSidecarRecords.js';
|
|
4
|
+
export type { AssetSidecarKind } from './assetSidecarRecords.js';
|
|
5
|
+
export type AssetSidecarRecoveryFailureReason = 'sidecar_missing' | 'sidecar_not_degraded' | 'replacement_missing' | 'replacement_too_large' | 'replacement_invalid_utf8' | 'replacement_invalid' | 'replacement_unterminated' | 'valid_history_missing' | 'corrective_record_required' | 'unsafe_corrective_record' | 'acknowledgement_required' | 'sidecar_too_large' | 'sidecar_changed' | 'replacement_changed' | 'backup_conflict';
|
|
6
|
+
export declare class AssetSidecarRecoveryError extends Error {
|
|
7
|
+
readonly reason: AssetSidecarRecoveryFailureReason;
|
|
8
|
+
readonly code = "ASSET_SIDECAR_RECOVERY_FAILED";
|
|
9
|
+
constructor(reason: AssetSidecarRecoveryFailureReason);
|
|
10
|
+
}
|
|
11
|
+
export interface AssetSidecarRecoveryFileReport {
|
|
12
|
+
rows: number;
|
|
13
|
+
validRows: number;
|
|
14
|
+
corruptRows: number;
|
|
15
|
+
unterminated: boolean;
|
|
16
|
+
digest: string;
|
|
17
|
+
}
|
|
18
|
+
export interface AssetSidecarReplacementReport extends AssetSidecarRecoveryFileReport {
|
|
19
|
+
preservedValidRows: number;
|
|
20
|
+
addedRecords: number;
|
|
21
|
+
correctiveRecords: number;
|
|
22
|
+
}
|
|
23
|
+
export interface AssetSidecarRecoveryReport {
|
|
24
|
+
mode: 'preview' | 'write';
|
|
25
|
+
sidecar: AssetSidecarKind;
|
|
26
|
+
changed: boolean;
|
|
27
|
+
wouldWrite: boolean;
|
|
28
|
+
acknowledgementRequired: boolean;
|
|
29
|
+
current: AssetSidecarRecoveryFileReport;
|
|
30
|
+
replacement: AssetSidecarReplacementReport;
|
|
31
|
+
backupId?: string;
|
|
32
|
+
lockReleaseWarning?: ReleaseLockReason;
|
|
33
|
+
}
|
|
34
|
+
export interface RecoverAssetSidecarOptions {
|
|
35
|
+
baseDir: string;
|
|
36
|
+
sidecar: AssetSidecarKind;
|
|
37
|
+
replacementPath: string;
|
|
38
|
+
write?: boolean;
|
|
39
|
+
acknowledgeCorruptHistory?: boolean;
|
|
40
|
+
maxFileBytes?: number;
|
|
41
|
+
deps?: {
|
|
42
|
+
beforeBackup?: () => void;
|
|
43
|
+
beforeReplace?: () => void;
|
|
44
|
+
lock?: AssetStoreLockDeps;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export declare const DEFAULT_SIDECAR_RECOVERY_MAX_FILE_BYTES: number;
|
|
48
|
+
export declare function recoverAssetSidecar(opts: RecoverAssetSidecarOptions): AssetSidecarRecoveryReport;
|