@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,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The generalized-R4 inert-family registry. The live PreToolUse enforce dispatch (enforce-notes-version.ts)
|
|
3
|
+
// loads EVERY live rule in scope and must decide what each one means for the attempt. Today it knows two
|
|
4
|
+
// answers: a rule it can face (the PROHIBIT forbidden-root family) or "I do not understand this", which
|
|
5
|
+
// fails the WHOLE attempt open. That binary is too coarse: it cannot tell a genuinely unknown rule apart
|
|
6
|
+
// from a rule it understands well enough to prove imposes NO effect on a tool attempt.
|
|
7
|
+
//
|
|
8
|
+
// This module owns that third answer. A rule is INERT-NON-ENFORCING when its maximum authority on a tool
|
|
9
|
+
// attempt is RECORD_ONLY: it observes and records, it never injects, steers, asks, or denies. Per P0.13
|
|
10
|
+
// (INV-CONFLICT-NEVER-SILENTLY-DENIES, NT:20260615 consolidated proposal), a conflict is two LIVE rules
|
|
11
|
+
// imposing INCOMPATIBLE effects. A rule that imposes no effect at all cannot be incompatible with a
|
|
12
|
+
// PROHIBIT's deny, so it is provably non-conflicting and the dispatch is safe to SKIP it rather than fail
|
|
13
|
+
// the attempt open. That skip is exactly what lets a CE0 consult-evidence RECORD_ONLY rule coexist in the
|
|
14
|
+
// same scope as the live notes-location DENY pilot without disarming it.
|
|
15
|
+
//
|
|
16
|
+
// SAFETY CONTRACT (why this is recognition, not a wildcard):
|
|
17
|
+
// * POSITIVE: each inert family is named by its EXACT schema tag. An unrecognized schema returns false,
|
|
18
|
+
// so the dispatch's fail-open boundary for the genuinely-unknown is preserved unchanged. The dangerous
|
|
19
|
+
// inversion ("anything we do not understand is inert") is precisely what this must never become.
|
|
20
|
+
// * NARROW: recognizing the schema is necessary but NOT sufficient. Within a recognized family the
|
|
21
|
+
// predicate re-derives that THIS version's response ceiling is RECORD_ONLY. The same ce0-rule-v1 schema
|
|
22
|
+
// can carry an AUTO_CORRECT ceiling (a CE2 concern that steers/injects and demands a new immutable rule
|
|
23
|
+
// version); that version is NOT inert and must NOT be skipped. The ceiling proof, not the schema tag,
|
|
24
|
+
// is the load-bearing safety property.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.isInertNonEnforcingRule = isInertNonEnforcingRule;
|
|
27
|
+
/** The closed registry of provably non-enforcing rule families. One entry today: the CE0 consult-evidence
|
|
28
|
+
* forcing function at its RECORD_ONLY ceiling. New families are added here deliberately, each with its own
|
|
29
|
+
* ceiling proof; membership is never inferred. */
|
|
30
|
+
const INERT_RULE_FAMILIES = [
|
|
31
|
+
{
|
|
32
|
+
schemaVersion: "ce0-rule-v1",
|
|
33
|
+
// Inert iff the ceiling is RECORD_ONLY. An AUTO_CORRECT version of the same schema can steer/inject and
|
|
34
|
+
// is therefore enforcing, not inert.
|
|
35
|
+
isInertVersion: (payload) => payload.responseCeiling === "RECORD_ONLY",
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* True iff `payload` is a LIVE rule the enforce dispatch can prove imposes NO effect on a tool attempt and
|
|
40
|
+
* may therefore SKIP (treat as inert) instead of failing the attempt open. Returns false for anything
|
|
41
|
+
* unrecognized, so an unknown rule still trips the dispatch's fail-open boundary. See the safety contract
|
|
42
|
+
* above: recognition is positive (exact schema) AND narrow (per-version ceiling proof).
|
|
43
|
+
*/
|
|
44
|
+
function isInertNonEnforcingRule(payload) {
|
|
45
|
+
if (typeof payload !== "object" || payload === null) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const obj = payload;
|
|
49
|
+
const family = INERT_RULE_FAMILIES.find((f) => f.schemaVersion === obj.schemaVersion);
|
|
50
|
+
return family ? family.isInertVersion(obj) : false;
|
|
51
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
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.INPUT_AUTHORITY_CONFIG_DOMAIN = exports.HOOK_CONFIG_LAYERS = void 0;
|
|
37
|
+
exports.resolveInputAuthority = resolveInputAuthority;
|
|
38
|
+
const crypto_1 = require("crypto");
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const canonical_json_1 = require("./canonical-json");
|
|
41
|
+
const wire_1 = require("../wire");
|
|
42
|
+
// The effective-hook-config resolver (R1 foundation), the mechanical proof behind
|
|
43
|
+
// INV-R1-SINGLE-INPUT-AUTHORITY (P0.19) made continuous by INV-R1-INPUT-AUTHORITY-IS-CONTINUOUS
|
|
44
|
+
// (P0.58) in notes/20260615-rules-as-node-and-action-interception-consolidated-proposal.md (§2.4).
|
|
45
|
+
//
|
|
46
|
+
// Claude Code runs every matching PreToolUse hook in PARALLEL and an `updatedInput` REPLACES the whole
|
|
47
|
+
// tool input, so there is no safe composition contract when two hooks both rewrite input. R1 may only
|
|
48
|
+
// emit a deny while it can mechanically prove the NARROW v1 condition: MLA is the SOLE effective
|
|
49
|
+
// PreToolUse hook that matches Write or Edit across the entire config hierarchy. This module is the
|
|
50
|
+
// PURE core of that proof. It is given the five already-loaded settings layers (user, project, local,
|
|
51
|
+
// plugin, managed) and:
|
|
52
|
+
// - enumerates every effective PreToolUse command hook,
|
|
53
|
+
// - identifies which ones match the governed tools (Write / Edit),
|
|
54
|
+
// - classifies each as MLA-owned or foreign (reusing the installer's own ownership predicate so the
|
|
55
|
+
// resolver can never drift from what `mla init` writes),
|
|
56
|
+
// - returns MLA_SOLE_AUTHORITY, or a typed unavailable reason,
|
|
57
|
+
// - and emits a deterministic, order-independent canonical snapshot + hash for the
|
|
58
|
+
// `inputAuthorityConfigHash` audit field on tool_attempt.
|
|
59
|
+
//
|
|
60
|
+
// It touches no network and no filesystem (the IO shell that reads the settings files and calls this
|
|
61
|
+
// lives in the runtime / `mla doctor`, which both reuse this resolver). It emits NO deny: it only
|
|
62
|
+
// reports whether a deny would be admissible. Every ambiguity fails CLOSED to UNAVAILABLE, never to a
|
|
63
|
+
// silent OBSERVE downgrade (P0.15).
|
|
64
|
+
exports.HOOK_CONFIG_LAYERS = ["user", "project", "local", "plugin", "managed"];
|
|
65
|
+
/** Domain tag for the snapshot hash; the single 0x00 separator (P0.53) prevents cross-domain collision. */
|
|
66
|
+
exports.INPUT_AUTHORITY_CONFIG_DOMAIN = "effective-hook-config-v1";
|
|
67
|
+
/** The script `mla init` registers as the managed PreToolUse hook; the MLA-ownership probe. */
|
|
68
|
+
const MLA_PRE_TOOL_USE_SCRIPT = "pre-tool-use.sh";
|
|
69
|
+
/** The tools the R1 pilot governs. Only a PreToolUse hook matching one of these can threaten deny. */
|
|
70
|
+
const GOVERNED_TOOLS = ["Write", "Edit"];
|
|
71
|
+
function isUnreadable(layer) {
|
|
72
|
+
return layer.unreadable === true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Interpret a Claude Code matcher against the governed tools. `""` is the catch-all (matches every
|
|
76
|
+
* tool). A non-empty matcher is a regex matched partially (Claude Code semantics). Returns null when
|
|
77
|
+
* the matcher is not a valid regex, so the caller can fail closed.
|
|
78
|
+
*/
|
|
79
|
+
function interpretMatcher(matcher) {
|
|
80
|
+
if (matcher === "")
|
|
81
|
+
return { write: true, edit: true };
|
|
82
|
+
let re;
|
|
83
|
+
try {
|
|
84
|
+
re = new RegExp(matcher);
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return { write: re.test("Write"), edit: re.test("Edit") };
|
|
90
|
+
}
|
|
91
|
+
/** Defensively pull every PreToolUse command hook out of one readable layer's settings. */
|
|
92
|
+
function extractPreToolUse(layerName, settings, problems) {
|
|
93
|
+
const out = [];
|
|
94
|
+
if (!settings || typeof settings !== "object")
|
|
95
|
+
return out;
|
|
96
|
+
const hooks = settings.hooks;
|
|
97
|
+
if (!hooks || typeof hooks !== "object")
|
|
98
|
+
return out;
|
|
99
|
+
const pre = hooks.PreToolUse;
|
|
100
|
+
if (pre === undefined)
|
|
101
|
+
return out;
|
|
102
|
+
if (!Array.isArray(pre)) {
|
|
103
|
+
problems.push(`${layerName}: PreToolUse is not an array`);
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
for (const entry of pre) {
|
|
107
|
+
if (!entry || typeof entry !== "object") {
|
|
108
|
+
problems.push(`${layerName}: a PreToolUse entry is not an object`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const rawMatcher = entry.matcher;
|
|
112
|
+
const matcher = rawMatcher === undefined ? "" : rawMatcher;
|
|
113
|
+
if (typeof matcher !== "string") {
|
|
114
|
+
problems.push(`${layerName}: a PreToolUse matcher is not a string`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const inner = entry.hooks;
|
|
118
|
+
if (inner === undefined)
|
|
119
|
+
continue;
|
|
120
|
+
if (!Array.isArray(inner)) {
|
|
121
|
+
problems.push(`${layerName}: a PreToolUse entry's hooks is not an array`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
for (const h of inner) {
|
|
125
|
+
if (!h || typeof h !== "object") {
|
|
126
|
+
problems.push(`${layerName}: a hook is not an object`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
// Only command hooks run a script that can return updatedInput; ignore any other type.
|
|
130
|
+
if (h.type !== "command")
|
|
131
|
+
continue;
|
|
132
|
+
const command = h.command;
|
|
133
|
+
if (typeof command !== "string" || command.length === 0) {
|
|
134
|
+
problems.push(`${layerName}: a command hook has no string command`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
out.push({ layer: layerName, matcher, command });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** Stable ordering over (command, matcher, layer) so the snapshot is independent of input order. */
|
|
143
|
+
function compareEntries(a, b) {
|
|
144
|
+
return (cmp(a.command, b.command) || cmp(a.matcher, b.matcher) || cmp(a.layer, b.layer));
|
|
145
|
+
}
|
|
146
|
+
function cmp(a, b) {
|
|
147
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
148
|
+
}
|
|
149
|
+
function classify(command, mlaHooksDir) {
|
|
150
|
+
const cmd = path.join(mlaHooksDir, MLA_PRE_TOOL_USE_SCRIPT);
|
|
151
|
+
return (0, wire_1.isManagedHookCommand)(command, MLA_PRE_TOOL_USE_SCRIPT, cmd) ? "MLA" : "FOREIGN";
|
|
152
|
+
}
|
|
153
|
+
/** Build the canonical snapshot object that is serialized and hashed. */
|
|
154
|
+
function buildSnapshot(rawEntries, unreadableLayers) {
|
|
155
|
+
const preToolUse = [...rawEntries]
|
|
156
|
+
.sort(compareEntries)
|
|
157
|
+
.map((e) => ({ layer: e.layer, matcher: e.matcher, command: e.command }));
|
|
158
|
+
return {
|
|
159
|
+
schemaVersion: exports.INPUT_AUTHORITY_CONFIG_DOMAIN,
|
|
160
|
+
preToolUse,
|
|
161
|
+
unreadableLayers: [...unreadableLayers].sort(),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/** SHA-256(domainTag || 0x00 || JCS(snapshot)), lowercase hex. Mirrors observed-rule-hash.ts. */
|
|
165
|
+
function hashSnapshot(jcs) {
|
|
166
|
+
const h = (0, crypto_1.createHash)("sha256");
|
|
167
|
+
h.update(exports.INPUT_AUTHORITY_CONFIG_DOMAIN, "utf8");
|
|
168
|
+
h.update(Buffer.from([0x00]));
|
|
169
|
+
h.update(jcs, "utf8");
|
|
170
|
+
return h.digest("hex");
|
|
171
|
+
}
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
// resolver
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
/**
|
|
176
|
+
* Resolve whether MLA is the sole effective Write/Edit input authority across the given config
|
|
177
|
+
* layers. Pure: no IO, no network, no deny. The result always carries the deterministic snapshot +
|
|
178
|
+
* hash (computed over whatever was readable) for the audit field; the `kind` decides admissibility.
|
|
179
|
+
*/
|
|
180
|
+
function resolveInputAuthority(layers, opts) {
|
|
181
|
+
const unreadableLayers = [];
|
|
182
|
+
const problems = [];
|
|
183
|
+
const rawEntries = [];
|
|
184
|
+
for (const layer of layers) {
|
|
185
|
+
if (isUnreadable(layer)) {
|
|
186
|
+
unreadableLayers.push(layer.name);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
rawEntries.push(...extractPreToolUse(layer.name, layer.settings, problems));
|
|
190
|
+
}
|
|
191
|
+
// Any matcher that does not compile is uninterpretable; fail closed rather than guess its scope.
|
|
192
|
+
for (const e of rawEntries) {
|
|
193
|
+
if (interpretMatcher(e.matcher) === null) {
|
|
194
|
+
problems.push(`${e.layer}: matcher ${JSON.stringify(e.matcher)} is not a valid regex`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// The interpreted set of hooks matching a governed tool (skips uninterpretable matchers defensively).
|
|
198
|
+
const matchedCommands = [];
|
|
199
|
+
for (const e of rawEntries) {
|
|
200
|
+
const m = interpretMatcher(e.matcher);
|
|
201
|
+
if (!m)
|
|
202
|
+
continue;
|
|
203
|
+
if (!m.write && !m.edit)
|
|
204
|
+
continue;
|
|
205
|
+
matchedCommands.push({
|
|
206
|
+
layer: e.layer,
|
|
207
|
+
matcher: e.matcher,
|
|
208
|
+
command: e.command,
|
|
209
|
+
matchesWrite: m.write,
|
|
210
|
+
matchesEdit: m.edit,
|
|
211
|
+
mutatorClass: classify(e.command, opts.mlaHooksDir),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
matchedCommands.sort((a, b) => cmp(a.command, b.command) || cmp(a.matcher, b.matcher) || cmp(a.layer, b.layer));
|
|
215
|
+
const snapshot = (0, canonical_json_1.canonicalize)(buildSnapshot(rawEntries, unreadableLayers));
|
|
216
|
+
const configHash = hashSnapshot(snapshot);
|
|
217
|
+
const unavailable = (reason, detail) => ({
|
|
218
|
+
kind: "UNAVAILABLE",
|
|
219
|
+
reason,
|
|
220
|
+
detail,
|
|
221
|
+
configHash,
|
|
222
|
+
snapshot,
|
|
223
|
+
matchedCommands,
|
|
224
|
+
});
|
|
225
|
+
// Severity order: an incomplete or uninterpretable picture beats any conclusion drawn from it.
|
|
226
|
+
if (unreadableLayers.length > 0) {
|
|
227
|
+
return unavailable("CONFIG_LAYER_UNREADABLE", `config layers unreadable: ${[...unreadableLayers].sort().join(", ")}`);
|
|
228
|
+
}
|
|
229
|
+
if (problems.length > 0) {
|
|
230
|
+
return unavailable("HOOK_ENTRY_UNINTERPRETABLE", problems.join("; "));
|
|
231
|
+
}
|
|
232
|
+
const foreign = matchedCommands.filter((c) => c.mutatorClass === "FOREIGN");
|
|
233
|
+
if (foreign.length > 0) {
|
|
234
|
+
return unavailable("FOREIGN_MUTATOR_PRESENT", `foreign Write/Edit PreToolUse mutators present: ${foreign.map((c) => c.command).join(", ")}`);
|
|
235
|
+
}
|
|
236
|
+
const mla = matchedCommands.filter((c) => c.mutatorClass === "MLA");
|
|
237
|
+
if (mla.length === 0) {
|
|
238
|
+
return unavailable("MLA_HOOK_ABSENT", `no MLA PreToolUse hook matches ${GOVERNED_TOOLS.join(" or ")}`);
|
|
239
|
+
}
|
|
240
|
+
return { kind: "MLA_SOLE_AUTHORITY", configHash, snapshot, matchedCommands };
|
|
241
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The final local interception schema (R0), applied into the one canonical CE0
|
|
3
|
+
// evidence database by the existing opener. There is no second database and no
|
|
4
|
+
// second migration framework: this DDL is the second db.exec() of openCe0Store's
|
|
5
|
+
// single bootstrap, alongside the CE0 forcing-function schema in ce0-store.ts.
|
|
6
|
+
//
|
|
7
|
+
// Source of truth: notes/20260615-rules-as-node-and-action-interception-consolidated
|
|
8
|
+
// -proposal.md §10.1 step 1. The schema is the FINAL state from a fresh database:
|
|
9
|
+
// all three tables exist from the first open (no R0-to-R1 schema delta), every
|
|
10
|
+
// invariant the proposal claims is a real SQLite mechanism (partial unique index,
|
|
11
|
+
// composite runtime-scope-safe foreign key, CHECK, or trigger), and SQLite is the
|
|
12
|
+
// sole local authority (decision 4: the hook never reads a bundle file off disk).
|
|
13
|
+
//
|
|
14
|
+
// local_rule_version The attested rule version (R1). The table is created up
|
|
15
|
+
// front so the evaluation record's version arm resolves at
|
|
16
|
+
// creation time, but R0 never writes a row here.
|
|
17
|
+
// tool_attempt One locally-minted ULID per intercepted tool call. PreToolUse
|
|
18
|
+
// carries no tool_use_id, so the attempt id is the local key.
|
|
19
|
+
// rule_evaluation_record One verdict per applicable rule per attempt. The observed
|
|
20
|
+
// arm (R0) carries the frozen observed-rule snapshot + hash;
|
|
21
|
+
// the version arm (R1) references local_rule_version.
|
|
22
|
+
//
|
|
23
|
+
// Comments are SQL block comments deliberately: this string is exec'd verbatim, and a
|
|
24
|
+
// leading double-hyphen line comment is a forbidden token in this codebase.
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.INTERCEPTION_SCHEMA = exports.CE0_INTERCEPTION_SCHEMA_VERSION = void 0;
|
|
27
|
+
// The schema version stamped into the database's user_version pragma. There is no R0-to-R1
|
|
28
|
+
// delta (the schema is created in its final state), so this is 1 from the first open. mla doctor
|
|
29
|
+
// reads user_version back and fails if it does not match this constant, which is how a stale or
|
|
30
|
+
// foreign database is caught before the deny pilot ever evaluates a rule (slice 9, §10.1 step 1(d)).
|
|
31
|
+
exports.CE0_INTERCEPTION_SCHEMA_VERSION = 1;
|
|
32
|
+
exports.INTERCEPTION_SCHEMA = `
|
|
33
|
+
CREATE TABLE IF NOT EXISTS local_rule_version (
|
|
34
|
+
version_id TEXT NOT NULL PRIMARY KEY, /* ULID */
|
|
35
|
+
rule_id TEXT NOT NULL, /* logical identity, minted at first attestation */
|
|
36
|
+
runtime_scope_id TEXT NOT NULL,
|
|
37
|
+
rule_payload TEXT NOT NULL, /* immutable canonical rule-version-v1 JSON; SOLE authority (decision 6) */
|
|
38
|
+
canonical_payload_hash TEXT NOT NULL, /* rule-version-v1 digest, SHA-256 lowercase-hex */
|
|
39
|
+
lifecycle_status TEXT NOT NULL
|
|
40
|
+
CHECK (lifecycle_status IN ('LIVE','SUPERSEDED','DEPRECATED','REVOKED')),
|
|
41
|
+
attestation_method TEXT NOT NULL
|
|
42
|
+
CHECK (attestation_method IN ('HUMAN_DIRECT','AGENT_ON_USER_REQUEST')),
|
|
43
|
+
attested_by TEXT NOT NULL,
|
|
44
|
+
supersedes_version_id TEXT REFERENCES local_rule_version(version_id),
|
|
45
|
+
derived_from_observed_hash TEXT,
|
|
46
|
+
attested_at TEXT NOT NULL,
|
|
47
|
+
CHECK (supersedes_version_id IS NULL OR supersedes_version_id <> version_id)
|
|
48
|
+
) STRICT;
|
|
49
|
+
|
|
50
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_one_live_version ON local_rule_version (runtime_scope_id, rule_id)
|
|
51
|
+
WHERE lifecycle_status = 'LIVE';
|
|
52
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_version_payload ON local_rule_version (runtime_scope_id, rule_id, canonical_payload_hash);
|
|
53
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_version_scope ON local_rule_version (version_id, runtime_scope_id);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS tool_attempt (
|
|
56
|
+
attempt_id TEXT NOT NULL PRIMARY KEY, /* ULID minted locally */
|
|
57
|
+
runtime_scope_id TEXT NOT NULL,
|
|
58
|
+
session_id TEXT NOT NULL,
|
|
59
|
+
tool_name TEXT NOT NULL,
|
|
60
|
+
evaluation_input_snapshot TEXT NOT NULL, /* canonical evaluation-input-v1 JSON (decision 4) */
|
|
61
|
+
evaluation_input_hash TEXT NOT NULL,
|
|
62
|
+
aggregate_decision TEXT NOT NULL DEFAULT 'NO_DECISION'
|
|
63
|
+
CHECK (aggregate_decision IN ('NO_DECISION','DENY')),
|
|
64
|
+
deny_emission_status TEXT NOT NULL DEFAULT 'NOT_APPLICABLE'
|
|
65
|
+
CHECK (deny_emission_status IN ('NOT_APPLICABLE','DECISION_RECORDED','RESPONSE_EMITTED')),
|
|
66
|
+
input_authority_config_hash TEXT,
|
|
67
|
+
created_at TEXT NOT NULL,
|
|
68
|
+
CHECK ((aggregate_decision = 'NO_DECISION' AND deny_emission_status = 'NOT_APPLICABLE')
|
|
69
|
+
OR (aggregate_decision = 'DENY' AND deny_emission_status IN ('DECISION_RECORDED','RESPONSE_EMITTED')))
|
|
70
|
+
) STRICT;
|
|
71
|
+
|
|
72
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_attempt_scope ON tool_attempt (attempt_id, runtime_scope_id);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS rule_evaluation_record (
|
|
75
|
+
evaluation_id TEXT NOT NULL PRIMARY KEY, /* ULID */
|
|
76
|
+
attempt_id TEXT NOT NULL,
|
|
77
|
+
runtime_scope_id TEXT NOT NULL,
|
|
78
|
+
result TEXT NOT NULL
|
|
79
|
+
CHECK (result IN ('COMPLIANT','VIOLATION','UNKNOWN')),
|
|
80
|
+
eligible_enforcement TEXT NOT NULL
|
|
81
|
+
CHECK (eligible_enforcement IN ('OBSERVE','ASK','DENY')),
|
|
82
|
+
effective_enforcement TEXT NOT NULL /* NONE when infra is unavailable (decision 5) */
|
|
83
|
+
CHECK (effective_enforcement IN ('NONE','OBSERVE','ASK','DENY')),
|
|
84
|
+
verdict_reason_code TEXT NOT NULL,
|
|
85
|
+
gate_reason_code TEXT,
|
|
86
|
+
evaluator_contract_version TEXT NOT NULL,
|
|
87
|
+
observed_rule_snapshot TEXT, /* canonical observed-rule-v1 JSON */
|
|
88
|
+
observed_rule_hash TEXT, /* observed-rule-v1 digest */
|
|
89
|
+
rule_version_id TEXT,
|
|
90
|
+
canonical_payload_hash TEXT,
|
|
91
|
+
created_at TEXT NOT NULL,
|
|
92
|
+
CHECK ((rule_version_id IS NULL) = (observed_rule_hash IS NOT NULL)),
|
|
93
|
+
CHECK ((observed_rule_hash IS NULL) = (observed_rule_snapshot IS NULL)),
|
|
94
|
+
CHECK ((rule_version_id IS NULL) = (canonical_payload_hash IS NULL)),
|
|
95
|
+
FOREIGN KEY (attempt_id, runtime_scope_id)
|
|
96
|
+
REFERENCES tool_attempt (attempt_id, runtime_scope_id) ON DELETE CASCADE,
|
|
97
|
+
FOREIGN KEY (rule_version_id, runtime_scope_id)
|
|
98
|
+
REFERENCES local_rule_version (version_id, runtime_scope_id)
|
|
99
|
+
) STRICT;
|
|
100
|
+
|
|
101
|
+
CREATE INDEX IF NOT EXISTS ix_eval_attempt ON rule_evaluation_record (attempt_id);
|
|
102
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_eval_observed ON rule_evaluation_record (attempt_id, observed_rule_hash)
|
|
103
|
+
WHERE observed_rule_hash IS NOT NULL;
|
|
104
|
+
CREATE UNIQUE INDEX IF NOT EXISTS ux_eval_version ON rule_evaluation_record (attempt_id, rule_version_id)
|
|
105
|
+
WHERE rule_version_id IS NOT NULL;
|
|
106
|
+
|
|
107
|
+
CREATE TRIGGER IF NOT EXISTS trg_version_immutable
|
|
108
|
+
BEFORE UPDATE ON local_rule_version
|
|
109
|
+
FOR EACH ROW WHEN NOT (
|
|
110
|
+
NEW.version_id = OLD.version_id
|
|
111
|
+
AND NEW.rule_id = OLD.rule_id
|
|
112
|
+
AND NEW.runtime_scope_id = OLD.runtime_scope_id
|
|
113
|
+
AND NEW.rule_payload = OLD.rule_payload
|
|
114
|
+
AND NEW.canonical_payload_hash = OLD.canonical_payload_hash
|
|
115
|
+
AND NEW.attestation_method = OLD.attestation_method
|
|
116
|
+
AND NEW.attested_by = OLD.attested_by
|
|
117
|
+
AND NEW.supersedes_version_id IS OLD.supersedes_version_id
|
|
118
|
+
AND NEW.derived_from_observed_hash IS OLD.derived_from_observed_hash
|
|
119
|
+
AND NEW.attested_at = OLD.attested_at
|
|
120
|
+
AND OLD.lifecycle_status = 'LIVE'
|
|
121
|
+
AND NEW.lifecycle_status IN ('SUPERSEDED','DEPRECATED','REVOKED'))
|
|
122
|
+
BEGIN
|
|
123
|
+
SELECT RAISE(ABORT, 'local_rule_version is immutable except a LIVE->SUPERSEDED/DEPRECATED/REVOKED lifecycle transition');
|
|
124
|
+
END;
|
|
125
|
+
|
|
126
|
+
CREATE TRIGGER IF NOT EXISTS trg_attempt_frozen
|
|
127
|
+
BEFORE UPDATE ON tool_attempt
|
|
128
|
+
FOR EACH ROW WHEN NOT (
|
|
129
|
+
OLD.aggregate_decision = 'DENY' AND NEW.aggregate_decision = 'DENY'
|
|
130
|
+
AND OLD.deny_emission_status = 'DECISION_RECORDED'
|
|
131
|
+
AND NEW.deny_emission_status = 'RESPONSE_EMITTED'
|
|
132
|
+
AND NEW.attempt_id = OLD.attempt_id
|
|
133
|
+
AND NEW.runtime_scope_id = OLD.runtime_scope_id
|
|
134
|
+
AND NEW.session_id = OLD.session_id
|
|
135
|
+
AND NEW.tool_name = OLD.tool_name
|
|
136
|
+
AND NEW.evaluation_input_snapshot = OLD.evaluation_input_snapshot
|
|
137
|
+
AND NEW.evaluation_input_hash = OLD.evaluation_input_hash
|
|
138
|
+
AND NEW.input_authority_config_hash IS OLD.input_authority_config_hash
|
|
139
|
+
AND NEW.created_at = OLD.created_at)
|
|
140
|
+
BEGIN
|
|
141
|
+
SELECT RAISE(ABORT, 'tool_attempt is immutable except the deny emission advance DECISION_RECORDED->RESPONSE_EMITTED');
|
|
142
|
+
END;
|
|
143
|
+
|
|
144
|
+
CREATE TRIGGER IF NOT EXISTS trg_eval_no_update
|
|
145
|
+
BEFORE UPDATE ON rule_evaluation_record
|
|
146
|
+
BEGIN
|
|
147
|
+
SELECT RAISE(ABORT, 'rule_evaluation_record is append-only (no UPDATE)');
|
|
148
|
+
END;
|
|
149
|
+
|
|
150
|
+
/* The proposal §10.1 names a temp sentinel (temp.sqlite_master / CREATE TEMP TABLE */
|
|
151
|
+
/* _ce0_retention). That form is unimplementable in SQLite 3.49.2: a trigger may not */
|
|
152
|
+
/* reference the temp database (temp.sqlite_master is rejected at CREATE TRIGGER), and the */
|
|
153
|
+
/* unqualified sqlite_temp_master binds to the trigger's own database (main.sqlite_temp_ */
|
|
154
|
+
/* master, which never exists) so it always raises. The sentinel is therefore a MAIN-schema */
|
|
155
|
+
/* table. Privacy and transaction-scoping are preserved by SQLite isolation: the retention */
|
|
156
|
+
/* pass runs ONE transaction that creates _ce0_retention, DELETEs the owning tool_attempt */
|
|
157
|
+
/* rows (the cascade reaches the evaluation rows while the sentinel is visible), then drops */
|
|
158
|
+
/* _ce0_retention. The table is never committed, so no other connection can ever observe it */
|
|
159
|
+
/* or piggyback on it; a direct DELETE on any other path finds no sentinel and aborts. */
|
|
160
|
+
CREATE TRIGGER IF NOT EXISTS trg_eval_no_direct_delete
|
|
161
|
+
BEFORE DELETE ON rule_evaluation_record
|
|
162
|
+
WHEN NOT EXISTS (SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = '_ce0_retention')
|
|
163
|
+
BEGIN
|
|
164
|
+
SELECT RAISE(ABORT, 'rule_evaluation_record delete only via tool_attempt retention cascade (open the retention sentinel)');
|
|
165
|
+
END;
|
|
166
|
+
|
|
167
|
+
/* Stamp the schema version last, after every object exists, so a half-applied schema never reads */
|
|
168
|
+
/* back as the current version. mla doctor compares this against CE0_INTERCEPTION_SCHEMA_VERSION. */
|
|
169
|
+
PRAGMA user_version = ${exports.CE0_INTERCEPTION_SCHEMA_VERSION};
|
|
170
|
+
`;
|