opencode-swarm 7.117.0 → 7.118.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/dist/cli/{config-doctor-htzxe394.js → config-doctor-p73yfdz0.js} +2 -2
- package/dist/cli/{curator-fr2m5hna.js → curator-920swpnk.js} +10 -10
- package/dist/cli/{curator-llm-factory-m0kvna8n.js → curator-llm-factory-kqt1f4sw.js} +10 -10
- package/dist/cli/{guardrail-explain-xjgk822d.js → guardrail-explain-bycz51b0.js} +11 -11
- package/dist/cli/{guardrail-log-dzbqcgz9.js → guardrail-log-qjqgg7pz.js} +3 -3
- package/dist/cli/{hive-promoter-zpk5jmx0.js → hive-promoter-w9pgfs6w.js} +15 -10
- package/dist/cli/{index-m7hc7nn7.js → index-11q1t6g0.js} +1 -1
- package/dist/cli/{index-vkdr12ek.js → index-34tsqvqb.js} +1894 -1327
- package/dist/cli/{index-n3sm9ewn.js → index-470tykwx.js} +2 -2
- package/dist/cli/{index-dc3j6jmk.js → index-5hze8ztj.js} +10 -21
- package/dist/cli/{index-m43zgtjn.js → index-bcg89pt8.js} +1 -1
- package/dist/cli/{index-8j5d3ytd.js → index-g2wrs7jy.js} +1 -1
- package/dist/cli/{index-af94ke83.js → index-jbdgpqpj.js} +3 -3
- package/dist/cli/{index-pgtqxat4.js → index-k83hebh1.js} +12 -12
- package/dist/cli/{index-nvc0nsvg.js → index-pdk1n8yh.js} +2 -0
- package/dist/cli/{index-c9m5skgs.js → index-pjhfe7c3.js} +2 -2
- package/dist/cli/{index-aws8yhwq.js → index-qx5z58a7.js} +19 -31
- package/dist/cli/{index-hskmya46.js → index-sb9x6xjj.js} +3 -3
- package/dist/cli/{index-4f4zv8pb.js → index-wpgv6hht.js} +25 -1
- package/dist/cli/index.js +10 -10
- package/dist/cli/{knowledge-escalator-bmk725p4.js → knowledge-escalator-gt1q632m.js} +4 -4
- package/dist/cli/{knowledge-events-px14n75v.js → knowledge-events-0bjdtdsb.js} +2 -2
- package/dist/cli/{knowledge-store-z9znvtn2.js → knowledge-store-fm2300xf.js} +7 -2
- package/dist/cli/{knowledge-validator-7r5t821g.js → knowledge-validator-91kx3eqv.js} +3 -3
- package/dist/cli/{schema-bqn7g3ez.js → schema-7c484wjz.js} +1 -1
- package/dist/cli/{skill-generator-4d3w8sqt.js → skill-generator-t47xyfps.js} +5 -5
- package/dist/commands/promote.d.ts +3 -0
- package/dist/commands/registry.d.ts +2 -2
- package/dist/config/schema.d.ts +8 -4
- package/dist/hooks/curator-postmortem.d.ts +1 -1
- package/dist/hooks/hive-policy.d.ts +81 -0
- package/dist/hooks/hive-promoter.d.ts +147 -28
- package/dist/hooks/hive-transaction.d.ts +113 -0
- package/dist/hooks/knowledge-events.d.ts +2 -1
- package/dist/hooks/knowledge-store.d.ts +6 -5
- package/dist/hooks/knowledge-types.d.ts +87 -0
- package/dist/index.js +325 -315
- package/dist/knowledge/hive-paths.d.ts +51 -0
- package/dist/services/knowledge-diagnostics.d.ts +18 -0
- package/package.json +1 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One promotion policy evaluator used by automatic promotion AND the manual
|
|
3
|
+
* `/swarm promote` command (issue #1847 §4).
|
|
4
|
+
*
|
|
5
|
+
* Previously, automatic promotion went through `isHiveEligible` (3 routes:
|
|
6
|
+
* hive_eligible+3 phases / hive-fast-track tag / age) while manual promotion
|
|
7
|
+
* (`promoteToHive` / `promoteFromSwarm`) bypassed policy entirely — an exact
|
|
8
|
+
* entry id was effectively authorization to skip the gate, with no durable
|
|
9
|
+
* override record. This module is the single policy function both paths share.
|
|
10
|
+
*
|
|
11
|
+
* Manual promotion that fails the policy MUST either be blocked, or proceed
|
|
12
|
+
* only with an explicit `--force` override that records a durable, audited
|
|
13
|
+
* override (actor, reason, source revision, failed gates). An exact entry id
|
|
14
|
+
* alone is NEVER authorization to bypass policy.
|
|
15
|
+
*
|
|
16
|
+
* Conservative application evidence (#1847 §2, AC5/AC6): the
|
|
17
|
+
* `validated_terminal_applications` gate counts ONLY validated terminal receipts
|
|
18
|
+
* tied to a real retrieval trace + result membership. Legacy records carry no
|
|
19
|
+
* evidence and receive NO synthetic credit. Until #1849 produces real receipts,
|
|
20
|
+
* the configured thresholds default to 0, so the gate is satisfied by absence
|
|
21
|
+
* (it neither credits nor blocks) and current behavior is preserved. Operators
|
|
22
|
+
* raise the thresholds to activate application-evidence gating.
|
|
23
|
+
*
|
|
24
|
+
* This module performs NO I/O and holds NO module-level mutable state
|
|
25
|
+
* (invariant 8). It is NOT imported on the plugin-init path (invariant 1).
|
|
26
|
+
*/
|
|
27
|
+
import type { KnowledgeConfig, PromotionEvidenceRecord, SwarmKnowledgeEntry } from './knowledge-types.js';
|
|
28
|
+
/** A single named policy gate and whether it passed. */
|
|
29
|
+
export interface PromotionPolicyGate {
|
|
30
|
+
name: string;
|
|
31
|
+
passed: boolean;
|
|
32
|
+
detail: string;
|
|
33
|
+
}
|
|
34
|
+
export interface PromotionPolicyInput {
|
|
35
|
+
entry: SwarmKnowledgeEntry;
|
|
36
|
+
config: KnowledgeConfig;
|
|
37
|
+
/**
|
|
38
|
+
* Validated terminal-application evidence for this entry's lesson
|
|
39
|
+
* (near-duplicate-clustered). Empty for legacy/no-evidence records. The
|
|
40
|
+
* promoter loads this OUTSIDE the hive transaction and passes it in.
|
|
41
|
+
*/
|
|
42
|
+
evidence: PromotionEvidenceRecord[];
|
|
43
|
+
}
|
|
44
|
+
export interface PromotionPolicyDecision {
|
|
45
|
+
eligible: boolean;
|
|
46
|
+
/** Every gate and whether it passed — surfaced in diagnostics + override audit. */
|
|
47
|
+
gates: PromotionPolicyGate[];
|
|
48
|
+
/** Human-readable summary of the deciding gate. */
|
|
49
|
+
reason: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Evaluate the one promotion policy. Used by auto promotion and manual promote.
|
|
53
|
+
* Returns the full gates list so diagnostics can explain each failed condition
|
|
54
|
+
* (AC: "Promotion diagnostics explain eligibility failures and lineage").
|
|
55
|
+
*/
|
|
56
|
+
export declare function evaluatePromotionPolicy(input: PromotionPolicyInput): PromotionPolicyDecision;
|
|
57
|
+
/**
|
|
58
|
+
* The three historical eligibility routes, preserved verbatim from the
|
|
59
|
+
* pre-#1847 `isHiveEligible`. Exposed so `isHiveEligible` can delegate to it
|
|
60
|
+
* (M1 fix — keeps the public export + its test consumers working) and so the
|
|
61
|
+
* policy gate above can report which route satisfied it.
|
|
62
|
+
*/
|
|
63
|
+
export declare function describeEligibilityRoute(entry: SwarmKnowledgeEntry, autoPromoteDays: number): {
|
|
64
|
+
passed: boolean;
|
|
65
|
+
detail: string;
|
|
66
|
+
};
|
|
67
|
+
/** Names of override-audit fields the promoter fills when a manual --force fires. */
|
|
68
|
+
export interface OverrideAuditDetail {
|
|
69
|
+
actor: 'manual-override';
|
|
70
|
+
reason: string;
|
|
71
|
+
failedGates: PromotionPolicyGate[];
|
|
72
|
+
entryId: string;
|
|
73
|
+
lesson: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the failed-gates summary for a manual override, given a decision that
|
|
77
|
+
* was NOT eligible. Used by the promoter to populate `lineage.override_failed_gates`.
|
|
78
|
+
*/
|
|
79
|
+
export declare function failedGateNames(decision: PromotionPolicyDecision): string[];
|
|
80
|
+
/** Count distinct canonical cohorts among promotion evidence (for diagnostics). */
|
|
81
|
+
export declare function countDistinctEvidenceCohorts(evidence: PromotionEvidenceRecord[]): number;
|
|
@@ -1,59 +1,178 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Hive promoter for opencode-swarm v6.17 two-tier knowledge system.
|
|
3
|
+
*
|
|
4
|
+
* #1847 (transactional hive promotion): every hive write is routed through ONE
|
|
5
|
+
* global cross-process transaction (`transactHiveStore` in `./hive-transaction`).
|
|
6
|
+
* The transaction holds the hive directory lock across read → normalize →
|
|
7
|
+
* eligibility → canonical-cohort counting → dedup/merge → append/update →
|
|
8
|
+
* source confirmation → cap → staged audit → atomic persist. No caller reads
|
|
9
|
+
* the hive, makes a promotion decision, and later calls a separate unlocked
|
|
10
|
+
* write API.
|
|
11
|
+
*
|
|
12
|
+
* Canonical project identity (#1846): cross-project confirmations key on the
|
|
13
|
+
* canonical `cohort_id` from `resolveCohortId`, not the worktree `project_name`.
|
|
14
|
+
* Sibling worktrees and remote aliases of one repository count as one project.
|
|
15
|
+
*
|
|
16
|
+
* Lineage (#1847 §3): promoted entries retain source entry id, source cohort,
|
|
17
|
+
* promotion event id, and actor. Manual promotion that fails policy proceeds
|
|
18
|
+
* only with an explicit `--force` override that records a durable audit entry;
|
|
19
|
+
* an exact entry id alone is never authorization to bypass policy.
|
|
20
|
+
*
|
|
21
|
+
* This module is NOT imported on the plugin-init path (invariant 1): promotion
|
|
22
|
+
* runs only on the lazy `/swarm promote`, close, curate, and postmortem paths.
|
|
23
|
+
*/
|
|
24
|
+
import { resolveCohortId } from '../knowledge/cohort-identity.js';
|
|
2
25
|
import { appendCuratorRecommendation, readCuratorSummary } from './curator.js';
|
|
3
|
-
import
|
|
4
|
-
|
|
26
|
+
import { transactHiveStore } from './hive-transaction.js';
|
|
27
|
+
import type { KnowledgeConfig, ProjectConfirmationRecord, PromotionEvidenceRecord, SwarmKnowledgeEntry } from './knowledge-types.js';
|
|
28
|
+
import { validateLesson } from './knowledge-validator.js';
|
|
29
|
+
/** Hive promotion summary for curator state. */
|
|
5
30
|
export interface HivePromotionSummary {
|
|
6
31
|
timestamp: string;
|
|
7
32
|
new_promotions: number;
|
|
8
33
|
encounters_incremented: number;
|
|
9
34
|
advancements: number;
|
|
10
35
|
total_hive_entries: number;
|
|
36
|
+
/** #1847: per-entry policy diagnostics for operator visibility. */
|
|
37
|
+
diagnostics?: string[];
|
|
11
38
|
}
|
|
12
39
|
/**
|
|
13
|
-
* Check whether a swarm
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @param autoPromoteDays - Number of days before age-based promotion kicks in
|
|
21
|
-
* @returns true if the entry is eligible for hive promotion
|
|
40
|
+
* Check whether a swarm entry is eligible for hive promotion via the historical
|
|
41
|
+
* 3 routes. Kept as a thin wrapper delegating to the canonical policy route
|
|
42
|
+
* description (M1 fix, #1847) so existing test consumers
|
|
43
|
+
* (`hive-promoter-inactive.test.ts`, `close.test.ts` mock) keep working.
|
|
44
|
+
*
|
|
45
|
+
* This is ONE of the gates inside {@link evaluatePromotionPolicy}; it is not
|
|
46
|
+
* the whole policy. New callers should use `evaluatePromotionPolicy`.
|
|
22
47
|
*/
|
|
23
48
|
export declare function isHiveEligible(entry: SwarmKnowledgeEntry, autoPromoteDays: number): boolean;
|
|
24
49
|
/**
|
|
25
|
-
*
|
|
26
|
-
* Also updates existing hive entries with new project confirmations.
|
|
27
|
-
* Returns a summary of the promotion activity for curator state.
|
|
50
|
+
* Count distinct projects/cohort identities in a hive entry's confirmed_by.
|
|
28
51
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
52
|
+
* #1847: dedup by canonical `cohort_id` (sibling worktrees + remote aliases of
|
|
53
|
+
* one repo count as one project). Legacy records written before #1847 lack
|
|
54
|
+
* `cohort_id`; for those, fall back to `project_name` — but two records sharing
|
|
55
|
+
* a `cohort_id` are NEVER counted as distinct. This means AC3 ("sibling
|
|
56
|
+
* worktrees count as one project") is enforced for confirmations written from
|
|
57
|
+
* this PR forward; legacy confirmations remain `project_name`-keyed and are NOT
|
|
58
|
+
* retroactively re-counted (consistent with the no-broad-rewrite non-goal).
|
|
32
59
|
*/
|
|
33
|
-
export declare function
|
|
60
|
+
export declare function countDistinctProjects(confirmedBy: ProjectConfirmationRecord[]): number;
|
|
61
|
+
/**
|
|
62
|
+
* Main promotion logic: checks swarm entries and promotes eligible ones to hive
|
|
63
|
+
* inside ONE cross-process transaction. Also updates existing hive entries with
|
|
64
|
+
* new canonical-cohort confirmations. Returns a summary for curator state.
|
|
65
|
+
*
|
|
66
|
+
* `directory` is required (#1847) to resolve the canonical cohort identity
|
|
67
|
+
* (#1846) for cross-project distinctness.
|
|
68
|
+
*
|
|
69
|
+
* @note The 'hive-fast-track' tag is privileged — it bypasses the 3-phase
|
|
70
|
+
* confirmation requirement inside the eligibility_route gate. It should only
|
|
71
|
+
* be set by authorized tooling (inferTags() never produces it automatically).
|
|
72
|
+
*/
|
|
73
|
+
export declare function checkHivePromotions(swarmEntries: SwarmKnowledgeEntry[], config: KnowledgeConfig, directory: string): Promise<HivePromotionSummary>;
|
|
34
74
|
export declare const _internals: {
|
|
35
75
|
readSwarmEntries: (directory: string) => Promise<SwarmKnowledgeEntry[]>;
|
|
36
76
|
checkHivePromotions: typeof checkHivePromotions;
|
|
37
77
|
readCuratorSummary: typeof readCuratorSummary;
|
|
38
78
|
appendCuratorRecommendation: typeof appendCuratorRecommendation;
|
|
79
|
+
resolveCohortId: typeof resolveCohortId;
|
|
80
|
+
transactHiveStore: typeof transactHiveStore;
|
|
81
|
+
validateLesson: typeof validateLesson;
|
|
82
|
+
/** Loads validated terminal-application evidence per swarm entry id. Empty
|
|
83
|
+
* until #1849 produces real receipts (conservative: no synthetic credit). */
|
|
84
|
+
loadPromotionEvidence: (_swarmEntries: SwarmKnowledgeEntry[]) => Promise<Record<string, PromotionEvidenceRecord[]>>;
|
|
85
|
+
/** Loads the default KnowledgeConfig (schema defaults) for manual promotion
|
|
86
|
+
* paths when the command did not load one. */
|
|
87
|
+
loadDefaultKnowledgeConfig: () => {
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
swarm_max_entries: number;
|
|
90
|
+
hive_max_entries: number;
|
|
91
|
+
auto_promote_days: number;
|
|
92
|
+
max_inject_count: number;
|
|
93
|
+
delegate_max_inject_count: number;
|
|
94
|
+
inject_char_budget: number;
|
|
95
|
+
max_lesson_display_chars: number;
|
|
96
|
+
dedup_threshold: number;
|
|
97
|
+
scope_filter: string[];
|
|
98
|
+
hive_enabled: boolean;
|
|
99
|
+
rejected_max_entries: number;
|
|
100
|
+
validation_enabled: boolean;
|
|
101
|
+
evergreen_confidence: number;
|
|
102
|
+
evergreen_utility: number;
|
|
103
|
+
low_utility_threshold: number;
|
|
104
|
+
min_retrievals_for_utility: number;
|
|
105
|
+
schema_version: number;
|
|
106
|
+
directive_min_confidence: number;
|
|
107
|
+
same_project_weight: number;
|
|
108
|
+
cross_project_weight: number;
|
|
109
|
+
min_encounter_score: number;
|
|
110
|
+
initial_encounter_score: number;
|
|
111
|
+
encounter_increment: number;
|
|
112
|
+
max_encounter_score: number;
|
|
113
|
+
default_max_phases: number;
|
|
114
|
+
todo_max_phases: number;
|
|
115
|
+
sweep_enabled: boolean;
|
|
116
|
+
confidence_floor_action: "none" | "demote" | "quarantine";
|
|
117
|
+
confidence_floor_min_outcomes: number;
|
|
118
|
+
confidence_floor_signal_threshold: number;
|
|
119
|
+
contradiction_threshold_action: "quarantine" | "tag_only";
|
|
120
|
+
contradiction_quarantine_threshold: number;
|
|
121
|
+
contradiction_quarantine_window_days: number;
|
|
122
|
+
promoted_demotion_min_negative_phases: number;
|
|
123
|
+
promoted_demotion_signal_threshold: number;
|
|
124
|
+
promotion_min_terminal_applications: number;
|
|
125
|
+
promotion_min_distinct_cohorts: number;
|
|
126
|
+
realtime_learning_nudge: {
|
|
127
|
+
enabled: boolean;
|
|
128
|
+
first_after_tool_calls: number;
|
|
129
|
+
repeat_after_tool_calls: number;
|
|
130
|
+
};
|
|
131
|
+
enrichment: {
|
|
132
|
+
max_calls_per_day: number;
|
|
133
|
+
quota_window: "utc" | "local";
|
|
134
|
+
batch_size?: number | undefined;
|
|
135
|
+
};
|
|
136
|
+
context_budget_threshold?: number | undefined;
|
|
137
|
+
retrieval?: {
|
|
138
|
+
mmr_lambda: number;
|
|
139
|
+
cold_start_bonus: number;
|
|
140
|
+
cold_start_max_age_phases: number;
|
|
141
|
+
synonym_min_cooccurrence: number;
|
|
142
|
+
synonym_map_max_pairs: number;
|
|
143
|
+
} | undefined;
|
|
144
|
+
};
|
|
39
145
|
};
|
|
40
146
|
/**
|
|
41
147
|
* Create a hook that promotes swarm entries to the hive.
|
|
42
148
|
* The hook fires unconditionally - the caller decides when to invoke it.
|
|
43
149
|
*/
|
|
44
150
|
export declare function createHivePromoterHook(directory: string, config: KnowledgeConfig): (input: unknown, output: unknown) => Promise<void>;
|
|
151
|
+
/** Options for manual promotion (override semantics, #1847 §4). */
|
|
152
|
+
export interface ManualPromotionOptions {
|
|
153
|
+
force?: boolean;
|
|
154
|
+
reason?: string;
|
|
155
|
+
}
|
|
45
156
|
/**
|
|
46
157
|
* Promote a lesson directly to the hive (manual promotion).
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
158
|
+
*
|
|
159
|
+
* Runs the one policy evaluator. On a policy FAIL:
|
|
160
|
+
* - without `force` → returns a diagnostic string listing the failed gates
|
|
161
|
+
* (does NOT promote);
|
|
162
|
+
* - with `force` + `reason` → promotes and writes a durable override audit
|
|
163
|
+
* record (actor='manual-override', failed gates recorded) inside the
|
|
164
|
+
* transaction.
|
|
165
|
+
* On a policy PASS → promotes with actor='manual'.
|
|
166
|
+
*
|
|
167
|
+
* An exact entry id / direct text alone is NEVER authorization to bypass policy.
|
|
51
168
|
*/
|
|
52
|
-
export declare function promoteToHive(directory: string, lesson: string, category?: string): Promise<string>;
|
|
169
|
+
export declare function promoteToHive(directory: string, lesson: string, category?: string, options?: ManualPromotionOptions, config?: KnowledgeConfig): Promise<string>;
|
|
53
170
|
/**
|
|
54
171
|
* Promote a lesson from swarm knowledge to hive.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
172
|
+
*
|
|
173
|
+
* Snapshots the swarm read OUTSIDE the hive transaction (the swarm store is
|
|
174
|
+
* read-only here — no two-phase locking needed). Then runs the one policy
|
|
175
|
+
* evaluator + override semantics inside the hive transaction.
|
|
58
176
|
*/
|
|
59
|
-
export declare function promoteFromSwarm(directory: string, lessonId: string): Promise<string>;
|
|
177
|
+
export declare function promoteFromSwarm(directory: string, lessonId: string, options?: ManualPromotionOptions, config?: KnowledgeConfig): Promise<string>;
|
|
178
|
+
export { resolveHiveKnowledgePath } from './knowledge-store.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One global, cross-process transaction primitive for hive storage (issue #1847 §1).
|
|
3
|
+
*
|
|
4
|
+
* Problem: hive promotion previously performed read → N× append → batch rewrite
|
|
5
|
+
* → cap enforcement as FOUR separate directory-lock acquisitions, with unlocked
|
|
6
|
+
* read/validate/dedup windows between them. Because the hive store
|
|
7
|
+
* (`shared-learnings.jsonl`) is a shared, cross-project, cross-process file,
|
|
8
|
+
* two opencode-swarm sessions could each read the same snapshot and one's
|
|
9
|
+
* rewrite silently dropped the other's entries (TOCTOU lost update — #1604).
|
|
10
|
+
*
|
|
11
|
+
* This module routes every hive writer through ONE critical section that spans,
|
|
12
|
+
* under a single directory lock:
|
|
13
|
+
* 1. read + mixed-schema normalize;
|
|
14
|
+
* 2. the caller's mutate (eligibility, canonical project counting, dedup,
|
|
15
|
+
* merge decision, append/update, source confirmation);
|
|
16
|
+
* 3. validate-before-commit;
|
|
17
|
+
* 4. priority-aware cap enforcement (in the same closure, not a separate call);
|
|
18
|
+
* 5. staged audit/reject appends (raw, under the held lock — never via
|
|
19
|
+
* `appendHiveKnowledgeEvent`, which would re-enter the directory lock and
|
|
20
|
+
* deadlock — see the curator precedent at `curator.ts:1966-1969`);
|
|
21
|
+
* 6. atomic persistence (temp + rename).
|
|
22
|
+
*
|
|
23
|
+
* Lock contract (AGENTS.md invariants 3, 8): `stale: 5000` to MATCH every other
|
|
24
|
+
* hive writer (`appendKnowledge`, `rewriteKnowledge`, `transactKnowledge`,
|
|
25
|
+
* `knowledge-application.ts`, `knowledge-escalator.ts`). proper-lockfile's stale
|
|
26
|
+
* mechanism is preemptive — a process that cannot acquire the lock and sees its
|
|
27
|
+
* mtime older than its OWN stale threshold force-breaks it. Using a longer stale
|
|
28
|
+
* here would let a concurrent 5s writer break this transaction mid-flight and
|
|
29
|
+
* tear the file, which is exactly the bug this PR fixes. The held closure is
|
|
30
|
+
* kept fast (in-memory mutate + one atomic write + raw appends) so 5s is never
|
|
31
|
+
* exceeded; all heavy work (git cohort resolution, evidence rollup, near-
|
|
32
|
+
* duplicate precompute) runs OUTSIDE the lock and is passed in via HiveTxContext.
|
|
33
|
+
*
|
|
34
|
+
* This module holds NO module-level mutable state (invariant 8) and is NOT
|
|
35
|
+
* imported on the plugin-init path (invariant 1).
|
|
36
|
+
*/
|
|
37
|
+
import * as path from 'node:path';
|
|
38
|
+
import lockfile from 'proper-lockfile';
|
|
39
|
+
import { atomicWriteFile } from '../evidence/task-file.js';
|
|
40
|
+
import { resolveHiveDataDir, resolveHiveEventsPath, resolveHiveKnowledgePath, resolveHiveRejectedPath } from '../knowledge/hive-paths.js';
|
|
41
|
+
import { readKnowledge, selectKnowledgeCapSurvivors } from './knowledge-store.js';
|
|
42
|
+
import type { HiveKnowledgeEntry, RejectedLesson } from './knowledge-types.js';
|
|
43
|
+
/**
|
|
44
|
+
* Context handed to a hive mutation closure. All expensive pre-work (cohort
|
|
45
|
+
* resolution, evidence loading, near-duplicate indexing) is performed by the
|
|
46
|
+
* caller OUTSIDE the lock and injected here so the held closure stays fast.
|
|
47
|
+
*/
|
|
48
|
+
export interface HiveTxContext {
|
|
49
|
+
/** Current hive entries (read + normalized under the lock). */
|
|
50
|
+
entries: HiveKnowledgeEntry[];
|
|
51
|
+
}
|
|
52
|
+
/** A pre-serialized knowledge-event line to append to the hive audit log. */
|
|
53
|
+
export interface HiveAuditEntry {
|
|
54
|
+
line: string;
|
|
55
|
+
}
|
|
56
|
+
/** The result a mutation closure returns. */
|
|
57
|
+
export type HiveMutationOutcome<T> = {
|
|
58
|
+
kind: 'commit';
|
|
59
|
+
entries: HiveKnowledgeEntry[];
|
|
60
|
+
/** Hive cap to enforce inside the same closure (omit for no cap). */
|
|
61
|
+
maxEntries?: number;
|
|
62
|
+
/** Rejected lessons to append to the hive rejected log under the lock. */
|
|
63
|
+
rejects?: RejectedLesson[];
|
|
64
|
+
/** Audit lines to append to the hive events log under the lock. */
|
|
65
|
+
audit?: HiveAuditEntry[];
|
|
66
|
+
/** Caller return value surfaced back through HiveTransactionResult. */
|
|
67
|
+
return: T;
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'noop';
|
|
70
|
+
return: T;
|
|
71
|
+
};
|
|
72
|
+
export interface HiveTransactionResult<T> {
|
|
73
|
+
/** True iff the hive file was rewritten within the transaction. */
|
|
74
|
+
committed: boolean;
|
|
75
|
+
/**
|
|
76
|
+
* The caller's return value. Present when `committed` is true OR the mutate
|
|
77
|
+
* closure returned a `noop` outcome. Undefined on lock-acquire / mkdir /
|
|
78
|
+
* validation-before-commit failure (F-004/PRR-1): callers MUST check
|
|
79
|
+
* `committed` (or `return !== undefined`) before dereferencing it.
|
|
80
|
+
*/
|
|
81
|
+
return: T | undefined;
|
|
82
|
+
/** Human-readable diagnostics (lock timeout, validation failure, etc.). */
|
|
83
|
+
diagnostics: string[];
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run `mutate` against the hive store inside one cross-process transaction.
|
|
87
|
+
*
|
|
88
|
+
* On lock-acquire failure or validation failure the prior hive file is left
|
|
89
|
+
* intact (the atomic write is not performed) and `committed` is false; the
|
|
90
|
+
* function never hangs. `mutate` receives the current (normalized) entries and
|
|
91
|
+
* returns either a `commit` (new entries + optional cap/rejects/audit) or a
|
|
92
|
+
* `noop`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function transactHiveStore<T>(mutate: (ctx: HiveTxContext) => Promise<HiveMutationOutcome<T>> | HiveMutationOutcome<T>): Promise<HiveTransactionResult<T>>;
|
|
95
|
+
/** Path to the hive events log (re-exported for callers building audit lines). */
|
|
96
|
+
export { resolveHiveEventsPath };
|
|
97
|
+
export declare const HIVE_TXN_LOCK_STALE_MS = 5000;
|
|
98
|
+
/**
|
|
99
|
+
* Test-only DI seam (AGENTS.md invariant 7). Tests inject a fake lockfile /
|
|
100
|
+
* readers / writers rather than `mock.module`-ing the consumers, which leaks
|
|
101
|
+
* across test files in Bun's shared runner.
|
|
102
|
+
*/
|
|
103
|
+
export declare const _internals: {
|
|
104
|
+
lockfile: typeof lockfile;
|
|
105
|
+
readKnowledge: typeof readKnowledge;
|
|
106
|
+
atomicWriteFile: typeof atomicWriteFile;
|
|
107
|
+
selectKnowledgeCapSurvivors: typeof selectKnowledgeCapSurvivors;
|
|
108
|
+
resolveHiveDataDir: typeof resolveHiveDataDir;
|
|
109
|
+
resolveHiveKnowledgePath: typeof resolveHiveKnowledgePath;
|
|
110
|
+
resolveHiveRejectedPath: typeof resolveHiveRejectedPath;
|
|
111
|
+
resolveHiveEventsPath: typeof resolveHiveEventsPath;
|
|
112
|
+
path: typeof path;
|
|
113
|
+
};
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* `directory` argument injected by `createSwarmTool` / hook constructors — never
|
|
20
20
|
* from the process working directory.
|
|
21
21
|
*/
|
|
22
|
+
import { resolveHiveEventsPath as resolveHiveEventsPathImpl } from '../knowledge/hive-paths.js';
|
|
22
23
|
import type { ConfidenceFloorOptions } from './knowledge-store.js';
|
|
23
24
|
import type { KnowledgeApplicationRecord, RetrievalOutcome } from './knowledge-types.js';
|
|
24
25
|
/** Current event-log record schema version. Bump when the on-disk shape changes. */
|
|
@@ -182,7 +183,7 @@ export declare const RECEIPT_EVENT_TYPES: ReadonlySet<string>;
|
|
|
182
183
|
export declare function resolveKnowledgeEventsPath(directory: string): string;
|
|
183
184
|
/** Returns the knowledge-counter-baseline.json path (link-aware). */
|
|
184
185
|
export declare function resolveKnowledgeCounterBaselinePath(directory: string): string;
|
|
185
|
-
export declare
|
|
186
|
+
export declare const resolveHiveEventsPath: typeof resolveHiveEventsPathImpl;
|
|
186
187
|
/** Returns the knowledge-application.jsonl path for legacy v2 audit records (link-aware). */
|
|
187
188
|
export declare function resolveLegacyApplicationLogPath(directory: string): string;
|
|
188
189
|
/** Generate a fresh trace id. One per retrieval; receipts reference it. */
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/** Core storage layer for the opencode-swarm v6.17 two-tier knowledge system. */
|
|
2
|
+
import { resolveHiveDataDir, resolveHiveEventsPath as resolveHiveEventsPathImpl, resolveHiveKnowledgePath as resolveHiveKnowledgePathImpl, resolveHiveRejectedPath as resolveHiveRejectedPathImpl } from '../knowledge/hive-paths.js';
|
|
2
3
|
import type { KnowledgeEntryBase, RejectedLesson, RetrievalOutcome } from './knowledge-types.js';
|
|
3
4
|
export declare function getPlatformConfigDir(): string;
|
|
4
5
|
export declare function resolveSwarmKnowledgePath(directory: string): string;
|
|
5
6
|
export declare function resolveSwarmRejectedPath(directory: string): string;
|
|
6
7
|
export declare function resolveSwarmRetractionsPath(directory: string): string;
|
|
7
|
-
export declare
|
|
8
|
-
export declare
|
|
9
|
-
export declare
|
|
8
|
+
export declare const resolveHiveKnowledgePath: typeof resolveHiveKnowledgePathImpl;
|
|
9
|
+
export declare const resolveHiveRejectedPath: typeof resolveHiveRejectedPathImpl;
|
|
10
|
+
export declare const resolveHiveEventsPath: typeof resolveHiveEventsPathImpl;
|
|
11
|
+
export { resolveHiveDataDir };
|
|
10
12
|
declare function parseKnowledgeContent<T>(content: string, max: number): T[];
|
|
11
13
|
export declare function readKnowledge<T>(filePath: string, maxEntries?: number): Promise<T[]>;
|
|
12
14
|
export declare function normalizeEntry<T>(raw: T): T;
|
|
@@ -29,7 +31,7 @@ export declare function transactKnowledge<T>(filePath: string, mutate: (entries:
|
|
|
29
31
|
export declare function getArchivedKnowledgeIds(directory: string): Promise<Set<string>>;
|
|
30
32
|
export declare function appendKnowledgeWithCapEnforcement<T>(filePath: string, entry: T, maxEntries: number): Promise<boolean>;
|
|
31
33
|
export declare function enforceKnowledgeCap<T>(filePath: string, maxEntries: number): Promise<void>;
|
|
32
|
-
declare function selectKnowledgeCapSurvivors<T>(entries: T[], maxEntries: number): T[];
|
|
34
|
+
export declare function selectKnowledgeCapSurvivors<T>(entries: T[], maxEntries: number): T[];
|
|
33
35
|
export interface SweepResult {
|
|
34
36
|
scanned: number;
|
|
35
37
|
aged: number;
|
|
@@ -136,4 +138,3 @@ export declare const _internals: {
|
|
|
136
138
|
bumpKnowledgeConfidenceBatch: typeof bumpKnowledgeConfidenceBatch;
|
|
137
139
|
getArchivedKnowledgeIds: typeof getArchivedKnowledgeIds;
|
|
138
140
|
};
|
|
139
|
-
export {};
|
|
@@ -7,6 +7,15 @@ export interface PhaseConfirmationRecord {
|
|
|
7
7
|
}
|
|
8
8
|
export interface ProjectConfirmationRecord {
|
|
9
9
|
project_name: string;
|
|
10
|
+
/**
|
|
11
|
+
* Canonical cohort id (issue #1847) — the identity used for cross-project
|
|
12
|
+
* distinctness, from `resolveCohortId` (#1846). Sibling worktrees and remote
|
|
13
|
+
* aliases of one repository share one `cohort_id`, so they count as a single
|
|
14
|
+
* project. Absent on legacy records written before #1847; such records are
|
|
15
|
+
* counted by `project_name` as a degraded fallback and are NEVER synthetically
|
|
16
|
+
* credited with a cohort id.
|
|
17
|
+
*/
|
|
18
|
+
cohort_id?: string;
|
|
10
19
|
confirmed_at: string;
|
|
11
20
|
phase_number?: number;
|
|
12
21
|
}
|
|
@@ -205,6 +214,69 @@ export interface SwarmKnowledgeEntry extends KnowledgeEntryBase {
|
|
|
205
214
|
confirmed_by: PhaseConfirmationRecord[];
|
|
206
215
|
project_name: string;
|
|
207
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* A single validated terminal application of a knowledge entry, usable as
|
|
219
|
+
* promotion evidence (issue #1847). Only receipts tied to a real retrieval
|
|
220
|
+
* trace AND a terminal outcome (`applied` / `violated` / `contradicted`) that
|
|
221
|
+
* is a member of that trace's result set qualify. Shown / retrieved / injected
|
|
222
|
+
* / acknowledged-only states do NOT qualify — they are display/attention
|
|
223
|
+
* signals, not application evidence.
|
|
224
|
+
*
|
|
225
|
+
* Production of real host traces is owned by #1849; this PR (#1847) owns the
|
|
226
|
+
* schema and the conservative promotion-side consumer. Legacy records carry no
|
|
227
|
+
* `PromotionEvidenceRecord`s and receive NO synthetic credit.
|
|
228
|
+
*/
|
|
229
|
+
export interface PromotionEvidenceRecord {
|
|
230
|
+
/** Canonical repository/cohort identity (from `resolveCohortId`, #1846). */
|
|
231
|
+
cohort_id: string;
|
|
232
|
+
/** Source cohort / link id when known (from the v2 LinkPointer). */
|
|
233
|
+
source_link_id?: string;
|
|
234
|
+
/** The hive/swarm entry id this evidence contributes to. */
|
|
235
|
+
entry_id: string;
|
|
236
|
+
/** Retrieval trace id that surfaced the entry (ties to a RetrievedEvent). */
|
|
237
|
+
retrieval_trace_id: string;
|
|
238
|
+
/** Terminal receipt outcome. */
|
|
239
|
+
receipt_outcome: 'applied' | 'violated' | 'contradicted';
|
|
240
|
+
/** Id of the ReceiptEvent this evidence was derived from. */
|
|
241
|
+
receipt_event_id: string;
|
|
242
|
+
phase?: string;
|
|
243
|
+
timestamp: string;
|
|
244
|
+
}
|
|
245
|
+
/** How a hive entry was promoted (issue #1847 §3 lineage). */
|
|
246
|
+
export type PromotionActor = 'auto' | 'manual' | 'manual-override';
|
|
247
|
+
/**
|
|
248
|
+
* Lineage + validated-evidence block attached to a promoted hive entry (issue
|
|
249
|
+
* #1847 §3). A promoted hive record must retain enough provenance to audit
|
|
250
|
+
* redaction/ownership and to trace back to its source without storing
|
|
251
|
+
* unnecessary sensitive content.
|
|
252
|
+
*
|
|
253
|
+
* All fields optional except `actor` so legacy on-disk records (which predate
|
|
254
|
+
* this block) load unchanged. Legacy records are NOT retroactively given a
|
|
255
|
+
* synthetic lineage block (no broad rewrite); consumers treat an absent
|
|
256
|
+
* `lineage` as "origin unknown, pre-#1847".
|
|
257
|
+
*/
|
|
258
|
+
export interface PromotionLineage {
|
|
259
|
+
/** UUID of the source swarm entry this hive record was promoted from. */
|
|
260
|
+
source_entry_id?: string;
|
|
261
|
+
/** Canonical cohort id of the source repository (#1846). */
|
|
262
|
+
source_cohort_id?: string;
|
|
263
|
+
/** Source entry content hash/revision (for drift detection). */
|
|
264
|
+
source_revision?: string;
|
|
265
|
+
/** Prior phase/confidence snapshot captured at promotion time. */
|
|
266
|
+
prior_confidence?: number;
|
|
267
|
+
prior_phases_alive?: number;
|
|
268
|
+
/** Ids of {@link PromotionEvidenceRecord}s that contributed to this promotion. */
|
|
269
|
+
contributing_evidence_ids?: string[];
|
|
270
|
+
/** For merged near-duplicates: losing entry ids preserved for audit. */
|
|
271
|
+
merged_from?: string[];
|
|
272
|
+
/** The promotion transaction/event id (ties to the hive audit-event log). */
|
|
273
|
+
promotion_event_id?: string;
|
|
274
|
+
/** Who/what initiated the promotion. */
|
|
275
|
+
actor: PromotionActor;
|
|
276
|
+
reason?: string;
|
|
277
|
+
/** When an override was used: the policy gates that failed. */
|
|
278
|
+
override_failed_gates?: string[];
|
|
279
|
+
}
|
|
208
280
|
export interface HiveKnowledgeEntry extends KnowledgeEntryBase {
|
|
209
281
|
tier: 'hive';
|
|
210
282
|
confirmed_by: ProjectConfirmationRecord[];
|
|
@@ -213,6 +285,9 @@ export interface HiveKnowledgeEntry extends KnowledgeEntryBase {
|
|
|
213
285
|
encounter_score: number;
|
|
214
286
|
/** @deprecated Legacy field for backward compatibility. Use encounter_score for weighting. */
|
|
215
287
|
encounter_count?: number;
|
|
288
|
+
/** #1847: promotion lineage + validated-evidence references. Optional so
|
|
289
|
+
* legacy on-disk records load unchanged; normalized in-memory on read. */
|
|
290
|
+
lineage?: PromotionLineage;
|
|
216
291
|
}
|
|
217
292
|
export interface RejectedLesson {
|
|
218
293
|
id: string;
|
|
@@ -298,6 +373,18 @@ export interface KnowledgeConfig {
|
|
|
298
373
|
* demotion counter increments for the current phase. Default: -0.3 (matches
|
|
299
374
|
* `OUTCOME_BLOCK_THRESHOLD`). */
|
|
300
375
|
promoted_demotion_signal_threshold: number;
|
|
376
|
+
/** #1847: minimum number of validated terminal-application receipts
|
|
377
|
+
* (PromotionEvidenceRecord) required for the `validated_terminal_applications`
|
|
378
|
+
* promotion gate. Default 0 — conservative: until #1849 produces real
|
|
379
|
+
* receipts, absence of evidence neither credits nor blocks. Raising this
|
|
380
|
+
* activates application-evidence gating. Legacy records get NO synthetic
|
|
381
|
+
* credit; they simply do not add to the count. */
|
|
382
|
+
promotion_min_terminal_applications: number;
|
|
383
|
+
/** #1847: minimum number of DISTINCT canonical cohort ids that must appear
|
|
384
|
+
* among the validated terminal-application receipts for the
|
|
385
|
+
* `validated_terminal_applications` gate. Default 0 (conservative; see
|
|
386
|
+
* `promotion_min_terminal_applications`). */
|
|
387
|
+
promotion_min_distinct_cohorts: number;
|
|
301
388
|
/** Change 5: retrieval-upgrade tuning (MMR / cold-start / synonyms). */
|
|
302
389
|
retrieval?: {
|
|
303
390
|
mmr_lambda?: number;
|