@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.
Files changed (240) hide show
  1. package/assets/gep/genes.jsonl +5 -5
  2. package/dist/algo/bans.d.ts +10 -1
  3. package/dist/algo/bans.js +56 -6
  4. package/dist/algo/candidateAssembly.d.ts +25 -2
  5. package/dist/algo/candidateAssembly.js +171 -26
  6. package/dist/algo/capabilityCandidates.js +10 -0
  7. package/dist/algo/conversationSniffer.js +25 -8
  8. package/dist/algo/cycleEngine.d.ts +63 -5
  9. package/dist/algo/cycleEngine.js +320 -42
  10. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  11. package/dist/algo/evolutionEvent.d.ts +26 -0
  12. package/dist/algo/evolutionEvent.js +32 -0
  13. package/dist/algo/exploration.d.ts +7 -0
  14. package/dist/algo/exploration.js +16 -3
  15. package/dist/algo/geneHealth.d.ts +36 -3
  16. package/dist/algo/geneHealth.js +47 -4
  17. package/dist/algo/geneIntake.d.ts +46 -1
  18. package/dist/algo/geneIntake.js +121 -10
  19. package/dist/algo/geneSelection.d.ts +126 -6
  20. package/dist/algo/geneSelection.js +479 -36
  21. package/dist/algo/index.d.ts +6 -1
  22. package/dist/algo/index.js +6 -1
  23. package/dist/algo/kautoProjection.d.ts +41 -0
  24. package/dist/algo/kautoProjection.js +95 -0
  25. package/dist/algo/kautoValidator.d.ts +68 -0
  26. package/dist/algo/kautoValidator.js +256 -0
  27. package/dist/algo/memoryGraph.d.ts +62 -0
  28. package/dist/algo/memoryGraph.js +86 -0
  29. package/dist/algo/orchestrator.d.ts +17 -1
  30. package/dist/algo/orchestrator.js +30 -4
  31. package/dist/algo/publishEligibility.d.ts +34 -0
  32. package/dist/algo/publishEligibility.js +52 -0
  33. package/dist/algo/solidify.d.ts +11 -2
  34. package/dist/algo/solidify.js +37 -7
  35. package/dist/algo/ucb1.d.ts +53 -0
  36. package/dist/algo/ucb1.js +156 -0
  37. package/dist/assetrepair/hubRejection.d.ts +12 -0
  38. package/dist/assetrepair/hubRejection.js +109 -0
  39. package/dist/assetrepair/index.d.ts +2 -0
  40. package/dist/assetrepair/index.js +2 -0
  41. package/dist/assetrepair/repair.d.ts +33 -0
  42. package/dist/assetrepair/repair.js +155 -0
  43. package/dist/assetstore/assetSidecarRecords.d.ts +28 -0
  44. package/dist/assetstore/assetSidecarRecords.js +384 -0
  45. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  46. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  47. package/dist/assetstore/assetStoreHealth.d.ts +81 -0
  48. package/dist/assetstore/assetStoreHealth.js +319 -0
  49. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  50. package/dist/assetstore/assetStoreLayout.js +6 -0
  51. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  52. package/dist/assetstore/assetStoreStorage.js +336 -0
  53. package/dist/assetstore/assetSyncLedger.d.ts +91 -1
  54. package/dist/assetstore/assetSyncLedger.js +718 -59
  55. package/dist/assetstore/foreignJsonlSource.d.ts +48 -0
  56. package/dist/assetstore/foreignJsonlSource.js +150 -0
  57. package/dist/assetstore/index.d.ts +5 -0
  58. package/dist/assetstore/index.js +5 -0
  59. package/dist/assetstore/learningHistory.js +3 -3
  60. package/dist/assetstore/localAssetStoreSnapshot.d.ts +51 -0
  61. package/dist/assetstore/localAssetStoreSnapshot.js +329 -0
  62. package/dist/assetstore/localJsonl.d.ts +12 -1
  63. package/dist/assetstore/localJsonl.js +251 -42
  64. package/dist/assetstore/provenance.d.ts +93 -4
  65. package/dist/assetstore/provenance.js +368 -84
  66. package/dist/assetstore/provider.d.ts +63 -0
  67. package/dist/assetstore/provider.js +97 -6
  68. package/dist/assetstore/reviewFilter.d.ts +19 -1
  69. package/dist/assetstore/reviewFilter.js +39 -1
  70. package/dist/assetstore/reviewLedger.d.ts +8 -2
  71. package/dist/assetstore/reviewLedger.js +71 -45
  72. package/dist/assetstore/unionReadStore.d.ts +25 -0
  73. package/dist/assetstore/unionReadStore.js +119 -0
  74. package/dist/benchmark/antiGeneBenchmark.d.ts +2 -0
  75. package/dist/benchmark/antiGeneBenchmark.js +4 -3
  76. package/dist/benchmark/antiGeneRollout.d.ts +2 -0
  77. package/dist/benchmark/antiGeneRollout.js +4 -3
  78. package/dist/benchmark/index.d.ts +3 -1
  79. package/dist/benchmark/index.js +3 -1
  80. package/dist/benchmark/selectionFlatAbstention.d.ts +152 -0
  81. package/dist/benchmark/selectionFlatAbstention.js +481 -0
  82. package/dist/benchmark/triggerShift.d.ts +62 -0
  83. package/dist/benchmark/triggerShift.js +106 -0
  84. package/dist/bootstrap/envFingerprint.d.ts +9 -0
  85. package/dist/bootstrap/envFingerprint.js +5 -0
  86. package/dist/bootstrap/index.d.ts +3 -1
  87. package/dist/bootstrap/index.js +3 -1
  88. package/dist/bootstrap/lifecycleBootstrap.d.ts +111 -0
  89. package/dist/bootstrap/lifecycleBootstrap.js +433 -0
  90. package/dist/bootstrap/v1EnvCompat.d.ts +113 -0
  91. package/dist/bootstrap/v1EnvCompat.js +303 -0
  92. package/dist/events/eventArchive.d.ts +2 -0
  93. package/dist/events/eventArchive.js +13 -3
  94. package/dist/events/eventSchema.d.ts +7 -7
  95. package/dist/events/eventStore.d.ts +2 -0
  96. package/dist/events/eventStore.js +5 -1
  97. package/dist/events/ingest.d.ts +2 -1
  98. package/dist/events/ingest.js +15 -0
  99. package/dist/events/paths.d.ts +12 -10
  100. package/dist/events/paths.js +24 -20
  101. package/dist/events/public.d.ts +2 -2
  102. package/dist/events/public.js +2 -2
  103. package/dist/events/reports.d.ts +2 -0
  104. package/dist/events/reports.js +4 -0
  105. package/dist/exec/autoExec.d.ts +68 -4
  106. package/dist/exec/autoExec.js +392 -33
  107. package/dist/exec/autonomousCycle.d.ts +31 -4
  108. package/dist/exec/autonomousCycle.js +71 -13
  109. package/dist/exec/claudeBridge.d.ts +80 -15
  110. package/dist/exec/claudeBridge.js +845 -76
  111. package/dist/exec/executionBinding.d.ts +414 -0
  112. package/dist/exec/executionBinding.js +588 -0
  113. package/dist/exec/index.d.ts +1 -0
  114. package/dist/exec/index.js +1 -0
  115. package/dist/exec/openPrRegistry.d.ts +8 -2
  116. package/dist/exec/openPrRegistry.js +32 -22
  117. package/dist/exec/prompt.js +14 -1
  118. package/dist/exec/proofOfWork.d.ts +1 -1
  119. package/dist/exec/proofOfWork.js +2 -2
  120. package/dist/exec/runnerRegistry.d.ts +153 -36
  121. package/dist/exec/runnerRegistry.js +848 -65
  122. package/dist/exec/selfPr.js +1 -7
  123. package/dist/feedback/envelope.d.ts +61 -0
  124. package/dist/feedback/envelope.js +168 -0
  125. package/dist/feedback/index.d.ts +1 -0
  126. package/dist/feedback/index.js +1 -0
  127. package/dist/hooks/hooks.js +1 -0
  128. package/dist/hub/assetCallLog.d.ts +35 -1
  129. package/dist/hub/assetCallLog.js +124 -1
  130. package/dist/hub/bindings.d.ts +8 -1
  131. package/dist/hub/bindings.js +29 -8
  132. package/dist/hub/capability.d.ts +130 -4
  133. package/dist/hub/conversationDistiller.d.ts +19 -0
  134. package/dist/hub/conversationDistiller.js +115 -37
  135. package/dist/hub/fake.d.ts +3 -2
  136. package/dist/hub/fake.js +2 -1
  137. package/dist/hub/index.d.ts +1 -0
  138. package/dist/hub/index.js +1 -0
  139. package/dist/hub/questionGenerator.d.ts +5 -1
  140. package/dist/hub/questionGenerator.js +8 -6
  141. package/dist/hub/recipeCompose.d.ts +27 -0
  142. package/dist/hub/recipeCompose.js +90 -0
  143. package/dist/hub/sanitize.js +122 -7
  144. package/dist/index.d.ts +6 -1
  145. package/dist/index.js +7 -1
  146. package/dist/issueReporter/index.d.ts +156 -0
  147. package/dist/issueReporter/index.js +1688 -0
  148. package/dist/mailbox/dispatch.d.ts +1 -1
  149. package/dist/mailbox/dispatch.js +22 -6
  150. package/dist/mailbox/envelope.d.ts +7 -1
  151. package/dist/mailbox/envelope.js +9 -2
  152. package/dist/mailbox/ipcServer.d.ts +12 -2
  153. package/dist/mailbox/ipcServer.js +183 -13
  154. package/dist/mailbox/store.d.ts +89 -3
  155. package/dist/mailbox/store.js +895 -41
  156. package/dist/modelCompatibility.d.ts +164 -0
  157. package/dist/modelCompatibility.js +309 -0
  158. package/dist/observers/valueDigestObserver.d.ts +9 -0
  159. package/dist/observers/valueDigestObserver.js +35 -2
  160. package/dist/ops/cleanup.js +1 -1
  161. package/dist/ops/evolutionGraphProjection.d.ts +20 -0
  162. package/dist/ops/evolutionGraphProjection.js +315 -0
  163. package/dist/ops/index.d.ts +2 -1
  164. package/dist/ops/index.js +2 -1
  165. package/dist/ops/savingsCore.js +1 -2
  166. package/dist/ops/selfUpdate.d.ts +18 -1
  167. package/dist/ops/selfUpdate.js +88 -23
  168. package/dist/ops/valueOutreach.d.ts +3 -1
  169. package/dist/ops/valueOutreach.js +5 -1
  170. package/dist/personality/schema.d.ts +24 -24
  171. package/dist/schema/evolutionGraph.d.ts +784 -0
  172. package/dist/schema/evolutionGraph.js +187 -0
  173. package/dist/schema/index.d.ts +1 -0
  174. package/dist/schema/index.js +1 -0
  175. package/dist/schema/proofOfWork.d.ts +125 -6
  176. package/dist/schema/proofOfWork.js +102 -4
  177. package/dist/schema/signal.d.ts +3 -3
  178. package/dist/schema/signal.js +1 -1
  179. package/dist/shadow/shadowHub.js +1 -0
  180. package/dist/signals/curriculum.d.ts +55 -0
  181. package/dist/signals/curriculum.js +202 -0
  182. package/dist/signals/cycleHistoryFromEvents.js +17 -8
  183. package/dist/signals/expand.d.ts +15 -1
  184. package/dist/signals/expand.js +169 -1
  185. package/dist/signals/extractor.d.ts +2 -2
  186. package/dist/signals/extractor.js +31 -6
  187. package/dist/signals/index.d.ts +4 -1
  188. package/dist/signals/index.js +4 -1
  189. package/dist/signals/metaSignals.d.ts +4 -0
  190. package/dist/signals/metaSignals.js +42 -0
  191. package/dist/signals/scopeVocabulary.d.ts +75 -0
  192. package/dist/signals/scopeVocabulary.js +91 -0
  193. package/dist/signals/signalGate.js +1 -1
  194. package/dist/signals/taskDomain.d.ts +22 -0
  195. package/dist/signals/taskDomain.js +43 -0
  196. package/dist/strategy/constraintAblation.d.ts +64 -0
  197. package/dist/strategy/constraintAblation.js +2820 -0
  198. package/dist/strategy/constraintAblationPredicates.d.ts +31 -0
  199. package/dist/strategy/constraintAblationPredicates.js +339 -0
  200. package/dist/strategy/index.d.ts +2 -1
  201. package/dist/strategy/index.js +2 -1
  202. package/dist/trace/index.d.ts +3 -1
  203. package/dist/trace/index.js +3 -1
  204. package/dist/trace/learningTrace.d.ts +216 -0
  205. package/dist/trace/learningTrace.js +298 -0
  206. package/dist/trace/proxyTurns.d.ts +31 -0
  207. package/dist/trace/proxyTurns.js +137 -0
  208. package/dist/trace/trajectory.d.ts +8 -0
  209. package/dist/trace/trajectory.js +14 -2
  210. package/dist/util/fetchPort.d.ts +1 -0
  211. package/dist/util/fetchPort.js +11 -0
  212. package/dist/util/fileLock.d.ts +113 -7
  213. package/dist/util/fileLock.js +1035 -80
  214. package/dist/util/index.d.ts +3 -1
  215. package/dist/util/index.js +2 -1
  216. package/dist/verify/index.d.ts +2 -1
  217. package/dist/verify/index.js +1 -1
  218. package/dist/verify/sandboxRunner.d.ts +30 -0
  219. package/dist/verify/sandboxRunner.js +370 -27
  220. package/dist/verify/sandboxedValidation.d.ts +16 -2
  221. package/dist/verify/sandboxedValidation.js +200 -22
  222. package/dist/verify/validation.d.ts +35 -4
  223. package/dist/verify/validation.js +164 -16
  224. package/dist/wire/geneHints.d.ts +88 -16
  225. package/dist/wire/geneHints.js +124 -15
  226. package/dist/wire/index.d.ts +8 -3
  227. package/dist/wire/index.js +2 -2
  228. package/dist/wire/schemaGate.d.ts +21 -0
  229. package/dist/wire/schemaGate.js +119 -10
  230. package/dist/workflow/dsl.d.ts +24 -3
  231. package/dist/workflow/dsl.js +4 -0
  232. package/dist/workflow/engine.d.ts +5 -1
  233. package/dist/workflow/engine.js +3 -0
  234. package/dist/workflow/index.d.ts +3 -1
  235. package/dist/workflow/index.js +3 -1
  236. package/dist/workflow/runtime.d.ts +110 -0
  237. package/dist/workflow/runtime.js +1298 -0
  238. package/dist/workflow/stateStore.d.ts +172 -0
  239. package/dist/workflow/stateStore.js +1044 -0
  240. 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;