@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,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NOTES_FORBIDDEN_ROOT_RELATIVE = void 0;
|
|
4
|
+
exports.selectNotesLocationDirective = selectNotesLocationDirective;
|
|
5
|
+
exports.buildObservedNotesRuleSpec = buildObservedNotesRuleSpec;
|
|
6
|
+
const applicability_1 = require("./applicability");
|
|
7
|
+
// The R0 notes-location pilot (proposal §2.4): the single, named rule R0 observes.
|
|
8
|
+
// This is NOT a generic rule-language framework and deliberately stays one rule:
|
|
9
|
+
// its applicability, forbidden root, and effect are fixed pilot constants, and the
|
|
10
|
+
// scan cache's only job is to tell us whether THIS workspace actually declares the
|
|
11
|
+
// rule (so we observe it only where it is in force) and to hand us the exact prose
|
|
12
|
+
// the agent was shown. Generalizing past one rule is explicitly a later ring.
|
|
13
|
+
// The explicit applicability descriptor for the pilot. It is run through the same
|
|
14
|
+
// parseApplicability validator every other applicability passes (never inferred):
|
|
15
|
+
// action-scoped, file-writing tools only, gated to Markdown by the file_path field.
|
|
16
|
+
const NOTES_LOCATION_APPLICABILITY_DESCRIPTOR = {
|
|
17
|
+
mode: "action",
|
|
18
|
+
tools: ["Write", "Edit"],
|
|
19
|
+
matcher: { field: "file_path", glob: "*.md" },
|
|
20
|
+
};
|
|
21
|
+
// The forbidden root the pilot protects, relative to the runtime project root. A
|
|
22
|
+
// Write/Edit of a Markdown file UNDER this root is the violation; anything outside
|
|
23
|
+
// is compliant. Kept as relative CONTENT so the rule is machine-independent.
|
|
24
|
+
exports.NOTES_FORBIDDEN_ROOT_RELATIVE = "notes";
|
|
25
|
+
// The effect the rule asserts when violated. PROHIBIT: rules constrain, never grant.
|
|
26
|
+
const NOTES_EFFECT = "PROHIBIT";
|
|
27
|
+
/**
|
|
28
|
+
* Select the single notes-location directive out of a scanned directive set, or
|
|
29
|
+
* null if this workspace declares no such rule. The predicate is deliberately
|
|
30
|
+
* TIGHT (one pilot, not a classifier): a directive qualifies only when its prose
|
|
31
|
+
* is about the notes/design-doc SUBJECT *and* carries a PLACEMENT sense (where it
|
|
32
|
+
* must or must not live). That keeps a bare "add release notes" mention from being
|
|
33
|
+
* mistaken for the rule. In observe-only R0 a false miss is a harmless
|
|
34
|
+
* NOT_APPLICABLE and a false match is a harmless OBSERVED; neither can enforce.
|
|
35
|
+
*/
|
|
36
|
+
function selectNotesLocationDirective(directives) {
|
|
37
|
+
return directives.find(isNotesLocationDirective) ?? null;
|
|
38
|
+
}
|
|
39
|
+
function isNotesLocationDirective(d) {
|
|
40
|
+
const t = d.text.toLowerCase();
|
|
41
|
+
const mentionsSubject = /\bnotes?\b/.test(t) || t.includes("design doc") || t.includes("design-doc");
|
|
42
|
+
const mentionsPlacement = t.includes("vault") ||
|
|
43
|
+
t.includes("standalone") ||
|
|
44
|
+
t.includes("not the") ||
|
|
45
|
+
t.includes("never the") ||
|
|
46
|
+
/\bgo(?:es)? in\b/.test(t) ||
|
|
47
|
+
/\bbelongs?\b/.test(t);
|
|
48
|
+
return mentionsSubject && mentionsPlacement;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert a selected notes-location directive into the in-memory ObservedRuleSpec
|
|
52
|
+
* the evaluator reads. The text comes from the scanned directive (the exact prose
|
|
53
|
+
* the agent saw); the applicability is parsed-and-validated from the pilot
|
|
54
|
+
* descriptor; the effect and forbidden root are pilot constants. Returns an error
|
|
55
|
+
* only if the pilot's own descriptor fails to parse, which is an infrastructure
|
|
56
|
+
* fault in MLA itself, never a rule violation.
|
|
57
|
+
*/
|
|
58
|
+
function buildObservedNotesRuleSpec(directive) {
|
|
59
|
+
const parsed = (0, applicability_1.parseApplicability)(NOTES_LOCATION_APPLICABILITY_DESCRIPTOR);
|
|
60
|
+
if (parsed.status !== "OK" || !parsed.applicability) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
diagnostic: `notes-location applicability ${parsed.status}: ${parsed.diagnostic ?? "unparseable"}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
spec: {
|
|
69
|
+
text: directive.text,
|
|
70
|
+
applicability: parsed.applicability,
|
|
71
|
+
effect: NOTES_EFFECT,
|
|
72
|
+
forbiddenRootRelativePath: exports.NOTES_FORBIDDEN_ROOT_RELATIVE,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OBSERVE_TIMEOUT_MS = void 0;
|
|
4
|
+
exports.parsePreToolUseInput = parsePreToolUseInput;
|
|
5
|
+
exports.observePreToolUse = observePreToolUse;
|
|
6
|
+
const evaluator_1 = require("./evaluator");
|
|
7
|
+
const notes_path_1 = require("./notes-path");
|
|
8
|
+
/** Production default for the hard evaluation timeout. */
|
|
9
|
+
exports.OBSERVE_TIMEOUT_MS = 500;
|
|
10
|
+
class TimeoutError extends Error {
|
|
11
|
+
}
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function describeError(err) {
|
|
16
|
+
return err instanceof Error ? err.message : String(err);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse and minimally validate the raw hook payload. Accepts either an already
|
|
20
|
+
* parsed object or the raw JSON string delivered on stdin. Returns null for any
|
|
21
|
+
* shape that is not a usable PreToolUse call; the adapter maps that null to an
|
|
22
|
+
* INFRA observation, never a violation.
|
|
23
|
+
*/
|
|
24
|
+
function parsePreToolUseInput(raw) {
|
|
25
|
+
let obj = raw;
|
|
26
|
+
if (typeof raw === "string") {
|
|
27
|
+
try {
|
|
28
|
+
obj = JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (!isPlainObject(obj)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const toolName = obj.tool_name;
|
|
38
|
+
if (typeof toolName !== "string" || toolName.length === 0) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const toolInput = obj.tool_input;
|
|
42
|
+
if (!isPlainObject(toolInput)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
46
|
+
return {
|
|
47
|
+
session_id: str(obj.session_id),
|
|
48
|
+
transcript_path: str(obj.transcript_path),
|
|
49
|
+
cwd: str(obj.cwd),
|
|
50
|
+
hook_event_name: str(obj.hook_event_name),
|
|
51
|
+
tool_name: toolName,
|
|
52
|
+
tool_input: toolInput,
|
|
53
|
+
tool_use_id: str(obj.tool_use_id),
|
|
54
|
+
permission_mode: str(obj.permission_mode),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function withTimeout(promise, ms) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const timer = setTimeout(() => reject(new TimeoutError("evaluation timed out")), ms);
|
|
60
|
+
if (typeof timer.unref === "function") {
|
|
61
|
+
timer.unref();
|
|
62
|
+
}
|
|
63
|
+
promise.then((value) => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
resolve(value);
|
|
66
|
+
}, (err) => {
|
|
67
|
+
clearTimeout(timer);
|
|
68
|
+
reject(err);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Observe a single PreToolUse call. Pure of enforcement: it computes what it
|
|
74
|
+
* observed and always returns an empty (decision-free) hook response.
|
|
75
|
+
*/
|
|
76
|
+
async function observePreToolUse(rawInput, config) {
|
|
77
|
+
const NO_DECISION = {};
|
|
78
|
+
const parsed = parsePreToolUseInput(rawInput);
|
|
79
|
+
if (!parsed) {
|
|
80
|
+
return { response: NO_DECISION, observation: { kind: "INFRA", diagnostic: "malformed hook input" } };
|
|
81
|
+
}
|
|
82
|
+
const call = { toolName: parsed.tool_name, toolInput: parsed.tool_input };
|
|
83
|
+
let selection;
|
|
84
|
+
try {
|
|
85
|
+
selection = (0, evaluator_1.selectRule)(call, config.applicability);
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
return {
|
|
89
|
+
response: NO_DECISION,
|
|
90
|
+
observation: { kind: "INFRA", diagnostic: `selector failure: ${describeError(err)}` },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Only an action rule that selected this call can be evaluated. Ambient rules
|
|
94
|
+
// (and any non-match) are never action gates, so there is nothing to observe.
|
|
95
|
+
if (selection === "NOT_APPLICABLE" || config.applicability.mode !== "action") {
|
|
96
|
+
return { response: NO_DECISION, observation: { kind: "NOT_APPLICABLE" } };
|
|
97
|
+
}
|
|
98
|
+
const rawFilePath = parsed.tool_input[config.applicability.matcher.field];
|
|
99
|
+
const classify = config.classify ?? notes_path_1.classifyTargetPath;
|
|
100
|
+
const timeoutMs = config.timeoutMs ?? exports.OBSERVE_TIMEOUT_MS;
|
|
101
|
+
let classification;
|
|
102
|
+
try {
|
|
103
|
+
classification = await withTimeout(classify(rawFilePath, config.notesScope), timeoutMs);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
const diagnostic = err instanceof TimeoutError ? "evaluation timed out" : `evaluator failure: ${describeError(err)}`;
|
|
107
|
+
return { response: NO_DECISION, observation: { kind: "INFRA", diagnostic } };
|
|
108
|
+
}
|
|
109
|
+
const verdict = (0, evaluator_1.verdictForForbiddenRoot)(classification);
|
|
110
|
+
return {
|
|
111
|
+
response: NO_DECISION,
|
|
112
|
+
observation: { kind: "OBSERVED", result: verdict.result, reasonCode: verdict.reasonCode },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ObservedRuleHashError = exports.OBSERVED_RULE_HASH_DOMAIN = void 0;
|
|
4
|
+
exports.buildObservedRulePayload = buildObservedRulePayload;
|
|
5
|
+
exports.serializeObservedRule = serializeObservedRule;
|
|
6
|
+
exports.observedRuleHash = observedRuleHash;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const canonical_json_1 = require("./canonical-json");
|
|
9
|
+
/**
|
|
10
|
+
* The `observed-rule-v1` canonical hash domain (proposal P0.36, sharpened by P0.53).
|
|
11
|
+
*
|
|
12
|
+
* It computes the content identity of an OBSERVED rule: the ephemeral, un-attested
|
|
13
|
+
* ObservedRuleSpec the scanner produced and the R0 evaluator reads. The digest is
|
|
14
|
+
*
|
|
15
|
+
* SHA-256( domainTag || 0x00 || JCS(payload) ) (lowercase hex)
|
|
16
|
+
*
|
|
17
|
+
* where JCS is the repo's existing RFC 8785 canonicalizer (canonical-json.ts) and the
|
|
18
|
+
* domain tag + single 0x00 separator (P0.53) guarantee this digest can NEVER collide
|
|
19
|
+
* with an attested version (`rule-version-v1`), an action-input snapshot
|
|
20
|
+
* (`evaluation-input-v1`), or any other hashed artifact, even when two normalized bodies
|
|
21
|
+
* are byte-identical. JCS escapes control characters (a literal NUL in prose is emitted
|
|
22
|
+
* as its JSON unicode escape, not as a raw byte), so the only raw 0x00 byte in the hash
|
|
23
|
+
* input is the separator; the boundary between tag and payload is unambiguous.
|
|
24
|
+
*
|
|
25
|
+
* PROVISIONAL FIELD SET. The payload hashed here is EXACTLY today's evaluator-consumed
|
|
26
|
+
* ObservedRuleSpec (text, applicability, effect, forbiddenRootRelativePath). The fuller
|
|
27
|
+
* observed-rule-v1 field family from the proposal (rationale, the compliance-evaluator
|
|
28
|
+
* version triple, deliveryChannels, the observed enforcementCeiling, runtimeScopeId, the
|
|
29
|
+
* schema / canonical-serialization version tags) is owned by the schema/identity contract
|
|
30
|
+
* the document agent has not yet committed. When that contract lands, this payload schema
|
|
31
|
+
* and its golden-vector corpus rotate together. The domain-separation MACHINERY below is
|
|
32
|
+
* final; the field set is what is pending. (See ObservedRuleSpec in types.ts.)
|
|
33
|
+
*
|
|
34
|
+
* Per-field NFC caveat (honest, P0.53). The contract says NFC is applied ONLY to prose
|
|
35
|
+
* fields, while filesystem-derived and opaque values are byte-for-byte. The vendored JCS
|
|
36
|
+
* primitive applies NFC to EVERY string. For the R0 notes-location pilot every non-prose
|
|
37
|
+
* field (the tool names, the effect token, the matcher field and glob, the relative
|
|
38
|
+
* forbidden root) is ASCII and therefore NFC-stable, so universal NFC is byte-identical to
|
|
39
|
+
* the per-field rule and the golden vectors are contract-correct. When a future
|
|
40
|
+
* ObservedRuleSpec carries a non-prose field that can hold non-NFC Unicode (for instance a
|
|
41
|
+
* forbidden path with combining marks), this domain must switch to a per-field-NFC encoder
|
|
42
|
+
* so those bytes are preserved verbatim. That boundary is recorded in the ledger.
|
|
43
|
+
*/
|
|
44
|
+
exports.OBSERVED_RULE_HASH_DOMAIN = "observed-rule-v1";
|
|
45
|
+
/** Thrown when a spec carries a field outside the observed-rule-v1 schema (fail-closed). */
|
|
46
|
+
class ObservedRuleHashError extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "ObservedRuleHashError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
exports.ObservedRuleHashError = ObservedRuleHashError;
|
|
53
|
+
// The closed key sets for each object in the payload schema. Unknown fields are an error
|
|
54
|
+
// (P0.53), so a forward-incompatible producer cannot silently mint a hash a consumer would
|
|
55
|
+
// compute differently. `glob` is the only optional key; it is omitted, never null, when absent.
|
|
56
|
+
const OBSERVED_RULE_KEYS = new Set(["text", "applicability", "effect", "forbiddenRootRelativePath"]);
|
|
57
|
+
const APPLICABILITY_AMBIENT_KEYS = new Set(["mode"]);
|
|
58
|
+
const APPLICABILITY_ACTION_KEYS = new Set(["mode", "tools", "matcher"]);
|
|
59
|
+
const MATCHER_KEYS = new Set(["field", "glob"]);
|
|
60
|
+
function rejectUnknownKeys(obj, allowed, context) {
|
|
61
|
+
for (const key of Object.keys(obj)) {
|
|
62
|
+
if (!allowed.has(key)) {
|
|
63
|
+
throw new ObservedRuleHashError(`unknown field '${key}' in ${context} is outside the ${exports.OBSERVED_RULE_HASH_DOMAIN} schema`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Sort + dedupe a SET-valued field by code unit (P0.53). Tool names are ASCII, so code
|
|
68
|
+
* unit and code point coincide; the comparison matches the JCS object-key ordering. */
|
|
69
|
+
function sortedDedupedSet(values) {
|
|
70
|
+
return Array.from(new Set(values)).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
71
|
+
}
|
|
72
|
+
function buildApplicabilityPayload(a) {
|
|
73
|
+
if (a.mode === "ambient") {
|
|
74
|
+
rejectUnknownKeys(a, APPLICABILITY_AMBIENT_KEYS, "applicability(ambient)");
|
|
75
|
+
return { mode: "ambient" };
|
|
76
|
+
}
|
|
77
|
+
rejectUnknownKeys(a, APPLICABILITY_ACTION_KEYS, "applicability(action)");
|
|
78
|
+
rejectUnknownKeys(a.matcher, MATCHER_KEYS, "applicability.matcher");
|
|
79
|
+
// Absent optional: OMIT the key (never null). Present: include verbatim.
|
|
80
|
+
const matcher = { field: a.matcher.field };
|
|
81
|
+
if (a.matcher.glob !== undefined) {
|
|
82
|
+
matcher.glob = a.matcher.glob;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
mode: "action",
|
|
86
|
+
// SET discipline: sorted + deduped so two logically-equal tool sets hash identically.
|
|
87
|
+
tools: sortedDedupedSet(a.tools),
|
|
88
|
+
matcher,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Build the closed canonical payload for an ObservedRuleSpec: the exact object that gets
|
|
93
|
+
* canonicalized and hashed. Rejects unknown fields, applies set discipline to `tools`, and
|
|
94
|
+
* omits an absent matcher glob. Field NFC (prose) is applied by the JCS encoder on the way
|
|
95
|
+
* out; see the per-field caveat in the file header.
|
|
96
|
+
*/
|
|
97
|
+
function buildObservedRulePayload(spec) {
|
|
98
|
+
rejectUnknownKeys(spec, OBSERVED_RULE_KEYS, "observed rule");
|
|
99
|
+
return {
|
|
100
|
+
text: spec.text,
|
|
101
|
+
applicability: buildApplicabilityPayload(spec.applicability),
|
|
102
|
+
effect: spec.effect,
|
|
103
|
+
forbiddenRootRelativePath: spec.forbiddenRootRelativePath,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** The exact RFC 8785 canonical JSON string that is hashed (UTF-8). Exposed for golden
|
|
107
|
+
* vectors and debugging; the digest is over these bytes prefixed by the domain. */
|
|
108
|
+
function serializeObservedRule(spec) {
|
|
109
|
+
return (0, canonical_json_1.canonicalize)(buildObservedRulePayload(spec));
|
|
110
|
+
}
|
|
111
|
+
/** The observed-rule-v1 content hash: SHA-256(domainTag || 0x00 || JCS(payload)), lowercase hex. */
|
|
112
|
+
function observedRuleHash(spec) {
|
|
113
|
+
const jcs = serializeObservedRule(spec);
|
|
114
|
+
const h = (0, crypto_1.createHash)("sha256");
|
|
115
|
+
h.update(exports.OBSERVED_RULE_HASH_DOMAIN, "utf8");
|
|
116
|
+
h.update(Buffer.from([0x00]));
|
|
117
|
+
h.update(jcs, "utf8");
|
|
118
|
+
return h.digest("hex");
|
|
119
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseUserPromptSubmitInput = parseUserPromptSubmitInput;
|
|
4
|
+
exports.observeUserPromptSubmit = observeUserPromptSubmit;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const ce0_store_1 = require("./ce0-store");
|
|
7
|
+
const canonical_json_1 = require("./canonical-json");
|
|
8
|
+
const memory_requirement_1 = require("./memory-requirement");
|
|
9
|
+
const requirement_subject_1 = require("./requirement-subject");
|
|
10
|
+
function isPlainObject(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function describeError(err) {
|
|
14
|
+
return err instanceof Error ? err.message : String(err);
|
|
15
|
+
}
|
|
16
|
+
function defaultNewId(kind) {
|
|
17
|
+
return `${kind === "assessment" ? "asm" : "obl"}:${(0, crypto_1.randomUUID)()}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse and minimally validate the raw hook payload. Accepts either an already parsed
|
|
21
|
+
* object or the raw JSON string delivered on stdin. Returns null for any shape that is
|
|
22
|
+
* not a usable UserPromptSubmit turn (the adapter maps that null to INFRA). A turn is
|
|
23
|
+
* usable iff it carries a non-empty string `prompt`; the session coordinate is checked
|
|
24
|
+
* separately so its absence gets a distinct diagnostic.
|
|
25
|
+
*/
|
|
26
|
+
function parseUserPromptSubmitInput(raw) {
|
|
27
|
+
let obj = raw;
|
|
28
|
+
if (typeof raw === "string") {
|
|
29
|
+
try {
|
|
30
|
+
obj = JSON.parse(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!isPlainObject(obj)) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const prompt = obj.prompt;
|
|
40
|
+
if (typeof prompt !== "string" || prompt.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
44
|
+
return {
|
|
45
|
+
session_id: str(obj.session_id),
|
|
46
|
+
transcript_path: str(obj.transcript_path),
|
|
47
|
+
cwd: str(obj.cwd),
|
|
48
|
+
hook_event_name: str(obj.hook_event_name),
|
|
49
|
+
prompt,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Observe a single user turn: classify its memory requirement, mint its LocalTurnIdentity
|
|
54
|
+
* and persist the assessment, and create a TurnRuleObligation iff the turn is REQUIRED.
|
|
55
|
+
* Always returns an empty (injection-free) hook response.
|
|
56
|
+
*/
|
|
57
|
+
function observeUserPromptSubmit(rawInput, config) {
|
|
58
|
+
const NO_INJECTION = {};
|
|
59
|
+
const parsed = parseUserPromptSubmitInput(rawInput);
|
|
60
|
+
if (!parsed) {
|
|
61
|
+
return { response: NO_INJECTION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
|
|
62
|
+
}
|
|
63
|
+
if (!parsed.session_id) {
|
|
64
|
+
return {
|
|
65
|
+
response: NO_INJECTION,
|
|
66
|
+
outcome: { kind: "INFRA", diagnostic: "missing session_id coordinate" },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const now = config.now ?? Date.now;
|
|
70
|
+
const newId = config.newId ?? defaultNewId;
|
|
71
|
+
const classification = (0, memory_requirement_1.classifyMemoryRequirement)(parsed.prompt);
|
|
72
|
+
// Narrow the session coordinate once, past the guard above, so it stays `string` inside the
|
|
73
|
+
// obligation builder closure (a property read on the mutable `parsed` would widen back to optional).
|
|
74
|
+
const sessionId = parsed.session_id;
|
|
75
|
+
try {
|
|
76
|
+
const draft = {
|
|
77
|
+
assessmentId: newId("assessment"),
|
|
78
|
+
workspaceId: config.workspaceId,
|
|
79
|
+
sessionId,
|
|
80
|
+
requirement: classification.requirement,
|
|
81
|
+
markersMatched: classification.markersMatched,
|
|
82
|
+
exclusionsMatched: classification.exclusionsMatched,
|
|
83
|
+
classifierVersion: classification.classifierVersion,
|
|
84
|
+
markerSetVersion: classification.markerSetVersion,
|
|
85
|
+
exclusionSetVersion: classification.exclusionSetVersion,
|
|
86
|
+
createdAt: now(),
|
|
87
|
+
// R4 P0.1 recall snapshot (proposal lines 287-295): the prompt's identity-only hash, born
|
|
88
|
+
// here at classification. Content-free: the raw prompt is never duplicated into the record.
|
|
89
|
+
promptHash: (0, canonical_json_1.sha256Hex)(parsed.prompt),
|
|
90
|
+
};
|
|
91
|
+
// Open the turn ATOMICALLY: the assessment and (for a REQUIRED turn) its obligation are written in
|
|
92
|
+
// one BEGIN IMMEDIATE transaction, so a persistence failure can never leave a REQUIRED assessment
|
|
93
|
+
// without the obligation that grades it (proposal §1.3 req 1, R4 P0.4). The obligation is built
|
|
94
|
+
// INSIDE the transaction from the freshly minted assessment so it carries the same localTurnSequence;
|
|
95
|
+
// a non-REQUIRED turn returns null and records only its assessment.
|
|
96
|
+
const { assessment, obligation } = (0, ce0_store_1.openTurnAtomically)(config.store, draft, (a) => classification.requirement !== "REQUIRED"
|
|
97
|
+
? null
|
|
98
|
+
: {
|
|
99
|
+
obligationId: newId("obligation"),
|
|
100
|
+
workspaceId: config.workspaceId,
|
|
101
|
+
sessionId,
|
|
102
|
+
localTurnSequence: a.localTurnSequence,
|
|
103
|
+
ruleId: config.ruleBinding.ruleId,
|
|
104
|
+
ruleVersionId: config.ruleBinding.ruleVersionId,
|
|
105
|
+
requiredSubjects: [(0, requirement_subject_1.buildRequiredSubjectFromPrompt)(parsed.prompt)],
|
|
106
|
+
subjectSatisfaction: [],
|
|
107
|
+
status: "OPEN",
|
|
108
|
+
stateVersion: 0,
|
|
109
|
+
deadlineClaimedAt: null,
|
|
110
|
+
deadlineClaimedVersion: null,
|
|
111
|
+
responseHash: null,
|
|
112
|
+
outcome: null,
|
|
113
|
+
canonicalPayloadHash: config.ruleBinding.canonicalPayloadHash,
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
response: NO_INJECTION,
|
|
117
|
+
outcome: {
|
|
118
|
+
kind: "ASSESSED",
|
|
119
|
+
requirement: classification.requirement,
|
|
120
|
+
assessmentId: assessment.assessmentId,
|
|
121
|
+
localTurnSequence: assessment.localTurnSequence,
|
|
122
|
+
obligationId: obligation ? obligation.obligationId : null,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return {
|
|
128
|
+
response: NO_INJECTION,
|
|
129
|
+
outcome: { kind: "INFRA", diagnostic: `persistence failure: ${describeError(err)}` },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|