@lmzhen/dsh-evolution-core 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -3485,35 +3485,6 @@ function clampedNumber(value, fallback, opts) {
3485
3485
  if (max !== void 0 && value > max) return fallback;
3486
3486
  return value;
3487
3487
  }
3488
- /**
3489
- * Clamp one numeric knob and warn ONCE per key.
3490
- *
3491
- * S2.9 (T3-10): `evolution-review` and `evolution-curator` each carried their own copy of this
3492
- * helper — their own once-per-key set and their own message — so the two settings readers of ONE
3493
- * policy row could drift apart. The logic lives here; each caller passes its own sink (and its own
3494
- * package prefix). A live field is read at every use, so the warning cannot be per-read.
3495
- *
3496
- * @param warned - the keys this caller already warned about.
3497
- * @param warn - the warning sink (each package prefixes its own name).
3498
- * @param name - the config key, for the warning.
3499
- * @param value - the value as supplied, or its volatile reference.
3500
- * @param fallback - the package default a corrected value falls back to.
3501
- * @param min - the smallest value the engine can act on.
3502
- * @param max - the largest value the carrier can hold, when bounded.
3503
- * @returns the value the engine may use.
3504
- */
3505
- function clampOnce(warned, warn, name, value, fallback, min, max) {
3506
- const current = typeof value === "object" ? value.get() : value;
3507
- const result = clampedNumber(current, fallback, max === void 0 ? { min } : {
3508
- min,
3509
- max
3510
- });
3511
- if (current !== void 0 && result !== current && !warned.has(name)) {
3512
- warned.add(name);
3513
- warn(`${name} provided an invalid value; falling back to the default`);
3514
- }
3515
- return result;
3516
- }
3517
3488
  //#endregion
3518
3489
  //#region lib/types/threats.js
3519
3490
  /**
@@ -6697,10 +6668,24 @@ const SERIALIZATIONS = [
6697
6668
  ];
6698
6669
  /** Parse + validate the table. A malformed table throws at import: a table the
6699
6670
  * gate cannot read must never degrade into "no declared write sites". */
6700
- function parseSites(raw) {
6701
- if (!Array.isArray(raw)) throw new Error("evolution-core: persisted-write-inventory.json must be an array of sites");
6671
+ function parseTable(raw) {
6672
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) throw new Error("evolution-core: persisted-write-inventory.json must be an object with { symbols, sites }");
6673
+ const table = raw;
6674
+ if (table.symbols === null || typeof table.symbols !== "object" || Array.isArray(table.symbols)) throw new Error("evolution-core: persisted-write-inventory.json needs a symbols legend (the roots every path is written against)");
6675
+ for (const [symbol, entry] of Object.entries(table.symbols)) {
6676
+ const problems = [];
6677
+ if (!symbol.startsWith("<") || !symbol.endsWith(">")) problems.push("symbol name (must be <name>)");
6678
+ if (entry === null || typeof entry !== "object") problems.push("entry");
6679
+ else {
6680
+ const symbolEntry = entry;
6681
+ if (typeof symbolEntry.resolver !== "string" || symbolEntry.resolver === "") problems.push("resolver");
6682
+ if (symbolEntry.segments !== void 0 && (!Array.isArray(symbolEntry.segments) || symbolEntry.segments.some((segment) => typeof segment !== "string"))) problems.push("segments");
6683
+ }
6684
+ if (problems.length > 0) throw new Error(`evolution-core: persisted-write-inventory.json symbol ${symbol} is malformed (missing/invalid: ${problems.join(", ")})`);
6685
+ }
6686
+ if (!Array.isArray(table.sites)) throw new Error("evolution-core: persisted-write-inventory.json needs a sites array");
6702
6687
  const sites = [];
6703
- for (const [index, entry] of raw.entries()) {
6688
+ for (const [index, entry] of table.sites.entries()) {
6704
6689
  const site = entry;
6705
6690
  const problems = [];
6706
6691
  if (typeof site.id !== "string" || site.id === "") problems.push("id");
@@ -6723,7 +6708,10 @@ function parseSites(raw) {
6723
6708
  });
6724
6709
  }
6725
6710
  if (new Set(sites.map((site) => site.id)).size !== sites.length) throw new Error("evolution-core: persisted-write-inventory.json declares duplicate site ids");
6726
- return sites;
6711
+ return {
6712
+ symbols: table.symbols,
6713
+ sites
6714
+ };
6727
6715
  }
6728
6716
  /** Instance keys held by writer services — the single source shared by the
6729
6717
  * claim site and the inventory row that declares it (rule N20 checks the row's
@@ -6732,6 +6720,7 @@ const INSTANCE_KEYS = {
6732
6720
  /** The per-home curator: report writing + the retention sweep. */
6733
6721
  curator: "evolution-curator" };
6734
6722
  let cachedSites;
6723
+ let cachedSymbols;
6735
6724
  /**
6736
6725
  * The declared persisted write sites, in file order.
6737
6726
  *
@@ -6755,8 +6744,17 @@ function persistedWriteSites() {
6755
6744
  const cause = error instanceof Error ? error.message : String(error);
6756
6745
  throw new Error(`evolution-core: persisted-write-inventory.json is unreadable (${cause}) — the package asset is missing or was not shipped; the write-inventory rules cannot be evaluated without it`);
6757
6746
  }
6758
- cachedSites = parseSites(JSON.parse(raw));
6759
- return cachedSites;
6747
+ const table = parseTable(JSON.parse(raw));
6748
+ cachedSites = table.sites;
6749
+ cachedSymbols = table.symbols;
6750
+ return table.sites;
6751
+ }
6752
+ /** The inventory's symbol legend — the roots every declared path is written against, with the
6753
+ * segments the gate checks against the resolver's source.
6754
+ * @internal no production caller — the gates read the JSON asset directly. */
6755
+ function writePathSymbols() {
6756
+ persistedWriteSites();
6757
+ return cachedSymbols ?? {};
6760
6758
  }
6761
6759
  /** Sites serialized by the per-home instance claim, with their instance keys.
6762
6760
  * @internal S2.4 (PLAN 2026-09-16): no production caller — the arch guard
@@ -13443,6 +13441,114 @@ async function applyNamespaceMigration(plan, deps) {
13443
13441
  };
13444
13442
  }
13445
13443
  //#endregion
13444
+ //#region lib/types/live.js
13445
+ /**
13446
+ * The family's ONE live-config reader.
13447
+ *
13448
+ * A config field reaches a plugin in two shapes: the platform's schema parser turns a
13449
+ * `.volatile()` declaration into a live reference (`{ get() }`, routed through cosmokit's
13450
+ * `createVolatile`), while a hand-built or pre-parse config carries the plain value. Reading a
13451
+ * live reference as if it were a value is SILENT (a boolean compare is simply never true, a
13452
+ * number folds as NaN) — so the two shapes are unwrapped in exactly one place here, and every
13453
+ * reader below goes through it.
13454
+ *
13455
+ * This module owns no policy: clamping semantics stay in `clampedNumber` (numeric.ts), and the
13456
+ * once-per-key warning text/rhythm is the one `clampOnce` has always had (it moved here so the
13457
+ * read happens in one place; callers import it from the package barrel, so the move is invisible).
13458
+ *
13459
+ * @module @lmzhen/dsh-evolution-core
13460
+ */
13461
+ /**
13462
+ * Unwrap a live reference; return a plain value unchanged. The ONLY place that decides the shape.
13463
+ *
13464
+ * The discriminant is `typeof get === 'function'` rather than a bare `typeof value === 'object'`: a
13465
+ * config field may legitimately carry an object (a policy record), and calling `.get()` on that
13466
+ * used to throw where a plain read was meant.
13467
+ *
13468
+ * @param value - the field as supplied: live reference, plain value, or absent.
13469
+ * @returns the plain value, or `undefined` when the reference/field is absent.
13470
+ */
13471
+ function unwrapLive(value) {
13472
+ const raw = value;
13473
+ if (typeof raw !== "object" || raw === null) return raw === void 0 ? void 0 : raw;
13474
+ const candidate = raw;
13475
+ return typeof candidate.get === "function" ? candidate.get.call(raw) : raw;
13476
+ }
13477
+ /**
13478
+ * Read a numeric knob (live or plain) and clamp it, warning ONCE per key.
13479
+ *
13480
+ * @param value - the field as supplied: live reference, plain value, or absent.
13481
+ * @param fallback - the package default an invalid value falls back to.
13482
+ * @param opts - bounds plus the optional warning triple.
13483
+ * @returns the value the engine may use.
13484
+ */
13485
+ function liveNumber(value, fallback, opts = {}) {
13486
+ const current = unwrapLive(value);
13487
+ const { min, max } = opts;
13488
+ const result = clampedNumber(current, fallback, min === void 0 ? max === void 0 ? void 0 : { max } : max === void 0 ? { min } : {
13489
+ min,
13490
+ max
13491
+ });
13492
+ const name = opts.name;
13493
+ if (current !== void 0 && result !== current && name !== void 0 && opts.warn !== void 0) {
13494
+ const warned = opts.warned ?? /* @__PURE__ */ new Set();
13495
+ if (!warned.has(name)) {
13496
+ warned.add(name);
13497
+ opts.warn(`${name} provided an invalid value; falling back to the default`);
13498
+ }
13499
+ }
13500
+ return result;
13501
+ }
13502
+ /**
13503
+ * Clamp one numeric knob and warn ONCE per key — the pre-0.22 signature, kept as a thin adapter so
13504
+ * existing callers migrate one at a time instead of in one commit.
13505
+ *
13506
+ * @param warned - the keys this caller already warned about.
13507
+ * @param warn - the warning sink (each package prefixes its own name).
13508
+ * @param name - the config key, for the warning.
13509
+ * @param value - the value as supplied, or its live reference.
13510
+ * @param fallback - the package default a corrected value falls back to.
13511
+ * @param min - the smallest value the engine can act on.
13512
+ * @param max - the largest value the carrier can hold, when bounded.
13513
+ * @returns the value the engine may use.
13514
+ */
13515
+ function clampOnce(warned, warn, name, value, fallback, min, max) {
13516
+ return liveNumber(value, fallback, {
13517
+ min,
13518
+ ...max === void 0 ? {} : { max },
13519
+ warned,
13520
+ warn,
13521
+ name
13522
+ });
13523
+ }
13524
+ /**
13525
+ * Read a boolean flag (live or plain). Absent ⇒ `fallback` (default `false`).
13526
+ *
13527
+ * The boolean counterpart of {@link liveNumber}: `value === true` is never true for a live
13528
+ * reference, which is the silent failure this reader exists to prevent.
13529
+ */
13530
+ function liveBool(value, fallback = false) {
13531
+ return unwrapLive(value) ?? fallback;
13532
+ }
13533
+ /**
13534
+ * Read a string (live or plain). Blank/whitespace counts as unset — the same rule `evolutionRoot` uses
13535
+ * for `DSH_HOME`.
13536
+ */
13537
+ function liveString(value, fallback) {
13538
+ const current = unwrapLive(value)?.trim();
13539
+ return current === void 0 || current.length === 0 ? fallback : current;
13540
+ }
13541
+ /**
13542
+ * Read an enumerated string (live or plain); a value outside `allowed` falls back.
13543
+ *
13544
+ * Used for the mode-like knobs (review mode, trigger, citation policy, write-confirm mode) where a
13545
+ * bare `?? default` would accept a typo the schema never saw.
13546
+ */
13547
+ function liveEnum(value, allowed, fallback) {
13548
+ const current = unwrapLive(value);
13549
+ return current !== void 0 && allowed.includes(current) ? current : fallback;
13550
+ }
13551
+ //#endregion
13446
13552
  //#region lib/types/opt-in.js
13447
13553
  /**
13448
13554
  * Tool names only a session that mounted the family's MODEL rows can see.
@@ -13583,4 +13689,4 @@ function foldUntrustedLines(text, limit) {
13583
13689
  return limit > 0 ? marked.slice(0, limit) : marked;
13584
13690
  }
13585
13691
  //#endregion
13586
- export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, ALIVE_LOCK_TAKEOVER_MS, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LONG_RUNS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, createGateSet, daysSinceIso, decideTakeover, declaredReportStamp, describeReferenceRewrite, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, historyIndexFile, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, latestActivityAt, latestVersionAt, lifecycleCandidate, listEventArchives, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, parseLockBody, partitionVersions, payloadText, pendingSelfCleanup, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, retainEventArchives, retirementReport, reviewPrompt, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillsRoot, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, unhookedSupportPointers, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, writeDurableTmp, writeJson, yamlPlainScalarNeedsQuotes };
13692
+ export { AGENT_PRESET_PACKAGE, AGENT_PRESET_ROOT_HINT, ALIVE_LOCK_TAKEOVER_MS, AUTHORING_DESCRIPTION_BAR, AUTHORING_SPLIT_LINE_CHARS, BASELINE_ACTION, CITATION_SUPPORT_DIRS, COMBINED_REVIEW_PLAN_PROMPT, COMBINED_REVIEW_PROMPT, COMPLETION_SKILL_REVIEW_PROMPT, CONTENT_SPLIT_HINT, COST_ASCII_TOKEN_HIGH, COST_ASCII_TOKEN_LOW, COST_CJK_TOKEN_HIGH, COST_CJK_TOKEN_LOW, CURATOR_DRY_RUN_BANNER, CURATOR_PROMPT, DEAD_LOCK_TAKEOVER_MS, DEDUP_MAX_PAIR_COMPARISONS, DEDUP_SIMILARITY_THRESHOLD, DEFAULT_ARCHIVE_AFTER_DAYS, DEFAULT_ARCHIVE_RETENTION_POLICY, DEFAULT_CITATION_POLICY, DEFAULT_CITATION_REF_BUDGET, DEFAULT_CONSOLIDATION_FAILURES, DEFAULT_CURATOR_BOOT_GRACE_SECONDS, DEFAULT_CURATOR_INTERVAL_HOURS, DEFAULT_CURATOR_MODEL, DEFAULT_CURATOR_REVIEW_MAX_TOKENS, DEFAULT_HEALTH_THRESHOLDS, DEFAULT_MAX_OPS_PER_PLAN, DEFAULT_MEMORY_CHAR_LIMIT, DEFAULT_MEMORY_REVIEW_MODEL, DEFAULT_MIN_IDLE_HOURS, DEFAULT_MUTATION_CAP, DEFAULT_PROBE_TIMEOUT_MS, DEFAULT_REFERENCE_REWRITE_POLICY, DEFAULT_REVIEW_CONTEXT_MESSAGES, DEFAULT_REVIEW_MEMORY_INTERVAL, DEFAULT_REVIEW_MESSAGE_CHARS, DEFAULT_REVIEW_SKILL_INTERVAL, DEFAULT_REVIEW_TIMEOUT_MS, DEFAULT_RUN_RECORDS, DEFAULT_SKILL_CONTENT_CHARS, DEFAULT_SKILL_LIMITS, DEFAULT_SKILL_REVIEW_COMPLETION_MIN_TOOL_CALLS, DEFAULT_SKILL_REVIEW_MODEL, DEFAULT_SKILL_REVIEW_TRIGGER, DEFAULT_SKILL_VERSION_KEEP, DEFAULT_STALE_AFTER_DAYS, DEFAULT_SUBSTANTIVE_MIN_AGENT_CHARS, DEFAULT_SUBSTANTIVE_MIN_TOOL_CALLS, DEFAULT_SUBSTANTIVE_MIN_USER_CHARS, DEFAULT_SUPPORT_FILE_CHAR_POLICY, DEFAULT_SUPPORT_READ_TOOL_NAMES, DEFAULT_THREAT_MAX_SCAN_CHARS, DEFAULT_USER_CHAR_LIMIT, DISPATCH_EVENT_TYPES, DRIFT_MAX_LINE_CHARS, DRIFT_SIGNALS_VERSION, DRIFT_SIGNAL_NOUNS, DSH_AUTHORING_STANDARDS, EMPTY_LOCK_TAKEOVER_MS, EMPTY_PATCH_SEED, ENTRY_DELIMITER, EVENT_ARCHIVE_PREFIX, EVENT_LOG_RETAIN_ARCHIVES, EVENT_LOG_ROTATE_AT, EVENT_LOG_VERSION, EVOLUTION_ERRORS, EVOLUTION_WRITE_TOOLS, EvolutionGateSet, FAMILY_SESSION_TOOL_NAMES, FORBIDDEN_CONTROL_KEYS, FUZZY_MAX_PATTERN_CHARS, FUZZY_MAX_WORK, HEALTH_STAMP_RE, HISTORY_DIR, HISTORY_INDEX_VERSION, HOOK_LINE_RE, INSTANCE_KEYS, KEEP_LINE_RE, LOCK_BODY_RE, LOCK_SUFFIX, LOCK_TEAR_TAKEOVER_MS, LONG_RUNS, LOW_QUALITY_THRESHOLD, LostWriteLock, MAINTAIN_OUTPUT_INSTRUCTION, MAINTAIN_PROMPT, MAX_DESCRIPTION_LENGTH, MAX_REQUEST_BODY_BYTES, MAX_RESTRUCTURE_MOVES, MAX_SKILL_CONTENT_CHARS, MAX_SKILL_FILE_BYTES, MAX_SKILL_NAME_LENGTH, MAX_SUPPORT_READ_PATHS, MAX_TIMER_DELAY_MS, MEMORY_GUIDANCE_SECTION_ORDER, MEMORY_REVIEW_PROMPT, MIN_STAMP_BODY_CHARS, MUTATIONS_FILE_VERSION, MemoryStore, NAMESPACE_MIGRATIONS, NATIVE_CALL_EVENT, NATIVE_RESULT_EVENT, PARAM_ALIASES, PARAM_EXPOSURE, PARAM_GROUP_LABELS, PARAM_NAMESPACES, PATTERN_OVERLAP, PLATFORM_CATALOG_DESCRIPTION_DEFAULT, PLATFORM_ESTIMATE_CHARS_PER_TOKEN, PLATFORM_SERVICE_PROBES, POLICY_STAGE_DEFAULTS, PROMPT_BUNDLE, PROMPT_BUNDLE_ID, PROMPT_BUNDLE_VERSION, PROTECTED_BUILTIN_SKILLS, PTC_DISPATCH_EVENT, PTC_DISPATCH_START_EVENT, QUALITY_WEIGHTS, READ_REQUIRED_ACTIONS, RESTRUCTURE_TARGET_RE, RETENTION_FEEDBACK_KEEP_RATIO, RETENTION_FEEDBACK_MIN_CHARS, RUNS_SCHEMA_VERSION, SKILLS_GUIDANCE, SKILLS_GUIDANCE_SECTION_ORDER, SKILL_ACTION_REQUIRED_FIELDS, SKILL_NAME_RE, SKILL_REVIEW_PLAN_PROMPT, SKILL_REVIEW_PROMPT, SNAPSHOT_EXTRA_NAME_RE, SUMMARY_DUPLICATE_HINT_THRESHOLD, SUPPORT_DIRS, SUPPRESSED_FILE_VERSION, SkillLibrary, TEXT_DIFF_MAX_CHARS, TEXT_DIFF_MAX_LINES, THREAT_EXEMPTION_HINT, TOKEN_ASCII_WEIGHT, TOKEN_CJK_WEIGHT, ToolDispatchNormalizer, ToolDispatchPayloadError, UNTRUSTED_LINE_BREAK, UNTRUSTED_LINE_MARK, UPSTREAM_LIMIT_CHARS_PER_TOKEN, VERSION_SUMMARY_MAX_CHARS, absentHostServices, advanceReview, affinity, ageCopyKey, allowRowCollisions, anchorVerdict, appendEvolutionEvent, applyCuratorLifecycleFields, applyCuratorMetaFields, applyNamespaceMigration, applyReferenceRewrite, assertDispatchPayload, assertSkillsRootAliasRetired, assessStructureHealth, authoringFeedback, basePresetPlugins, blobPath, bodyCost, buildCuratorRunReport, buildLearnPrompt, bumpPatch, bumpSupportRead, bumpUse, bumpView, callingScope, canonicalWriteId, claimInstance, clampOnce, clampedNumber, classifyNamespaceMigration, clearReviewChannel, collectReadSkillNames, composePresetComposition, composePresetEntry, composePresetInsert, composePresetRow, computeDedupGroups, computeDriftSignals, computeLifecycleTransitions, computePrefixClusters, computeQualityScores, computeScopeView, contentHash, contentRetentionFeedback, countDispatches, createGateSet, daysSinceIso, decideTakeover, declaredReportStamp, describeReferenceRewrite, dispatchKeyOf, dispatchStepOf, displayBodyOf, duplicateHeadings, elapsedSince, emptyRecord, entryTarget, errorText, evaluateThreat, eventsFile, evidenceKindIndex, evolutionActivityProjection, evolutionEventPayloadIssue, evolutionEvidenceProjection, evolutionHome, evolutionIoAdapter, evolutionReadsProjection, evolutionRoot, evolutionSignalsProjection, filterUnreadSkillOps, findDriftSignal, foldCuratorFields, foldToolDispatches, foldTurnEvent, foldTurnEvents, foldUntrustedLines, frontmatterBlock, frontmatterCatalogInvalid, fuzzyPatch, getRecord, historyIndexFile, historyRoot, identity, idleDays, initialTurnFold, installEvolutionProjections, instanceClaimKey, instanceClaimedWriteSites, instanceHolder, isAbsent, isBookkeepingFrame, isCommittedWarning, isDeprecatedParamId, isFileShapedPath, isLoopbackRequest, isMissingPath, isPresent, isProcessAlive, isProgramToolName, isReviewChannelSession, isSkillReadToolName, isSkillToolName, isUnknown, isUnreadWrite, latestActivityAt, latestVersionAt, lifecycleCandidate, listEventArchives, liveBool, liveEnum, liveNumber, liveString, loadMutations, loadRowOverrides, loadSuppressedNames, loadUsage, loadVersionContent, loadVersions, makeSerialQueue, mapProbe, markAgentCreated, markReviewChannel, markerEntryName, memoryRoot, mergePresetRow, missingSupportPointers, mutateUsage, mutationsFile, narrowNameMatches, nearDuplicateSummaries, neutralizePromptVariables, newRunRegistry, newSkillLibrary, nextHistoryIndex, nodeEvolutionIo, normalizeFrontmatter, normalizeUsageRecord, normalizeVersionSummary, observeEvent, orderVersions, overlongLines, paramNamespace, paramRowId, paramSettingsId, parseCuratorNominations, parseEvolutionEvents, parseFrontmatter, parseHistoryIndex, parseLockBody, partitionVersions, payloadText, pendingSelfCleanup, persistedWriteSite, persistedWriteSites, pickWithPolicy, planNamespaceMigration, planReferenceRewrite, planRehoming, policyStageLimits, presetPatchText, presetRowBlock, presetRowBody, presetRowId, probeAbsent, probeList, probeMtime, probePresent, probeReason, probeText, probeUnknown, projectionText, readDispatchSignal, readEvolutionEvents, readEvolutionTimeline, readHistoryIndex, readJsonBody, readJsonObject, readNumberParam, readParam, readingStats, recordMutation, recordVersions, redactSecrets, relatedSkillNames, releaseInstance, removePresetRow, renameWithRetry, renderCuratorReportMarkdown, renderLongRunTable, reportTime, reportsSweepLockTarget, resolveBasePresetPatch, resolveCitations, resolveExecOrigins, resolveOrigins, resolveParamId, resolveRootConfig, resolveSkillsRoot, retainEventArchives, retirementReport, reviewPrompt, runsFile, saveSuppressedNames, saveUsage, scanBodyHooks, scanContentThreats, scanMemoryThreats, scanThreats, scopedProbeReport, sessionAudited, sessionEvidenceIndex, sessionLastEventTime, sessionReadNames, sessionReadSkillNames, sessionSeesFamilyTools, sessionTurnSignals, skillReadNameOf, skillRowCells, skillStateKey, skillsRoot, supportFileReadOf, suppressedFile, sweepReports, sweepReviewChannelSessions, textDiffFacts, tokenLineFor, transactIo, transactTaskGuard, trimPatternBoundaries, unhookedSupportPointers, unwrapLive, updateSuppressedNames, usageFile, usageObserved, userSetKeys, validateFrontmatter, validateRestructureTarget, valueOr, verifyPromptBundle, versionActionKind, versionRowCells, versionSummaryPrompt, versionTarget, vocabulary, writeDurableTmp, writeJson, writePathSymbols, yamlPlainScalarNeedsQuotes };
@@ -77,6 +77,7 @@ export * from './usage.ts';
77
77
  export * from './constants.ts';
78
78
  export * from './params.ts';
79
79
  export * from './namespace-migration.ts';
80
+ export * from './live.ts';
80
81
  export * from './numeric.ts';
81
82
  export * from './opt-in.ts';
82
83
  export * from './payload.ts';
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Canonical type of a config field that may arrive as a live reference or a plain value.
3
+ *
4
+ * Spelled STRUCTURALLY on purpose: the platform's `Volatile<T>` (cordis) satisfies it for every payload
5
+ * shape, so a field declared `Volatile<number | undefined>` and one declared `Volatile<number>` both pass
6
+ * without the readers importing cordis or narrowing the payload. Where a plugin wants the
7
+ * canonical spelling it may still write `Volatile<T>` in its own Config.
8
+ */
9
+ export type LiveValue<T> = T | {
10
+ get(): T | undefined;
11
+ } | undefined;
12
+ /**
13
+ * Unwrap a live reference; return a plain value unchanged. The ONLY place that decides the shape.
14
+ *
15
+ * The discriminant is `typeof get === 'function'` rather than a bare `typeof value === 'object'`: a
16
+ * config field may legitimately carry an object (a policy record), and calling `.get()` on that
17
+ * used to throw where a plain read was meant.
18
+ *
19
+ * @param value - the field as supplied: live reference, plain value, or absent.
20
+ * @returns the plain value, or `undefined` when the reference/field is absent.
21
+ */
22
+ export declare function unwrapLive<T>(value: LiveValue<T>): T | undefined;
23
+ /** Options for {@link liveNumber}; the warning triple is all-or-nothing. */
24
+ export interface LiveNumberOptions {
25
+ /** Inclusive lower bound; omit for no lower bound (0 is then a legitimate value). */
26
+ readonly min?: number;
27
+ /** Inclusive upper bound. */
28
+ readonly max?: number;
29
+ /** The keys this caller already warned about (caller-owned, so the warning stays once-per-key). */
30
+ readonly warned?: Set<string>;
31
+ /** The warning sink (each package prefixes its own name). */
32
+ readonly warn?: (message: string) => void;
33
+ /** The config key, for the warning text. Required together with `warned` and `warn`. */
34
+ readonly name?: string;
35
+ }
36
+ /**
37
+ * Read a numeric knob (live or plain) and clamp it, warning ONCE per key.
38
+ *
39
+ * @param value - the field as supplied: live reference, plain value, or absent.
40
+ * @param fallback - the package default an invalid value falls back to.
41
+ * @param opts - bounds plus the optional warning triple.
42
+ * @returns the value the engine may use.
43
+ */
44
+ export declare function liveNumber(value: LiveValue<number>, fallback: number, opts?: LiveNumberOptions): number;
45
+ /**
46
+ * Clamp one numeric knob and warn ONCE per key — the pre-0.22 signature, kept as a thin adapter so
47
+ * existing callers migrate one at a time instead of in one commit.
48
+ *
49
+ * @param warned - the keys this caller already warned about.
50
+ * @param warn - the warning sink (each package prefixes its own name).
51
+ * @param name - the config key, for the warning.
52
+ * @param value - the value as supplied, or its live reference.
53
+ * @param fallback - the package default a corrected value falls back to.
54
+ * @param min - the smallest value the engine can act on.
55
+ * @param max - the largest value the carrier can hold, when bounded.
56
+ * @returns the value the engine may use.
57
+ */
58
+ export declare function clampOnce(warned: Set<string>, warn: (message: string) => void, name: string, value: LiveValue<number>, fallback: number, min: number, max?: number): number;
59
+ /**
60
+ * Read a boolean flag (live or plain). Absent ⇒ `fallback` (default `false`).
61
+ *
62
+ * The boolean counterpart of {@link liveNumber}: `value === true` is never true for a live
63
+ * reference, which is the silent failure this reader exists to prevent.
64
+ */
65
+ export declare function liveBool(value: LiveValue<boolean>, fallback?: boolean): boolean;
66
+ /**
67
+ * Read a string (live or plain). Blank/whitespace counts as unset — the same rule `evolutionRoot` uses
68
+ * for `DSH_HOME`.
69
+ */
70
+ export declare function liveString(value: LiveValue<string>, fallback: string): string;
71
+ /**
72
+ * Read an enumerated string (live or plain); a value outside `allowed` falls back.
73
+ *
74
+ * Used for the mode-like knobs (review mode, trigger, citation policy, write-confirm mode) where a
75
+ * bare `?? default` would accept a typo the schema never saw.
76
+ */
77
+ export declare function liveEnum<T extends string>(value: LiveValue<T>, allowed: readonly T[], fallback: T): T;
78
+ //# sourceMappingURL=live.d.ts.map
@@ -35,24 +35,4 @@ export declare function clampedNumber(value: number | undefined, fallback: numbe
35
35
  min?: number;
36
36
  max?: number;
37
37
  }): number;
38
- /**
39
- * Clamp one numeric knob and warn ONCE per key.
40
- *
41
- * S2.9 (T3-10): `evolution-review` and `evolution-curator` each carried their own copy of this
42
- * helper — their own once-per-key set and their own message — so the two settings readers of ONE
43
- * policy row could drift apart. The logic lives here; each caller passes its own sink (and its own
44
- * package prefix). A live field is read at every use, so the warning cannot be per-read.
45
- *
46
- * @param warned - the keys this caller already warned about.
47
- * @param warn - the warning sink (each package prefixes its own name).
48
- * @param name - the config key, for the warning.
49
- * @param value - the value as supplied, or its volatile reference.
50
- * @param fallback - the package default a corrected value falls back to.
51
- * @param min - the smallest value the engine can act on.
52
- * @param max - the largest value the carrier can hold, when bounded.
53
- * @returns the value the engine may use.
54
- */
55
- export declare function clampOnce(warned: Set<string>, warn: (message: string) => void, name: string, value: number | {
56
- get(): number | undefined;
57
- } | undefined, fallback: number, min: number, max?: number): number;
58
38
  //# sourceMappingURL=numeric.d.ts.map
@@ -36,6 +36,18 @@ export interface PersistedWriteSite {
36
36
  /** One line on what the file holds; the inventory reads as a whole. */
37
37
  readonly note: string;
38
38
  }
39
+ /** One root symbol of the inventory's own legend. `segments` is the path tail the symbol appends to
40
+ * its base (empty for the home itself), and `basedOn` names the symbol it extends. The gate
41
+ * `verify-write-inventory.mjs` checks those segments against the resolver's SOURCE, so the legend
42
+ * cannot drift from the code that resolves it. */
43
+ export interface WritePathSymbol {
44
+ /** Name of the core resolver that produces this root (or 'caller' for a caller-supplied path). */
45
+ readonly resolver: string;
46
+ /** Path segments appended to the base root, in order. */
47
+ readonly segments: readonly string[];
48
+ /** The symbol this one extends, when it is not rooted at the DSH home itself. */
49
+ readonly basedOn?: string;
50
+ }
39
51
  /** Instance keys held by writer services — the single source shared by the
40
52
  * claim site and the inventory row that declares it (rule N20 checks the row's
41
53
  * `instance` names one of these). */
@@ -58,6 +70,10 @@ export declare const INSTANCE_KEYS: {
58
70
  * @returns the sites, in file order.
59
71
  */
60
72
  export declare function persistedWriteSites(): readonly PersistedWriteSite[];
73
+ /** The inventory's symbol legend — the roots every declared path is written against, with the
74
+ * segments the gate checks against the resolver's source.
75
+ * @internal no production caller — the gates read the JSON asset directly. */
76
+ export declare function writePathSymbols(): Readonly<Record<string, WritePathSymbol>>;
61
77
  /** Sites serialized by the per-home instance claim, with their instance keys.
62
78
  * @internal S2.4 (PLAN 2026-09-16): no production caller — the arch guard
63
79
  * reads the JSON asset directly; the tests are the only in-tree consumers. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lmzhen/dsh-evolution-core",
3
3
  "description": "Shared stores, prompts, signals and lifecycle logic for the dsh-evolution plugin family (community build)",
4
- "version": "0.21.0",
4
+ "version": "0.22.0",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -1,137 +1,176 @@
1
- [
2
- {
3
- "id": "usage",
4
- "path": "<skillsRoot>/.usage.json",
5
- "writer": "evolution-core/src/usage.ts",
6
- "serializedBy": "transact",
7
- "marker": "transactIo(io, usageFile(root)",
8
- "state": [],
9
- "note": "Skill usage counters. RMW under the IO backend's cross-process lock; the reader side rebuilds from the events log."
1
+ {
2
+ "symbols": {
3
+ "<dshHome>": {
4
+ "resolver": "evolutionRoot",
5
+ "segments": []
6
+ },
7
+ "<evolutionHome>": {
8
+ "resolver": "evolutionHome",
9
+ "segments": [
10
+ "evolution"
11
+ ],
12
+ "basedOn": "<dshHome>"
13
+ },
14
+ "<skillsRoot>": {
15
+ "resolver": "skillsRoot",
16
+ "segments": [
17
+ "skills"
18
+ ],
19
+ "basedOn": "<dshHome>"
20
+ },
21
+ "<memoryRoot>": {
22
+ "resolver": "memoryRoot",
23
+ "segments": [
24
+ "memories"
25
+ ],
26
+ "basedOn": "<dshHome>"
27
+ },
28
+ "<historyRoot>": {
29
+ "resolver": "historyRoot",
30
+ "segments": [
31
+ ".history"
32
+ ],
33
+ "basedOn": "<skillsRoot>"
34
+ },
35
+ "<callerSupplied>": {
36
+ "resolver": "caller"
37
+ }
10
38
  },
11
- {
12
- "id": "suppressed",
13
- "path": "<skillsRoot>/.curator-suppressed.json",
14
- "writer": "evolution-core/src/usage.ts",
15
- "serializedBy": "transact",
16
- "marker": "transactIo(io, suppressedFile(root)",
17
- "state": [],
18
- "note": "Curator suppression list (archived bundled skills that must not re-seed)."
19
- },
20
- {
21
- "id": "mutations",
22
- "path": "<skillsRoot>/.mutations.json",
23
- "writer": "evolution-core/src/mutations.ts",
24
- "serializedBy": "transact",
25
- "marker": "transactIo(io, mutationsFile(root)",
26
- "state": [],
27
- "note": "Skill mutation audit records (bounded ring)."
28
- },
29
- {
30
- "id": "events",
31
- "path": "<evolutionHome>/evolution/events.json",
32
- "writer": "evolution-core/src/evolution-events.ts",
33
- "serializedBy": "transact",
34
- "marker": "transactIo(io, path",
35
- "state": [],
36
- "note": "The rc.68 evolution event log; feedback/skill-usage stats fold it."
37
- },
38
- {
39
- "id": "memory",
40
- "path": "<memoryRoot>/<target>.md",
41
- "writer": "evolution-core/src/memory-store.ts",
42
- "serializedBy": "transact",
43
- "marker": "transactIo(this.io, fileFor(this.root, target)",
44
- "state": [],
45
- "note": "Durable memory documents written by the memory tool."
46
- },
47
- {
48
- "id": "state",
49
- "path": "<evolutionHome>/<review|curator|pending>-state.json",
50
- "writer": "evolution-state-json/src/index.ts",
51
- "serializedBy": "transact",
52
- "marker": "transactIo(io(), join(root, file)",
53
- "state": [],
54
- "note": "The durable state stack (review/curator/pending). S4.4/O-6 moved its three warn/rewrite sets into the provider instance (QuarantineMemory), so no module-scope N12 store remains here."
55
- },
56
- {
57
- "id": "curator-reports",
58
- "path": "<evolutionHome>/reports/curator-*.json",
59
- "writer": "evolution-curator/src/index.ts",
60
- "serializedBy": "transact",
61
- "marker": "transactIo(this.io, reportsSweepLockTarget()",
62
- "state": [],
63
- "note": "Curator run reports + their retention sweep. The sweep is a multi-step list + delete-beyond-the-window over a DIRECTORY, so it has no per-file lock: v43 FLOW2-1 put it inside the IO backend's cross-process write lock (transactIo) on a per-home lock target, <evolutionHome>/reports/.retention (lock file <target>.lock, pid:token body). The former declaration here was instance-claim, which is in-process ONLY (instance-scope.ts is a module-scope Map). Two curator ROWS in one process still cannot sweep at once (the loser yields on that claim), and report writes are name-unique per runId. Trigger for the remaining gap: a second PROCESS sharing DSH_HOME now WAITS on the lock instead of sweeping concurrently, and on a backend without transact there is no lock at all, so the sweep degrades to best-effort idempotent deletes."
64
- },
65
- {
66
- "id": "runs-index",
67
- "path": "<evolutionHome>/runs.json",
68
- "writer": "evolution-core/src/run-registry.ts",
69
- "serializedBy": "transact",
70
- "marker": "transactIo(options.io, path",
71
- "state": [],
72
- "note": "The run registry's index (0.19.0/S2): what is in flight, how each run ended, where its report lives. Read-modify-write under the IO backend's cross-process lock, and it MERGES instead of overwriting — the desktop and the web plane share one DSH_HOME, so two hosts each know their own runs and this file is their shared view. resultRef only POINTS at the report; results never live here."
73
- },
74
- {
75
- "id": "maintain-reports",
76
- "path": "<evolutionHome>/reports/maintain-*.json",
77
- "writer": "evolution-commands/src/index.ts",
78
- "serializedBy": "transact",
79
- "marker": "transactIo(io, reportsSweepLockTarget()",
80
- "state": [],
81
- "note": "One report per maintenance run (0.19.0/S2), written once at settle by that run's owner under a name unique to the run, so the write itself needs no per-file lock. The retention sweep is multi-step (list the directory, then delete beyond the window) and takes the SAME per-home <evolutionHome>/reports/.retention lock the curator's sweep holds — one lock for one directory. On a backend without transact it degrades to idempotent best-effort deletes."
82
- },
83
- {
84
- "id": "skill-tree",
85
- "path": "<skillsRoot>/**",
86
- "writer": "evolution-core/src/skill-store.ts",
87
- "serializedBy": "write-lock",
88
- "marker": "LOCK_SUFFIX",
89
- "state": [],
90
- "note": "The skill tree itself. Per-target `<path>.lock` claims (pid:token body) guard every byte writer; destructive movers refuse a live lock."
91
- },
92
- {
93
- "id": "activity-log",
94
- "path": "<evolutionRoot>/activity.json",
95
- "writer": "evolution-activity/src/index.ts",
96
- "serializedBy": "transact",
97
- "marker": "transactIo(io, activityFile(root)",
98
- "state": [],
99
- "note": "The plan-applied activity sidecar. Declared by v46 S1.12: the file carried a transactional write that no inventory row vouched for, and the N20 reverse scan is what named it."
100
- },
101
- {
102
- "id": "skill-history-index",
103
- "path": "<historyRoot>/skills/<name>/index.json",
104
- "writer": "evolution-core/src/skill-history.ts",
105
- "serializedBy": "transact",
106
- "marker": "transactIo(io, file,",
107
- "state": [],
108
- "note": "One skill's recorded-version index (content chain + blob references). Declared by v46 S1.12: the replace path was already transactional but undeclared, so a regression to a plain write would have been invisible."
109
- },
110
- {
111
- "id": "skill-store-transact-primitive",
112
- "path": "<caller-supplied>",
113
- "writer": "evolution-core/src/skill-store.ts",
114
- "serializedBy": "transact",
115
- "marker": "transactIo(ioLike, path, task)",
116
- "state": [],
117
- "note": "The store's transaction helper: it prefers the backend's own transact and falls back to the seam's transactIo. The target is the caller's path — each durable target has its own row above; this row vouches for the primitive itself."
118
- },
119
- {
120
- "id": "feedback-events",
121
- "path": "<dshHome>/evolution/events.json",
122
- "writer": "evolution-feedback/src/index.ts",
123
- "serializedBy": "transact",
124
- "marker": "transactIo(io, eventsPath,",
125
- "state": [],
126
- "note": "Feedback appends to the family event log under the cross-process lock. Declared by v46 S1.12 (N20 reverse)."
127
- },
128
- {
129
- "id": "pending-archive",
130
- "path": "<stateHome>/pending-state-archive.json",
131
- "writer": "evolution-state-json/src/index.ts",
132
- "serializedBy": "transact",
133
- "marker": "transactIo(io(), pathOf(PENDING_ARCHIVE_FILE)",
134
- "state": [],
135
- "note": "The resolved-pending archive (the legacy merge target). The store already declared its record map; v46 S1.12 declared the archive append as well — the second site in one file is exactly what the reverse scan exists to find."
136
- }
137
- ]
39
+ "sites": [
40
+ {
41
+ "id": "usage",
42
+ "path": "<skillsRoot>/.usage.json",
43
+ "writer": "evolution-core/src/usage.ts",
44
+ "serializedBy": "transact",
45
+ "marker": "transactIo(io, usageFile(root)",
46
+ "state": [],
47
+ "note": "Skill usage counters. RMW under the IO backend's cross-process lock; the reader side rebuilds from the events log."
48
+ },
49
+ {
50
+ "id": "suppressed",
51
+ "path": "<skillsRoot>/.curator-suppressed.json",
52
+ "writer": "evolution-core/src/usage.ts",
53
+ "serializedBy": "transact",
54
+ "marker": "transactIo(io, suppressedFile(root)",
55
+ "state": [],
56
+ "note": "Curator suppression list (archived bundled skills that must not re-seed)."
57
+ },
58
+ {
59
+ "id": "mutations",
60
+ "path": "<skillsRoot>/.mutations.json",
61
+ "writer": "evolution-core/src/mutations.ts",
62
+ "serializedBy": "transact",
63
+ "marker": "transactIo(io, mutationsFile(root)",
64
+ "state": [],
65
+ "note": "Skill mutation audit records (bounded ring)."
66
+ },
67
+ {
68
+ "id": "events",
69
+ "path": "<evolutionHome>/events.json",
70
+ "writer": "evolution-core/src/evolution-events.ts",
71
+ "serializedBy": "transact",
72
+ "marker": "transactIo(io, path",
73
+ "state": [],
74
+ "note": "The rc.68 evolution event log; feedback/skill-usage stats fold it. Shares ONE file with the \"feedback-events\" site (two writers, one log under the IO lock)."
75
+ },
76
+ {
77
+ "id": "memory",
78
+ "path": "<memoryRoot>/<target>.md",
79
+ "writer": "evolution-core/src/memory-store.ts",
80
+ "serializedBy": "transact",
81
+ "marker": "transactIo(this.io, fileFor(this.root, target)",
82
+ "state": [],
83
+ "note": "Durable memory documents written by the memory tool."
84
+ },
85
+ {
86
+ "id": "state",
87
+ "path": "<evolutionHome>/<review|curator|pending>-state.json",
88
+ "writer": "evolution-state-json/src/index.ts",
89
+ "serializedBy": "transact",
90
+ "marker": "transactIo(io(), join(root, file)",
91
+ "state": [],
92
+ "note": "The durable state stack (review/curator/pending). S4.4/O-6 moved its three warn/rewrite sets into the provider instance (QuarantineMemory), so no module-scope N12 store remains here."
93
+ },
94
+ {
95
+ "id": "curator-reports",
96
+ "path": "<evolutionHome>/reports/curator-*.json",
97
+ "writer": "evolution-curator/src/index.ts",
98
+ "serializedBy": "transact",
99
+ "marker": "transactIo(this.io, reportsSweepLockTarget()",
100
+ "state": [],
101
+ "note": "Curator run reports + their retention sweep. The sweep is a multi-step list + delete-beyond-the-window over a DIRECTORY, so it has no per-file lock: v43 FLOW2-1 put it inside the IO backend's cross-process write lock (transactIo) on a per-home lock target, <evolutionHome>/reports/.retention (lock file <target>.lock, pid:token body). The former declaration here was instance-claim, which is in-process ONLY (instance-scope.ts is a module-scope Map). Two curator ROWS in one process still cannot sweep at once (the loser yields on that claim), and report writes are name-unique per runId. Trigger for the remaining gap: a second PROCESS sharing DSH_HOME now WAITS on the lock instead of sweeping concurrently, and on a backend without transact there is no lock at all, so the sweep degrades to best-effort idempotent deletes."
102
+ },
103
+ {
104
+ "id": "runs-index",
105
+ "path": "<evolutionHome>/runs.json",
106
+ "writer": "evolution-core/src/run-registry.ts",
107
+ "serializedBy": "transact",
108
+ "marker": "transactIo(options.io, path",
109
+ "state": [],
110
+ "note": "The run registry's index (0.19.0/S2): what is in flight, how each run ended, where its report lives. Read-modify-write under the IO backend's cross-process lock, and it MERGES instead of overwriting — the desktop and the web plane share one DSH_HOME, so two hosts each know their own runs and this file is their shared view. resultRef only POINTS at the report; results never live here."
111
+ },
112
+ {
113
+ "id": "maintain-reports",
114
+ "path": "<evolutionHome>/reports/maintain-*.json",
115
+ "writer": "evolution-commands/src/index.ts",
116
+ "serializedBy": "transact",
117
+ "marker": "transactIo(io, reportsSweepLockTarget()",
118
+ "state": [],
119
+ "note": "One report per maintenance run (0.19.0/S2), written once at settle by that run's owner under a name unique to the run, so the write itself needs no per-file lock. The retention sweep is multi-step (list the directory, then delete beyond the window) and takes the SAME per-home <evolutionHome>/reports/.retention lock the curator's sweep holds — one lock for one directory. On a backend without transact it degrades to idempotent best-effort deletes."
120
+ },
121
+ {
122
+ "id": "skill-tree",
123
+ "path": "<skillsRoot>/**",
124
+ "writer": "evolution-core/src/skill-store.ts",
125
+ "serializedBy": "write-lock",
126
+ "marker": "LOCK_SUFFIX",
127
+ "state": [],
128
+ "note": "The skill tree itself. Per-target `<path>.lock` claims (pid:token body) guard every byte writer; destructive movers refuse a live lock."
129
+ },
130
+ {
131
+ "id": "activity-log",
132
+ "path": "<evolutionHome>/activity.json",
133
+ "writer": "evolution-activity/src/index.ts",
134
+ "serializedBy": "transact",
135
+ "marker": "transactIo(io, activityFile(root)",
136
+ "state": [],
137
+ "note": "The plan-applied activity sidecar. Declared by v46 S1.12: the file carried a transactional write that no inventory row vouched for, and the N20 reverse scan is what named it."
138
+ },
139
+ {
140
+ "id": "skill-history-index",
141
+ "path": "<historyRoot>/skills/<name>/index.json",
142
+ "writer": "evolution-core/src/skill-history.ts",
143
+ "serializedBy": "transact",
144
+ "marker": "transactIo(io, file,",
145
+ "state": [],
146
+ "note": "One skill's recorded-version index (content chain + blob references). Declared by v46 S1.12: the replace path was already transactional but undeclared, so a regression to a plain write would have been invisible."
147
+ },
148
+ {
149
+ "id": "skill-store-transact-primitive",
150
+ "path": "<callerSupplied>",
151
+ "writer": "evolution-core/src/skill-store.ts",
152
+ "serializedBy": "transact",
153
+ "marker": "transactIo(ioLike, path, task)",
154
+ "state": [],
155
+ "note": "The store's transaction helper: it prefers the backend's own transact and falls back to the seam's transactIo. The target is the caller's path — each durable target has its own row above; this row vouches for the primitive itself."
156
+ },
157
+ {
158
+ "id": "feedback-events",
159
+ "path": "<evolutionHome>/events.json",
160
+ "writer": "evolution-feedback/src/index.ts",
161
+ "serializedBy": "transact",
162
+ "marker": "transactIo(io, eventsPath,",
163
+ "state": [],
164
+ "note": "Feedback appends to the family event log under the cross-process lock. Declared by v46 S1.12 (N20 reverse). Shares ONE file with the \"events\" site (two writers, one log under the IO lock)."
165
+ },
166
+ {
167
+ "id": "pending-archive",
168
+ "path": "<evolutionHome>/pending-state-archive.json",
169
+ "writer": "evolution-state-json/src/index.ts",
170
+ "serializedBy": "transact",
171
+ "marker": "transactIo(io(), pathOf(PENDING_ARCHIVE_FILE)",
172
+ "state": [],
173
+ "note": "The resolved-pending archive (the legacy merge target). The store already declared its record map; v46 S1.12 declared the archive append as well — the second site in one file is exactly what the reverse scan exists to find."
174
+ }
175
+ ]
176
+ }