@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,267 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.insertToolAttempt = insertToolAttempt;
4
+ exports.getToolAttempt = getToolAttempt;
5
+ exports.advanceDenyEmissionToResponseEmitted = advanceDenyEmissionToResponseEmitted;
6
+ exports.countDenyDecisionsAwaitingEmission = countDenyDecisionsAwaitingEmission;
7
+ exports.countFailOpenEnforcementViolations = countFailOpenEnforcementViolations;
8
+ exports.insertRuleEvaluationRecord = insertRuleEvaluationRecord;
9
+ exports.getRuleEvaluationRecord = getRuleEvaluationRecord;
10
+ exports.listEvaluationsForAttempt = listEvaluationsForAttempt;
11
+ exports.listObservedRulesInScope = listObservedRulesInScope;
12
+ exports.resolveObservedSnapshotInScope = resolveObservedSnapshotInScope;
13
+ // ---------------------------------------------------------------------------
14
+ // tool_attempt
15
+ // ---------------------------------------------------------------------------
16
+ function insertToolAttempt(store, rec) {
17
+ store.db
18
+ .prepare(`INSERT INTO tool_attempt
19
+ (attempt_id, runtime_scope_id, session_id, tool_name, evaluation_input_snapshot,
20
+ evaluation_input_hash, aggregate_decision, deny_emission_status,
21
+ input_authority_config_hash, created_at)
22
+ VALUES
23
+ (@attempt_id, @runtime_scope_id, @session_id, @tool_name, @evaluation_input_snapshot,
24
+ @evaluation_input_hash, @aggregate_decision, @deny_emission_status,
25
+ @input_authority_config_hash, @created_at)`)
26
+ .run({
27
+ attempt_id: rec.attemptId,
28
+ runtime_scope_id: rec.runtimeScopeId,
29
+ session_id: rec.sessionId,
30
+ tool_name: rec.toolName,
31
+ evaluation_input_snapshot: rec.evaluationInputSnapshot,
32
+ evaluation_input_hash: rec.evaluationInputHash,
33
+ aggregate_decision: rec.aggregateDecision,
34
+ deny_emission_status: rec.denyEmissionStatus,
35
+ input_authority_config_hash: rec.inputAuthorityConfigHash,
36
+ created_at: rec.createdAt,
37
+ });
38
+ }
39
+ function mapAttemptRow(row) {
40
+ return {
41
+ attemptId: row.attempt_id,
42
+ runtimeScopeId: row.runtime_scope_id,
43
+ sessionId: row.session_id,
44
+ toolName: row.tool_name,
45
+ evaluationInputSnapshot: row.evaluation_input_snapshot,
46
+ evaluationInputHash: row.evaluation_input_hash,
47
+ aggregateDecision: row.aggregate_decision,
48
+ denyEmissionStatus: row.deny_emission_status,
49
+ inputAuthorityConfigHash: row.input_authority_config_hash ?? null,
50
+ createdAt: row.created_at,
51
+ };
52
+ }
53
+ function getToolAttempt(store, attemptId) {
54
+ const row = store.db
55
+ .prepare(`SELECT * FROM tool_attempt WHERE attempt_id = ?`)
56
+ .get(attemptId);
57
+ if (!row)
58
+ return null;
59
+ return mapAttemptRow(row);
60
+ }
61
+ /**
62
+ * Advance a committed deny from DECISION_RECORDED to RESPONSE_EMITTED, the SINGLE mutation
63
+ * trg_attempt_frozen permits on tool_attempt (interception-schema.ts). The deny pilot (slice 10)
64
+ * persists the DECISION_RECORDED row and COMMITS it BEFORE emitting the deny response, then calls this
65
+ * to mark the response emitted. A crash between the deny commit and this advance therefore leaves an
66
+ * honest DECISION_RECORDED row (recoverable, never NO_DECISION) per R1-4. The WHERE clause touches ONLY
67
+ * a row still held at DENY / DECISION_RECORDED, so it changes nothing but deny_emission_status (the
68
+ * trigger's one allowed delta) and a re-run or a wrong-state row is a harmless no-op, never an abort.
69
+ */
70
+ function advanceDenyEmissionToResponseEmitted(store, attemptId) {
71
+ store.db
72
+ .prepare(`UPDATE tool_attempt
73
+ SET deny_emission_status = 'RESPONSE_EMITTED'
74
+ WHERE attempt_id = ?
75
+ AND aggregate_decision = 'DENY'
76
+ AND deny_emission_status = 'DECISION_RECORDED'`)
77
+ .run(attemptId);
78
+ }
79
+ /**
80
+ * Count the committed denies still held at DECISION_RECORDED: a deny whose decision was committed but
81
+ * whose response was never advanced to RESPONSE_EMITTED. These are the honest crash-window leftovers of
82
+ * the deny pilot (a crash between the deny commit and advanceDenyEmissionToResponseEmitted), recoverable
83
+ * and never lost (R1-4). `mla doctor` surfaces this count (P0.60 honest deny-emission accounting) so an
84
+ * operator can see whether the hook is recording denials it never emitted; a non-zero count is honest,
85
+ * not corruption, so it never goes RED.
86
+ */
87
+ function countDenyDecisionsAwaitingEmission(store) {
88
+ const row = store.db
89
+ .prepare(`SELECT COUNT(*) AS n
90
+ FROM tool_attempt
91
+ WHERE aggregate_decision = 'DENY'
92
+ AND deny_emission_status = 'DECISION_RECORDED'`)
93
+ .get();
94
+ return row.n;
95
+ }
96
+ /**
97
+ * Count the violations that ALREADY failed open: a VIOLATION whose effective enforcement degraded to NONE
98
+ * because a deny-admission gate fired (RULE_ENFORCEMENT_UNAVAILABLE, decision 5). Unlike a crash-window
99
+ * emission leftover, this is NOT recoverable: the prohibited action already passed un-governed. A version
100
+ * arm only reaches effective NONE on a gated DENY, and an observed arm always enforces OBSERVE, so
101
+ * (result = VIOLATION AND effective_enforcement = NONE) is exactly the historical fail-open set (the same
102
+ * condition the `mla rules activity` enforcementUnavailable column reports). `mla doctor` surfaces this
103
+ * count so an operator can confirm at a glance whether enforcement has ever silently missed on this box.
104
+ */
105
+ function countFailOpenEnforcementViolations(store) {
106
+ const row = store.db
107
+ .prepare(`SELECT COUNT(*) AS n
108
+ FROM rule_evaluation_record
109
+ WHERE result = 'VIOLATION'
110
+ AND effective_enforcement = 'NONE'`)
111
+ .get();
112
+ return row.n;
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // rule_evaluation_record
116
+ // ---------------------------------------------------------------------------
117
+ function insertRuleEvaluationRecord(store, rec) {
118
+ store.db
119
+ .prepare(`INSERT INTO rule_evaluation_record
120
+ (evaluation_id, attempt_id, runtime_scope_id, result, eligible_enforcement,
121
+ effective_enforcement, verdict_reason_code, gate_reason_code, evaluator_contract_version,
122
+ observed_rule_snapshot, observed_rule_hash, rule_version_id, canonical_payload_hash,
123
+ created_at)
124
+ VALUES
125
+ (@evaluation_id, @attempt_id, @runtime_scope_id, @result, @eligible_enforcement,
126
+ @effective_enforcement, @verdict_reason_code, @gate_reason_code, @evaluator_contract_version,
127
+ @observed_rule_snapshot, @observed_rule_hash, @rule_version_id, @canonical_payload_hash,
128
+ @created_at)`)
129
+ .run({
130
+ evaluation_id: rec.evaluationId,
131
+ attempt_id: rec.attemptId,
132
+ runtime_scope_id: rec.runtimeScopeId,
133
+ result: rec.result,
134
+ eligible_enforcement: rec.eligibleEnforcement,
135
+ effective_enforcement: rec.effectiveEnforcement,
136
+ verdict_reason_code: rec.verdictReasonCode,
137
+ gate_reason_code: rec.gateReasonCode,
138
+ evaluator_contract_version: rec.evaluatorContractVersion,
139
+ observed_rule_snapshot: rec.observedRuleSnapshot,
140
+ observed_rule_hash: rec.observedRuleHash,
141
+ rule_version_id: rec.ruleVersionId,
142
+ canonical_payload_hash: rec.canonicalPayloadHash,
143
+ created_at: rec.createdAt,
144
+ });
145
+ }
146
+ function mapEvaluationRow(row) {
147
+ return {
148
+ evaluationId: row.evaluation_id,
149
+ attemptId: row.attempt_id,
150
+ runtimeScopeId: row.runtime_scope_id,
151
+ result: row.result,
152
+ eligibleEnforcement: row.eligible_enforcement,
153
+ effectiveEnforcement: row.effective_enforcement,
154
+ verdictReasonCode: row.verdict_reason_code,
155
+ gateReasonCode: row.gate_reason_code ?? null,
156
+ evaluatorContractVersion: row.evaluator_contract_version,
157
+ observedRuleSnapshot: row.observed_rule_snapshot ?? null,
158
+ observedRuleHash: row.observed_rule_hash ?? null,
159
+ ruleVersionId: row.rule_version_id ?? null,
160
+ canonicalPayloadHash: row.canonical_payload_hash ?? null,
161
+ createdAt: row.created_at,
162
+ };
163
+ }
164
+ function getRuleEvaluationRecord(store, evaluationId) {
165
+ const row = store.db
166
+ .prepare(`SELECT * FROM rule_evaluation_record WHERE evaluation_id = ?`)
167
+ .get(evaluationId);
168
+ if (!row)
169
+ return null;
170
+ return mapEvaluationRow(row);
171
+ }
172
+ /** List one attempt's evaluation records, ordered by evaluation_id so a re-read is stable. */
173
+ function listEvaluationsForAttempt(store, attemptId) {
174
+ const rows = store.db
175
+ .prepare(`SELECT * FROM rule_evaluation_record WHERE attempt_id = ? ORDER BY evaluation_id`)
176
+ .all(attemptId);
177
+ return rows.map(mapEvaluationRow);
178
+ }
179
+ /**
180
+ * List the observed rules R0 has recorded in ONE runtime scope, one row per distinct
181
+ * observed_rule_hash. The observed arm is identified by a non-null observed_rule_hash (the version
182
+ * arm carries none). For each rule it reports the latest verdict and timestamp (the newest row by
183
+ * created_at then evaluation_id, both descending, so a deterministic single row wins), the total
184
+ * observation count, and whether a LocalRuleVersion in this same scope derives from that observed
185
+ * hash. A pure read that NEVER crosses runtime scopes (proposal §2.3 / P0.51) and never infers a
186
+ * logical rule id. Rows are ordered by observed_rule_hash so the listing is stable across reads.
187
+ */
188
+ function listObservedRulesInScope(store, runtimeScopeId) {
189
+ const rows = store.db
190
+ .prepare(`WITH observed AS (
191
+ SELECT
192
+ observed_rule_hash,
193
+ observed_rule_snapshot,
194
+ result,
195
+ created_at,
196
+ ROW_NUMBER() OVER (
197
+ PARTITION BY observed_rule_hash
198
+ ORDER BY created_at DESC, evaluation_id DESC
199
+ ) AS rn,
200
+ COUNT(*) OVER (PARTITION BY observed_rule_hash) AS observation_count
201
+ FROM rule_evaluation_record
202
+ WHERE runtime_scope_id = @scope
203
+ AND observed_rule_hash IS NOT NULL
204
+ )
205
+ SELECT
206
+ o.observed_rule_hash AS observed_rule_hash,
207
+ o.observed_rule_snapshot AS observed_rule_snapshot,
208
+ o.result AS result,
209
+ o.created_at AS created_at,
210
+ o.observation_count AS observation_count,
211
+ EXISTS (
212
+ SELECT 1 FROM local_rule_version v
213
+ WHERE v.runtime_scope_id = @scope
214
+ AND v.derived_from_observed_hash = o.observed_rule_hash
215
+ ) AS has_local_version
216
+ FROM observed o
217
+ WHERE o.rn = 1
218
+ ORDER BY o.observed_rule_hash`)
219
+ .all({ scope: runtimeScopeId });
220
+ return rows.map((row) => ({
221
+ observedRuleHash: row.observed_rule_hash,
222
+ ruleText: observedRuleText(row.observed_rule_snapshot),
223
+ latestResult: row.result,
224
+ latestObservedAt: row.created_at,
225
+ observationCount: row.observation_count,
226
+ hasLocalVersion: row.has_local_version === 1,
227
+ }));
228
+ }
229
+ /** Read the directive prose from a frozen observed-rule-v1 snapshot; "" when none is present. */
230
+ function observedRuleText(snapshot) {
231
+ const parsed = JSON.parse(snapshot);
232
+ return typeof parsed.text === "string" ? parsed.text : "";
233
+ }
234
+ /**
235
+ * Resolve the EXACT observed-rule snapshot R0 recorded under one (runtime scope, observed hash), the
236
+ * read R1 attestation builds on (proposal §10.1 step 6, §2.4). It scans ONLY rule_evaluation_record
237
+ * rows in the given scope whose observed_rule_hash matches (the observed arm; the version arm carries
238
+ * a null hash), grouping by the snapshot body so byte-identical observations collapse. Cardinality is
239
+ * explicit and never guesses: zero groups is NOT_FOUND (nothing to attest); exactly one group is
240
+ * FOUND with that snapshot and the observation count; two or more DISTINCT groups under the same
241
+ * (scope, hash) is a COLLISION that refuses to pick one (hash corruption). It NEVER crosses a runtime
242
+ * scope (the WHERE pins scope) and NEVER infers a logical rule id (it returns the frozen snapshot, no
243
+ * rule identity).
244
+ */
245
+ function resolveObservedSnapshotInScope(store, runtimeScopeId, observedRuleHash) {
246
+ const groups = store.db
247
+ .prepare(`SELECT observed_rule_snapshot AS snapshot, COUNT(*) AS n
248
+ FROM rule_evaluation_record
249
+ WHERE runtime_scope_id = @scope
250
+ AND observed_rule_hash = @hash
251
+ AND observed_rule_snapshot IS NOT NULL
252
+ GROUP BY observed_rule_snapshot
253
+ ORDER BY observed_rule_snapshot`)
254
+ .all({ scope: runtimeScopeId, hash: observedRuleHash });
255
+ if (groups.length === 0) {
256
+ return { kind: "NOT_FOUND", runtimeScopeId, observedRuleHash };
257
+ }
258
+ if (groups.length > 1) {
259
+ return { kind: "COLLISION", runtimeScopeId, observedRuleHash, distinctSnapshotCount: groups.length };
260
+ }
261
+ return {
262
+ kind: "FOUND",
263
+ observedRuleHash,
264
+ observedRuleSnapshot: groups[0].snapshot,
265
+ observationCount: groups[0].n,
266
+ };
267
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.readUserHookConfigLayer = readUserHookConfigLayer;
37
+ exports.resolveLiveInputAuthority = resolveLiveInputAuthority;
38
+ const fs = __importStar(require("fs"));
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const config_1 = require("../config");
42
+ const input_authority_resolver_1 = require("./input-authority-resolver");
43
+ // The production input-authority loader for the R1 pilot (P0.58). It feeds the pure resolver the single
44
+ // user config layer from ~/.claude/settings.json, the only layer the installer writes for the
45
+ // single-operator pilot (P0.3). An absent settings file is a readable empty layer (the resolver concludes
46
+ // MLA_HOOK_ABSENT, the honest "not wired" state); a file that exists but will not parse is marked
47
+ // unreadable so the resolver fails CLOSED (CONFIG_LAYER_UNREADABLE) rather than concluding MLA is sole
48
+ // authority. This is the single source of truth shared by `mla doctor` and the live PreToolUse hook so
49
+ // both judge admissibility off identical inputs.
50
+ function readUserHookConfigLayer(homeDir = os.homedir()) {
51
+ const settingsPath = path.join(homeDir, ".claude", "settings.json");
52
+ if (!fs.existsSync(settingsPath)) {
53
+ return { name: "user", settings: {} };
54
+ }
55
+ try {
56
+ return { name: "user", settings: JSON.parse(fs.readFileSync(settingsPath, "utf8")) };
57
+ }
58
+ catch (e) {
59
+ return { name: "user", unreadable: true, error: e.message };
60
+ }
61
+ }
62
+ function resolveLiveInputAuthority(opts = {}) {
63
+ return (0, input_authority_resolver_1.resolveInputAuthority)([readUserHookConfigLayer(opts.homeDir)], {
64
+ mlaHooksDir: opts.mlaHooksDir ?? config_1.HOOKS_DIR,
65
+ });
66
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateLocalMatcher = evaluateLocalMatcher;
4
+ exports.parseLocalMatcherRule = parseLocalMatcherRule;
5
+ const command_match_1 = require("./command-match");
6
+ const content_match_1 = require("./content-match");
7
+ const evaluator_1 = require("./evaluator");
8
+ /**
9
+ * Evaluate a tool call against an observe-only local matcher rule. Returns null
10
+ * when the rule does not select this call (the tool is not in its list), which is
11
+ * distinct from an applied rule that returns UNKNOWN. When it applies, the verdict
12
+ * is the reduction over every named field:
13
+ * - content: any field CONTAINS_FORBIDDEN wins (VIOLATION); else any provably
14
+ * clean string field is COMPLIANT; else INDETERMINATE (UNKNOWN). A present,
15
+ * clean field is not dragged down by an absent sibling field.
16
+ * - command: any field MATCHES_FORBIDDEN wins (VIOLATION); else NO_MATCH
17
+ * (UNKNOWN, opaque, never COMPLIANT); else INDETERMINATE (UNKNOWN).
18
+ */
19
+ function evaluateLocalMatcher(call, rule) {
20
+ if (!rule.tools.includes(call.toolName)) {
21
+ return null;
22
+ }
23
+ const values = rule.fields.map((field) => call.toolInput[field]);
24
+ if (rule.kind === "content") {
25
+ const classes = values.map((v) => (0, content_match_1.classifyContent)(v, rule.forbiddenSubstrings));
26
+ return (0, evaluator_1.verdictForForbiddenContent)(reduceContent(classes));
27
+ }
28
+ const classes = values.map((v) => (0, command_match_1.classifyCommand)(v, rule.forbiddenSequences));
29
+ return (0, evaluator_1.verdictForForbiddenCommand)(reduceCommand(classes));
30
+ }
31
+ /** A forbidden hit anywhere wins; else a provable clean read; else indeterminate. */
32
+ function reduceContent(classes) {
33
+ if (classes.includes("CONTAINS_FORBIDDEN")) {
34
+ return "CONTAINS_FORBIDDEN";
35
+ }
36
+ if (classes.includes("NO_FORBIDDEN")) {
37
+ return "NO_FORBIDDEN";
38
+ }
39
+ return "INDETERMINATE";
40
+ }
41
+ /** A forbidden hit anywhere wins; else an opaque non-match; else indeterminate. */
42
+ function reduceCommand(classes) {
43
+ if (classes.includes("MATCHES_FORBIDDEN")) {
44
+ return "MATCHES_FORBIDDEN";
45
+ }
46
+ if (classes.includes("NO_MATCH")) {
47
+ return "NO_MATCH";
48
+ }
49
+ return "INDETERMINATE";
50
+ }
51
+ function isPlainObject(value) {
52
+ return typeof value === "object" && value !== null && !Array.isArray(value);
53
+ }
54
+ function isNonEmptyStringArray(value) {
55
+ return Array.isArray(value) && value.length > 0 && value.every((v) => typeof v === "string" && v.length > 0);
56
+ }
57
+ function invalid(diagnostic) {
58
+ return { status: "INVALID", diagnostic };
59
+ }
60
+ /**
61
+ * Validate an untrusted local matcher rule descriptor (e.g. read from disk). A
62
+ * misconfiguration that could never fire usefully (empty tools, empty fields, no
63
+ * usable forbidden needle) is rejected at parse time rather than silently always
64
+ * returning UNKNOWN at evaluation time.
65
+ */
66
+ function parseLocalMatcherRule(raw) {
67
+ if (!isPlainObject(raw)) {
68
+ return invalid("rule must be an object");
69
+ }
70
+ if (!isNonEmptyStringArray(raw.tools)) {
71
+ return invalid("tools must be a non-empty array of non-empty strings");
72
+ }
73
+ if (!isNonEmptyStringArray(raw.fields)) {
74
+ return invalid("fields must be a non-empty array of non-empty strings");
75
+ }
76
+ if (raw.kind === "content") {
77
+ if (!isNonEmptyStringArray(raw.forbiddenSubstrings)) {
78
+ return invalid("content rule needs at least one non-empty forbidden substring");
79
+ }
80
+ return {
81
+ status: "OK",
82
+ rule: {
83
+ kind: "content",
84
+ tools: [...raw.tools],
85
+ fields: [...raw.fields],
86
+ forbiddenSubstrings: [...raw.forbiddenSubstrings],
87
+ },
88
+ };
89
+ }
90
+ if (raw.kind === "command") {
91
+ const sequences = raw.forbiddenSequences;
92
+ if (!Array.isArray(sequences) ||
93
+ sequences.length === 0 ||
94
+ !sequences.every((seq) => isNonEmptyStringArray(seq))) {
95
+ return invalid("command rule needs at least one non-empty forbidden token sequence");
96
+ }
97
+ return {
98
+ status: "OK",
99
+ rule: {
100
+ kind: "command",
101
+ tools: [...raw.tools],
102
+ fields: [...raw.fields],
103
+ forbiddenSequences: sequences.map((seq) => [...seq]),
104
+ },
105
+ };
106
+ }
107
+ return invalid(`unknown matcher kind: ${String(raw.kind)}`);
108
+ }
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BUILTIN_LOCAL_OBSERVE_RULES = void 0;
4
+ exports.observeLocalMatchers = observeLocalMatchers;
5
+ const local_matcher_1 = require("./local-matcher");
6
+ const observe_adapter_1 = require("./observe-adapter");
7
+ /**
8
+ * Evaluate every supplied local matcher rule against a raw PreToolUse payload
9
+ * (an object or the JSON string delivered on stdin). Rules that do not select the
10
+ * call (wrong tool) are omitted; only rules that actually applied contribute an
11
+ * observation. A payload that is not a usable PreToolUse call yields [].
12
+ */
13
+ function observeLocalMatchers(rawInput, rules) {
14
+ const parsed = (0, observe_adapter_1.parsePreToolUseInput)(rawInput);
15
+ if (parsed === null) {
16
+ return [];
17
+ }
18
+ const call = { toolName: parsed.tool_name, toolInput: parsed.tool_input };
19
+ const observations = [];
20
+ for (const { id, rule } of rules) {
21
+ const verdict = (0, local_matcher_1.evaluateLocalMatcher)(call, rule);
22
+ if (verdict !== null) {
23
+ observations.push({ ruleId: id, result: verdict.result, reasonCode: verdict.reasonCode });
24
+ }
25
+ }
26
+ return observations;
27
+ }
28
+ // Candidate observe-only rules derived directly from An's own documented rules
29
+ // (global CLAUDE.md + this repo's feedback memory). They are DATA, not armed: nothing
30
+ // in this slice wires them onto the hot path or denies on them. They exist so the
31
+ // future recording/arming slice (An-owned, once the identity contract lands) has a
32
+ // concrete, well-formed starting set rather than an empty one. Each is a sound,
33
+ // observe-only matcher: a CONTENT rule produces a real COMPLIANT/VIOLATION (the field
34
+ // is fully observable); a COMMAND rule only ever produces VIOLATION-or-UNKNOWN.
35
+ exports.BUILTIN_LOCAL_OBSERVE_RULES = [
36
+ {
37
+ // An's #1 AI-smell rule: never write an em dash or a double dash into a file.
38
+ id: "no-em-dash-or-double-dash-in-writes",
39
+ rule: {
40
+ kind: "content",
41
+ tools: ["Write", "Edit"],
42
+ fields: ["content", "new_string"],
43
+ forbiddenSubstrings: ["—", "--"],
44
+ },
45
+ },
46
+ {
47
+ // "push only when explicitly asked" (merge != push).
48
+ id: "no-unrequested-git-push",
49
+ rule: {
50
+ kind: "command",
51
+ tools: ["Bash"],
52
+ fields: ["command"],
53
+ forbiddenSequences: [["git", "push"]],
54
+ },
55
+ },
56
+ {
57
+ // "NEVER create feature branches; work directly on main."
58
+ id: "no-feature-branch",
59
+ rule: {
60
+ kind: "command",
61
+ tools: ["Bash"],
62
+ fields: ["command"],
63
+ forbiddenSequences: [
64
+ ["git", "checkout", "-b"],
65
+ ["git", "switch", "-c"],
66
+ ],
67
+ },
68
+ },
69
+ {
70
+ // "never hand-roll prisma migrate deploy; use make test-db."
71
+ id: "no-hand-rolled-prisma-migrate-deploy",
72
+ rule: {
73
+ kind: "command",
74
+ tools: ["Bash"],
75
+ fields: ["command"],
76
+ forbiddenSequences: [["prisma", "migrate", "deploy"]],
77
+ },
78
+ },
79
+ ];