@meetless/mla 0.1.4

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 (202) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +81 -0
  3. package/dist/build-info.json +9 -0
  4. package/dist/bundles/ask-core.js +396 -0
  5. package/dist/bundles/mcp.js +16592 -0
  6. package/dist/bundles/trace-core.js +263 -0
  7. package/dist/cli.js +828 -0
  8. package/dist/commands/activate.js +781 -0
  9. package/dist/commands/adoption.js +130 -0
  10. package/dist/commands/ask.js +290 -0
  11. package/dist/commands/context.js +114 -0
  12. package/dist/commands/debug.js +313 -0
  13. package/dist/commands/doctor.js +1021 -0
  14. package/dist/commands/enrich.js +427 -0
  15. package/dist/commands/evidence.js +229 -0
  16. package/dist/commands/flush.js +184 -0
  17. package/dist/commands/graph.js +104 -0
  18. package/dist/commands/init.js +272 -0
  19. package/dist/commands/internal-active-review.js +322 -0
  20. package/dist/commands/internal-auto-index.js +188 -0
  21. package/dist/commands/internal-capture-decisions.js +320 -0
  22. package/dist/commands/internal-evidence-correlate.js +239 -0
  23. package/dist/commands/internal-evidence-hooks.js +240 -0
  24. package/dist/commands/internal-evidence-inject.js +231 -0
  25. package/dist/commands/internal-finalize.js +221 -0
  26. package/dist/commands/internal-pretool-observe.js +225 -0
  27. package/dist/commands/internal-refresh.js +136 -0
  28. package/dist/commands/internal-session-nudge.js +120 -0
  29. package/dist/commands/internal-steer-sync.js +117 -0
  30. package/dist/commands/internal-turn-recap.js +140 -0
  31. package/dist/commands/kb.js +375 -0
  32. package/dist/commands/kb_add.js +681 -0
  33. package/dist/commands/kb_forget.js +283 -0
  34. package/dist/commands/kb_move.js +45 -0
  35. package/dist/commands/kb_pending.js +410 -0
  36. package/dist/commands/kb_personal.js +149 -0
  37. package/dist/commands/kb_promote.js +188 -0
  38. package/dist/commands/kb_purge.js +168 -0
  39. package/dist/commands/kb_reingest.js +335 -0
  40. package/dist/commands/kb_retime.js +170 -0
  41. package/dist/commands/kb_review.js +391 -0
  42. package/dist/commands/kb_revision.js +179 -0
  43. package/dist/commands/kb_show.js +385 -0
  44. package/dist/commands/label.js +226 -0
  45. package/dist/commands/login.js +295 -0
  46. package/dist/commands/logout.js +108 -0
  47. package/dist/commands/mcp-supervisor.js +93 -0
  48. package/dist/commands/mcp.js +227 -0
  49. package/dist/commands/queue-prune.js +98 -0
  50. package/dist/commands/review.js +358 -0
  51. package/dist/commands/rewire.js +124 -0
  52. package/dist/commands/rules.js +728 -0
  53. package/dist/commands/scan-context.js +67 -0
  54. package/dist/commands/session.js +347 -0
  55. package/dist/commands/stats.js +479 -0
  56. package/dist/commands/status.js +61 -0
  57. package/dist/commands/summary.js +250 -0
  58. package/dist/commands/turn.js +114 -0
  59. package/dist/commands/uninstall.js +222 -0
  60. package/dist/commands/whoami.js +102 -0
  61. package/dist/commands/workspace.js +130 -0
  62. package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
  63. package/dist/hooks-template/ce0-session-start.sh +49 -0
  64. package/dist/hooks-template/ce0-stop.sh +29 -0
  65. package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
  66. package/dist/hooks-template/common.sh +934 -0
  67. package/dist/hooks-template/event-batch-filter.jq +67 -0
  68. package/dist/hooks-template/flush.sh +503 -0
  69. package/dist/hooks-template/post-tool-use.sh +423 -0
  70. package/dist/hooks-template/pre-tool-use.sh +69 -0
  71. package/dist/hooks-template/session-start.sh +140 -0
  72. package/dist/hooks-template/stop.sh +308 -0
  73. package/dist/hooks-template/user-prompt-submit.sh +1162 -0
  74. package/dist/lib/activation.js +79 -0
  75. package/dist/lib/active-conflict-cache.js +141 -0
  76. package/dist/lib/active-memory.js +59 -0
  77. package/dist/lib/active-review-runner.js +26 -0
  78. package/dist/lib/agent-decision/index.js +25 -0
  79. package/dist/lib/agent-decision/keys.js +49 -0
  80. package/dist/lib/agent-decision/normalize-claude.js +183 -0
  81. package/dist/lib/agent-decision/types.js +21 -0
  82. package/dist/lib/agent-decision/validate.js +216 -0
  83. package/dist/lib/analytics/capture.js +96 -0
  84. package/dist/lib/analytics/command-event.js +267 -0
  85. package/dist/lib/analytics/consent.js +58 -0
  86. package/dist/lib/analytics/coverage-gap.js +96 -0
  87. package/dist/lib/analytics/envelope.js +236 -0
  88. package/dist/lib/analytics/event-id.js +86 -0
  89. package/dist/lib/analytics/evidence.js +150 -0
  90. package/dist/lib/analytics/followthrough.js +194 -0
  91. package/dist/lib/analytics/forwarder.js +109 -0
  92. package/dist/lib/analytics/logs.js +78 -0
  93. package/dist/lib/analytics/metrics.js +78 -0
  94. package/dist/lib/analytics/recorder.js +92 -0
  95. package/dist/lib/analytics/review-analytics.js +75 -0
  96. package/dist/lib/analytics/sequence.js +77 -0
  97. package/dist/lib/analytics/store.js +131 -0
  98. package/dist/lib/analytics/turn-recap.js +279 -0
  99. package/dist/lib/artifact_id.js +108 -0
  100. package/dist/lib/auth-breaker.js +161 -0
  101. package/dist/lib/auto-index.js +112 -0
  102. package/dist/lib/classifier.js +88 -0
  103. package/dist/lib/config.js +298 -0
  104. package/dist/lib/conflict-advisory.js +64 -0
  105. package/dist/lib/debug-bundle.js +520 -0
  106. package/dist/lib/enrichment/ingest.js +301 -0
  107. package/dist/lib/enrichment/plan.js +253 -0
  108. package/dist/lib/enrichment/protocol.js +359 -0
  109. package/dist/lib/enrichment/scout-brief.js +176 -0
  110. package/dist/lib/failure-telemetry.js +444 -0
  111. package/dist/lib/git.js +200 -0
  112. package/dist/lib/governance-cache.js +77 -0
  113. package/dist/lib/governed-path-cache.js +76 -0
  114. package/dist/lib/http.js +677 -0
  115. package/dist/lib/identity-envelope.js +23 -0
  116. package/dist/lib/kb-candidate.js +65 -0
  117. package/dist/lib/kb_acl.js +98 -0
  118. package/dist/lib/login.js +353 -0
  119. package/dist/lib/mcp-fetchers.js +130 -0
  120. package/dist/lib/mcp-restart.js +47 -0
  121. package/dist/lib/observability.js +805 -0
  122. package/dist/lib/open-url.js +33 -0
  123. package/dist/lib/orphan-guard.js +70 -0
  124. package/dist/lib/packaged.js +21 -0
  125. package/dist/lib/reconcile-sessions.js +171 -0
  126. package/dist/lib/redactor.js +89 -0
  127. package/dist/lib/relationship-candidate-query.js +27 -0
  128. package/dist/lib/render.js +611 -0
  129. package/dist/lib/rules/applicability.js +64 -0
  130. package/dist/lib/rules/attest-code-rule-version.js +47 -0
  131. package/dist/lib/rules/attest-notes-location.js +217 -0
  132. package/dist/lib/rules/attest-rule-version.js +69 -0
  133. package/dist/lib/rules/canonical-json.js +97 -0
  134. package/dist/lib/rules/ce0-emit.js +64 -0
  135. package/dist/lib/rules/ce0-evidence.js +281 -0
  136. package/dist/lib/rules/ce0-recall-sample.js +82 -0
  137. package/dist/lib/rules/ce0-rule.js +55 -0
  138. package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
  139. package/dist/lib/rules/ce0-store.js +683 -0
  140. package/dist/lib/rules/ce0-telemetry-project.js +93 -0
  141. package/dist/lib/rules/ce0-telemetry.js +158 -0
  142. package/dist/lib/rules/code-rule-registry.js +17 -0
  143. package/dist/lib/rules/command-match.js +185 -0
  144. package/dist/lib/rules/consult-evidence-binding.js +27 -0
  145. package/dist/lib/rules/consultation-capture-adapter.js +193 -0
  146. package/dist/lib/rules/content-match.js +56 -0
  147. package/dist/lib/rules/deny-admission.js +99 -0
  148. package/dist/lib/rules/durable-observation.js +190 -0
  149. package/dist/lib/rules/enforce-notes-version.js +421 -0
  150. package/dist/lib/rules/evaluation-input-hash.js +126 -0
  151. package/dist/lib/rules/evaluator.js +108 -0
  152. package/dist/lib/rules/inert-rule-families.js +51 -0
  153. package/dist/lib/rules/input-authority-resolver.js +241 -0
  154. package/dist/lib/rules/interception-schema.js +170 -0
  155. package/dist/lib/rules/interception-store.js +267 -0
  156. package/dist/lib/rules/live-input-authority.js +66 -0
  157. package/dist/lib/rules/local-matcher.js +108 -0
  158. package/dist/lib/rules/local-observe.js +79 -0
  159. package/dist/lib/rules/local-rule-version-repo.js +214 -0
  160. package/dist/lib/rules/memory-requirement.js +109 -0
  161. package/dist/lib/rules/notes-observe.js +39 -0
  162. package/dist/lib/rules/notes-path.js +261 -0
  163. package/dist/lib/rules/notes-rule.js +75 -0
  164. package/dist/lib/rules/observe-adapter.js +114 -0
  165. package/dist/lib/rules/observed-rule-hash.js +119 -0
  166. package/dist/lib/rules/prompt-submit-adapter.js +132 -0
  167. package/dist/lib/rules/requirement-subject.js +240 -0
  168. package/dist/lib/rules/rule-activity.js +67 -0
  169. package/dist/lib/rules/rule-version-hash.js +151 -0
  170. package/dist/lib/rules/runtime-scope.js +55 -0
  171. package/dist/lib/rules/stop-adapter.js +116 -0
  172. package/dist/lib/rules/stop-response-snapshot.js +174 -0
  173. package/dist/lib/rules/types.js +10 -0
  174. package/dist/lib/rules/ulid.js +46 -0
  175. package/dist/lib/rules/version-evaluation.js +156 -0
  176. package/dist/lib/scanner/agent-memory.js +99 -0
  177. package/dist/lib/scanner/bootstrap-summary.js +87 -0
  178. package/dist/lib/scanner/cache.js +59 -0
  179. package/dist/lib/scanner/frontmatter.js +42 -0
  180. package/dist/lib/scanner/parse-directives.js +69 -0
  181. package/dist/lib/scanner/parse-structured.js +72 -0
  182. package/dist/lib/scanner/render.js +73 -0
  183. package/dist/lib/scanner/scan.js +132 -0
  184. package/dist/lib/scanner/score.js +38 -0
  185. package/dist/lib/scanner/scout-mission.js +126 -0
  186. package/dist/lib/scanner/types.js +7 -0
  187. package/dist/lib/session-scope.js +195 -0
  188. package/dist/lib/spool.js +355 -0
  189. package/dist/lib/staleness.js +100 -0
  190. package/dist/lib/steer-cache.js +87 -0
  191. package/dist/lib/tagged-reference.js +20 -0
  192. package/dist/lib/temporal.js +109 -0
  193. package/dist/lib/turn-recap-emit.js +67 -0
  194. package/dist/lib/unwire.js +253 -0
  195. package/dist/lib/update-check.js +469 -0
  196. package/dist/lib/update-notifier.js +217 -0
  197. package/dist/lib/upgrade-apply.js +643 -0
  198. package/dist/lib/wire.js +1087 -0
  199. package/dist/lib/workspace.js +96 -0
  200. package/dist/lib/zip.js +154 -0
  201. package/dist/pretool-entry.js +37 -0
  202. package/package.json +75 -0
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ // Coverage-gap classification (spec §7.5, INV-COVERAGE-GAP-1).
3
+ //
4
+ // A zero-result or low-confidence retrieval is the most actionable forward
5
+ // signal mla produces, but ONLY if it distinguishes cause. "Capture more docs"
6
+ // is the wrong fix when the real problem is ranking, staleness, or a permission
7
+ // filter. This module is the single, pure classifier that maps the inject-time
8
+ // retrieval signals to one closed `coverage_gap_type`, so the inject command and
9
+ // its test run the identical code with no I/O.
10
+ //
11
+ // The classification is INJECT-TIME: it answers "why did this retrieval fail to
12
+ // help" from what we knew when we surfaced (or failed to surface) evidence. The
13
+ // seventh type, `candidates_found_not_used`, is OUTCOME-time (the agent had
14
+ // usable candidates but referenced none) and is owned by the correlator, never
15
+ // emitted here.
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.classifyCoverageGap = classifyCoverageGap;
18
+ exports.coerceTopicCategory = coerceTopicCategory;
19
+ exports.coerceRetrievalConfidence = coerceRetrievalConfidence;
20
+ exports.coverageGapEventId = coverageGapEventId;
21
+ exports.coverageGapNotUsedEventId = coverageGapNotUsedEventId;
22
+ exports.buildCoverageGapPayload = buildCoverageGapPayload;
23
+ const envelope_1 = require("./envelope");
24
+ const event_id_1 = require("./event-id");
25
+ // classifyCoverageGap returns the single canonical gap type, or null when there
26
+ // is no coverage gap to report (a healthy, confident retrieval). Precedence is
27
+ // most-specific cause first, so a single inject is attributed to exactly one
28
+ // type even when several signals are set (INV-COVERAGE-GAP-1):
29
+ //
30
+ // retrieval_error a bug shadows every other reading
31
+ // permission_filtered an ACL drop is an access problem, not knowledge
32
+ // no_candidate_found nothing came back at all
33
+ // stale_or_conflicting candidates exist but the KB needs reconciling
34
+ // low_confidence_candidates candidates exist but ranking is weak
35
+ // (null) confident, non-empty retrieval: no gap
36
+ //
37
+ // candidates_found_not_used is deliberately NOT reachable here: it is the
38
+ // outcome-time type the correlator emits when usable candidates were ignored.
39
+ function classifyCoverageGap(signals) {
40
+ if (signals.retrievalError)
41
+ return "retrieval_error";
42
+ if (signals.permissionFiltered)
43
+ return "permission_filtered";
44
+ if (signals.zeroResults)
45
+ return "no_candidate_found";
46
+ if (signals.staleOrConflicting)
47
+ return "stale_or_conflicting_candidates";
48
+ if (signals.retrievalConfidence === "low")
49
+ return "low_confidence_candidates";
50
+ return null;
51
+ }
52
+ // Coerce a free-form topic string to the closed query_topic_category enum,
53
+ // defaulting to "unknown" so an unrecognized topic never leaks a raw string
54
+ // past the PII boundary (INV-POSTHOG-PII-1).
55
+ function coerceTopicCategory(raw) {
56
+ if (raw && envelope_1.QUERY_TOPIC_CATEGORIES.includes(raw)) {
57
+ return raw;
58
+ }
59
+ return "unknown";
60
+ }
61
+ // Coerce a free-form confidence string to the closed enum, defaulting to "low"
62
+ // (the same conservative default buildInjectPayload uses) so an unknown
63
+ // confidence never inflates the dashboard.
64
+ function coerceRetrievalConfidence(raw) {
65
+ if (raw && envelope_1.RETRIEVAL_CONFIDENCES.includes(raw)) {
66
+ return raw;
67
+ }
68
+ return "low";
69
+ }
70
+ // Deterministic event_id for an inject-time coverage gap. One inject produces at
71
+ // most one inject-time gap, so the inject_id IS the natural business key; the
72
+ // `coverage_gap:` prefix keeps it from colliding with the inject event's own id
73
+ // (which is the bare inject_id) under control's (workspace_id, event_id) dedupe.
74
+ function coverageGapEventId(injectId) {
75
+ return (0, event_id_1.deterministicEventId)(`coverage_gap:${injectId}`, 1);
76
+ }
77
+ // Deterministic event_id for the OUTCOME-time `candidates_found_not_used` gap the
78
+ // correlator emits when a confident, non-empty inject closed as ignored. A
79
+ // distinct business-key prefix from the inject-time gap so a single inject can
80
+ // carry both an inject-time gap and (in principle) an outcome-time gap without an
81
+ // event_id collision; in practice they are mutually exclusive (the correlator
82
+ // only emits this when no inject-time gap exists).
83
+ function coverageGapNotUsedEventId(injectId) {
84
+ return (0, event_id_1.deterministicEventId)(`coverage_gap_not_used:${injectId}`, 1);
85
+ }
86
+ // Build the typed, PII-bounded coverage-gap payload. ids/counts/enums/booleans
87
+ // only (no raw query text or paths): the raw topic stays in Langfuse.
88
+ function buildCoverageGapPayload(input) {
89
+ return {
90
+ inject_id: input.injectId,
91
+ coverage_gap_type: input.coverageGapType,
92
+ query_topic_category: input.queryTopicCategory,
93
+ retrieval_confidence: input.retrievalConfidence,
94
+ zero_results: input.zeroResults,
95
+ };
96
+ }
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ // The analytics event envelope + closed enums + the typed event union.
3
+ //
4
+ // Spec section 6 (the event catalog) and section 10 (the implementation
5
+ // contract). Every remotely emitted event carries the envelope (INV-JOIN-1);
6
+ // every payload is ids/counts/rates/enums/booleans/durations only, never raw
7
+ // text/paths/argv/queries/errors (INV-POSTHOG-PII-1). Events are FLAT: the
8
+ // envelope fields and the payload fields sit at the same top level (matching
9
+ // the local jsonl examples in section 7.4).
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.CE0_HOOKS = exports.OBLIGATION_OUTCOME_LABELS = exports.CONSULTATION_RESULTS = exports.CONSULTATION_EXECUTIONS = exports.MEMORY_REQUIREMENTS = exports.REVIEW_DECISIONS = exports.RELATION_EDGE_TYPES = exports.COMMAND_SCOPES = exports.RETRIEVAL_CONFIDENCES = exports.INJECT_OUTCOMES = exports.WINDOW_CLOSED_REASONS = exports.COVERAGE_GAP_TYPES = exports.QUERY_TOPIC_CATEGORIES = exports.GOVERNED_RELATION_TYPES = exports.TOUCHED_SURFACES = exports.COMMAND_OUTCOMES = exports.SOURCE_SURFACES = exports.EVENT_SOURCES = exports.EVENT_TYPES = exports.SCHEMA_VERSION = void 0;
12
+ exports.makeEnvelope = makeEnvelope;
13
+ exports.buildAttribution = buildAttribution;
14
+ exports.envelopeMissingKeys = envelopeMissingKeys;
15
+ exports.assertEnvelopeComplete = assertEnvelopeComplete;
16
+ exports.isRemotelyEmittable = isRemotelyEmittable;
17
+ // INV-SCHEMA-1: every payload carries schema_version and is forward-compatible.
18
+ exports.SCHEMA_VERSION = 1;
19
+ // --- closed enums (section 6.3) ---------------------------------------------
20
+ // As const tuples so membership can be validated at the privacy boundary; no
21
+ // open string ever reaches PostHog.
22
+ exports.EVENT_TYPES = [
23
+ "mla_command",
24
+ "mla_evidence_inject",
25
+ "mla_evidence_outcome",
26
+ "mla_coverage_gap",
27
+ "mla_contradiction",
28
+ "mla_review_decision",
29
+ "mla_stats_viewed",
30
+ // CE0 evidence-consultation telemetry (§6.4). Named per the ratified proposal
31
+ // contract (no `mla_` prefix): these four are the PostHog projection of the
32
+ // obligation lifecycle and the dashboards in §6.4 query them by these names.
33
+ "memory_requirement_assessed",
34
+ "evidence_consultation_completed",
35
+ "evidence_obligation_finalized",
36
+ "evidence_hook_health",
37
+ ];
38
+ exports.EVENT_SOURCES = ["cli", "hook", "mcp", "control", "intel"];
39
+ // The emission-surface label carried in the attribution block (spec section 3.7
40
+ // / T1.10). Derived from the typed `source` enum, NOT a free string, so the two
41
+ // axes can never drift: `source` is the closed emission channel (cli|hook|mcp),
42
+ // `sourceSurface` is its human-facing uppercase form. Kept distinct from the
43
+ // product-origin axis (`source:"mla"`), which is a constant on every event.
44
+ exports.SOURCE_SURFACES = {
45
+ cli: "CLI",
46
+ hook: "HOOK",
47
+ mcp: "MCP",
48
+ control: "CONTROL",
49
+ intel: "INTEL",
50
+ };
51
+ exports.COMMAND_OUTCOMES = [
52
+ "success",
53
+ "user_error",
54
+ "system_error",
55
+ "auth_error",
56
+ "network_error",
57
+ "permission_denied",
58
+ "validation_error",
59
+ "noop",
60
+ "cancelled",
61
+ "timeout",
62
+ ];
63
+ exports.TOUCHED_SURFACES = [
64
+ "code",
65
+ "tests",
66
+ "docs",
67
+ "config",
68
+ "migration",
69
+ "infra",
70
+ "unknown",
71
+ ];
72
+ exports.GOVERNED_RELATION_TYPES = [
73
+ "architecture",
74
+ "api_contract",
75
+ "migration",
76
+ "security",
77
+ "product_decision",
78
+ "data_model",
79
+ "unknown",
80
+ ];
81
+ exports.QUERY_TOPIC_CATEGORIES = [
82
+ "architecture",
83
+ "testing",
84
+ "deployment",
85
+ "product_decision",
86
+ "customer_context",
87
+ "security",
88
+ "data_model",
89
+ "api_contract",
90
+ "migration",
91
+ "process",
92
+ "unknown",
93
+ ];
94
+ exports.COVERAGE_GAP_TYPES = [
95
+ "no_candidate_found",
96
+ "low_confidence_candidates",
97
+ "candidates_found_not_used",
98
+ "stale_or_conflicting_candidates",
99
+ "retrieval_error",
100
+ "permission_filtered",
101
+ ];
102
+ exports.WINDOW_CLOSED_REASONS = ["turn_limit", "time_limit", "still_open"];
103
+ exports.INJECT_OUTCOMES = ["used", "ignored", "unknown", "pending"];
104
+ exports.RETRIEVAL_CONFIDENCES = ["high", "medium", "low"];
105
+ // Command scope: where the command's effect landed. local = no backend hop;
106
+ // workspace = a single-workspace remote op; global = cross-workspace. Used by
107
+ // mla_command and mla_stats_viewed.
108
+ exports.COMMAND_SCOPES = ["local", "workspace", "global", "unknown"];
109
+ // The relationship edge classes mla curates (kb review / contradiction). These
110
+ // are the governed-relation lifecycle types, not the PII enums above.
111
+ exports.RELATION_EDGE_TYPES = [
112
+ "CONTRADICTS",
113
+ "SUPERSEDES",
114
+ "STALE_RELIES_ON",
115
+ "REFINES",
116
+ "unknown",
117
+ ];
118
+ exports.REVIEW_DECISIONS = ["accept", "reject", "reclassify", "no_relation"];
119
+ // --- CE0 evidence-consultation telemetry enums (§6.4) -----------------------
120
+ // The wire forms of the rules-layer CE0 enums. Re-declared here, in the analytics
121
+ // layer, on purpose: the privacy boundary validates membership against THESE closed
122
+ // tuples, and the analytics layer must not depend up into lib/rules. The string
123
+ // values mirror the rules-layer unions (MemoryRequirement, ConsultationExecution,
124
+ // ObligationOutcome); ce0-telemetry.ts is the seam that maps one onto the other.
125
+ exports.MEMORY_REQUIREMENTS = ["REQUIRED", "NOT_REQUIRED", "UNKNOWN"];
126
+ exports.CONSULTATION_EXECUTIONS = ["COMPLETE", "FAILED", "UNKNOWN"];
127
+ exports.CONSULTATION_RESULTS = ["RESULTS_RETURNED", "NO_MATCH"];
128
+ exports.OBLIGATION_OUTCOME_LABELS = [
129
+ "NOT_DUE",
130
+ "COMPLIANT_ON_TIME",
131
+ "CONSULTED_LATE_WITH_EVIDENCE",
132
+ "CONSULTED_LATE_NO_EVIDENCE",
133
+ "MISSED",
134
+ "UNKNOWN",
135
+ "CANCELLED",
136
+ ];
137
+ exports.CE0_HOOKS = [
138
+ "USER_PROMPT_SUBMIT",
139
+ "CONSULTATION_CAPTURE",
140
+ "STOP",
141
+ "OFFLINE_LABEL_IMPORT",
142
+ ];
143
+ function makeEnvelope(input) {
144
+ const source = input.source ?? "cli";
145
+ return {
146
+ schema_version: exports.SCHEMA_VERSION,
147
+ event_id: input.event_id,
148
+ event_type: input.event_type,
149
+ created_at: input.created_at,
150
+ emitted_at: input.emitted_at ?? input.created_at,
151
+ workspace_id: input.workspace_id,
152
+ distinct_id: input.distinct_id,
153
+ session_id: input.session_id,
154
+ run_id: input.run_id,
155
+ trace_id: input.trace_id,
156
+ source,
157
+ attribution: buildAttribution({
158
+ source,
159
+ workspaceId: input.workspace_id,
160
+ actorWorkspaceUserId: input.actor_workspace_user_id ?? null,
161
+ agentSessionId: input.session_id,
162
+ repoFingerprint: input.repo_fingerprint ?? null,
163
+ }),
164
+ };
165
+ }
166
+ // Assemble the attribution block (T1.10). Pure: every field is mapped from a
167
+ // caller-supplied id/constant, no I/O. sourceSurface is derived from the closed
168
+ // `source` enum so it can never carry an open string; the `?? "CLI"` is a
169
+ // defensive default for the (type-impossible) case of an unmapped source.
170
+ function buildAttribution(input) {
171
+ return {
172
+ source: "mla",
173
+ sourceProduct: "MLA",
174
+ sourceSurface: exports.SOURCE_SURFACES[input.source] ?? "CLI",
175
+ actorWorkspaceUserId: input.actorWorkspaceUserId,
176
+ workspaceId: input.workspaceId,
177
+ agentSessionId: input.agentSessionId,
178
+ repoFingerprint: input.repoFingerprint,
179
+ };
180
+ }
181
+ // --- validators (INV-JOIN-1, the test contract) -----------------------------
182
+ // The eight join fields the test contract requires on every event. workspace_id
183
+ // and session_id are allowed to be null here (an unbound local run); presence of
184
+ // the KEY is asserted, while remote-emittability (non-null workspace+session) is
185
+ // a separate, stricter check (isRemotelyEmittable).
186
+ const REQUIRED_ENVELOPE_KEYS = [
187
+ "schema_version",
188
+ "event_id",
189
+ "event_type",
190
+ "created_at",
191
+ "workspace_id",
192
+ "session_id",
193
+ "run_id",
194
+ "trace_id",
195
+ ];
196
+ function envelopeMissingKeys(ev) {
197
+ const missing = [];
198
+ for (const k of REQUIRED_ENVELOPE_KEYS) {
199
+ if (!(k in ev)) {
200
+ missing.push(k);
201
+ continue;
202
+ }
203
+ const v = ev[k];
204
+ // null is allowed for workspace_id / session_id (unbound run); undefined
205
+ // never is. Every other required key must be a non-empty value.
206
+ if (v === undefined) {
207
+ missing.push(k);
208
+ }
209
+ else if (v === null && k !== "workspace_id" && k !== "session_id") {
210
+ missing.push(k);
211
+ }
212
+ else if (typeof v === "string" && v.length === 0) {
213
+ missing.push(k);
214
+ }
215
+ }
216
+ return missing;
217
+ }
218
+ // Throws if any required envelope field is missing. Used as the assertion in
219
+ // the envelope test (INV-JOIN-1) and as a defensive gate before remote ship.
220
+ function assertEnvelopeComplete(ev) {
221
+ const missing = envelopeMissingKeys(ev);
222
+ if (missing.length > 0) {
223
+ throw new Error(`analytics event missing required envelope field(s): ${missing.join(", ")}`);
224
+ }
225
+ }
226
+ // INV-JOIN-1 applies to REMOTELY emitted events: they need a real workspace and
227
+ // session to join. An event with a null workspace_id or session_id is recorded
228
+ // locally (the operator's own view) but never shipped. The forwarder filters on
229
+ // this.
230
+ function isRemotelyEmittable(ev) {
231
+ return (envelopeMissingKeys(ev).length === 0 &&
232
+ typeof ev.workspace_id === "string" &&
233
+ ev.workspace_id.length > 0 &&
234
+ typeof ev.session_id === "string" &&
235
+ ev.session_id.length > 0);
236
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ // Event identity (spec section 10.2, INV-IDEMPOTENCY-1, INV-REMOTE-DEDUPE-1).
3
+ //
4
+ // Two minting strategies, picked by who is the system of record for the event:
5
+ //
6
+ // - CLI-ORIGIN events (mla_command, mla_evidence_inject): the CLI is the only
7
+ // writer, the event is produced exactly once, and a re-ship must reuse the
8
+ // SAME id. So we mint a UUID once and persist it in the local jsonl; every
9
+ // later forward reads it back. We do NOT content-hash these: identical
10
+ // commands at the same second would collide, and any serialization change
11
+ // would silently drift the id.
12
+ //
13
+ // - SERVER-RECOMPUTABLE events (mla_evidence_outcome, mla_review_decision):
14
+ // these are derived from a business key plus a monotonic version, and may be
15
+ // recomputed by more than one writer (the correlator, a backfill). A
16
+ // deterministic id makes that idempotent: sha256(businessKey + ":" + version).
17
+ //
18
+ // Control dedupes on the PAIR (workspace_id, event_id), so a deterministic id
19
+ // scoped only by business key never collides across workspaces.
20
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ var desc = Object.getOwnPropertyDescriptor(m, k);
23
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
24
+ desc = { enumerable: true, get: function() { return m[k]; } };
25
+ }
26
+ Object.defineProperty(o, k2, desc);
27
+ }) : (function(o, m, k, k2) {
28
+ if (k2 === undefined) k2 = k;
29
+ o[k2] = m[k];
30
+ }));
31
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
32
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
33
+ }) : function(o, v) {
34
+ o["default"] = v;
35
+ });
36
+ var __importStar = (this && this.__importStar) || (function () {
37
+ var ownKeys = function(o) {
38
+ ownKeys = Object.getOwnPropertyNames || function (o) {
39
+ var ar = [];
40
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
41
+ return ar;
42
+ };
43
+ return ownKeys(o);
44
+ };
45
+ return function (mod) {
46
+ if (mod && mod.__esModule) return mod;
47
+ var result = {};
48
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
49
+ __setModuleDefault(result, mod);
50
+ return result;
51
+ };
52
+ })();
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.mintEventId = mintEventId;
55
+ exports.deterministicEventId = deterministicEventId;
56
+ exports.outcomeEventId = outcomeEventId;
57
+ exports.reviewDecisionEventId = reviewDecisionEventId;
58
+ const crypto = __importStar(require("crypto"));
59
+ // Mint a fresh, persisted-once id for a CLI-origin event. uuid (not a hash) so
60
+ // two structurally identical events are still distinct, and so the id is stable
61
+ // across re-serialization.
62
+ function mintEventId() {
63
+ return crypto.randomUUID();
64
+ }
65
+ // Deterministic id for a server-recomputable event. businessKey is the stable
66
+ // natural key (e.g. an inject_id or decision_id); version is a monotonically
67
+ // increasing integer so a corrected recomputation produces a NEW id rather than
68
+ // silently overwriting the prior landing. The ":" separator is unambiguous
69
+ // because neither side contains it (ids are uuids/hex, version is an integer).
70
+ function deterministicEventId(businessKey, version) {
71
+ if (!businessKey) {
72
+ throw new Error("deterministicEventId requires a non-empty businessKey");
73
+ }
74
+ if (!Number.isInteger(version) || version < 0) {
75
+ throw new Error(`deterministicEventId requires a non-negative integer version, got ${version}`);
76
+ }
77
+ return crypto.createHash("sha256").update(`${businessKey}:${version}`).digest("hex");
78
+ }
79
+ // Convenience wrappers naming the two server-recomputable event families, so
80
+ // callers can't accidentally pass the wrong version field.
81
+ function outcomeEventId(injectId, outcomeVersion) {
82
+ return deterministicEventId(injectId, outcomeVersion);
83
+ }
84
+ function reviewDecisionEventId(decisionId, decisionVersion) {
85
+ return deterministicEventId(decisionId, decisionVersion);
86
+ }
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ // The evidence-grounding lifecycle: build an inject payload at surface time, and
3
+ // derive its outcome once the correlation window closes. Both are pure functions
4
+ // over data the caller supplies, so the Stop-hook correlator and its test run the
5
+ // identical code with no I/O or clock baked in.
6
+ //
7
+ // The window is "next 3 turns OR 15 minutes, whichever first" (spec §0, §7.4).
8
+ // The inject is written immediately and counted as outcome=pending; the local
9
+ // correlator (v1: the Stop hook, INV-CORRELATOR-1) calls deriveOutcome to close
10
+ // eligible windows and append mla_evidence_outcome. Server-side correlation may
11
+ // validate or enrich, never be the only writer.
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.OUTCOME_VERSION = exports.WINDOW_MS = exports.WINDOW_TURNS = void 0;
14
+ exports.buildInjectPayload = buildInjectPayload;
15
+ exports.deriveOutcome = deriveOutcome;
16
+ const envelope_1 = require("./envelope");
17
+ const event_id_1 = require("./event-id");
18
+ const followthrough_1 = require("./followthrough");
19
+ // The turn window covers the inject turn and its next 3 turns; the wall-clock
20
+ // window is 15 minutes. Whichever fills first closes the inject (§0).
21
+ exports.WINDOW_TURNS = 3;
22
+ exports.WINDOW_MS = 15 * 60 * 1000;
23
+ // The current outcome schema generation. Bumped only when the outcome derivation
24
+ // changes meaning, so a recomputed outcome gets a NEW deterministic event_id
25
+ // rather than silently overwriting the prior landing (event-id.ts).
26
+ exports.OUTCOME_VERSION = 1;
27
+ // buildInjectPayload normalizes the hook's raw retrieval result into the typed,
28
+ // PII-bounded inject payload. It mints the inject_id ONCE (CLI-origin identity,
29
+ // §10.2) and stamps the window deadline from the supplied clock so the function
30
+ // stays hermetic.
31
+ function buildInjectPayload(input) {
32
+ const confidence = envelope_1.RETRIEVAL_CONFIDENCES.includes(input.retrieval_confidence)
33
+ ? input.retrieval_confidence
34
+ : "low";
35
+ const offered = input.offered_source_ids.filter((s) => typeof s === "string" && s.length > 0);
36
+ const offeredCount = Number.isFinite(input.evidence_offered)
37
+ ? input.evidence_offered
38
+ : offered.length;
39
+ return {
40
+ inject_id: input.injectId ?? (0, event_id_1.mintEventId)(),
41
+ turn_index: input.turn_index,
42
+ evidence_offered: offeredCount,
43
+ offered_source_ids: offered,
44
+ evidence_tokens: Number.isFinite(input.evidence_tokens) ? input.evidence_tokens : 0,
45
+ retrieval_confidence: confidence,
46
+ retrieval_latency_ms: Number.isFinite(input.retrieval_latency_ms)
47
+ ? input.retrieval_latency_ms
48
+ : 0,
49
+ zero_results: offeredCount === 0,
50
+ window_deadline: new Date(input.createdAtMs + exports.WINDOW_MS).toISOString(),
51
+ };
52
+ }
53
+ // deriveOutcome closes one inject's window if it is eligible, else returns null
54
+ // (the inject stays pending). Close precedence (§0, "whichever first"):
55
+ //
56
+ // turn_limit the full turn window has been observed (maxTurn >= turn+window).
57
+ // The agent had its whole opportunity; not-referenced => ignored.
58
+ // time_limit the 15-minute deadline passed before the turn window filled.
59
+ // The session went idle, so a not-referenced inject is unknown,
60
+ // not ignored: we did not observe the full opportunity. This is
61
+ // what makes Unknown Coverage a meaningful honesty term.
62
+ // still_open neither => null, stays pending.
63
+ //
64
+ // v1 used := referenced (§4.2): an inject that was pulled or cited is "used".
65
+ // The schema keeps referenced and used as separate fields so a later correlator
66
+ // can tighten "used" to material incorporation without a migration.
67
+ function deriveOutcome(inject, calls, citations, ctx) {
68
+ // No numeric turn means we cannot align the inject to pulls/citations; it can
69
+ // never be correlated, so leave it pending rather than guess an outcome.
70
+ if (inject.turn_index === null)
71
+ return null;
72
+ const window = ctx.window ?? exports.WINDOW_TURNS;
73
+ const maxTurn = ctx.maxTurnBySession.get(inject.session_id) ?? inject.turn_index;
74
+ const deadlineMs = Date.parse(inject.window_deadline);
75
+ let reason;
76
+ if (maxTurn >= inject.turn_index + window) {
77
+ reason = "turn_limit";
78
+ }
79
+ else if (Number.isFinite(deadlineMs) && ctx.nowMs >= deadlineMs) {
80
+ reason = "time_limit";
81
+ }
82
+ else {
83
+ return null; // still open -> stays pending
84
+ }
85
+ // Reuse the ONE shared join (INV-ADOPTION-SOURCE-1): score this single inject
86
+ // turn against the window's pulls and citations.
87
+ const injectTurn = {
88
+ session_id: inject.session_id,
89
+ turn_index: inject.turn_index,
90
+ injected_source_ids: inject.offered_source_ids,
91
+ };
92
+ const [row] = (0, followthrough_1.computeFollowthrough)([injectTurn], calls, citations, window);
93
+ const pulled_within_window = row.a1a_pull;
94
+ const report_cited = row.a1b_push_reference;
95
+ const referenced = row.a1c_any;
96
+ // The offered ids that were referenced (pulled or cited), original form, deduped
97
+ // by the same normId rule the join uses.
98
+ const referencedByNorm = new Map();
99
+ for (const id of [...row.pulled_overlap, ...row.cited_overlap]) {
100
+ const n = (0, followthrough_1.normId)(id);
101
+ if (!referencedByNorm.has(n))
102
+ referencedByNorm.set(n, id);
103
+ }
104
+ const referenced_source_ids = Array.from(referencedByNorm.values());
105
+ let outcome;
106
+ if (referenced)
107
+ outcome = "used";
108
+ else if (reason === "turn_limit")
109
+ outcome = "ignored";
110
+ else
111
+ outcome = "unknown";
112
+ // offered_reference_rate: distinct offered ids referenced / distinct offered ids
113
+ // (the recall direction). null when nothing was offered.
114
+ const distinctOffered = new Set(inject.offered_source_ids.map(followthrough_1.normId));
115
+ const offered_reference_rate = distinctOffered.size
116
+ ? referenced_source_ids.length / distinctOffered.size
117
+ : null;
118
+ // citation_precision (v1 hallucinated-id guard): of the distinct ids the report
119
+ // cited in this window, the fraction that resolve to one of THIS inject's
120
+ // offered ids. null when the report cited nothing in the window (undefined, not
121
+ // zero). v1 caveat: an id pointing to a real-but-not-offered doc counts against
122
+ // precision, so this is a conservative proxy, in step with the §4.2 v1 framing.
123
+ const citedInWindow = [];
124
+ for (const c of citations) {
125
+ if (c.session_id !== inject.session_id)
126
+ continue;
127
+ if (c.turn_index < inject.turn_index || c.turn_index > inject.turn_index + window)
128
+ continue;
129
+ citedInWindow.push(...c.source_ids);
130
+ }
131
+ const distinctCited = new Set(citedInWindow.map(followthrough_1.normId));
132
+ const validOffered = new Set(row.cited_overlap.map(followthrough_1.normId));
133
+ const citation_precision = distinctCited.size ? validOffered.size / distinctCited.size : null;
134
+ const payload = {
135
+ inject_id: inject.inject_id,
136
+ outcome_version: exports.OUTCOME_VERSION,
137
+ outcome,
138
+ pulled_within_window,
139
+ report_cited,
140
+ referenced,
141
+ referenced_source_ids,
142
+ citation_precision,
143
+ offered_reference_rate,
144
+ window_closed_reason: reason,
145
+ };
146
+ return {
147
+ event_id: (0, event_id_1.outcomeEventId)(inject.inject_id, exports.OUTCOME_VERSION),
148
+ payload,
149
+ };
150
+ }