@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,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
|
+
];
|