@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,56 @@
1
+ "use strict";
2
+ // CONTENT matcher: the pure classifier for the em-dash-ban rule class (GAP2).
3
+ //
4
+ // The proposal routes the BEHAVIORAL em-dash ban (over the agent's chat output,
5
+ // emitted at Stop) to a Stop-hook detect-only check, because the output text is
6
+ // not a tool call and so is not observable at PreToolUse. This module handles a
7
+ // DIFFERENT, mechanically-decidable case the proposal did not cover: a forbidden
8
+ // substring written INTO a file, i.e. literally present in a Write `content` or
9
+ // Edit `new_string` payload field. That field is fully observable at PreToolUse,
10
+ // so BOTH polarities are provable here:
11
+ //
12
+ // needle present -> CONTAINS_FORBIDDEN (the bytes are right there)
13
+ // string, no needle -> NO_FORBIDDEN (absence is provable: we see the whole field)
14
+ // non-string / no needles -> INDETERMINATE
15
+ //
16
+ // This is the key asymmetry versus Bash (see command-match.ts): for an opaque
17
+ // shell string, absence of a pattern cannot prove the effect is absent, so a
18
+ // non-match degrades to UNKNOWN. For a concrete content field, we hold the entire
19
+ // value, so a non-match is a genuine COMPLIANT. The two checks COMPLEMENT each
20
+ // other: Stop catches an em-dash in chat output; this catches an em-dash written
21
+ // into a file.
22
+ //
23
+ // Matching is codepoint-exact with NO normalization: an em-dash needle (U+2014)
24
+ // must never be conflated with an en-dash (U+2013) or an ASCII hyphen. The rule
25
+ // is a byte-level ban, and normalizing would silently widen or narrow it.
26
+ Object.defineProperty(exports, "__esModule", { value: true });
27
+ exports.classifyContent = classifyContent;
28
+ /**
29
+ * Classify a candidate content value against a set of forbidden substrings.
30
+ *
31
+ * INDETERMINATE (degrades to UNKNOWN, never a verdict) when:
32
+ * - the value is not a string (the field is absent or the tool input is shaped
33
+ * unexpectedly), or
34
+ * - there is nothing meaningful to look for: an empty needle set, or a set whose
35
+ * every needle is the empty string. An empty needle would `.includes("")`-match
36
+ * every string, so empty needles are DROPPED, never allowed to flag all content.
37
+ *
38
+ * Otherwise CONTAINS_FORBIDDEN iff any non-empty needle occurs as an exact
39
+ * (codepoint) substring; NO_FORBIDDEN if none do. An empty content string with a
40
+ * real needle set is observably clean -> NO_FORBIDDEN, not indeterminate.
41
+ */
42
+ function classifyContent(rawValue, forbiddenSubstrings) {
43
+ if (typeof rawValue !== "string") {
44
+ return "INDETERMINATE";
45
+ }
46
+ const needles = forbiddenSubstrings.filter((n) => typeof n === "string" && n.length > 0);
47
+ if (needles.length === 0) {
48
+ return "INDETERMINATE";
49
+ }
50
+ for (const needle of needles) {
51
+ if (rawValue.includes(needle)) {
52
+ return "CONTAINS_FORBIDDEN";
53
+ }
54
+ }
55
+ return "NO_FORBIDDEN";
56
+ }
@@ -0,0 +1,99 @@
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.ENFORCEMENT_GATE_REASON_CODES = void 0;
37
+ exports.isEnforcementGateReasonCode = isEnforcementGateReasonCode;
38
+ exports.projectEligibleEnforcement = projectEligibleEnforcement;
39
+ exports.resolveAttestedPathRoot = resolveAttestedPathRoot;
40
+ exports.admitEnforcement = admitEnforcement;
41
+ exports.planDenyAccounting = planDenyAccounting;
42
+ const path = __importStar(require("path"));
43
+ exports.ENFORCEMENT_GATE_REASON_CODES = [
44
+ "RULE_ENFORCEMENT_UNAVAILABLE",
45
+ "NOT_LIVE_CAPS_AT_OBSERVE",
46
+ "UNKNOWN_EVALUATION_NEVER_DENIES",
47
+ "WORKSPACE_POLICY_FORBIDS_DENY",
48
+ "UNRESOLVED_CONFLICT_NEVER_DENIES",
49
+ ];
50
+ function isEnforcementGateReasonCode(x) {
51
+ return (typeof x === "string" &&
52
+ exports.ENFORCEMENT_GATE_REASON_CODES.includes(x));
53
+ }
54
+ /*
55
+ * R1-3: eligibility is projected through the evaluation `result` only. The attested enforcement
56
+ * ceiling applies exclusively to a VIOLATION; anything else (including UNKNOWN) is OBSERVE.
57
+ */
58
+ function projectEligibleEnforcement(result, enforcementCeiling) {
59
+ return result === "VIOLATION" ? enforcementCeiling : "OBSERVE";
60
+ }
61
+ function resolveAttestedPathRoot(input) {
62
+ const relative = (input.configuredRelativeForbiddenPath ?? "").trim();
63
+ const root = (input.activeRuntimeProjectRoot ?? "").trim();
64
+ if (relative.length === 0) {
65
+ return { admitted: false, reason: "ATTESTED_ROOT_CONTENT_MISSING" };
66
+ }
67
+ if (root.length === 0) {
68
+ return { admitted: false, reason: "ACTIVE_RUNTIME_ROOT_UNRESOLVED" };
69
+ }
70
+ return { admitted: true, forbiddenRoot: path.join(root, relative) };
71
+ }
72
+ /*
73
+ * R1-5: lower the eligible enforcement to effective enforcement through the deny-admission gates.
74
+ * Only a DENY eligibility is gated; OBSERVE and ASK pass through untouched with no gate reason.
75
+ * A DENY is admitted only when MLA is the sole effective input authority AND the attested path
76
+ * root is admissible; otherwise it fails open to NONE with the single primary gate reason
77
+ * RULE_ENFORCEMENT_UNAVAILABLE. The decision is gated by admissibility, never by deny "strength".
78
+ */
79
+ function admitEnforcement(args) {
80
+ if (args.eligibleEnforcement !== "DENY") {
81
+ return { effectiveEnforcement: args.eligibleEnforcement, gateReasonCode: null };
82
+ }
83
+ if (args.inputAuthority.kind !== "MLA_SOLE_AUTHORITY" || !args.pathRoot.admitted) {
84
+ return { effectiveEnforcement: "NONE", gateReasonCode: "RULE_ENFORCEMENT_UNAVAILABLE" };
85
+ }
86
+ return { effectiveEnforcement: "DENY", gateReasonCode: null };
87
+ }
88
+ /*
89
+ * P0.60: honest deny-emission accounting. An effective DENY is planned as an aggregate DENY whose
90
+ * emission state begins at DECISION_RECORDED: slice 10 persists and commits this row BEFORE
91
+ * emitting the deny response, so a crash after the commit but before emission leaves an honest
92
+ * DECISION_RECORDED (recoverable, never NO_DECISION). Any non-deny effective enforcement plans no
93
+ * decision at all.
94
+ */
95
+ function planDenyAccounting(effective) {
96
+ return effective === "DENY"
97
+ ? { aggregateDecision: "DENY", denyEmissionStatus: "DECISION_RECORDED" }
98
+ : { aggregateDecision: "NO_DECISION", denyEmissionStatus: "NOT_APPLICABLE" };
99
+ }
@@ -0,0 +1,190 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PATH_CANONICALIZER_VERSION = exports.MATCHER_SCHEMA_VERSION = exports.EVALUATOR_CONTRACT_VERSION = void 0;
4
+ exports.verdictFromEvaluationInput = verdictFromEvaluationInput;
5
+ exports.replayVerdictFromSnapshot = replayVerdictFromSnapshot;
6
+ exports.recordR0Observation = recordR0Observation;
7
+ exports.observeAndRecordNotesRule = observeAndRecordNotesRule;
8
+ const evaluation_input_hash_1 = require("./evaluation-input-hash");
9
+ const interception_store_1 = require("./interception-store");
10
+ const observed_rule_hash_1 = require("./observed-rule-hash");
11
+ const notes_rule_1 = require("./notes-rule");
12
+ const notes_path_1 = require("./notes-path");
13
+ const observe_adapter_1 = require("./observe-adapter");
14
+ const evaluator_1 = require("./evaluator");
15
+ const ulid_1 = require("./ulid");
16
+ // Persistence slice 3 (proposal §10.1): the durable R0 observation seam. The observe-only
17
+ // pipeline computed a verdict in process but persisted NOTHING; this module gives an applicable
18
+ // interception a durable home. On an applicable Write/Edit of a Markdown file it mints two local
19
+ // ULIDs and writes, in ONE transaction, a tool_attempt (carrying the canonical evaluation-input-v1
20
+ // snapshot + hash) and one observed-arm rule_evaluation_record. Then it returns the same empty,
21
+ // decision-free hook response the observe slice always returned.
22
+ //
23
+ // Two invariants make the durable record trustworthy, both load-bearing for the Slice 6 replay:
24
+ // - The persisted verdict is derived PURELY from the stored target + forbidden root by
25
+ // verdictFromEvaluationInput. The host-aware classifyTargetPath stays a side channel; only this
26
+ // snapshot-pure verdict is persisted, so a replay over the stored snapshot reproduces it exactly.
27
+ // - Observe never grants: the attempt is NO_DECISION / NOT_APPLICABLE deny status and the
28
+ // evaluation arm is OBSERVE/OBSERVE with no attested version (rule_version_id NULL).
29
+ //
30
+ // NOT_APPLICABLE (no rule, wrong tool, glob non-match) and INFRA (malformed payload, missing
31
+ // session id, misconfigured pilot) persist nothing: an absent rule is not an observation, and an
32
+ // MLA infrastructure-health fact is never a rule verdict.
33
+ // The evaluation-input-v1 version triple. These pin the exact contract the four-state evaluator,
34
+ // the action-applicability matcher, and the notes-path canonicalizer agreed on; they are part of
35
+ // the persisted snapshot and MUST equal the Slice 4 golden-vector corpus values byte-for-byte.
36
+ exports.EVALUATOR_CONTRACT_VERSION = "four-state-evaluator-v1";
37
+ exports.MATCHER_SCHEMA_VERSION = "action-applicability-v1";
38
+ exports.PATH_CANONICALIZER_VERSION = "notes-path-v1";
39
+ /** The snapshot-pure verdict: the SOLE rule by which a stored observation (and its later replay)
40
+ * derives a three-state result from the stored target + forbidden root. It is a pure string
41
+ * comparison over the already-canonicalized posix relative path, with no filesystem probe, so a
42
+ * replay from the snapshot alone is deterministic. The prefix test is boundary-correct: a sibling
43
+ * of the forbidden root (e.g. "notes-archive/x.md" against "notes") is NOT under it. */
44
+ function verdictFromEvaluationInput(target, forbiddenRootRelativePath) {
45
+ switch (target.kind) {
46
+ case "UNKNOWN":
47
+ return { result: "UNKNOWN", verdictReasonCode: "CANONICALIZATION_FAILED" };
48
+ case "OUTSIDE_RUNTIME_SCOPE":
49
+ return { result: "COMPLIANT", verdictReasonCode: "COMPLIANT_OUTSIDE_FORBIDDEN_ROOT" };
50
+ case "RUNTIME_RELATIVE": {
51
+ const underForbidden = target.path === forbiddenRootRelativePath ||
52
+ target.path.startsWith(forbiddenRootRelativePath + "/");
53
+ return underForbidden
54
+ ? { result: "VIOLATION", verdictReasonCode: "FORBIDDEN_PATH_MATCH" }
55
+ : { result: "COMPLIANT", verdictReasonCode: "COMPLIANT_OUTSIDE_FORBIDDEN_ROOT" };
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Replay the durable R0 verdict from a tool_attempt's stored evaluation_input_snapshot ALONE
61
+ * (proposal §10.2 R0-5). Parses the canonical evaluation-input-v1 JSON and re-derives the verdict
62
+ * via verdictFromEvaluationInput over its `target` + `forbiddenRootRelativePath`. It reads no
63
+ * version table, no rule_evaluation_record, and never touches the filesystem: the snapshot is the
64
+ * whole replay basis. Because recordR0Observation persisted the verdict by this very same pure rule
65
+ * over the very same fields, a replay reproduces the stored result by construction, so a stored row
66
+ * can be audited for tamper by re-deriving its verdict from its own snapshot.
67
+ */
68
+ function replayVerdictFromSnapshot(evaluationInputSnapshot) {
69
+ const input = JSON.parse(evaluationInputSnapshot);
70
+ return verdictFromEvaluationInput(input.target, input.forbiddenRootRelativePath);
71
+ }
72
+ /**
73
+ * Persist one R0 observation as the two-record pair, atomically. Mints two distinct ULIDs (one
74
+ * attempt, one evaluation), builds the canonical evaluation-input-v1 snapshot + hash and the inline
75
+ * observed-rule snapshot + hash, derives the snapshot-pure verdict, and writes both rows inside a
76
+ * single BEGIN IMMEDIATE transaction so an interception is never half-recorded. Pure of I/O beyond
77
+ * the local store: no filesystem, no network.
78
+ */
79
+ function recordR0Observation(store, subject, ctx) {
80
+ const attemptId = (0, ulid_1.ulid)(ctx.now, ctx.rand);
81
+ const evaluationId = (0, ulid_1.ulid)(ctx.now, ctx.rand);
82
+ const evaluationInput = {
83
+ toolName: subject.toolName,
84
+ target: subject.target,
85
+ forbiddenRootRelativePath: subject.spec.forbiddenRootRelativePath,
86
+ evaluatorContractVersion: exports.EVALUATOR_CONTRACT_VERSION,
87
+ matcherSchemaVersion: exports.MATCHER_SCHEMA_VERSION,
88
+ pathCanonicalizerVersion: exports.PATH_CANONICALIZER_VERSION,
89
+ };
90
+ const verdict = verdictFromEvaluationInput(subject.target, subject.spec.forbiddenRootRelativePath);
91
+ const attempt = {
92
+ attemptId,
93
+ runtimeScopeId: ctx.runtimeScopeId,
94
+ sessionId: ctx.sessionId,
95
+ toolName: subject.toolName,
96
+ evaluationInputSnapshot: (0, evaluation_input_hash_1.serializeEvaluationInput)(evaluationInput),
97
+ evaluationInputHash: (0, evaluation_input_hash_1.evaluationInputHash)(evaluationInput),
98
+ aggregateDecision: "NO_DECISION",
99
+ denyEmissionStatus: "NOT_APPLICABLE",
100
+ inputAuthorityConfigHash: null,
101
+ createdAt: ctx.createdAt,
102
+ };
103
+ const evaluation = {
104
+ evaluationId,
105
+ attemptId,
106
+ runtimeScopeId: ctx.runtimeScopeId,
107
+ result: verdict.result,
108
+ eligibleEnforcement: "OBSERVE",
109
+ effectiveEnforcement: "OBSERVE",
110
+ verdictReasonCode: verdict.verdictReasonCode,
111
+ gateReasonCode: null,
112
+ evaluatorContractVersion: exports.EVALUATOR_CONTRACT_VERSION,
113
+ observedRuleSnapshot: (0, observed_rule_hash_1.serializeObservedRule)(subject.spec),
114
+ observedRuleHash: (0, observed_rule_hash_1.observedRuleHash)(subject.spec),
115
+ ruleVersionId: null,
116
+ canonicalPayloadHash: null,
117
+ createdAt: ctx.createdAt,
118
+ };
119
+ store.db
120
+ .transaction(() => {
121
+ (0, interception_store_1.insertToolAttempt)(store, attempt);
122
+ (0, interception_store_1.insertRuleEvaluationRecord)(store, evaluation);
123
+ })
124
+ .immediate();
125
+ return {
126
+ attemptId,
127
+ evaluationId,
128
+ result: verdict.result,
129
+ verdictReasonCode: verdict.verdictReasonCode,
130
+ };
131
+ }
132
+ const NO_DECISION = {};
133
+ /**
134
+ * The durable PreToolUse seam for the notes-location pilot. Parses the hook payload, selects the
135
+ * pilot directive, runs the pure selector, classifies the target, and on an applicable call persists
136
+ * the two-record observation. Always returns the empty, decision-free hook response; the durable
137
+ * outcome travels on the side channel.
138
+ *
139
+ * Skip semantics (persist nothing): a malformed payload or a missing session id is INFRA (a hook
140
+ * without a session id is malformed; we refuse to fabricate the NOT NULL session_id); no declared
141
+ * rule, a non-Write/Edit tool, or a glob non-match is NOT_APPLICABLE; a misconfigured pilot
142
+ * descriptor is INFRA.
143
+ */
144
+ async function observeAndRecordNotesRule(store, input) {
145
+ const parsed = (0, observe_adapter_1.parsePreToolUseInput)(input.rawStdin);
146
+ if (!parsed) {
147
+ return { response: NO_DECISION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
148
+ }
149
+ // tool_attempt.session_id is NOT NULL and we never fabricate identity: a payload without one is
150
+ // an infrastructure fault, not an observation.
151
+ if (parsed.session_id === undefined) {
152
+ return { response: NO_DECISION, outcome: { kind: "INFRA", diagnostic: "missing session_id" } };
153
+ }
154
+ const directive = (0, notes_rule_1.selectNotesLocationDirective)(input.directives);
155
+ if (!directive) {
156
+ return { response: NO_DECISION, outcome: { kind: "NOT_APPLICABLE" } };
157
+ }
158
+ const built = (0, notes_rule_1.buildObservedNotesRuleSpec)(directive);
159
+ if (!built.ok) {
160
+ return { response: NO_DECISION, outcome: { kind: "INFRA", diagnostic: built.diagnostic } };
161
+ }
162
+ const spec = built.spec;
163
+ const call = { toolName: parsed.tool_name, toolInput: parsed.tool_input };
164
+ if ((0, evaluator_1.selectRule)(call, spec.applicability) === "NOT_APPLICABLE" || spec.applicability.mode !== "action") {
165
+ return { response: NO_DECISION, outcome: { kind: "NOT_APPLICABLE" } };
166
+ }
167
+ // The selector proved the tool is in the pilot's {Write, Edit} list and the matcher field holds a
168
+ // *.md string, so this narrowing is sound.
169
+ const toolName = parsed.tool_name;
170
+ const rawFilePath = parsed.tool_input[spec.applicability.matcher.field];
171
+ const classify = input.classifyRuntime ?? notes_path_1.classifyRuntimeTarget;
172
+ const target = await classify(rawFilePath, input.runtimeProjectRoot);
173
+ const persisted = recordR0Observation(store, { toolName, target, spec }, {
174
+ runtimeScopeId: input.runtimeScopeId,
175
+ sessionId: parsed.session_id,
176
+ createdAt: input.createdAt,
177
+ now: input.now,
178
+ rand: input.rand,
179
+ });
180
+ return {
181
+ response: NO_DECISION,
182
+ outcome: {
183
+ kind: "PERSISTED",
184
+ attemptId: persisted.attemptId,
185
+ evaluationId: persisted.evaluationId,
186
+ result: persisted.result,
187
+ verdictReasonCode: persisted.verdictReasonCode,
188
+ },
189
+ };
190
+ }