@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,240 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONSULTATION_SOURCES = exports.SUBJECT_TERM_OVERLAP_THRESHOLD_VERSION = exports.SUBJECT_TERM_OVERLAP_THRESHOLD = exports.SUBJECT_MATCH_VERSION = exports.SUBJECT_STOPWORDS = exports.SUBJECT_STOPWORD_SET_VERSION = exports.SUBJECT_FINGERPRINT_SCHEMA_VERSION = exports.REQUIREMENT_SUBJECT_EXTRACTOR_VERSION = void 0;
|
|
4
|
+
exports.normalizeSubjectTerms = normalizeSubjectTerms;
|
|
5
|
+
exports.buildRequirementSubjectPayload = buildRequirementSubjectPayload;
|
|
6
|
+
exports.requirementSubjectFingerprint = requirementSubjectFingerprint;
|
|
7
|
+
exports.extractRequirementSubject = extractRequirementSubject;
|
|
8
|
+
exports.buildRequiredSubjectFromPrompt = buildRequiredSubjectFromPrompt;
|
|
9
|
+
exports.buildConsultationSubjectFromQuery = buildConsultationSubjectFromQuery;
|
|
10
|
+
exports.matchConsultationSubject = matchConsultationSubject;
|
|
11
|
+
exports.consultationContributesProof = consultationContributesProof;
|
|
12
|
+
exports.selectEligibleConsultations = selectEligibleConsultations;
|
|
13
|
+
exports.recomputeSubjectSatisfaction = recomputeSubjectSatisfaction;
|
|
14
|
+
exports.isObligationSatisfied = isObligationSatisfied;
|
|
15
|
+
const canonical_json_1 = require("./canonical-json");
|
|
16
|
+
exports.REQUIREMENT_SUBJECT_EXTRACTOR_VERSION = "prompt-terms-v1";
|
|
17
|
+
exports.SUBJECT_FINGERPRINT_SCHEMA_VERSION = "requirement-subject-v1";
|
|
18
|
+
exports.SUBJECT_STOPWORD_SET_VERSION = "seed-v1";
|
|
19
|
+
/** Minimum length for a kept term; drops single-character noise left by punctuation /
|
|
20
|
+
* contraction splits ("don't" -> "don","t" -> "don"). */
|
|
21
|
+
const MIN_TERM_LENGTH = 2;
|
|
22
|
+
const sortedFrozen = (words) => Object.freeze(Array.from(new Set(words)).sort());
|
|
23
|
+
/** The versioned seed stopword set: English function words plus the question and
|
|
24
|
+
* governance scaffolding that wraps a governed subject ("what did we decide about X",
|
|
25
|
+
* "why did we choose Y", "who owns Z", "are we still doing W"). Content nouns that ARE
|
|
26
|
+
* the subject (policy, canonical, architecture, decision, model, ...) are deliberately
|
|
27
|
+
* absent. Frozen so identity is byte-stable; the corpus pins the exact behavior. */
|
|
28
|
+
exports.SUBJECT_STOPWORDS = sortedFrozen([
|
|
29
|
+
"a", "about", "an", "and", "any", "are", "as", "at",
|
|
30
|
+
"be", "been", "being", "between", "but", "by",
|
|
31
|
+
"can", "choose", "chose", "chosen", "could",
|
|
32
|
+
"decide", "decided", "did", "do", "does", "doing",
|
|
33
|
+
"for", "from",
|
|
34
|
+
"had", "has", "have", "how",
|
|
35
|
+
"i", "in", "into", "is", "it", "its",
|
|
36
|
+
"my", "nor", "of", "on", "or", "our", "over",
|
|
37
|
+
"own", "owned", "owns",
|
|
38
|
+
"approve", "approved", "approves",
|
|
39
|
+
"should", "so", "still",
|
|
40
|
+
"that", "the", "their", "them", "then", "there", "these", "this", "those", "to",
|
|
41
|
+
"under", "us",
|
|
42
|
+
"was", "we", "were", "what", "when", "where", "which", "who", "why", "will", "with", "would",
|
|
43
|
+
"you", "your",
|
|
44
|
+
]);
|
|
45
|
+
const STOPWORD_SET = new Set(exports.SUBJECT_STOPWORDS);
|
|
46
|
+
/**
|
|
47
|
+
* Lift the deterministic subject terms from a prompt: lowercase, split on any
|
|
48
|
+
* non-alphanumeric run, drop stopwords and sub-`MIN_TERM_LENGTH` tokens, then return the
|
|
49
|
+
* sorted, deduped set. Order-independent and idempotent.
|
|
50
|
+
*/
|
|
51
|
+
function normalizeSubjectTerms(text) {
|
|
52
|
+
const tokens = text.toLowerCase().split(/[^a-z0-9]+/);
|
|
53
|
+
const kept = new Set();
|
|
54
|
+
for (const tok of tokens) {
|
|
55
|
+
if (tok.length < MIN_TERM_LENGTH)
|
|
56
|
+
continue;
|
|
57
|
+
if (STOPWORD_SET.has(tok))
|
|
58
|
+
continue;
|
|
59
|
+
kept.add(tok);
|
|
60
|
+
}
|
|
61
|
+
return Array.from(kept).sort();
|
|
62
|
+
}
|
|
63
|
+
const sortedUniq = (xs) => Array.from(new Set(xs)).sort();
|
|
64
|
+
/**
|
|
65
|
+
* Build the closed canonical fingerprint payload. Every id / term set is sorted +
|
|
66
|
+
* deduped so the digest is a pure function of SET content (identity), and the schema
|
|
67
|
+
* version is pinned so a version bump rotates every digest.
|
|
68
|
+
*/
|
|
69
|
+
function buildRequirementSubjectPayload(fields) {
|
|
70
|
+
return {
|
|
71
|
+
schemaVersion: exports.SUBJECT_FINGERPRINT_SCHEMA_VERSION,
|
|
72
|
+
normalizedTerms: sortedUniq(fields.normalizedTerms),
|
|
73
|
+
entityIds: sortedUniq(fields.entityIds),
|
|
74
|
+
decisionIds: sortedUniq(fields.decisionIds),
|
|
75
|
+
conceptIds: sortedUniq(fields.conceptIds),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** Identity fingerprint over the structured fields (sha256 hex). Identity only. */
|
|
79
|
+
function requirementSubjectFingerprint(fields) {
|
|
80
|
+
return (0, canonical_json_1.sha256Hex)((0, canonical_json_1.canonicalize)(buildRequirementSubjectPayload(fields)));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Extract a structured RequirementSubject from a raw prompt. The term set is lifted
|
|
84
|
+
* deterministically here; entity / decision / concept ids are accepted from an upstream
|
|
85
|
+
* resolver (none wired in v1, so they default empty) rather than fabricated. `subjectId`
|
|
86
|
+
* is derived from the identity fingerprint, so the same structured content always yields
|
|
87
|
+
* the same handle.
|
|
88
|
+
*/
|
|
89
|
+
function extractRequirementSubject(prompt, resolved = {}) {
|
|
90
|
+
const fields = {
|
|
91
|
+
normalizedTerms: normalizeSubjectTerms(prompt),
|
|
92
|
+
entityIds: sortedUniq(resolved.entityIds ?? []),
|
|
93
|
+
decisionIds: sortedUniq(resolved.decisionIds ?? []),
|
|
94
|
+
conceptIds: sortedUniq(resolved.conceptIds ?? []),
|
|
95
|
+
};
|
|
96
|
+
const fingerprint = requirementSubjectFingerprint(fields);
|
|
97
|
+
return { subjectId: `subj:${fingerprint}`, ...fields, fingerprint };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The obligation's required subject, lifted from the user prompt. A thin, named call
|
|
101
|
+
* site over the single `extractRequirementSubject` normalizer: both the obligation side
|
|
102
|
+
* and the consultation side share ONE normalizer, never a second extractor. It takes no
|
|
103
|
+
* resolved-ids argument by construction, because the proactive-enrich path surfaces no
|
|
104
|
+
* resolved ids at prompt-submit time; a required subject is strictly terms-only and
|
|
105
|
+
* cannot carry fabricated ids.
|
|
106
|
+
*/
|
|
107
|
+
function buildRequiredSubjectFromPrompt(prompt) {
|
|
108
|
+
return extractRequirementSubject(prompt, {});
|
|
109
|
+
}
|
|
110
|
+
exports.SUBJECT_MATCH_VERSION = "deterministic-intersection-v1";
|
|
111
|
+
/** Fraction of the REQUIRED subject's terms that a consultation must contain for a
|
|
112
|
+
* term-based FULL match. Directional: it measures whether the consultation COVERS the
|
|
113
|
+
* required subject, not mutual similarity. */
|
|
114
|
+
exports.SUBJECT_TERM_OVERLAP_THRESHOLD = 0.5;
|
|
115
|
+
exports.SUBJECT_TERM_OVERLAP_THRESHOLD_VERSION = "required-containment-half-v1";
|
|
116
|
+
/**
|
|
117
|
+
* A consultation's query subject, lifted from the retrieval query the agent issued. The
|
|
118
|
+
* SAME normalizer as `buildRequiredSubjectFromPrompt`, so identical text yields a
|
|
119
|
+
* byte-identical subject (the two sides can never silently diverge). Resolved ids are
|
|
120
|
+
* admitted ONLY when the call already surfaced them (e.g. a kb_doc_detail citation id);
|
|
121
|
+
* they default empty, never invented here.
|
|
122
|
+
*/
|
|
123
|
+
function buildConsultationSubjectFromQuery(query, resolved = {}) {
|
|
124
|
+
return extractRequirementSubject(query, resolved);
|
|
125
|
+
}
|
|
126
|
+
function intersects(a, b) {
|
|
127
|
+
if (a.length === 0 || b.length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
const set = new Set(a);
|
|
130
|
+
return b.some((x) => set.has(x));
|
|
131
|
+
}
|
|
132
|
+
/** Fraction of `required`'s terms present in `consultation` (directional containment).
|
|
133
|
+
* Zero when `required` has no terms (nothing to cover). */
|
|
134
|
+
function requiredTermContainment(required, consultation) {
|
|
135
|
+
if (required.length === 0)
|
|
136
|
+
return 0;
|
|
137
|
+
const set = new Set(consultation);
|
|
138
|
+
const hit = required.reduce((n, t) => (set.has(t) ? n + 1 : n), 0);
|
|
139
|
+
return hit / required.length;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The CE0 deterministic coverage ladder (§1.6). candidateMatch is true iff a non-empty id
|
|
143
|
+
* intersection on entityIds / decisionIds / conceptIds, OR a required-term containment at
|
|
144
|
+
* or above the versioned threshold. Anything uncertain (including a disjoint pair or a
|
|
145
|
+
* degenerate required subject) fails toward silence: result UNKNOWN, candidateMatch false,
|
|
146
|
+
* which neither satisfies nor violates. This grader never asserts PARTIAL or NONE; the
|
|
147
|
+
* crude term test is not entitled to claim non-coverage, so a non-match is UNKNOWN, not NONE.
|
|
148
|
+
*
|
|
149
|
+
* Source-independent by design: a PROACTIVE_PUSH does NOT blanket match. A push is graded
|
|
150
|
+
* by this same per-subject test; it contributes a proof to a required subject only when its
|
|
151
|
+
* query subjects cover that subject, exactly as `recomputeSubjectSatisfaction` attributes proofs.
|
|
152
|
+
*/
|
|
153
|
+
function matchConsultationSubject(required, consultation) {
|
|
154
|
+
const idIntersect = intersects(required.entityIds, consultation.entityIds) ||
|
|
155
|
+
intersects(required.decisionIds, consultation.decisionIds) ||
|
|
156
|
+
intersects(required.conceptIds, consultation.conceptIds);
|
|
157
|
+
const termMatch = requiredTermContainment(required.normalizedTerms, consultation.normalizedTerms) >=
|
|
158
|
+
exports.SUBJECT_TERM_OVERLAP_THRESHOLD;
|
|
159
|
+
const candidateMatch = idIntersect || termMatch;
|
|
160
|
+
return {
|
|
161
|
+
subjectId: required.subjectId,
|
|
162
|
+
result: candidateMatch ? "FULL" : "UNKNOWN",
|
|
163
|
+
candidateMatch,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/** How a governed-memory consultation was initiated. The tuple is the SINGLE source of both
|
|
167
|
+
* the closed value set and the stable sort order the offline projector resolves a finalized
|
|
168
|
+
* obligation's `satisfiedBySources` into. CE0 emits only PROACTIVE_PUSH and AGENT_PULL; today
|
|
169
|
+
* only AGENT_PULL is written (the PostToolUse capture seam). STOP_RECOVERY_PULL is a held seam:
|
|
170
|
+
* it lives in the enum so its ordering is fixed up front, but CE0 (RECORD_ONLY) never writes
|
|
171
|
+
* it. */
|
|
172
|
+
exports.CONSULTATION_SOURCES = ["PROACTIVE_PUSH", "AGENT_PULL", "STOP_RECOVERY_PULL"];
|
|
173
|
+
/**
|
|
174
|
+
* A consultation contributes proofs iff it COMPLETED and its evidence reached the parent
|
|
175
|
+
* answering context. Result (RESULTS_RETURNED vs NO_MATCH) does NOT gate: asking governed
|
|
176
|
+
* memory about a subject and completing, even with zero hits, is a consultation of that
|
|
177
|
+
* subject. FAILED, UNKNOWN, and undelivered consultations never contribute (we cannot
|
|
178
|
+
* attest they consulted).
|
|
179
|
+
*/
|
|
180
|
+
function consultationContributesProof(c) {
|
|
181
|
+
return c.execution === "COMPLETE" && c.deliveredToAnsweringContext === true;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* The eligible consultation set for an obligation: contributing consultations (above)
|
|
185
|
+
* recorded on or before the claimed deadline. Pass `deadlineOrderingToken = null` while the
|
|
186
|
+
* obligation has no deadline yet (the first Stop has not claimed it), so every contributing
|
|
187
|
+
* consultation is on time. Once the deadline is claimed at orderingToken D, a consultation at
|
|
188
|
+
* orderingToken > D is late: it stays factual telemetry but can never produce an on-time proof.
|
|
189
|
+
*/
|
|
190
|
+
function selectEligibleConsultations(consultations, deadlineOrderingToken) {
|
|
191
|
+
return consultations.filter((c) => consultationContributesProof(c) &&
|
|
192
|
+
(deadlineOrderingToken === null || c.orderingToken <= deadlineOrderingToken));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Accumulate one SubjectSatisfactionProof per COVERED required subject across all ELIGIBLE
|
|
196
|
+
* consultations: a required subject is covered iff some eligible consultation's query
|
|
197
|
+
* subjects match it (via `matchConsultationSubject`), and the proof records the consultation
|
|
198
|
+
* that covered it. Two consultations can jointly satisfy two subjects; one consultation can
|
|
199
|
+
* satisfy several. This is NOT a coverage table: there is no per-subject grade, only the
|
|
200
|
+
* presence or absence of a proof.
|
|
201
|
+
*
|
|
202
|
+
* Deterministic and idempotent: consultations are considered in ascending (orderingToken,
|
|
203
|
+
* then consultationId), so the EARLIEST eligible consultation wins a subject's proof
|
|
204
|
+
* regardless of input order, and a duplicated consultation yields the identical proof. Proofs
|
|
205
|
+
* are emitted in `requiredSubjects` order, at most one per subjectId (a subject listed twice
|
|
206
|
+
* still yields one proof). Uncovered required subjects yield no proof.
|
|
207
|
+
*/
|
|
208
|
+
function recomputeSubjectSatisfaction(requiredSubjects, eligibleConsultations) {
|
|
209
|
+
const ordered = [...eligibleConsultations].sort((a, b) => a.orderingToken !== b.orderingToken
|
|
210
|
+
? a.orderingToken - b.orderingToken
|
|
211
|
+
: a.consultationId < b.consultationId
|
|
212
|
+
? -1
|
|
213
|
+
: a.consultationId > b.consultationId
|
|
214
|
+
? 1
|
|
215
|
+
: 0);
|
|
216
|
+
const proofs = [];
|
|
217
|
+
const proven = new Set();
|
|
218
|
+
for (const required of requiredSubjects) {
|
|
219
|
+
if (proven.has(required.subjectId))
|
|
220
|
+
continue;
|
|
221
|
+
const hit = ordered.find((c) => c.consultationSubjects.some((qs) => matchConsultationSubject(required, qs).candidateMatch));
|
|
222
|
+
if (hit) {
|
|
223
|
+
proofs.push({ subjectId: required.subjectId, consultationId: hit.consultationId });
|
|
224
|
+
proven.add(required.subjectId);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return proofs;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* SATISFIED iff every required subject has a proof. An empty required set does NOT vacuously
|
|
231
|
+
* satisfy (fail toward silence): satisfaction requires demonstrated consultation of at least
|
|
232
|
+
* one subject. On-time-ness is already enforced by `selectEligibleConsultations`, so a proof
|
|
233
|
+
* set built from eligible consultations is on time by construction.
|
|
234
|
+
*/
|
|
235
|
+
function isObligationSatisfied(requiredSubjects, proofs) {
|
|
236
|
+
if (requiredSubjects.length === 0)
|
|
237
|
+
return false;
|
|
238
|
+
const proven = new Set(proofs.map((p) => p.subjectId));
|
|
239
|
+
return requiredSubjects.every((r) => proven.has(r.subjectId));
|
|
240
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// R2-LOCAL accountability projection: the §2.6 "observed N, violated M" measurement.
|
|
3
|
+
//
|
|
4
|
+
// The terminal-outcome half of R2 (project a COMMITTED violation, ie "the action the deny named
|
|
5
|
+
// actually happened") is BLOCKED BY DESIGN: the supported Claude Code PreToolUse payload carries no
|
|
6
|
+
// tool_use_id (§9.10), and heuristic post correlation by timestamp / tool name / input hash / transcript
|
|
7
|
+
// position is FORBIDDEN because parallel identical calls make it unsound (§2.6, lines 2209-2221). So this
|
|
8
|
+
// module does NOT correlate anything. It projects ONLY the records MLA already owns at the moment it
|
|
9
|
+
// evaluates a rule at PreToolUse: the tool_attempt and the per-rule rule_evaluation_record.
|
|
10
|
+
//
|
|
11
|
+
// That projection is exactly the measurement §2.6 (lines 2224-2231) says licenses promoting a rule out of
|
|
12
|
+
// DRY_RUN: "you cannot justify promoting a rule from DRY_RUN to ask or deny until you can show 'this rule
|
|
13
|
+
// was observed N times and violated M of them.' The violation log IS that measurement." It needs no
|
|
14
|
+
// correlation and is not blocked. §3.7 ("Phase R2 (still local): accountability and instrumentation")
|
|
15
|
+
// scopes it to first-class local violation events and a console summary, which is this.
|
|
16
|
+
//
|
|
17
|
+
// The measurement is keyed on each LIVE version. A version's track record starts when it goes live: when a
|
|
18
|
+
// rule is superseded and re-attested, the new LIVE version's version_id is fresh, so its counts honestly
|
|
19
|
+
// start at zero (the old version's history is not silently inherited). Pure core over a real ce0 store.
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.summarizeRuleActivity = summarizeRuleActivity;
|
|
22
|
+
/**
|
|
23
|
+
* Project the §2.6 observed/violated measurement for every LIVE rule version in `runtimeScopeId`.
|
|
24
|
+
*
|
|
25
|
+
* Each LIVE version is LEFT JOINed to its version-arm evaluations (so a brand-new LIVE rule with no
|
|
26
|
+
* activity still surfaces as an all-zero row, the floor the operator sees), and each evaluation to its
|
|
27
|
+
* attempt (to tell a violation that emitted a deny from one that was only observed). The join keys on
|
|
28
|
+
* rule_version_id, so observed-only arms (rule_version_id IS NULL, recorded for never-attested rules) and
|
|
29
|
+
* superseded versions never inflate a LIVE version's count. Scope-isolated and ordered by ruleId so the
|
|
30
|
+
* result is deterministic across runs.
|
|
31
|
+
*/
|
|
32
|
+
function summarizeRuleActivity(store, runtimeScopeId) {
|
|
33
|
+
const rows = store.db
|
|
34
|
+
.prepare(`SELECT
|
|
35
|
+
v.rule_id AS rule_id,
|
|
36
|
+
v.version_id AS version_id,
|
|
37
|
+
COUNT(e.evaluation_id) AS observed,
|
|
38
|
+
COALESCE(SUM(CASE WHEN e.result = 'COMPLIANT' THEN 1 ELSE 0 END), 0) AS compliant,
|
|
39
|
+
COALESCE(SUM(CASE WHEN e.result = 'VIOLATION' THEN 1 ELSE 0 END), 0) AS violation,
|
|
40
|
+
COALESCE(SUM(CASE WHEN e.effective_enforcement = 'DENY'
|
|
41
|
+
AND a.deny_emission_status = 'RESPONSE_EMITTED'
|
|
42
|
+
THEN 1 ELSE 0 END), 0) AS denied_emitted,
|
|
43
|
+
COALESCE(SUM(CASE WHEN e.result = 'VIOLATION'
|
|
44
|
+
AND e.effective_enforcement = 'NONE'
|
|
45
|
+
THEN 1 ELSE 0 END), 0) AS enforcement_unavailable
|
|
46
|
+
FROM local_rule_version v
|
|
47
|
+
LEFT JOIN rule_evaluation_record e
|
|
48
|
+
ON e.rule_version_id = v.version_id
|
|
49
|
+
AND e.runtime_scope_id = v.runtime_scope_id
|
|
50
|
+
LEFT JOIN tool_attempt a
|
|
51
|
+
ON a.attempt_id = e.attempt_id
|
|
52
|
+
AND a.runtime_scope_id = e.runtime_scope_id
|
|
53
|
+
WHERE v.runtime_scope_id = ?
|
|
54
|
+
AND v.lifecycle_status = 'LIVE'
|
|
55
|
+
GROUP BY v.rule_id, v.version_id
|
|
56
|
+
ORDER BY v.rule_id`)
|
|
57
|
+
.all(runtimeScopeId);
|
|
58
|
+
return rows.map((r) => ({
|
|
59
|
+
ruleId: r.rule_id,
|
|
60
|
+
versionId: r.version_id,
|
|
61
|
+
observed: r.observed,
|
|
62
|
+
compliant: r.compliant,
|
|
63
|
+
violation: r.violation,
|
|
64
|
+
deniedEmitted: r.denied_emitted,
|
|
65
|
+
enforcementUnavailable: r.enforcement_unavailable,
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuleVersionHashError = exports.RULE_VERSION_HASH_DOMAIN = void 0;
|
|
4
|
+
exports.buildRuleVersionPayload = buildRuleVersionPayload;
|
|
5
|
+
exports.serializeRuleVersion = serializeRuleVersion;
|
|
6
|
+
exports.ruleVersionHash = ruleVersionHash;
|
|
7
|
+
const crypto_1 = require("crypto");
|
|
8
|
+
const canonical_json_1 = require("./canonical-json");
|
|
9
|
+
/**
|
|
10
|
+
* The `rule-version-v1` canonical hash domain (proposal P0.36, sharpened by P0.53; RulePayloadV1
|
|
11
|
+
* at §3.6).
|
|
12
|
+
*
|
|
13
|
+
* It computes the content identity of an ATTESTED rule version. The digest is
|
|
14
|
+
*
|
|
15
|
+
* SHA-256( domainTag || 0x00 || JCS(payload) ) (lowercase hex)
|
|
16
|
+
*
|
|
17
|
+
* over the IMMUTABLE `RulePayloadV1` ONLY. The version ENVELOPE (ruleId, versionId,
|
|
18
|
+
* lifecycleStatus, supersedesVersionId, derivedFromObservedRuleHash, attestedBy, attestedAt,
|
|
19
|
+
* attestationMethod) is deliberately OUTSIDE the hash: a hash cannot include itself, and issuance
|
|
20
|
+
* metadata is not enforcement-relevant (P0.16, P0.54). JCS is the repo's existing RFC 8785
|
|
21
|
+
* canonicalizer (canonical-json.ts); the domain tag + single 0x00 separator (P0.53) guarantee this
|
|
22
|
+
* digest can NEVER collide with an observed rule (`observed-rule-v1`), an action-input snapshot
|
|
23
|
+
* (`evaluation-input-v1`), or any other hashed artifact, even when two bodies are byte-identical.
|
|
24
|
+
* JCS escapes control characters, so the only raw 0x00 byte in the hash input is the separator.
|
|
25
|
+
*
|
|
26
|
+
* `runtimeScopeId` is INSIDE the hash (P0.51): the same rule bound to a different checkout scope is
|
|
27
|
+
* a different payload with a different digest, which is what makes the payload-scope == envelope-scope
|
|
28
|
+
* rule (§3.6) checkable. Because every field except `text`, `applicability`, and the carried
|
|
29
|
+
* `forbiddenRootRelativePath` is fixed by the notes-location pilot contract (§2.4), two operators
|
|
30
|
+
* attesting the same observed snapshot in the same runtime scope mint a byte-identical payload and the
|
|
31
|
+
* same digest.
|
|
32
|
+
*
|
|
33
|
+
* Per-field NFC caveat (honest, P0.53). The vendored JCS primitive applies NFC to EVERY string,
|
|
34
|
+
* while the contract reserves NFC for prose. For the notes-location pilot every non-prose field (the
|
|
35
|
+
* tool names, the effect / strength / ceiling / policy tokens, the version tags, the relative
|
|
36
|
+
* forbidden root, the runtime scope) is ASCII and therefore NFC-stable, so universal NFC is
|
|
37
|
+
* byte-identical to the per-field rule and the golden vectors are contract-correct. A future payload
|
|
38
|
+
* carrying a non-prose field that can hold non-NFC Unicode (for instance a forbidden path with
|
|
39
|
+
* combining marks) must switch to a per-field-NFC encoder; that boundary is recorded in the ledger.
|
|
40
|
+
*/
|
|
41
|
+
exports.RULE_VERSION_HASH_DOMAIN = "rule-version-v1";
|
|
42
|
+
/** Thrown when a payload carries a field outside the rule-version-v1 schema (fail-closed). */
|
|
43
|
+
class RuleVersionHashError extends Error {
|
|
44
|
+
constructor(message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = "RuleVersionHashError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.RuleVersionHashError = RuleVersionHashError;
|
|
50
|
+
// The closed key sets for each object in the payload schema. Unknown fields are an error (P0.53),
|
|
51
|
+
// so a forward-incompatible producer cannot silently mint a hash a consumer would compute
|
|
52
|
+
// differently. `rationale` is the only optional top-level key; it is omitted, never null, when absent.
|
|
53
|
+
const RULE_PAYLOAD_KEYS = new Set([
|
|
54
|
+
"text",
|
|
55
|
+
"rationale",
|
|
56
|
+
"applicability",
|
|
57
|
+
"compliance",
|
|
58
|
+
"effect",
|
|
59
|
+
"strength",
|
|
60
|
+
"deliveryChannels",
|
|
61
|
+
"enforcementCeiling",
|
|
62
|
+
"infrastructureFailurePolicy",
|
|
63
|
+
"runtimeScopeId",
|
|
64
|
+
"payloadSchemaVersion",
|
|
65
|
+
"canonicalSerializationVersion",
|
|
66
|
+
]);
|
|
67
|
+
const COMPLIANCE_KEYS = new Set([
|
|
68
|
+
"evaluatorContractVersion",
|
|
69
|
+
"matcherSchemaVersion",
|
|
70
|
+
"pathCanonicalizerVersion",
|
|
71
|
+
"config",
|
|
72
|
+
]);
|
|
73
|
+
const COMPLIANCE_CONFIG_KEYS = new Set(["forbiddenRootRelativePath"]);
|
|
74
|
+
const APPLICABILITY_AMBIENT_KEYS = new Set(["mode"]);
|
|
75
|
+
const APPLICABILITY_ACTION_KEYS = new Set(["mode", "tools", "matcher"]);
|
|
76
|
+
const MATCHER_KEYS = new Set(["field", "glob"]);
|
|
77
|
+
function rejectUnknownKeys(obj, allowed, context) {
|
|
78
|
+
for (const key of Object.keys(obj)) {
|
|
79
|
+
if (!allowed.has(key)) {
|
|
80
|
+
throw new RuleVersionHashError(`unknown field '${key}' in ${context} is outside the ${exports.RULE_VERSION_HASH_DOMAIN} schema`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Sort + dedupe a SET-valued field by code unit (P0.53). The pilot's tool names and delivery
|
|
85
|
+
* channels are ASCII, so code unit and code point coincide and the order matches JCS key ordering. */
|
|
86
|
+
function sortedDedupedSet(values) {
|
|
87
|
+
return Array.from(new Set(values)).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
88
|
+
}
|
|
89
|
+
function buildApplicabilityPayload(a) {
|
|
90
|
+
if (a.mode === "ambient") {
|
|
91
|
+
rejectUnknownKeys(a, APPLICABILITY_AMBIENT_KEYS, "applicability(ambient)");
|
|
92
|
+
return { mode: "ambient" };
|
|
93
|
+
}
|
|
94
|
+
rejectUnknownKeys(a, APPLICABILITY_ACTION_KEYS, "applicability(action)");
|
|
95
|
+
rejectUnknownKeys(a.matcher, MATCHER_KEYS, "applicability.matcher");
|
|
96
|
+
const matcher = { field: a.matcher.field };
|
|
97
|
+
if (a.matcher.glob !== undefined) {
|
|
98
|
+
matcher.glob = a.matcher.glob;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
mode: "action",
|
|
102
|
+
tools: sortedDedupedSet(a.tools),
|
|
103
|
+
matcher,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Build the closed canonical payload for a RulePayloadV1: the exact object that gets canonicalized
|
|
108
|
+
* and hashed. Rejects unknown fields at every level, applies set discipline to deliveryChannels and
|
|
109
|
+
* applicability.tools, and omits an absent rationale (and matcher glob). Field NFC (prose) is applied
|
|
110
|
+
* by the JCS encoder on the way out; see the per-field caveat in the file header.
|
|
111
|
+
*/
|
|
112
|
+
function buildRuleVersionPayload(payload) {
|
|
113
|
+
rejectUnknownKeys(payload, RULE_PAYLOAD_KEYS, "rule payload");
|
|
114
|
+
rejectUnknownKeys(payload.compliance, COMPLIANCE_KEYS, "compliance");
|
|
115
|
+
rejectUnknownKeys(payload.compliance.config, COMPLIANCE_CONFIG_KEYS, "compliance.config");
|
|
116
|
+
const base = {
|
|
117
|
+
text: payload.text,
|
|
118
|
+
applicability: buildApplicabilityPayload(payload.applicability),
|
|
119
|
+
compliance: {
|
|
120
|
+
evaluatorContractVersion: payload.compliance.evaluatorContractVersion,
|
|
121
|
+
matcherSchemaVersion: payload.compliance.matcherSchemaVersion,
|
|
122
|
+
pathCanonicalizerVersion: payload.compliance.pathCanonicalizerVersion,
|
|
123
|
+
config: { forbiddenRootRelativePath: payload.compliance.config.forbiddenRootRelativePath },
|
|
124
|
+
},
|
|
125
|
+
effect: payload.effect,
|
|
126
|
+
strength: payload.strength,
|
|
127
|
+
deliveryChannels: sortedDedupedSet(payload.deliveryChannels),
|
|
128
|
+
enforcementCeiling: payload.enforcementCeiling,
|
|
129
|
+
infrastructureFailurePolicy: payload.infrastructureFailurePolicy,
|
|
130
|
+
runtimeScopeId: payload.runtimeScopeId,
|
|
131
|
+
payloadSchemaVersion: payload.payloadSchemaVersion,
|
|
132
|
+
canonicalSerializationVersion: payload.canonicalSerializationVersion,
|
|
133
|
+
};
|
|
134
|
+
// Absent optional: OMIT the key (never null). Present: include verbatim. Key ORDER is irrelevant
|
|
135
|
+
// (JCS sorts), so a conditional spread is the canonical builder, never a mutation.
|
|
136
|
+
return payload.rationale !== undefined ? { ...base, rationale: payload.rationale } : base;
|
|
137
|
+
}
|
|
138
|
+
/** The exact RFC 8785 canonical JSON string that is hashed (UTF-8). Exposed for golden vectors and
|
|
139
|
+
* debugging; the digest is over these bytes prefixed by the domain. */
|
|
140
|
+
function serializeRuleVersion(payload) {
|
|
141
|
+
return (0, canonical_json_1.canonicalize)(buildRuleVersionPayload(payload));
|
|
142
|
+
}
|
|
143
|
+
/** The rule-version-v1 content hash: SHA-256(domainTag || 0x00 || JCS(payload)), lowercase hex. */
|
|
144
|
+
function ruleVersionHash(payload) {
|
|
145
|
+
const jcs = serializeRuleVersion(payload);
|
|
146
|
+
const h = (0, crypto_1.createHash)("sha256");
|
|
147
|
+
h.update(exports.RULE_VERSION_HASH_DOMAIN, "utf8");
|
|
148
|
+
h.update(Buffer.from([0x00]));
|
|
149
|
+
h.update(jcs, "utf8");
|
|
150
|
+
return h.digest("hex");
|
|
151
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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.resolveActiveRuntimeScopeId = resolveActiveRuntimeScopeId;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const wire_1 = require("../wire");
|
|
39
|
+
// The active runtime scope id (proposal §2.3 / §10.1, P0.51 / decision 7). Every local interception
|
|
40
|
+
// row, the tool attempts, the evaluation records, and the attested versions, is keyed by
|
|
41
|
+
// runtime_scope_id, NEVER by a bare workspaceId. The id is the realpath-resolved checkout root of the
|
|
42
|
+
// activated runtime project: from the working directory, walk to the repo root and canonicalize. For
|
|
43
|
+
// R0/R1 there is NO runtime-scope table (decision 2); the resolved path string IS the identity, so a
|
|
44
|
+
// read or write derives it deterministically from the cwd rather than reading a row. resolveProjectRoot
|
|
45
|
+
// performs the git-toplevel walk (falling back to the cwd outside a repo); realpath then canonicalizes
|
|
46
|
+
// it so worktrees and symlinked paths resolve to one stable identity.
|
|
47
|
+
function resolveActiveRuntimeScopeId(cwd) {
|
|
48
|
+
const root = (0, wire_1.resolveProjectRoot)(cwd);
|
|
49
|
+
try {
|
|
50
|
+
return fs.realpathSync(root);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return root;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseStopInput = parseStopInput;
|
|
4
|
+
exports.observeStop = observeStop;
|
|
5
|
+
const ce0_store_1 = require("./ce0-store");
|
|
6
|
+
const stop_response_snapshot_1 = require("./stop-response-snapshot");
|
|
7
|
+
function isPlainObject(value) {
|
|
8
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
function describeError(err) {
|
|
11
|
+
return err instanceof Error ? err.message : String(err);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse the raw Stop payload. Accepts either an already parsed object or the raw JSON string.
|
|
15
|
+
* A Stop has no required payload field beyond being an object, so the only null case is a shape
|
|
16
|
+
* that is not a plain object (or an unparseable string); the adapter maps that null to INFRA.
|
|
17
|
+
* The session coordinate and transcript_path are read but not required here, so their absence gets a
|
|
18
|
+
* distinct diagnostic / disposition at the adapter rather than collapsing into "malformed".
|
|
19
|
+
*/
|
|
20
|
+
function parseStopInput(raw) {
|
|
21
|
+
let obj = raw;
|
|
22
|
+
if (typeof raw === "string") {
|
|
23
|
+
try {
|
|
24
|
+
obj = JSON.parse(raw);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!isPlainObject(obj))
|
|
31
|
+
return null;
|
|
32
|
+
const session = obj.session_id;
|
|
33
|
+
const transcript = obj.transcript_path;
|
|
34
|
+
return {
|
|
35
|
+
session_id: typeof session === "string" ? session : undefined,
|
|
36
|
+
transcript_path: typeof transcript === "string" ? transcript : undefined,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Map a Stage A deadline-claim result onto the adapter's Stage A outcome. */
|
|
40
|
+
function stageAOutcome(result, identity) {
|
|
41
|
+
switch (result.status) {
|
|
42
|
+
case "NO_OBLIGATION":
|
|
43
|
+
return { kind: "NOT_APPLICABLE" };
|
|
44
|
+
case "CLAIMED":
|
|
45
|
+
return {
|
|
46
|
+
kind: "CLAIMED",
|
|
47
|
+
obligationId: result.claim.obligationId,
|
|
48
|
+
localTurnSequence: identity.localTurnSequence,
|
|
49
|
+
deadlineClaimedAt: result.claim.deadlineClaimedAt,
|
|
50
|
+
deadlineClaimedVersion: result.claim.deadlineClaimedVersion,
|
|
51
|
+
stateVersion: result.claim.stateVersion,
|
|
52
|
+
};
|
|
53
|
+
case "ALREADY_CLAIMED":
|
|
54
|
+
return {
|
|
55
|
+
kind: "ALREADY_CLAIMED",
|
|
56
|
+
obligationId: result.claim.obligationId,
|
|
57
|
+
localTurnSequence: identity.localTurnSequence,
|
|
58
|
+
deadlineClaimedAt: result.claim.deadlineClaimedAt,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Stage B: best-effort, outside the Stage A transaction. Read the transcript snapshot and, when it
|
|
64
|
+
* succeeds, record the response pair idempotently onto the turn's assessment. readStopResponseSnapshot
|
|
65
|
+
* never throws; a transcript failure resolves to UNLABELABLE with a stable reason and the snapshot
|
|
66
|
+
* fields stay null. A Stage B store-write fault is the same DB-fault class Stage A already surfaces as
|
|
67
|
+
* INFRA, so it is left to propagate to the adapter's outer catch rather than masked here.
|
|
68
|
+
*/
|
|
69
|
+
function recordStageB(store, identity, transcriptPath) {
|
|
70
|
+
const snap = (0, stop_response_snapshot_1.readStopResponseSnapshot)(transcriptPath);
|
|
71
|
+
if (!snap.ok)
|
|
72
|
+
return { kind: "UNLABELABLE", reason: snap.reason };
|
|
73
|
+
const written = (0, ce0_store_1.recordStopResponseSnapshot)(store, identity, {
|
|
74
|
+
responseHash: snap.responseHash,
|
|
75
|
+
responseSourceRef: snap.responseSourceRef,
|
|
76
|
+
});
|
|
77
|
+
return { kind: written.status };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Observe one Stop. Resolves the turn's LocalTurnIdentity, runs Stage A (freeze the obligation's
|
|
81
|
+
* eligibility boundary and stamp the observation) and Stage B (best-effort response snapshot), and
|
|
82
|
+
* returns an empty (injection-free) hook response.
|
|
83
|
+
*/
|
|
84
|
+
function observeStop(rawInput, config) {
|
|
85
|
+
const NO_INJECTION = {};
|
|
86
|
+
const now = config.now ?? Date.now;
|
|
87
|
+
const parsed = parseStopInput(rawInput);
|
|
88
|
+
if (!parsed) {
|
|
89
|
+
return { response: NO_INJECTION, outcome: { kind: "INFRA", diagnostic: "malformed hook input" } };
|
|
90
|
+
}
|
|
91
|
+
if (!parsed.session_id) {
|
|
92
|
+
return {
|
|
93
|
+
response: NO_INJECTION,
|
|
94
|
+
outcome: { kind: "INFRA", diagnostic: "missing session_id coordinate" },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const identity = (0, ce0_store_1.resolveLatestTurnIdentity)(config.store, {
|
|
99
|
+
workspaceId: config.workspaceId,
|
|
100
|
+
sessionId: parsed.session_id,
|
|
101
|
+
});
|
|
102
|
+
if (!identity) {
|
|
103
|
+
return { response: NO_INJECTION, outcome: { kind: "NOT_APPLICABLE" } };
|
|
104
|
+
}
|
|
105
|
+
const claim = (0, ce0_store_1.claimFirstStop)(config.store, identity, config.ruleVersionId, now);
|
|
106
|
+
const outcome = stageAOutcome(claim, identity);
|
|
107
|
+
const snapshot = recordStageB(config.store, identity, parsed.transcript_path);
|
|
108
|
+
return { response: NO_INJECTION, outcome, snapshot };
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
response: NO_INJECTION,
|
|
113
|
+
outcome: { kind: "INFRA", diagnostic: `persistence failure: ${describeError(err)}` },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|