@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.
- package/LICENSE +201 -0
- package/README.md +81 -0
- package/dist/build-info.json +9 -0
- package/dist/bundles/ask-core.js +396 -0
- package/dist/bundles/mcp.js +16592 -0
- package/dist/bundles/trace-core.js +263 -0
- package/dist/cli.js +828 -0
- package/dist/commands/activate.js +781 -0
- package/dist/commands/adoption.js +130 -0
- package/dist/commands/ask.js +290 -0
- package/dist/commands/context.js +114 -0
- package/dist/commands/debug.js +313 -0
- package/dist/commands/doctor.js +1021 -0
- package/dist/commands/enrich.js +427 -0
- package/dist/commands/evidence.js +229 -0
- package/dist/commands/flush.js +184 -0
- package/dist/commands/graph.js +104 -0
- package/dist/commands/init.js +272 -0
- package/dist/commands/internal-active-review.js +322 -0
- package/dist/commands/internal-auto-index.js +188 -0
- package/dist/commands/internal-capture-decisions.js +320 -0
- package/dist/commands/internal-evidence-correlate.js +239 -0
- package/dist/commands/internal-evidence-hooks.js +240 -0
- package/dist/commands/internal-evidence-inject.js +231 -0
- package/dist/commands/internal-finalize.js +221 -0
- package/dist/commands/internal-pretool-observe.js +225 -0
- package/dist/commands/internal-refresh.js +136 -0
- package/dist/commands/internal-session-nudge.js +120 -0
- package/dist/commands/internal-steer-sync.js +117 -0
- package/dist/commands/internal-turn-recap.js +140 -0
- package/dist/commands/kb.js +375 -0
- package/dist/commands/kb_add.js +681 -0
- package/dist/commands/kb_forget.js +283 -0
- package/dist/commands/kb_move.js +45 -0
- package/dist/commands/kb_pending.js +410 -0
- package/dist/commands/kb_personal.js +149 -0
- package/dist/commands/kb_promote.js +188 -0
- package/dist/commands/kb_purge.js +168 -0
- package/dist/commands/kb_reingest.js +335 -0
- package/dist/commands/kb_retime.js +170 -0
- package/dist/commands/kb_review.js +391 -0
- package/dist/commands/kb_revision.js +179 -0
- package/dist/commands/kb_show.js +385 -0
- package/dist/commands/label.js +226 -0
- package/dist/commands/login.js +295 -0
- package/dist/commands/logout.js +108 -0
- package/dist/commands/mcp-supervisor.js +93 -0
- package/dist/commands/mcp.js +227 -0
- package/dist/commands/queue-prune.js +98 -0
- package/dist/commands/review.js +358 -0
- package/dist/commands/rewire.js +124 -0
- package/dist/commands/rules.js +728 -0
- package/dist/commands/scan-context.js +67 -0
- package/dist/commands/session.js +347 -0
- package/dist/commands/stats.js +479 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/summary.js +250 -0
- package/dist/commands/turn.js +114 -0
- package/dist/commands/uninstall.js +222 -0
- package/dist/commands/whoami.js +102 -0
- package/dist/commands/workspace.js +130 -0
- package/dist/hooks-template/ce0-post-tool-use.sh +34 -0
- package/dist/hooks-template/ce0-session-start.sh +49 -0
- package/dist/hooks-template/ce0-stop.sh +29 -0
- package/dist/hooks-template/ce0-user-prompt-submit.sh +38 -0
- package/dist/hooks-template/common.sh +934 -0
- package/dist/hooks-template/event-batch-filter.jq +67 -0
- package/dist/hooks-template/flush.sh +503 -0
- package/dist/hooks-template/post-tool-use.sh +423 -0
- package/dist/hooks-template/pre-tool-use.sh +69 -0
- package/dist/hooks-template/session-start.sh +140 -0
- package/dist/hooks-template/stop.sh +308 -0
- package/dist/hooks-template/user-prompt-submit.sh +1162 -0
- package/dist/lib/activation.js +79 -0
- package/dist/lib/active-conflict-cache.js +141 -0
- package/dist/lib/active-memory.js +59 -0
- package/dist/lib/active-review-runner.js +26 -0
- package/dist/lib/agent-decision/index.js +25 -0
- package/dist/lib/agent-decision/keys.js +49 -0
- package/dist/lib/agent-decision/normalize-claude.js +183 -0
- package/dist/lib/agent-decision/types.js +21 -0
- package/dist/lib/agent-decision/validate.js +216 -0
- package/dist/lib/analytics/capture.js +96 -0
- package/dist/lib/analytics/command-event.js +267 -0
- package/dist/lib/analytics/consent.js +58 -0
- package/dist/lib/analytics/coverage-gap.js +96 -0
- package/dist/lib/analytics/envelope.js +236 -0
- package/dist/lib/analytics/event-id.js +86 -0
- package/dist/lib/analytics/evidence.js +150 -0
- package/dist/lib/analytics/followthrough.js +194 -0
- package/dist/lib/analytics/forwarder.js +109 -0
- package/dist/lib/analytics/logs.js +78 -0
- package/dist/lib/analytics/metrics.js +78 -0
- package/dist/lib/analytics/recorder.js +92 -0
- package/dist/lib/analytics/review-analytics.js +75 -0
- package/dist/lib/analytics/sequence.js +77 -0
- package/dist/lib/analytics/store.js +131 -0
- package/dist/lib/analytics/turn-recap.js +279 -0
- package/dist/lib/artifact_id.js +108 -0
- package/dist/lib/auth-breaker.js +161 -0
- package/dist/lib/auto-index.js +112 -0
- package/dist/lib/classifier.js +88 -0
- package/dist/lib/config.js +298 -0
- package/dist/lib/conflict-advisory.js +64 -0
- package/dist/lib/debug-bundle.js +520 -0
- package/dist/lib/enrichment/ingest.js +301 -0
- package/dist/lib/enrichment/plan.js +253 -0
- package/dist/lib/enrichment/protocol.js +359 -0
- package/dist/lib/enrichment/scout-brief.js +176 -0
- package/dist/lib/failure-telemetry.js +444 -0
- package/dist/lib/git.js +200 -0
- package/dist/lib/governance-cache.js +77 -0
- package/dist/lib/governed-path-cache.js +76 -0
- package/dist/lib/http.js +677 -0
- package/dist/lib/identity-envelope.js +23 -0
- package/dist/lib/kb-candidate.js +65 -0
- package/dist/lib/kb_acl.js +98 -0
- package/dist/lib/login.js +353 -0
- package/dist/lib/mcp-fetchers.js +130 -0
- package/dist/lib/mcp-restart.js +47 -0
- package/dist/lib/observability.js +805 -0
- package/dist/lib/open-url.js +33 -0
- package/dist/lib/orphan-guard.js +70 -0
- package/dist/lib/packaged.js +21 -0
- package/dist/lib/reconcile-sessions.js +171 -0
- package/dist/lib/redactor.js +89 -0
- package/dist/lib/relationship-candidate-query.js +27 -0
- package/dist/lib/render.js +611 -0
- package/dist/lib/rules/applicability.js +64 -0
- package/dist/lib/rules/attest-code-rule-version.js +47 -0
- package/dist/lib/rules/attest-notes-location.js +217 -0
- package/dist/lib/rules/attest-rule-version.js +69 -0
- package/dist/lib/rules/canonical-json.js +97 -0
- package/dist/lib/rules/ce0-emit.js +64 -0
- package/dist/lib/rules/ce0-evidence.js +281 -0
- package/dist/lib/rules/ce0-recall-sample.js +82 -0
- package/dist/lib/rules/ce0-rule.js +55 -0
- package/dist/lib/rules/ce0-sampling-bucket.js +15 -0
- package/dist/lib/rules/ce0-store.js +683 -0
- package/dist/lib/rules/ce0-telemetry-project.js +93 -0
- package/dist/lib/rules/ce0-telemetry.js +158 -0
- package/dist/lib/rules/code-rule-registry.js +17 -0
- package/dist/lib/rules/command-match.js +185 -0
- package/dist/lib/rules/consult-evidence-binding.js +27 -0
- package/dist/lib/rules/consultation-capture-adapter.js +193 -0
- package/dist/lib/rules/content-match.js +56 -0
- package/dist/lib/rules/deny-admission.js +99 -0
- package/dist/lib/rules/durable-observation.js +190 -0
- package/dist/lib/rules/enforce-notes-version.js +421 -0
- package/dist/lib/rules/evaluation-input-hash.js +126 -0
- package/dist/lib/rules/evaluator.js +108 -0
- package/dist/lib/rules/inert-rule-families.js +51 -0
- package/dist/lib/rules/input-authority-resolver.js +241 -0
- package/dist/lib/rules/interception-schema.js +170 -0
- package/dist/lib/rules/interception-store.js +267 -0
- package/dist/lib/rules/live-input-authority.js +66 -0
- package/dist/lib/rules/local-matcher.js +108 -0
- package/dist/lib/rules/local-observe.js +79 -0
- package/dist/lib/rules/local-rule-version-repo.js +214 -0
- package/dist/lib/rules/memory-requirement.js +109 -0
- package/dist/lib/rules/notes-observe.js +39 -0
- package/dist/lib/rules/notes-path.js +261 -0
- package/dist/lib/rules/notes-rule.js +75 -0
- package/dist/lib/rules/observe-adapter.js +114 -0
- package/dist/lib/rules/observed-rule-hash.js +119 -0
- package/dist/lib/rules/prompt-submit-adapter.js +132 -0
- package/dist/lib/rules/requirement-subject.js +240 -0
- package/dist/lib/rules/rule-activity.js +67 -0
- package/dist/lib/rules/rule-version-hash.js +151 -0
- package/dist/lib/rules/runtime-scope.js +55 -0
- package/dist/lib/rules/stop-adapter.js +116 -0
- package/dist/lib/rules/stop-response-snapshot.js +174 -0
- package/dist/lib/rules/types.js +10 -0
- package/dist/lib/rules/ulid.js +46 -0
- package/dist/lib/rules/version-evaluation.js +156 -0
- package/dist/lib/scanner/agent-memory.js +99 -0
- package/dist/lib/scanner/bootstrap-summary.js +87 -0
- package/dist/lib/scanner/cache.js +59 -0
- package/dist/lib/scanner/frontmatter.js +42 -0
- package/dist/lib/scanner/parse-directives.js +69 -0
- package/dist/lib/scanner/parse-structured.js +72 -0
- package/dist/lib/scanner/render.js +73 -0
- package/dist/lib/scanner/scan.js +132 -0
- package/dist/lib/scanner/score.js +38 -0
- package/dist/lib/scanner/scout-mission.js +126 -0
- package/dist/lib/scanner/types.js +7 -0
- package/dist/lib/session-scope.js +195 -0
- package/dist/lib/spool.js +355 -0
- package/dist/lib/staleness.js +100 -0
- package/dist/lib/steer-cache.js +87 -0
- package/dist/lib/tagged-reference.js +20 -0
- package/dist/lib/temporal.js +109 -0
- package/dist/lib/turn-recap-emit.js +67 -0
- package/dist/lib/unwire.js +253 -0
- package/dist/lib/update-check.js +469 -0
- package/dist/lib/update-notifier.js +217 -0
- package/dist/lib/upgrade-apply.js +643 -0
- package/dist/lib/wire.js +1087 -0
- package/dist/lib/workspace.js +96 -0
- package/dist/lib/zip.js +154 -0
- package/dist/pretool-entry.js +37 -0
- package/package.json +75 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/lib/agent-decision/validate.ts
|
|
3
|
+
//
|
|
4
|
+
// Hand-rolled structural validator for the canonical agent-decision contract.
|
|
5
|
+
//
|
|
6
|
+
// Deviation noted (spec build-order item 1 suggested "TypeScript / Zod"): the mla
|
|
7
|
+
// CLI deliberately keeps a minimal dependency surface and ships NO Zod. This
|
|
8
|
+
// validator is the contract's teeth instead: it enforces every field rule and the
|
|
9
|
+
// cross-field invariants (INV-CHOICE-ID, decisionKind/multiSelect coherence,
|
|
10
|
+
// free_text/no_match coupling) that the spec spells out in sections 6 and 7. It
|
|
11
|
+
// returns a flat list of human-readable error strings; empty means valid.
|
|
12
|
+
//
|
|
13
|
+
// It is used by the contract tests and, fail-soft, at capture time so a malformed
|
|
14
|
+
// decision is logged and skipped rather than crashing the hook it rides on.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.validateCanonicalDecisionPayload = validateCanonicalDecisionPayload;
|
|
17
|
+
exports.isValidCanonicalDecisionPayload = isValidCanonicalDecisionPayload;
|
|
18
|
+
const DECISION_KINDS = ["choice", "multi_choice", "free_text"];
|
|
19
|
+
const ANSWER_TYPES = ["choice_label", "multi_choice_labels", "free_text"];
|
|
20
|
+
const MATCH_STATUSES = ["exact_unique", "exact_ambiguous", "no_match"];
|
|
21
|
+
const CAPTURED_BY = ["post_tool_use", "stop_transcript_scan"];
|
|
22
|
+
const CHOICE_ID_RE = /^choice_\d+$/;
|
|
23
|
+
function isPlainObject(v) {
|
|
24
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
25
|
+
}
|
|
26
|
+
function isNonEmptyString(v) {
|
|
27
|
+
return typeof v === "string" && v.length > 0;
|
|
28
|
+
}
|
|
29
|
+
function validatePrompt(prompt, errs) {
|
|
30
|
+
if (!isPlainObject(prompt)) {
|
|
31
|
+
errs.push("prompt: must be an object {title, body}");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (typeof prompt.title !== "string")
|
|
35
|
+
errs.push("prompt.title: must be a string");
|
|
36
|
+
if (typeof prompt.body !== "string")
|
|
37
|
+
errs.push("prompt.body: must be a string");
|
|
38
|
+
}
|
|
39
|
+
function validateChoices(choices, errs) {
|
|
40
|
+
const ids = new Set();
|
|
41
|
+
if (!Array.isArray(choices)) {
|
|
42
|
+
errs.push("choices: must be an array");
|
|
43
|
+
return ids;
|
|
44
|
+
}
|
|
45
|
+
choices.forEach((c, i) => {
|
|
46
|
+
if (!isPlainObject(c)) {
|
|
47
|
+
errs.push(`choices[${i}]: must be an object {id,label,description?}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// INV-CHOICE-ID: positional, collision-safe, never a slug.
|
|
51
|
+
if (typeof c.id !== "string" || !CHOICE_ID_RE.test(c.id)) {
|
|
52
|
+
errs.push(`choices[${i}].id: must match choice_<index>, got ${JSON.stringify(c.id)}`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
if (ids.has(c.id))
|
|
56
|
+
errs.push(`choices[${i}].id: duplicate id ${c.id}`);
|
|
57
|
+
ids.add(c.id);
|
|
58
|
+
}
|
|
59
|
+
if (typeof c.label !== "string")
|
|
60
|
+
errs.push(`choices[${i}].label: must be a string`);
|
|
61
|
+
if (c.description !== undefined && typeof c.description !== "string") {
|
|
62
|
+
errs.push(`choices[${i}].description: must be a string when present`);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
return ids;
|
|
66
|
+
}
|
|
67
|
+
function validateAnswer(answer, choiceIds, errs) {
|
|
68
|
+
if (!isPlainObject(answer)) {
|
|
69
|
+
errs.push("answer: must be an object");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const a = answer;
|
|
73
|
+
if (typeof a.type !== "string" || !ANSWER_TYPES.includes(a.type)) {
|
|
74
|
+
errs.push(`answer.type: must be one of ${ANSWER_TYPES.join(", ")}`);
|
|
75
|
+
}
|
|
76
|
+
if (typeof a.choiceMatchStatus !== "string" || !MATCH_STATUSES.includes(a.choiceMatchStatus)) {
|
|
77
|
+
errs.push(`answer.choiceMatchStatus: must be one of ${MATCH_STATUSES.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
if (!("raw" in a)) {
|
|
80
|
+
errs.push("answer.raw: must be present (INV-RAW-PRESERVATION applies to the raw answer too)");
|
|
81
|
+
}
|
|
82
|
+
// value shape depends on type.
|
|
83
|
+
if (a.type === "multi_choice_labels") {
|
|
84
|
+
if (!Array.isArray(a.value) || !a.value.every((x) => typeof x === "string")) {
|
|
85
|
+
errs.push("answer.value: must be a string[] when type is multi_choice_labels");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (a.type === "choice_label" || a.type === "free_text") {
|
|
89
|
+
if (typeof a.value !== "string") {
|
|
90
|
+
errs.push(`answer.value: must be a string when type is ${a.type}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// choiceId presence/format rules (single-select).
|
|
94
|
+
if (a.choiceId !== undefined) {
|
|
95
|
+
if (typeof a.choiceId !== "string" || !CHOICE_ID_RE.test(a.choiceId)) {
|
|
96
|
+
errs.push(`answer.choiceId: must match choice_<index>, got ${JSON.stringify(a.choiceId)}`);
|
|
97
|
+
}
|
|
98
|
+
else if (!choiceIds.has(a.choiceId)) {
|
|
99
|
+
errs.push(`answer.choiceId: ${a.choiceId} is not one of the offered choices`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (a.choiceIds !== undefined) {
|
|
103
|
+
if (!Array.isArray(a.choiceIds) || !a.choiceIds.every((x) => typeof x === "string" && CHOICE_ID_RE.test(x))) {
|
|
104
|
+
errs.push("answer.choiceIds: must be an array of choice_<index> ids when present");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
for (const cid of a.choiceIds) {
|
|
108
|
+
if (!choiceIds.has(cid))
|
|
109
|
+
errs.push(`answer.choiceIds: ${cid} is not one of the offered choices`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Cross-field coupling (spec section 6, derivation refined for multi-select).
|
|
114
|
+
// no_match is incompatible only with a singular choice_label claim. A single
|
|
115
|
+
// free_text answer carries it, and a multi_choice answer where no provided
|
|
116
|
+
// value matched an offered label may carry it too.
|
|
117
|
+
if (a.choiceMatchStatus === "no_match" && a.choiceId !== undefined) {
|
|
118
|
+
errs.push("answer: no_match must not carry a choiceId");
|
|
119
|
+
}
|
|
120
|
+
if (a.type === "choice_label") {
|
|
121
|
+
if (a.choiceMatchStatus === "no_match") {
|
|
122
|
+
errs.push("answer: choice_label cannot have choiceMatchStatus no_match");
|
|
123
|
+
}
|
|
124
|
+
if (a.choiceId === undefined) {
|
|
125
|
+
errs.push("answer: choice_label requires a choiceId");
|
|
126
|
+
}
|
|
127
|
+
if (a.choiceIds !== undefined) {
|
|
128
|
+
errs.push("answer: choice_label is single-select; use choiceId, not choiceIds");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (a.type === "free_text") {
|
|
132
|
+
if (a.choiceMatchStatus !== "no_match") {
|
|
133
|
+
errs.push("answer: free_text requires choiceMatchStatus no_match");
|
|
134
|
+
}
|
|
135
|
+
if (a.choiceId !== undefined)
|
|
136
|
+
errs.push("answer: free_text must not carry a choiceId");
|
|
137
|
+
if (a.choiceIds !== undefined)
|
|
138
|
+
errs.push("answer: free_text must not carry choiceIds");
|
|
139
|
+
}
|
|
140
|
+
if (a.type === "multi_choice_labels" && a.choiceId !== undefined) {
|
|
141
|
+
errs.push("answer: multi_choice_labels is multi-select; use choiceIds, not a singular choiceId");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Returns a flat list of validation errors; empty array means the payload
|
|
145
|
+
// satisfies the canonical contract.
|
|
146
|
+
function validateCanonicalDecisionPayload(payload) {
|
|
147
|
+
const errs = [];
|
|
148
|
+
if (!isPlainObject(payload)) {
|
|
149
|
+
return ["payload: must be an object"];
|
|
150
|
+
}
|
|
151
|
+
const p = payload;
|
|
152
|
+
if (!isNonEmptyString(p.provider))
|
|
153
|
+
errs.push("provider: must be a non-empty string");
|
|
154
|
+
if (!isNonEmptyString(p.providerSource))
|
|
155
|
+
errs.push("providerSource: must be a non-empty string");
|
|
156
|
+
if (!isNonEmptyString(p.providerEventId))
|
|
157
|
+
errs.push("providerEventId: must be a non-empty string");
|
|
158
|
+
if (p.providerToolName !== undefined && p.providerToolName !== null && typeof p.providerToolName !== "string") {
|
|
159
|
+
errs.push("providerToolName: must be a string, null, or omitted");
|
|
160
|
+
}
|
|
161
|
+
if (p.providerSessionId !== undefined && p.providerSessionId !== null && typeof p.providerSessionId !== "string") {
|
|
162
|
+
errs.push("providerSessionId: must be a string, null, or omitted");
|
|
163
|
+
}
|
|
164
|
+
if (typeof p.decisionKind !== "string" || !DECISION_KINDS.includes(p.decisionKind)) {
|
|
165
|
+
errs.push(`decisionKind: must be one of ${DECISION_KINDS.join(", ")}`);
|
|
166
|
+
}
|
|
167
|
+
validatePrompt(p.prompt, errs);
|
|
168
|
+
const choiceIds = validateChoices(p.choices, errs);
|
|
169
|
+
validateAnswer(p.answer, choiceIds, errs);
|
|
170
|
+
// decisionKind is derived from multiSelect + match outcome (spec section 6);
|
|
171
|
+
// enforce the coupling so the three fields can never drift.
|
|
172
|
+
const ansType = isPlainObject(p.answer) ? p.answer.type : undefined;
|
|
173
|
+
if (p.decisionKind === "multi_choice" && ansType !== "multi_choice_labels") {
|
|
174
|
+
errs.push("answer.type: must be multi_choice_labels when decisionKind is multi_choice");
|
|
175
|
+
}
|
|
176
|
+
if (p.decisionKind === "choice" && ansType !== "choice_label") {
|
|
177
|
+
errs.push("answer.type: must be choice_label when decisionKind is choice");
|
|
178
|
+
}
|
|
179
|
+
if (p.decisionKind === "free_text" && ansType !== "free_text") {
|
|
180
|
+
errs.push("answer.type: must be free_text when decisionKind is free_text");
|
|
181
|
+
}
|
|
182
|
+
if (typeof p.multiSelect !== "boolean") {
|
|
183
|
+
errs.push("multiSelect: must be a boolean");
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Biconditional per spec section 6: multiSelect true iff decisionKind multi_choice.
|
|
187
|
+
if (p.multiSelect && p.decisionKind !== "multi_choice") {
|
|
188
|
+
errs.push("decisionKind: must be multi_choice when multiSelect is true");
|
|
189
|
+
}
|
|
190
|
+
if (!p.multiSelect && p.decisionKind === "multi_choice") {
|
|
191
|
+
errs.push("multiSelect: must be true when decisionKind is multi_choice");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (typeof p.capturedBy !== "string" || !CAPTURED_BY.includes(p.capturedBy)) {
|
|
195
|
+
errs.push(`capturedBy: must be one of ${CAPTURED_BY.join(", ")}`);
|
|
196
|
+
}
|
|
197
|
+
if (p.turnIndex !== undefined && p.turnIndex !== null && typeof p.turnIndex !== "number") {
|
|
198
|
+
errs.push("turnIndex: must be a number, null, or omitted");
|
|
199
|
+
}
|
|
200
|
+
if (p.traceId !== undefined && p.traceId !== null && typeof p.traceId !== "string") {
|
|
201
|
+
errs.push("traceId: must be a string, null, or omitted");
|
|
202
|
+
}
|
|
203
|
+
if (p.actorDisplayName !== undefined && p.actorDisplayName !== null && typeof p.actorDisplayName !== "string") {
|
|
204
|
+
errs.push("actorDisplayName: must be a string, null, or omitted");
|
|
205
|
+
}
|
|
206
|
+
if (!("rawProviderPayload" in p)) {
|
|
207
|
+
errs.push("rawProviderPayload: must be present (INV-RAW-PRESERVATION)");
|
|
208
|
+
}
|
|
209
|
+
if (p.occurredAt !== undefined && typeof p.occurredAt !== "string") {
|
|
210
|
+
errs.push("occurredAt: must be an ISO string when present");
|
|
211
|
+
}
|
|
212
|
+
return errs;
|
|
213
|
+
}
|
|
214
|
+
function isValidCanonicalDecisionPayload(payload) {
|
|
215
|
+
return validateCanonicalDecisionPayload(payload).length === 0;
|
|
216
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// captureCommandEvent: the run-finalize entry point that records one normalized
|
|
3
|
+
// `mla_command` journey event (spec section 6.2, section 11.4). It is the only
|
|
4
|
+
// thing cli.ts has to call. It runs after the command result is known and is
|
|
5
|
+
// fully defensive: any failure here is swallowed so analytics can never change a
|
|
6
|
+
// command's exit code or break its output.
|
|
7
|
+
//
|
|
8
|
+
// Order (matters): derive the sequence fields BEFORE recording, since they are
|
|
9
|
+
// read from the strictly-prior `mla_command` rows in the local jsonl; then record
|
|
10
|
+
// locally (durable, consent-gated); then best-effort forward to control (bounded,
|
|
11
|
+
// telemetry-gated). The local append happens even with remote telemetry off
|
|
12
|
+
// (local-first, INV-LOCAL-STATS-1); the forward is a no-op unless opted in.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.buildCommandPayload = buildCommandPayload;
|
|
15
|
+
exports.captureCommandEvent = captureCommandEvent;
|
|
16
|
+
const command_event_1 = require("./command-event");
|
|
17
|
+
const sequence_1 = require("./sequence");
|
|
18
|
+
const store_1 = require("./store");
|
|
19
|
+
const recorder_1 = require("./recorder");
|
|
20
|
+
// Build the normalized command payload (pure). Exported so the privacy test can
|
|
21
|
+
// assert directly on what would be emitted, no I/O.
|
|
22
|
+
function buildCommandPayload(params) {
|
|
23
|
+
const { command, subcommand, flags_shape } = (0, command_event_1.normalizeCommand)(params.argv);
|
|
24
|
+
const { outcome, error_class, retryable } = (0, command_event_1.classifyOutcome)(params.exitCode, params.threw, params.thrown);
|
|
25
|
+
const seq = (0, sequence_1.computeSequence)(params.sessionId, params.startedAtMs, params.env);
|
|
26
|
+
const duration_ms = Math.max(0, params.nowMs - params.startedAtMs);
|
|
27
|
+
return {
|
|
28
|
+
command,
|
|
29
|
+
subcommand,
|
|
30
|
+
flags_shape,
|
|
31
|
+
scope: (0, command_event_1.classifyScope)(command, flags_shape),
|
|
32
|
+
duration_ms,
|
|
33
|
+
exit_code: params.exitCode,
|
|
34
|
+
outcome,
|
|
35
|
+
error_class,
|
|
36
|
+
retryable,
|
|
37
|
+
// The CLI command itself does not edit a code surface (that signal is for
|
|
38
|
+
// hook-origin events that wrap an edit). Honest default for a command event.
|
|
39
|
+
touched_surface: "unknown",
|
|
40
|
+
mla_version: params.mlaVersion,
|
|
41
|
+
git_sha: params.gitSha,
|
|
42
|
+
command_index_in_session: seq.command_index_in_session,
|
|
43
|
+
preceded_by: seq.preceded_by,
|
|
44
|
+
session_idle_gap_ms: seq.session_idle_gap_ms,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function captureCommandEvent(params) {
|
|
48
|
+
const env = params.env ?? process.env;
|
|
49
|
+
try {
|
|
50
|
+
// `_internal` subcommands (evidence-inject, evidence-correlate, auto-index,
|
|
51
|
+
// finalize-session, active-review) are machine-internal plumbing spawned by
|
|
52
|
+
// hooks, not user journey steps. Emitting an mla_command for them pollutes the
|
|
53
|
+
// command-journey funnel with `command:"_internal", subcommand:null` noise, so
|
|
54
|
+
// skip the journey event entirely. The remote flush is kept: an internal
|
|
55
|
+
// command flushes its own buffer before this point, but keeping the flush
|
|
56
|
+
// preserves forwarding for any value events still buffered (no-op when empty).
|
|
57
|
+
const isInternal = (0, command_event_1.normalizeCommand)(params.argv).command === "_internal";
|
|
58
|
+
if (!isInternal) {
|
|
59
|
+
const payload = buildCommandPayload({
|
|
60
|
+
argv: params.argv,
|
|
61
|
+
exitCode: params.exitCode,
|
|
62
|
+
threw: params.threw,
|
|
63
|
+
thrown: params.thrown,
|
|
64
|
+
mlaVersion: params.mlaVersion,
|
|
65
|
+
gitSha: params.gitSha,
|
|
66
|
+
startedAtMs: params.startedAtMs,
|
|
67
|
+
nowMs: params.nowMs,
|
|
68
|
+
sessionId: params.sessionId,
|
|
69
|
+
env,
|
|
70
|
+
});
|
|
71
|
+
const nowIso = new Date(params.nowMs).toISOString();
|
|
72
|
+
const ctx = {
|
|
73
|
+
workspaceId: params.workspaceId,
|
|
74
|
+
sessionId: params.sessionId,
|
|
75
|
+
distinctId: params.actorUserId ?? (0, store_1.machineId)(),
|
|
76
|
+
// The un-collapsed actor cuid for attribution (T1.10): honest null on an
|
|
77
|
+
// actorless run, unlike distinctId which falls back to a hashed machine id.
|
|
78
|
+
actorWorkspaceUserId: params.actorUserId,
|
|
79
|
+
source: "cli",
|
|
80
|
+
now: nowIso,
|
|
81
|
+
};
|
|
82
|
+
// Local append (durable) + buffer. Mints the CLI-origin event_id once.
|
|
83
|
+
(0, recorder_1.recordAnalyticsEvent)(ctx, { eventType: "mla_command", payload: payload }, env, params.onError);
|
|
84
|
+
}
|
|
85
|
+
// Best-effort, bounded, telemetry-gated remote forward. Skipped entirely when
|
|
86
|
+
// the run has no control config.
|
|
87
|
+
if (params.cfg) {
|
|
88
|
+
await (0, recorder_1.flushAnalyticsEvents)(params.cfg, env, params.onError);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// Analytics must never break a command. Surface on the debug hook only.
|
|
93
|
+
if (params.onError)
|
|
94
|
+
params.onError(err);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// mla_command normalization (spec section 6.2, INV-ARGV-1, INV-POSTHOG-PII-1).
|
|
3
|
+
//
|
|
4
|
+
// Turns a raw argv into the three privacy-safe shape fields the journey event
|
|
5
|
+
// carries: a known `command`, a known `subcommand` (or null), and a `flags_shape`
|
|
6
|
+
// built from approved flag NAMES only. Raw argv is never emitted: positional
|
|
7
|
+
// arguments (queries, paths, ids) do not start with a dash and are dropped; flag
|
|
8
|
+
// VALUES are split off at `=` or live in a separate token and are likewise
|
|
9
|
+
// dropped; an unrecognized command or flag is normalized away rather than passed
|
|
10
|
+
// through. This is the single chokepoint that keeps INV-ARGV-1 true.
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.APPROVED_FLAGS = exports.KNOWN_SUBCOMMANDS = exports.KNOWN_COMMANDS = void 0;
|
|
13
|
+
exports.normalizeCommand = normalizeCommand;
|
|
14
|
+
exports.classifyScope = classifyScope;
|
|
15
|
+
exports.classifyOutcome = classifyOutcome;
|
|
16
|
+
// The closed set of top-level commands `mla` dispatches (cli.ts:215). A first
|
|
17
|
+
// token outside this set is normalized to "unknown" so a typo'd path or secret
|
|
18
|
+
// pasted as argv[0] never reaches the wire.
|
|
19
|
+
exports.KNOWN_COMMANDS = new Set([
|
|
20
|
+
"init",
|
|
21
|
+
"rewire",
|
|
22
|
+
"activate",
|
|
23
|
+
"deactivate",
|
|
24
|
+
"mute",
|
|
25
|
+
"unmute",
|
|
26
|
+
"workspace",
|
|
27
|
+
"doctor",
|
|
28
|
+
"flush",
|
|
29
|
+
"review",
|
|
30
|
+
"cases",
|
|
31
|
+
"session",
|
|
32
|
+
"ask",
|
|
33
|
+
"kb",
|
|
34
|
+
"summary",
|
|
35
|
+
"label",
|
|
36
|
+
"adoption",
|
|
37
|
+
"stats",
|
|
38
|
+
"whoami",
|
|
39
|
+
"login",
|
|
40
|
+
"logout",
|
|
41
|
+
"debug",
|
|
42
|
+
"_internal",
|
|
43
|
+
]);
|
|
44
|
+
// Known subcommand keywords per command. A second token is emitted as
|
|
45
|
+
// `subcommand` ONLY when it is in this set; otherwise it is a positional (an id,
|
|
46
|
+
// a query, a doc path) and `subcommand` is null. This is what stops
|
|
47
|
+
// `mla review <case-id>` or `mla ask "<query>"` from leaking the positional as a
|
|
48
|
+
// subcommand.
|
|
49
|
+
exports.KNOWN_SUBCOMMANDS = {
|
|
50
|
+
kb: new Set([
|
|
51
|
+
"add",
|
|
52
|
+
"show",
|
|
53
|
+
"reingest",
|
|
54
|
+
"forget",
|
|
55
|
+
"purge",
|
|
56
|
+
"move",
|
|
57
|
+
"review",
|
|
58
|
+
"pending",
|
|
59
|
+
"personal",
|
|
60
|
+
"promote",
|
|
61
|
+
"share",
|
|
62
|
+
"retime",
|
|
63
|
+
"summary",
|
|
64
|
+
]),
|
|
65
|
+
workspace: new Set(["show", "use"]),
|
|
66
|
+
session: new Set(["show"]),
|
|
67
|
+
stats: new Set(["evidence"]),
|
|
68
|
+
_internal: new Set(["finalize-session", "active-review", "auto-index"]),
|
|
69
|
+
};
|
|
70
|
+
// Approved flag NAMES (INV-ARGV-1). A token starting with a dash is reduced to
|
|
71
|
+
// its name (leading dashes stripped, anything after `=` discarded) and kept only
|
|
72
|
+
// if it is in this set. The set is flag NAMES the CLI actually parses; values are
|
|
73
|
+
// never emitted regardless of allowlisting. Unknown flag names are dropped, not
|
|
74
|
+
// surfaced, so a future flag is invisible to analytics until it is added here
|
|
75
|
+
// (privacy-conservative by construction).
|
|
76
|
+
exports.APPROVED_FLAGS = new Set([
|
|
77
|
+
"accept",
|
|
78
|
+
"actor",
|
|
79
|
+
"agent",
|
|
80
|
+
"all",
|
|
81
|
+
"allow-file-missing",
|
|
82
|
+
"allow-provenance-change",
|
|
83
|
+
"anchor-type",
|
|
84
|
+
"apply",
|
|
85
|
+
"as-of",
|
|
86
|
+
"audit-all",
|
|
87
|
+
"cached",
|
|
88
|
+
"control-token",
|
|
89
|
+
"control-url",
|
|
90
|
+
"create",
|
|
91
|
+
"doc",
|
|
92
|
+
"dry-run",
|
|
93
|
+
"effective-date",
|
|
94
|
+
"evidence",
|
|
95
|
+
"force",
|
|
96
|
+
"from-root",
|
|
97
|
+
"gc",
|
|
98
|
+
"glob",
|
|
99
|
+
"global",
|
|
100
|
+
"harmful",
|
|
101
|
+
"help",
|
|
102
|
+
"here",
|
|
103
|
+
"include-tombstoned",
|
|
104
|
+
"ingest-run-id",
|
|
105
|
+
"intel-url",
|
|
106
|
+
"is-inside-work-tree",
|
|
107
|
+
"json",
|
|
108
|
+
"last",
|
|
109
|
+
"markdown",
|
|
110
|
+
"marker",
|
|
111
|
+
"max",
|
|
112
|
+
"min",
|
|
113
|
+
"mode",
|
|
114
|
+
"name",
|
|
115
|
+
"no-flush",
|
|
116
|
+
"no-install-flock",
|
|
117
|
+
"no-post-tool-use",
|
|
118
|
+
"no-project-rules",
|
|
119
|
+
"no-relation",
|
|
120
|
+
"noisy",
|
|
121
|
+
"note",
|
|
122
|
+
"oneline",
|
|
123
|
+
"open",
|
|
124
|
+
"path",
|
|
125
|
+
"plain",
|
|
126
|
+
"posture",
|
|
127
|
+
"prevented-mistake",
|
|
128
|
+
"profile",
|
|
129
|
+
"provenance",
|
|
130
|
+
"purge-expired",
|
|
131
|
+
"queue",
|
|
132
|
+
"quiet",
|
|
133
|
+
"reap-only",
|
|
134
|
+
"reason",
|
|
135
|
+
"reclassify",
|
|
136
|
+
"reject",
|
|
137
|
+
"repair",
|
|
138
|
+
"scope-section",
|
|
139
|
+
"session",
|
|
140
|
+
"show-current",
|
|
141
|
+
"show-toplevel",
|
|
142
|
+
"skill-only",
|
|
143
|
+
"stat",
|
|
144
|
+
"unsafe-capture-non-bash",
|
|
145
|
+
"useful",
|
|
146
|
+
"verbose",
|
|
147
|
+
"window",
|
|
148
|
+
"workspace",
|
|
149
|
+
"workspace-id",
|
|
150
|
+
"yes",
|
|
151
|
+
]);
|
|
152
|
+
// Coarse command-to-scope map (where the command's effect primarily lands).
|
|
153
|
+
// Command-granularity on purpose: a per-subcommand effect tracker is not worth
|
|
154
|
+
// the maintenance for a breakdown dimension. Anchored to the spec's own example
|
|
155
|
+
// (section 11: `kb review` -> scope "local"), so `kb` is local even though some
|
|
156
|
+
// subcommands sync. `ask` / `adoption` / `summary` fundamentally read or compute
|
|
157
|
+
// over workspace data, so they are "workspace". A `--global` flag overrides to
|
|
158
|
+
// "global". Anything unrecognized is "unknown" (never guessed).
|
|
159
|
+
const WORKSPACE_SCOPE_COMMANDS = new Set(["ask", "adoption", "summary"]);
|
|
160
|
+
// Normalize the first token to a known command, "help"/"version" for the usage
|
|
161
|
+
// and version shortcuts, or "unknown".
|
|
162
|
+
function normalizeCommandToken(first) {
|
|
163
|
+
if (first === undefined || first === "help" || first === "--help" || first === "-h") {
|
|
164
|
+
return "help";
|
|
165
|
+
}
|
|
166
|
+
if (first === "--version" || first === "-v")
|
|
167
|
+
return "version";
|
|
168
|
+
if (exports.KNOWN_COMMANDS.has(first))
|
|
169
|
+
return first;
|
|
170
|
+
return "unknown";
|
|
171
|
+
}
|
|
172
|
+
// Reduce a raw token to its flag name, or null if it is not a flag (positional)
|
|
173
|
+
// or not approved. `--window=7d` -> "window"; `--window 7d` -> "window" (the
|
|
174
|
+
// "7d" is a separate token that returns null here); `query text` -> null.
|
|
175
|
+
function flagName(token) {
|
|
176
|
+
if (!token.startsWith("-"))
|
|
177
|
+
return null;
|
|
178
|
+
const stripped = token.replace(/^-+/, "");
|
|
179
|
+
if (!stripped)
|
|
180
|
+
return null;
|
|
181
|
+
const name = stripped.split("=", 1)[0].toLowerCase();
|
|
182
|
+
return exports.APPROVED_FLAGS.has(name) ? name : null;
|
|
183
|
+
}
|
|
184
|
+
// Build the privacy-safe command shape from argv. Pure, no I/O.
|
|
185
|
+
function normalizeCommand(argv) {
|
|
186
|
+
const command = normalizeCommandToken(argv[0]);
|
|
187
|
+
let subcommand = null;
|
|
188
|
+
const knownSubs = exports.KNOWN_SUBCOMMANDS[command];
|
|
189
|
+
if (knownSubs && typeof argv[1] === "string" && knownSubs.has(argv[1])) {
|
|
190
|
+
subcommand = argv[1];
|
|
191
|
+
}
|
|
192
|
+
// Scan ALL tokens for approved flags (flags can follow positionals). Dedupe and
|
|
193
|
+
// sort for a stable shape so PostHog funnels group identical invocations.
|
|
194
|
+
const flags = new Set();
|
|
195
|
+
for (const token of argv) {
|
|
196
|
+
const name = flagName(token);
|
|
197
|
+
if (name)
|
|
198
|
+
flags.add(name);
|
|
199
|
+
}
|
|
200
|
+
const flags_shape = [...flags].sort();
|
|
201
|
+
return { command, subcommand, flags_shape };
|
|
202
|
+
}
|
|
203
|
+
// Scope of the command's effect (best-effort, command-granularity). `--global`
|
|
204
|
+
// in flags_shape wins.
|
|
205
|
+
function classifyScope(command, flags_shape) {
|
|
206
|
+
if (flags_shape.includes("global"))
|
|
207
|
+
return "global";
|
|
208
|
+
if (command === "unknown")
|
|
209
|
+
return "unknown";
|
|
210
|
+
if (WORKSPACE_SCOPE_COMMANDS.has(command))
|
|
211
|
+
return "workspace";
|
|
212
|
+
return "local";
|
|
213
|
+
}
|
|
214
|
+
// Map a finished run (exit code + optional thrown error) to a closed-enum
|
|
215
|
+
// outcome, a PII-safe error_class (a class/category token, NEVER a message), and
|
|
216
|
+
// a retryable hint. The error's `status` (set by lib/http.buildError on HTTP
|
|
217
|
+
// failures) drives the HTTP mapping; otherwise the error code/name is inspected
|
|
218
|
+
// for the common network failure modes.
|
|
219
|
+
function classifyOutcome(exitCode, threw, thrown) {
|
|
220
|
+
if (exitCode === 0) {
|
|
221
|
+
return { outcome: "success", error_class: null, retryable: false };
|
|
222
|
+
}
|
|
223
|
+
if (!threw) {
|
|
224
|
+
// A command returned non-zero without throwing: a handled, user-facing
|
|
225
|
+
// failure (bad input, not found, usage error -> exit 2). No exception object
|
|
226
|
+
// to classify, so no error_class.
|
|
227
|
+
return { outcome: "user_error", error_class: null, retryable: false };
|
|
228
|
+
}
|
|
229
|
+
const err = thrown;
|
|
230
|
+
const status = err?.status;
|
|
231
|
+
if (typeof status === "number") {
|
|
232
|
+
if (status === 401)
|
|
233
|
+
return { outcome: "auth_error", error_class: "http_401", retryable: false };
|
|
234
|
+
if (status === 403)
|
|
235
|
+
return { outcome: "permission_denied", error_class: "http_403", retryable: false };
|
|
236
|
+
if (status === 408)
|
|
237
|
+
return { outcome: "timeout", error_class: "http_408", retryable: true };
|
|
238
|
+
if (status === 429)
|
|
239
|
+
return { outcome: "system_error", error_class: "http_429", retryable: true };
|
|
240
|
+
if (status === 400 || status === 422) {
|
|
241
|
+
return { outcome: "validation_error", error_class: `http_${status}`, retryable: false };
|
|
242
|
+
}
|
|
243
|
+
if (status >= 500)
|
|
244
|
+
return { outcome: "system_error", error_class: `http_${status}`, retryable: true };
|
|
245
|
+
// Other 4xx: a client-side problem the user must fix.
|
|
246
|
+
return { outcome: "user_error", error_class: `http_${status}`, retryable: false };
|
|
247
|
+
}
|
|
248
|
+
// No HTTP status: a network-layer or programmatic error. fetch() rejects with a
|
|
249
|
+
// TypeError ("fetch failed") wrapping a cause; AbortController aborts surface as
|
|
250
|
+
// an AbortError; node socket errors carry an errno code.
|
|
251
|
+
const code = (err?.code || "").toUpperCase();
|
|
252
|
+
const name = err?.name || "";
|
|
253
|
+
if (code === "ETIMEDOUT" || name === "AbortError" || name === "TimeoutError") {
|
|
254
|
+
return { outcome: "timeout", error_class: "timeout", retryable: true };
|
|
255
|
+
}
|
|
256
|
+
if (code === "ECONNREFUSED" ||
|
|
257
|
+
code === "ENOTFOUND" ||
|
|
258
|
+
code === "ECONNRESET" ||
|
|
259
|
+
code === "EAI_AGAIN" ||
|
|
260
|
+
name === "FetchError" ||
|
|
261
|
+
name === "TypeError" // node's fetch wraps network failures as TypeError
|
|
262
|
+
) {
|
|
263
|
+
return { outcome: "network_error", error_class: "network_error", retryable: true };
|
|
264
|
+
}
|
|
265
|
+
// Unknown thrown error: a class name is PII-safe (it is a type, not a message).
|
|
266
|
+
return { outcome: "system_error", error_class: name || "Error", retryable: false };
|
|
267
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Analytics consent: the three privacy postures (spec section 9, INV-CONSENT-1).
|
|
3
|
+
//
|
|
4
|
+
// Local recording, remote ids-only analytics, and content-bearing trace upload
|
|
5
|
+
// are genuinely different privacy decisions; a user may want ids-only analytics
|
|
6
|
+
// on but content traces off. We reconcile with the EXISTING CLI kill switch
|
|
7
|
+
// (TELEMETRY.md) rather than inventing a parallel flag set:
|
|
8
|
+
//
|
|
9
|
+
// MEETLESS_LOCAL_STATS default ON -> write ~/.meetless/events.jsonl
|
|
10
|
+
// MEETLESS_TELEMETRY default OFF -> ship ids-only events to control->PostHog
|
|
11
|
+
// AND keep its existing kill-switch role
|
|
12
|
+
// MEETLESS_TRACE_UPLOAD default ON* -> content-bearing traces (Langfuse) + Sentry
|
|
13
|
+
//
|
|
14
|
+
// (*) MEETLESS_TRACE_UPLOAD's ABSENCE preserves today's trace-plane behavior so
|
|
15
|
+
// dogfood keeps working; the effective posture is still "off unless your server
|
|
16
|
+
// opts in" because control refuses with TRACING_NOT_ENABLED_FOR_WORKSPACE. The
|
|
17
|
+
// flag is an explicit content-trace sub-kill, independent of the analytics opt-in.
|
|
18
|
+
//
|
|
19
|
+
// The master kill switch (telemetryDisabled: MEETLESS_TELEMETRY in {off,0,false,no}
|
|
20
|
+
// OR truthy MEETLESS_NO_TELEMETRY) wins over BOTH remote planes.
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.localStatsEnabled = localStatsEnabled;
|
|
23
|
+
exports.remoteAnalyticsEnabled = remoteAnalyticsEnabled;
|
|
24
|
+
exports.traceUploadEnabled = traceUploadEnabled;
|
|
25
|
+
const observability_1 = require("../observability");
|
|
26
|
+
function isOff(v) {
|
|
27
|
+
const t = (v || "").trim().toLowerCase();
|
|
28
|
+
return t === "off" || t === "0" || t === "false" || t === "no";
|
|
29
|
+
}
|
|
30
|
+
function isOn(v) {
|
|
31
|
+
const t = (v || "").trim().toLowerCase();
|
|
32
|
+
return t === "on" || t === "1" || t === "true" || t === "yes";
|
|
33
|
+
}
|
|
34
|
+
// Local jsonl recording for `mla stats`. Default ON. Only an explicit off-value
|
|
35
|
+
// disables it. Independent of the remote planes: `mla stats` (local) works even
|
|
36
|
+
// with all remote telemetry off (INV-LOCAL-STATS-1, INV-CONSENT-1).
|
|
37
|
+
function localStatsEnabled(env = process.env) {
|
|
38
|
+
return !isOff(env.MEETLESS_LOCAL_STATS);
|
|
39
|
+
}
|
|
40
|
+
// Remote ids-only analytics upload (CLI -> control -> PostHog/rollups). Default
|
|
41
|
+
// OFF: it is an explicit opt-in via a truthy MEETLESS_TELEMETRY. The master kill
|
|
42
|
+
// (telemetryDisabled) always wins. Note the asymmetry with the trace plane:
|
|
43
|
+
// analytics is opt-IN (silent unless turned on), trace upload is opt-OUT.
|
|
44
|
+
function remoteAnalyticsEnabled(env = process.env) {
|
|
45
|
+
if ((0, observability_1.telemetryDisabled)(env))
|
|
46
|
+
return false;
|
|
47
|
+
return isOn(env.MEETLESS_TELEMETRY);
|
|
48
|
+
}
|
|
49
|
+
// Content-bearing trace upload (Langfuse spans) + Sentry error reporting. The
|
|
50
|
+
// master kill wins; an explicit MEETLESS_TRACE_UPLOAD off-value is the content
|
|
51
|
+
// sub-kill; absence preserves the pre-existing trace-plane behavior (still
|
|
52
|
+
// server-gated downstream). This is what gates initSentry() and the trace
|
|
53
|
+
// flushFn at their cli.ts call sites.
|
|
54
|
+
function traceUploadEnabled(env = process.env) {
|
|
55
|
+
if ((0, observability_1.telemetryDisabled)(env))
|
|
56
|
+
return false;
|
|
57
|
+
return !isOff(env.MEETLESS_TRACE_UPLOAD);
|
|
58
|
+
}
|