@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,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
|
+
}
|