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