@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,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// CE0 offline telemetry projection: the CE0 durable store -> the §6.4 analytics events the harness
|
|
3
|
+
// measures precision/recall and outcome rates with. Pure: a store record in, an analytics
|
|
4
|
+
// RecordInput out; no I/O, no clock (the `mla evidence ce0-emit-telemetry` sweep supplies the store
|
|
5
|
+
// and the recorder supplies the envelope).
|
|
6
|
+
//
|
|
7
|
+
// Brutal honesty about scope. The CE0 store is a GRADING store, not a complete telemetry source, so
|
|
8
|
+
// only the two events it honestly backs are projected:
|
|
9
|
+
//
|
|
10
|
+
// memory_requirement_assessed one per assessment row (the precision/recall denominator)
|
|
11
|
+
// evidence_obligation_finalized one per FINALIZED obligation row
|
|
12
|
+
//
|
|
13
|
+
// The other two §6.4 events are live-only and deliberately NOT projected here: a fabricated
|
|
14
|
+
// latency_ms (evidence_consultation_completed) or per-hook duration (evidence_hook_health) has no
|
|
15
|
+
// honest offline value, and zero would be a false measurement.
|
|
16
|
+
//
|
|
17
|
+
// work_type rides as an accurate "not recorded" constant (CE0 does not classify the turn), as does
|
|
18
|
+
// the finalized event's answer_disposition (a human label set offline, never on the live obligation,
|
|
19
|
+
// so null is honest here). The sampling_bucket is NOT a constant: every assessment row carries its
|
|
20
|
+
// own deterministic bucket (R3 P0.9), projected verbatim so the offline unflagged-recall sample is
|
|
21
|
+
// reconstructible. satisfied_by_sources is ALSO not a constant: the live obligation's
|
|
22
|
+
// subjectSatisfaction is always [] (the runtime only records facts), so the projector recomputes the
|
|
23
|
+
// proof set offline exactly as ce0-export does, then resolves each proof's consultation back to the
|
|
24
|
+
// §1.6 source that initiated it. The recompute is bounded by the same frozen deadline the first Stop
|
|
25
|
+
// claimed, so a late consultation can never manufacture an on-time source.
|
|
26
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
27
|
+
exports.CE0_WORK_TYPE_UNKNOWN = void 0;
|
|
28
|
+
exports.ce0TurnId = ce0TurnId;
|
|
29
|
+
exports.projectAssessedEvent = projectAssessedEvent;
|
|
30
|
+
exports.projectFinalizedEvent = projectFinalizedEvent;
|
|
31
|
+
const ce0_store_1 = require("./ce0-store");
|
|
32
|
+
const requirement_subject_1 = require("./requirement-subject");
|
|
33
|
+
const ce0_telemetry_1 = require("./ce0-telemetry");
|
|
34
|
+
/** CE0 does not classify the turn's work type. */
|
|
35
|
+
exports.CE0_WORK_TYPE_UNKNOWN = "unknown";
|
|
36
|
+
/** The CE0 turn coordinate carried in telemetry: the (session, sequence) pair the analytics side
|
|
37
|
+
* joins on. The assessment/obligation rows store both halves; this is their canonical rendering. */
|
|
38
|
+
function ce0TurnId(sessionId, localTurnSequence) {
|
|
39
|
+
return `${sessionId}:${localTurnSequence}`;
|
|
40
|
+
}
|
|
41
|
+
/** Project one assessment row into a memory_requirement_assessed event. EVERY assessment is a
|
|
42
|
+
* telemetry fact (REQUIRED or not): the negative half is the precision/recall denominator. */
|
|
43
|
+
function projectAssessedEvent(rec) {
|
|
44
|
+
return (0, ce0_telemetry_1.buildMemoryRequirementAssessedEvent)({
|
|
45
|
+
assessmentId: rec.assessmentId,
|
|
46
|
+
turnId: ce0TurnId(rec.sessionId, rec.localTurnSequence),
|
|
47
|
+
localTurnSequence: rec.localTurnSequence,
|
|
48
|
+
memoryRequirement: rec.requirement,
|
|
49
|
+
workType: exports.CE0_WORK_TYPE_UNKNOWN,
|
|
50
|
+
classifierVersion: rec.classifierVersion,
|
|
51
|
+
markerSetVersion: rec.markerSetVersion,
|
|
52
|
+
markersMatched: rec.markersMatched,
|
|
53
|
+
samplingBucket: rec.samplingBucket,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Recompute the distinct §1.6 sources that proved a required subject for this obligation, in
|
|
58
|
+
* canonical CONSULTATION_SOURCES order. The live subjectSatisfaction is always [] in CE0, so this
|
|
59
|
+
* mirrors ce0-export: map the turn's consultations onto the reducer input, take the eligible set
|
|
60
|
+
* bounded by the frozen deadline, recompute the proof set, then resolve each proof's consultation
|
|
61
|
+
* back to its source. Deduped (the tuple lists each source once) and stably ordered.
|
|
62
|
+
*/
|
|
63
|
+
function resolveSatisfiedSources(obl, consultations) {
|
|
64
|
+
const eligible = (0, requirement_subject_1.selectEligibleConsultations)(consultations.map(ce0_store_1.consultationRecordToReducerInput), obl.deadlineClaimedAt);
|
|
65
|
+
const proofs = (0, requirement_subject_1.recomputeSubjectSatisfaction)(obl.requiredSubjects, eligible);
|
|
66
|
+
const sourceById = new Map(consultations.map((c) => [c.consultationId, c.source]));
|
|
67
|
+
const proven = new Set();
|
|
68
|
+
for (const proof of proofs) {
|
|
69
|
+
const source = sourceById.get(proof.consultationId);
|
|
70
|
+
if (source)
|
|
71
|
+
proven.add(source);
|
|
72
|
+
}
|
|
73
|
+
return requirement_subject_1.CONSULTATION_SOURCES.filter((s) => proven.has(s));
|
|
74
|
+
}
|
|
75
|
+
/** Project one FINALIZED obligation row into an evidence_obligation_finalized event. The DB
|
|
76
|
+
* invariant ties FINALIZED to a non-null outcome, so a null outcome means the caller handed in a
|
|
77
|
+
* non-finalized row; reject it loudly rather than emit a malformed event. The turn's consultations
|
|
78
|
+
* are passed alongside the obligation so the projector can recompute which sources proved a subject
|
|
79
|
+
* (the live subjectSatisfaction is always [] in CE0). */
|
|
80
|
+
function projectFinalizedEvent(obl, consultations) {
|
|
81
|
+
if (obl.outcome === null) {
|
|
82
|
+
throw new Error(`projectFinalizedEvent: obligation ${obl.obligationId} has no outcome (not FINALIZED)`);
|
|
83
|
+
}
|
|
84
|
+
return (0, ce0_telemetry_1.buildEvidenceObligationFinalizedEvent)({
|
|
85
|
+
obligationId: obl.obligationId,
|
|
86
|
+
localTurnSequence: obl.localTurnSequence,
|
|
87
|
+
ruleVersionId: obl.ruleVersionId,
|
|
88
|
+
stateVersion: obl.stateVersion,
|
|
89
|
+
outcome: obl.outcome,
|
|
90
|
+
satisfiedBySources: resolveSatisfiedSources(obl, consultations),
|
|
91
|
+
answerDisposition: null,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// CE0 minimal telemetry: the EXACTLY four PostHog events the harness needs to measure
|
|
3
|
+
// precision, recall, the consultation rate, and hook health
|
|
4
|
+
// (notes/20260617-evidence-consultation-forcing-function-proposal.md §6.4, R3 P1.6):
|
|
5
|
+
//
|
|
6
|
+
// memory_requirement_assessed every classified turn (the precision/recall denominator)
|
|
7
|
+
// evidence_consultation_completed every governed-memory consultation, keyed by the consultation
|
|
8
|
+
// evidence_obligation_finalized emitted offline by the label importer (§2.3)
|
|
9
|
+
// evidence_hook_health the latency/health watchdog
|
|
10
|
+
//
|
|
11
|
+
// These are PURE projections: a CE0 record -> the analytics RecordInput the recorder ships. No I/O,
|
|
12
|
+
// no clock; the recorder supplies the envelope (workspace, session, run, trace, timestamps). The
|
|
13
|
+
// privacy boundary (INV-POSTHOG-PII-1) holds field by field: ids, enums, counts, booleans,
|
|
14
|
+
// durations, and hashes only. markersMatched is hashed here, never emitted verbatim.
|
|
15
|
+
//
|
|
16
|
+
// eventId strategy mirrors event-id.ts's server-recomputable family. ALL FOUR events carry a
|
|
17
|
+
// deterministic id so a hook re-firing across processes dedupes on (businessKey, version): the
|
|
18
|
+
// assessment on (assessmentId, 0), the consultation on (consultationId, 0), the finalization on
|
|
19
|
+
// (obligationId, stateVersion), and the health watchdog on (hook + operationIdentity, 0). The health
|
|
20
|
+
// event's operationIdentity is the stable per-hook coordinate the hook acted on (§6.4 P0.2), so a
|
|
21
|
+
// retried hook hashes to the same id rather than minting a fresh CLI-origin id and double-counting.
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.hashMarkerSet = hashMarkerSet;
|
|
57
|
+
exports.buildMemoryRequirementAssessedEvent = buildMemoryRequirementAssessedEvent;
|
|
58
|
+
exports.buildEvidenceConsultationCompletedEvent = buildEvidenceConsultationCompletedEvent;
|
|
59
|
+
exports.buildEvidenceObligationFinalizedEvent = buildEvidenceObligationFinalizedEvent;
|
|
60
|
+
exports.buildEvidenceHookHealthEvent = buildEvidenceHookHealthEvent;
|
|
61
|
+
const crypto = __importStar(require("crypto"));
|
|
62
|
+
const event_id_1 = require("../analytics/event-id");
|
|
63
|
+
const envelope_1 = require("../analytics/envelope");
|
|
64
|
+
const OBLIGATION_OUTCOME_SET = new Set(envelope_1.OBLIGATION_OUTCOME_LABELS);
|
|
65
|
+
/** Hash a marker set to a stable hex digest. Order-independent (the SET, not the sequence, is the
|
|
66
|
+
* identity) and one-way, so the matched markers never leave the device as raw text (§6.4 privacy). */
|
|
67
|
+
function hashMarkerSet(markers) {
|
|
68
|
+
const canonical = [...new Set(markers)].sort().join("\n");
|
|
69
|
+
return crypto.createHash("sha256").update(canonical).digest("hex");
|
|
70
|
+
}
|
|
71
|
+
/** One event per classified turn (REQUIRED or not). Without it the precision/recall denominator
|
|
72
|
+
* (the turns you did NOT flag) is unobservable. Carries no obligationId by design. */
|
|
73
|
+
function buildMemoryRequirementAssessedEvent(input) {
|
|
74
|
+
return {
|
|
75
|
+
eventType: "memory_requirement_assessed",
|
|
76
|
+
eventId: (0, event_id_1.deterministicEventId)(input.assessmentId, 0),
|
|
77
|
+
payload: {
|
|
78
|
+
assessment_id: input.assessmentId,
|
|
79
|
+
turn_id: input.turnId,
|
|
80
|
+
local_turn_sequence: input.localTurnSequence,
|
|
81
|
+
memory_requirement: input.memoryRequirement,
|
|
82
|
+
work_type: input.workType,
|
|
83
|
+
classifier_version: input.classifierVersion,
|
|
84
|
+
marker_set_version: input.markerSetVersion,
|
|
85
|
+
markers_matched_hashed: hashMarkerSet(input.markersMatched),
|
|
86
|
+
sampling_bucket: input.samplingBucket,
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** One event per governed-memory consultation, keyed by the consultation (NOT the obligation). No
|
|
91
|
+
* FULL/PARTIAL/NONE coverage summary in CE0: coverage grading is offline (§1.6). `ruleVersionId` and
|
|
92
|
+
* `latencyMs` are optional (§6.4 P1.2 / P0.2) and carried only when the turn supplies them, mirroring
|
|
93
|
+
* the health event's optional `turnId`. */
|
|
94
|
+
function buildEvidenceConsultationCompletedEvent(input) {
|
|
95
|
+
// §6.4 / P0.3: result is present IFF the consultation is COMPLETE.
|
|
96
|
+
const complete = input.execution === "COMPLETE";
|
|
97
|
+
if (complete !== (input.result !== null)) {
|
|
98
|
+
throw new Error(`evidence_consultation_completed: result must be present IFF execution is COMPLETE ` +
|
|
99
|
+
`(execution=${input.execution}, result=${input.result})`);
|
|
100
|
+
}
|
|
101
|
+
const payload = {
|
|
102
|
+
consultation_id: input.consultationId,
|
|
103
|
+
local_turn_sequence: input.localTurnSequence,
|
|
104
|
+
source: input.source,
|
|
105
|
+
execution: input.execution,
|
|
106
|
+
result: input.result,
|
|
107
|
+
delivered_to_answering_context: input.deliveredToAnsweringContext,
|
|
108
|
+
};
|
|
109
|
+
if (input.ruleVersionId !== undefined)
|
|
110
|
+
payload.rule_version_id = input.ruleVersionId;
|
|
111
|
+
if (input.latencyMs !== undefined)
|
|
112
|
+
payload.latency_ms = input.latencyMs;
|
|
113
|
+
return {
|
|
114
|
+
eventType: "evidence_consultation_completed",
|
|
115
|
+
eventId: (0, event_id_1.deterministicEventId)(input.consultationId, 0),
|
|
116
|
+
payload,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Emitted by the offline label importer (§2.3) when an obligation finalizes. Server-recomputable on
|
|
120
|
+
* (obligationId, stateVersion): a re-import at the same stateVersion dedupes; a later finalization
|
|
121
|
+
* (a new stateVersion) is a distinct row. */
|
|
122
|
+
function buildEvidenceObligationFinalizedEvent(input) {
|
|
123
|
+
if (!OBLIGATION_OUTCOME_SET.has(input.outcome)) {
|
|
124
|
+
throw new Error(`evidence_obligation_finalized: outcome "${input.outcome}" is not a §6.4 ObligationOutcome`);
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
eventType: "evidence_obligation_finalized",
|
|
128
|
+
eventId: (0, event_id_1.deterministicEventId)(input.obligationId, input.stateVersion),
|
|
129
|
+
payload: {
|
|
130
|
+
obligation_id: input.obligationId,
|
|
131
|
+
local_turn_sequence: input.localTurnSequence,
|
|
132
|
+
rule_version_id: input.ruleVersionId,
|
|
133
|
+
state_version: input.stateVersion,
|
|
134
|
+
outcome: input.outcome,
|
|
135
|
+
satisfied_by_sources: input.satisfiedBySources,
|
|
136
|
+
answer_disposition: input.answerDisposition,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The latency/health watchdog: one row per hook invocation. Keyed by (hook, operationIdentity) so a
|
|
141
|
+
* hook that re-fires across processes (a Stop continuation, a retry) re-appends the SAME deterministic
|
|
142
|
+
* eventId and the projection dedups it instead of double-counting (§6.4 P0.2). */
|
|
143
|
+
function buildEvidenceHookHealthEvent(input) {
|
|
144
|
+
const payload = {
|
|
145
|
+
hook: input.hook,
|
|
146
|
+
operation_identity: input.operationIdentity,
|
|
147
|
+
duration_ms: input.durationMs,
|
|
148
|
+
failed: input.failed,
|
|
149
|
+
reason: input.reason,
|
|
150
|
+
};
|
|
151
|
+
if (input.turnId !== undefined)
|
|
152
|
+
payload.turn_id = input.turnId;
|
|
153
|
+
return {
|
|
154
|
+
eventType: "evidence_hook_health",
|
|
155
|
+
eventId: (0, event_id_1.deterministicEventId)(`${input.hook}:${input.operationIdentity}`, 0),
|
|
156
|
+
payload,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCodeRule = getCodeRule;
|
|
4
|
+
const canonical_json_1 = require("./canonical-json");
|
|
5
|
+
const ce0_rule_1 = require("./ce0-rule");
|
|
6
|
+
/** The closed registry, keyed by code-rule name. Adding a rule here is the only way to enroll one. */
|
|
7
|
+
const CODE_RULES = {
|
|
8
|
+
[ce0_rule_1.CONSULT_EVIDENCE_RULE_ID]: {
|
|
9
|
+
ruleId: ce0_rule_1.CONSULT_EVIDENCE_RULE_ID,
|
|
10
|
+
serializedPayload: (0, canonical_json_1.canonicalize)(ce0_rule_1.CONSULT_EVIDENCE_RULE_PAYLOAD),
|
|
11
|
+
canonicalPayloadHash: ce0_rule_1.CONSULT_EVIDENCE_CANONICAL_PAYLOAD_HASH,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
/** Resolve a code-rule by name; null when no such rule ships in source (never fabricates one). */
|
|
15
|
+
function getCodeRule(name) {
|
|
16
|
+
return CODE_RULES[name] ?? null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// COMMAND matcher: the pure tokenizer + classifier for the git/prisma rule class
|
|
3
|
+
// (GAP2).
|
|
4
|
+
//
|
|
5
|
+
// The proposal declares Bash PATH enforcement out of v1 (§552-556 / §3167) because
|
|
6
|
+
// a shell string is opaque: cp/mv/python/redirection/eval can perform an effect
|
|
7
|
+
// without the literal tokens appearing, so you can never prove a command is SAFE
|
|
8
|
+
// for a path rule. This module covers the decidable HALF the proposal left out:
|
|
9
|
+
// the POSITIVE literal match. If the forbidden tokens (e.g. "git push") appear as
|
|
10
|
+
// a contiguous run of unquoted, uncommented words, the command performs that
|
|
11
|
+
// operation. Opacity can only ADD effects, never remove the literal one, so a
|
|
12
|
+
// positive match is a sound VIOLATION.
|
|
13
|
+
//
|
|
14
|
+
// The asymmetry is deliberate and is the whole reason this matcher exists:
|
|
15
|
+
// forbidden token run present -> MATCHES_FORBIDDEN -> VIOLATION
|
|
16
|
+
// no run found -> NO_MATCH -> UNKNOWN (NOT compliant)
|
|
17
|
+
// non-string / no usable needle -> INDETERMINATE -> UNKNOWN
|
|
18
|
+
// There is NO command COMPLIANT. A non-match cannot prove the command will not push
|
|
19
|
+
// (an alias, a wrapper script, eval, or $VAR expansion could), so the absence of
|
|
20
|
+
// the run is UNKNOWN, never proof of compliance. That is the inverse of the CONTENT
|
|
21
|
+
// matcher, whose field is fully observable and so CAN produce a real COMPLIANT.
|
|
22
|
+
//
|
|
23
|
+
// Soundness of a positive match rests on three tokenizer guarantees:
|
|
24
|
+
// 1. quotes collapse a run into ONE token, so `echo "git push"` is not a match;
|
|
25
|
+
// 2. a `#` at a word boundary starts a comment, so `ls # git push` is not a match;
|
|
26
|
+
// 3. statement separators (newline, ; | &, parens) break a segment, so `git ;
|
|
27
|
+
// push` is two statements, not the `git push` invocation.
|
|
28
|
+
// Known, ACCEPTED limitation: a command reached indirectly (an absolute path like
|
|
29
|
+
// `/usr/bin/git push`, a subshell `(git push)` with no inner spaces, an alias) will
|
|
30
|
+
// MISS. A miss is a false negative that degrades to UNKNOWN, which is the safe,
|
|
31
|
+
// non-denying state. This matcher is OBSERVE-ONLY in this slice; it must never deny
|
|
32
|
+
// until a tokenized pattern is human-attested, which is the safety valve for the
|
|
33
|
+
// residual risk that a contrived redirect target places the tokens consecutively.
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.tokenizeCommand = tokenizeCommand;
|
|
36
|
+
exports.classifyCommand = classifyCommand;
|
|
37
|
+
// Statement separators that break a token run. A forbidden sequence can never
|
|
38
|
+
// match ACROSS one of these, so two adjacent statements cannot be read as a single
|
|
39
|
+
// command invocation. Redirections (< >) are deliberately NOT here: they leave
|
|
40
|
+
// their operator as its own token, which already breaks contiguity.
|
|
41
|
+
const SEPARATORS = new Set([";", "|", "&", "(", ")", "\n"]);
|
|
42
|
+
const DQUOTE_ESCAPABLE = new Set(['"', "\\", "$", "`", "\n"]);
|
|
43
|
+
/**
|
|
44
|
+
* A deliberately small POSIX-ish tokenizer, scoped to what a SOUND positive match
|
|
45
|
+
* needs: single quotes (literal), double quotes (with the POSIX backslash escapes),
|
|
46
|
+
* backslash escaping and line continuation, `#` comments at a word boundary, and
|
|
47
|
+
* the statement separators above. It does not expand variables, globs, aliases, or
|
|
48
|
+
* substitutions: those only ever cause a MISS (UNKNOWN), never a false match.
|
|
49
|
+
*/
|
|
50
|
+
function tokenizeCommand(raw) {
|
|
51
|
+
const segments = [];
|
|
52
|
+
let segment = [];
|
|
53
|
+
let token = "";
|
|
54
|
+
let inToken = false;
|
|
55
|
+
const endToken = () => {
|
|
56
|
+
if (inToken) {
|
|
57
|
+
segment.push(token);
|
|
58
|
+
token = "";
|
|
59
|
+
inToken = false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const endSegment = () => {
|
|
63
|
+
endToken();
|
|
64
|
+
if (segment.length > 0) {
|
|
65
|
+
segments.push(segment);
|
|
66
|
+
segment = [];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
let i = 0;
|
|
70
|
+
while (i < raw.length) {
|
|
71
|
+
const c = raw[i];
|
|
72
|
+
if (c === "'") {
|
|
73
|
+
inToken = true;
|
|
74
|
+
i++;
|
|
75
|
+
while (i < raw.length && raw[i] !== "'") {
|
|
76
|
+
token += raw[i];
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
i++; // consume the closing quote (or run off the end on an unbalanced quote)
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (c === '"') {
|
|
83
|
+
inToken = true;
|
|
84
|
+
i++;
|
|
85
|
+
while (i < raw.length && raw[i] !== '"') {
|
|
86
|
+
if (raw[i] === "\\" && i + 1 < raw.length && DQUOTE_ESCAPABLE.has(raw[i + 1])) {
|
|
87
|
+
token += raw[i + 1];
|
|
88
|
+
i += 2;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
token += raw[i];
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
i++;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (c === "\\") {
|
|
98
|
+
if (i + 1 < raw.length && raw[i + 1] === "\n") {
|
|
99
|
+
i += 2; // line continuation: both chars vanish
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (i + 1 < raw.length) {
|
|
103
|
+
token += raw[i + 1];
|
|
104
|
+
inToken = true;
|
|
105
|
+
i += 2;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
token += c;
|
|
109
|
+
inToken = true;
|
|
110
|
+
i++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// A hash starts a comment only at a word boundary (not mid-token), matching
|
|
114
|
+
// shell semantics: `abc#def` is one word, ` # ...` is a comment.
|
|
115
|
+
if (c === "#" && !inToken) {
|
|
116
|
+
while (i < raw.length && raw[i] !== "\n") {
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (c === " " || c === "\t" || c === "\r") {
|
|
122
|
+
endToken();
|
|
123
|
+
i++;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (SEPARATORS.has(c)) {
|
|
127
|
+
endSegment();
|
|
128
|
+
i++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
token += c;
|
|
132
|
+
inToken = true;
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
endSegment();
|
|
136
|
+
return segments;
|
|
137
|
+
}
|
|
138
|
+
/** True iff `needle` occurs as a contiguous run inside `haystack`. */
|
|
139
|
+
function containsRun(haystack, needle) {
|
|
140
|
+
if (needle.length === 0 || needle.length > haystack.length) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
for (let start = 0; start + needle.length <= haystack.length; start++) {
|
|
144
|
+
let all = true;
|
|
145
|
+
for (let k = 0; k < needle.length; k++) {
|
|
146
|
+
if (haystack[start + k] !== needle[k]) {
|
|
147
|
+
all = false;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (all) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Classify a candidate command string against a set of forbidden token sequences.
|
|
159
|
+
*
|
|
160
|
+
* INDETERMINATE when the value is not a string, or when no usable forbidden
|
|
161
|
+
* sequence remains after dropping sequences that are empty or contain an empty
|
|
162
|
+
* token (an empty token would degenerate matching). Otherwise MATCHES_FORBIDDEN
|
|
163
|
+
* iff some forbidden sequence is a contiguous run within some statement segment;
|
|
164
|
+
* NO_MATCH if none are. NO_MATCH is NOT compliance (see module header).
|
|
165
|
+
*/
|
|
166
|
+
function classifyCommand(rawCommand, forbiddenSequences) {
|
|
167
|
+
if (typeof rawCommand !== "string") {
|
|
168
|
+
return "INDETERMINATE";
|
|
169
|
+
}
|
|
170
|
+
const needles = forbiddenSequences.filter((seq) => Array.isArray(seq) &&
|
|
171
|
+
seq.length > 0 &&
|
|
172
|
+
seq.every((t) => typeof t === "string" && t.length > 0));
|
|
173
|
+
if (needles.length === 0) {
|
|
174
|
+
return "INDETERMINATE";
|
|
175
|
+
}
|
|
176
|
+
const segments = tokenizeCommand(rawCommand);
|
|
177
|
+
for (const segment of segments) {
|
|
178
|
+
for (const needle of needles) {
|
|
179
|
+
if (containsRun(segment, needle)) {
|
|
180
|
+
return "MATCHES_FORBIDDEN";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return "NO_MATCH";
|
|
185
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveConsultEvidenceRuleBinding = resolveConsultEvidenceRuleBinding;
|
|
4
|
+
const ce0_rule_1 = require("./ce0-rule");
|
|
5
|
+
const local_rule_version_repo_1 = require("./local-rule-version-repo");
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the consult-evidence obligation binding for a runtime scope. Returns the LIVE attested version's
|
|
8
|
+
* id + stored hash when one exists for the scope; otherwise the frozen compile-time identity (the unarmed
|
|
9
|
+
* measurement default). The two branches differ only in the version id unless a seed bump rotated the hash.
|
|
10
|
+
*/
|
|
11
|
+
function resolveConsultEvidenceRuleBinding(store, runtimeScopeId) {
|
|
12
|
+
const live = (0, local_rule_version_repo_1.getLiveLocalRuleVersion)(store, runtimeScopeId, ce0_rule_1.CONSULT_EVIDENCE_RULE_ID);
|
|
13
|
+
if (live) {
|
|
14
|
+
return {
|
|
15
|
+
ruleId: live.ruleId,
|
|
16
|
+
ruleVersionId: live.versionId,
|
|
17
|
+
canonicalPayloadHash: live.canonicalPayloadHash,
|
|
18
|
+
attested: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
ruleId: ce0_rule_1.CONSULT_EVIDENCE_RULE_ID,
|
|
23
|
+
ruleVersionId: ce0_rule_1.CONSULT_EVIDENCE_RULE_VERSION_ID,
|
|
24
|
+
canonicalPayloadHash: ce0_rule_1.CONSULT_EVIDENCE_CANONICAL_PAYLOAD_HASH,
|
|
25
|
+
attested: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.classifyConsultationTool = classifyConsultationTool;
|
|
4
|
+
exports.classifyRetrievalEnvelope = classifyRetrievalEnvelope;
|
|
5
|
+
exports.parsePostToolUseInput = parsePostToolUseInput;
|
|
6
|
+
exports.captureMemoryConsultation = captureMemoryConsultation;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const ce0_store_1 = require("./ce0-store");
|
|
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() {
|
|
17
|
+
return `con:${(0, crypto_1.randomUUID)()}`;
|
|
18
|
+
}
|
|
19
|
+
const CONSULTATION_TOOL_PATTERN = /(?:^|__)meetless__(retrieve_knowledge|kb_doc_detail|query)$/;
|
|
20
|
+
/**
|
|
21
|
+
* Recognize the governed-memory pull surface from the real (double-prefixed) hook tool name,
|
|
22
|
+
* e.g. `mcp__meetless__meetless__retrieve_knowledge`. Returns null for everything else,
|
|
23
|
+
* including the verdict write and a same-named tool on a foreign server.
|
|
24
|
+
*/
|
|
25
|
+
function classifyConsultationTool(toolName) {
|
|
26
|
+
const match = CONSULTATION_TOOL_PATTERN.exec(toolName);
|
|
27
|
+
return match ? match[1] : null;
|
|
28
|
+
}
|
|
29
|
+
/** The subject text of a pull: the query for retrieve_knowledge / query, the document id
|
|
30
|
+
* for kb_doc_detail. Null (a missing or blank field) makes the event uncapturable. */
|
|
31
|
+
function extractConsultationText(tool, toolInput) {
|
|
32
|
+
const field = tool === "kb_doc_detail" ? "document_id" : "query";
|
|
33
|
+
const value = toolInput[field];
|
|
34
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
35
|
+
}
|
|
36
|
+
function extractResultPayload(response) {
|
|
37
|
+
const content = response.content;
|
|
38
|
+
if (!Array.isArray(content) || content.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
const first = content[0];
|
|
41
|
+
if (!isPlainObject(first) || typeof first.text !== "string")
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(first.text);
|
|
45
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** A clean empty governed result (count 0, or an empty candidates/results array) is a
|
|
52
|
+
* NO_MATCH; any other parseable success carries a result. kb_doc_detail and ask answers
|
|
53
|
+
* have no emptiness signal, so they read as RESULTS_RETURNED. */
|
|
54
|
+
function payloadCarriesResults(payload) {
|
|
55
|
+
if (typeof payload.count === "number")
|
|
56
|
+
return payload.count > 0;
|
|
57
|
+
if (Array.isArray(payload.candidates))
|
|
58
|
+
return payload.candidates.length > 0;
|
|
59
|
+
if (Array.isArray(payload.results))
|
|
60
|
+
return payload.results.length > 0;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Classify the MCP CallToolResult envelope into the §1.6 execution / result pair. Only an
|
|
65
|
+
* explicit handler error is FAILED; a valid parseable response is COMPLETE (with a result
|
|
66
|
+
* subtype that is telemetry only and does not gate satisfaction); anything malformed,
|
|
67
|
+
* missing, or uncorrelatable is UNKNOWN. result is present iff COMPLETE.
|
|
68
|
+
*/
|
|
69
|
+
function classifyRetrievalEnvelope(toolResponse) {
|
|
70
|
+
if (!isPlainObject(toolResponse)) {
|
|
71
|
+
return { execution: "UNKNOWN" };
|
|
72
|
+
}
|
|
73
|
+
if (toolResponse.isError === true) {
|
|
74
|
+
return { execution: "FAILED" };
|
|
75
|
+
}
|
|
76
|
+
const payload = extractResultPayload(toolResponse);
|
|
77
|
+
if (payload === null) {
|
|
78
|
+
return { execution: "UNKNOWN" };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
execution: "COMPLETE",
|
|
82
|
+
result: payloadCarriesResults(payload) ? "RESULTS_RETURNED" : "NO_MATCH",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Parse and minimally validate the raw PostToolUse payload. Accepts either an already
|
|
87
|
+
* parsed object or the raw JSON string. Returns null for any shape that is not a usable
|
|
88
|
+
* PostToolUse event (a non-empty string tool_name and an object tool_input); the adapter
|
|
89
|
+
* maps that null to INFRA. The session coordinate is checked separately so its absence
|
|
90
|
+
* gets a distinct diagnostic.
|
|
91
|
+
*/
|
|
92
|
+
function parsePostToolUseInput(raw) {
|
|
93
|
+
let obj = raw;
|
|
94
|
+
if (typeof raw === "string") {
|
|
95
|
+
try {
|
|
96
|
+
obj = JSON.parse(raw);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!isPlainObject(obj))
|
|
103
|
+
return null;
|
|
104
|
+
const toolName = obj.tool_name;
|
|
105
|
+
if (typeof toolName !== "string" || toolName.length === 0)
|
|
106
|
+
return null;
|
|
107
|
+
if (!isPlainObject(obj.tool_input))
|
|
108
|
+
return null;
|
|
109
|
+
const str = (v) => (typeof v === "string" ? v : undefined);
|
|
110
|
+
return {
|
|
111
|
+
session_id: str(obj.session_id),
|
|
112
|
+
transcript_path: str(obj.transcript_path),
|
|
113
|
+
cwd: str(obj.cwd),
|
|
114
|
+
hook_event_name: str(obj.hook_event_name),
|
|
115
|
+
tool_name: toolName,
|
|
116
|
+
tool_input: obj.tool_input,
|
|
117
|
+
tool_response: obj.tool_response,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Capture one governed-memory consultation. Records a ConsultationAttempt fact under the
|
|
122
|
+
* turn's LocalTurnIdentity and returns an empty (injection-free) hook response.
|
|
123
|
+
*/
|
|
124
|
+
function captureMemoryConsultation(rawInput, config) {
|
|
125
|
+
const NO_INJECTION = {};
|
|
126
|
+
const parsed = parsePostToolUseInput(rawInput);
|
|
127
|
+
if (!parsed) {
|
|
128
|
+
return { response: NO_INJECTION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
|
|
129
|
+
}
|
|
130
|
+
const tool = classifyConsultationTool(parsed.tool_name);
|
|
131
|
+
if (!tool) {
|
|
132
|
+
return { response: NO_INJECTION, outcome: { kind: "NOT_APPLICABLE" } };
|
|
133
|
+
}
|
|
134
|
+
if (!parsed.session_id) {
|
|
135
|
+
return {
|
|
136
|
+
response: NO_INJECTION,
|
|
137
|
+
outcome: { kind: "INFRA", diagnostic: "missing session_id coordinate" },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const queryText = extractConsultationText(tool, parsed.tool_input);
|
|
141
|
+
if (queryText === null) {
|
|
142
|
+
return {
|
|
143
|
+
response: NO_INJECTION,
|
|
144
|
+
outcome: { kind: "INFRA", diagnostic: "missing consultation query" },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const identity = (0, ce0_store_1.resolveLatestTurnIdentity)(config.store, {
|
|
148
|
+
workspaceId: config.workspaceId,
|
|
149
|
+
sessionId: parsed.session_id,
|
|
150
|
+
});
|
|
151
|
+
if (!identity) {
|
|
152
|
+
return {
|
|
153
|
+
response: NO_INJECTION,
|
|
154
|
+
outcome: { kind: "INFRA", diagnostic: "no turn identity for session" },
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const now = config.now ?? Date.now;
|
|
158
|
+
const newId = config.newId ?? defaultNewId;
|
|
159
|
+
const envelope = classifyRetrievalEnvelope(parsed.tool_response);
|
|
160
|
+
try {
|
|
161
|
+
const draft = {
|
|
162
|
+
consultationId: newId(),
|
|
163
|
+
workspaceId: config.workspaceId,
|
|
164
|
+
sessionId: parsed.session_id,
|
|
165
|
+
localTurnSequence: identity.localTurnSequence,
|
|
166
|
+
source: "AGENT_PULL",
|
|
167
|
+
consultationSubjects: [(0, requirement_subject_1.buildConsultationSubjectFromQuery)(queryText)],
|
|
168
|
+
execution: envelope.execution,
|
|
169
|
+
result: envelope.result ?? null,
|
|
170
|
+
deliveredToAnsweringContext: true,
|
|
171
|
+
createdAt: now(),
|
|
172
|
+
};
|
|
173
|
+
const rec = (0, ce0_store_1.appendConsultationAttempt)(config.store, draft);
|
|
174
|
+
return {
|
|
175
|
+
response: NO_INJECTION,
|
|
176
|
+
outcome: {
|
|
177
|
+
kind: "CAPTURED",
|
|
178
|
+
consultationId: rec.consultationId,
|
|
179
|
+
source: "AGENT_PULL",
|
|
180
|
+
execution: rec.execution,
|
|
181
|
+
result: rec.result,
|
|
182
|
+
localTurnSequence: rec.localTurnSequence,
|
|
183
|
+
orderingToken: rec.orderingToken,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
catch (err) {
|
|
188
|
+
return {
|
|
189
|
+
response: NO_INJECTION,
|
|
190
|
+
outcome: { kind: "INFRA", diagnostic: `persistence failure: ${describeError(err)}` },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|